1 | /* |
2 | src/window.cpp -- Top-level window widget |
3 | |
4 | NanoGUI was developed by Wenzel Jakob <wenzel.jakob@epfl.ch>. |
5 | The widget drawing code is based on the NanoVG demo application |
6 | by Mikko Mononen. |
7 | |
8 | All rights reserved. Use of this source code is governed by a |
9 | BSD-style license that can be found in the LICENSE.txt file. |
10 | */ |
11 | |
12 | #include <nanogui/window.h> |
13 | #include <nanogui/theme.h> |
14 | #include <nanogui/opengl.h> |
15 | #include <nanogui/screen.h> |
16 | #include <nanogui/layout.h> |
17 | #include <nanogui/serializer/core.h> |
18 | |
19 | NAMESPACE_BEGIN(nanogui) |
20 | |
21 | Window::Window(Widget *parent, const std::string &title) |
22 | : Widget(parent), mTitle(title), mButtonPanel(nullptr), mModal(false), mDrag(false) { } |
23 | |
24 | Vector2i Window::preferredSize(NVGcontext *ctx) const { |
25 | if (mButtonPanel) |
26 | mButtonPanel->setVisible(false); |
27 | Vector2i result = Widget::preferredSize(ctx); |
28 | if (mButtonPanel) |
29 | mButtonPanel->setVisible(true); |
30 | |
31 | nvgFontSize(ctx, 18.0f); |
32 | nvgFontFace(ctx, "sans-bold" ); |
33 | float bounds[4]; |
34 | nvgTextBounds(ctx, 0, 0, mTitle.c_str(), nullptr, bounds); |
35 | |
36 | return result.cwiseMax(Vector2i( |
37 | bounds[2]-bounds[0] + 20, bounds[3]-bounds[1] |
38 | )); |
39 | } |
40 | |
41 | Widget *Window::buttonPanel() { |
42 | if (!mButtonPanel) { |
43 | mButtonPanel = new Widget(this); |
44 | mButtonPanel->setLayout(new BoxLayout(Orientation::Horizontal, Alignment::Middle, 0, 4)); |
45 | } |
46 | return mButtonPanel; |
47 | } |
48 | |
49 | void Window::performLayout(NVGcontext *ctx) { |
50 | if (!mButtonPanel) { |
51 | Widget::performLayout(ctx); |
52 | } else { |
53 | mButtonPanel->setVisible(false); |
54 | Widget::performLayout(ctx); |
55 | for (auto w : mButtonPanel->children()) { |
56 | w->setFixedSize(Vector2i(22, 22)); |
57 | w->setFontSize(15); |
58 | } |
59 | mButtonPanel->setVisible(true); |
60 | mButtonPanel->setSize(Vector2i(width(), 22)); |
61 | mButtonPanel->setPosition(Vector2i(width() - (mButtonPanel->preferredSize(ctx).x() + 5), 3)); |
62 | mButtonPanel->performLayout(ctx); |
63 | } |
64 | } |
65 | |
66 | void Window::draw(NVGcontext *ctx) { |
67 | int ds = mTheme->mWindowDropShadowSize, cr = mTheme->mWindowCornerRadius; |
68 | int hh = mTheme->mWindowHeaderHeight; |
69 | |
70 | /* Draw window */ |
71 | nvgSave(ctx); |
72 | nvgBeginPath(ctx); |
73 | nvgRoundedRect(ctx, mPos.x(), mPos.y(), mSize.x(), mSize.y(), cr); |
74 | |
75 | nvgFillColor(ctx, mMouseFocus ? mTheme->mWindowFillFocused |
76 | : mTheme->mWindowFillUnfocused); |
77 | nvgFill(ctx); |
78 | |
79 | |
80 | /* Draw a drop shadow */ |
81 | NVGpaint shadowPaint = nvgBoxGradient( |
82 | ctx, mPos.x(), mPos.y(), mSize.x(), mSize.y(), cr*2, ds*2, |
83 | mTheme->mDropShadow, mTheme->mTransparent); |
84 | |
85 | nvgSave(ctx); |
86 | nvgResetScissor(ctx); |
87 | nvgBeginPath(ctx); |
88 | nvgRect(ctx, mPos.x()-ds,mPos.y()-ds, mSize.x()+2*ds, mSize.y()+2*ds); |
89 | nvgRoundedRect(ctx, mPos.x(), mPos.y(), mSize.x(), mSize.y(), cr); |
90 | nvgPathWinding(ctx, NVG_HOLE); |
91 | nvgFillPaint(ctx, shadowPaint); |
92 | nvgFill(ctx); |
93 | nvgRestore(ctx); |
94 | |
95 | if (!mTitle.empty()) { |
96 | /* Draw header */ |
97 | NVGpaint = nvgLinearGradient( |
98 | ctx, mPos.x(), mPos.y(), mPos.x(), |
99 | mPos.y() + hh, |
100 | mTheme->mWindowHeaderGradientTop, |
101 | mTheme->mWindowHeaderGradientBot); |
102 | |
103 | nvgBeginPath(ctx); |
104 | nvgRoundedRect(ctx, mPos.x(), mPos.y(), mSize.x(), hh, cr); |
105 | |
106 | nvgFillPaint(ctx, headerPaint); |
107 | nvgFill(ctx); |
108 | |
109 | nvgBeginPath(ctx); |
110 | nvgRoundedRect(ctx, mPos.x(), mPos.y(), mSize.x(), hh, cr); |
111 | nvgStrokeColor(ctx, mTheme->mWindowHeaderSepTop); |
112 | |
113 | nvgSave(ctx); |
114 | nvgIntersectScissor(ctx, mPos.x(), mPos.y(), mSize.x(), 0.5f); |
115 | nvgStroke(ctx); |
116 | nvgRestore(ctx); |
117 | |
118 | nvgBeginPath(ctx); |
119 | nvgMoveTo(ctx, mPos.x() + 0.5f, mPos.y() + hh - 1.5f); |
120 | nvgLineTo(ctx, mPos.x() + mSize.x() - 0.5f, mPos.y() + hh - 1.5); |
121 | nvgStrokeColor(ctx, mTheme->mWindowHeaderSepBot); |
122 | nvgStroke(ctx); |
123 | |
124 | nvgFontSize(ctx, 18.0f); |
125 | nvgFontFace(ctx, "sans-bold" ); |
126 | nvgTextAlign(ctx, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE); |
127 | |
128 | nvgFontBlur(ctx, 2); |
129 | nvgFillColor(ctx, mTheme->mDropShadow); |
130 | nvgText(ctx, mPos.x() + mSize.x() / 2, |
131 | mPos.y() + hh / 2, mTitle.c_str(), nullptr); |
132 | |
133 | nvgFontBlur(ctx, 0); |
134 | nvgFillColor(ctx, mFocused ? mTheme->mWindowTitleFocused |
135 | : mTheme->mWindowTitleUnfocused); |
136 | nvgText(ctx, mPos.x() + mSize.x() / 2, mPos.y() + hh / 2 - 1, |
137 | mTitle.c_str(), nullptr); |
138 | } |
139 | |
140 | nvgRestore(ctx); |
141 | Widget::draw(ctx); |
142 | } |
143 | |
144 | void Window::dispose() { |
145 | Widget *widget = this; |
146 | while (widget->parent()) |
147 | widget = widget->parent(); |
148 | ((Screen *) widget)->disposeWindow(this); |
149 | } |
150 | |
151 | void Window::center() { |
152 | Widget *widget = this; |
153 | while (widget->parent()) |
154 | widget = widget->parent(); |
155 | ((Screen *) widget)->centerWindow(this); |
156 | } |
157 | |
158 | bool Window::mouseDragEvent(const Vector2i &, const Vector2i &rel, |
159 | int button, int /* modifiers */) { |
160 | if (mDrag && (button & (1 << GLFW_MOUSE_BUTTON_1)) != 0) { |
161 | mPos += rel; |
162 | mPos = mPos.cwiseMax(Vector2i::Zero()); |
163 | mPos = mPos.cwiseMin(parent()->size() - mSize); |
164 | return true; |
165 | } |
166 | return false; |
167 | } |
168 | |
169 | bool Window::mouseButtonEvent(const Vector2i &p, int button, bool down, int modifiers) { |
170 | if (Widget::mouseButtonEvent(p, button, down, modifiers)) |
171 | return true; |
172 | if (button == GLFW_MOUSE_BUTTON_1) { |
173 | mDrag = down && (p.y() - mPos.y()) < mTheme->mWindowHeaderHeight; |
174 | return true; |
175 | } |
176 | return false; |
177 | } |
178 | |
179 | bool Window::scrollEvent(const Vector2i &p, const Vector2f &rel) { |
180 | Widget::scrollEvent(p, rel); |
181 | return true; |
182 | } |
183 | |
184 | void Window::refreshRelativePlacement() { |
185 | /* Overridden in \ref Popup */ |
186 | } |
187 | |
188 | void Window::save(Serializer &s) const { |
189 | Widget::save(s); |
190 | s.set("title" , mTitle); |
191 | s.set("modal" , mModal); |
192 | } |
193 | |
194 | bool Window::load(Serializer &s) { |
195 | if (!Widget::load(s)) return false; |
196 | if (!s.get("title" , mTitle)) return false; |
197 | if (!s.get("modal" , mModal)) return false; |
198 | mDrag = false; |
199 | return true; |
200 | } |
201 | |
202 | NAMESPACE_END(nanogui) |
203 | |