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
19NAMESPACE_BEGIN(nanogui)
20
21Window::Window(Widget *parent, const std::string &title)
22 : Widget(parent), mTitle(title), mButtonPanel(nullptr), mModal(false), mDrag(false) { }
23
24Vector2i 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
41Widget *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
49void 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
66void 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 headerPaint = 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
144void Window::dispose() {
145 Widget *widget = this;
146 while (widget->parent())
147 widget = widget->parent();
148 ((Screen *) widget)->disposeWindow(this);
149}
150
151void Window::center() {
152 Widget *widget = this;
153 while (widget->parent())
154 widget = widget->parent();
155 ((Screen *) widget)->centerWindow(this);
156}
157
158bool 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
169bool 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
179bool Window::scrollEvent(const Vector2i &p, const Vector2f &rel) {
180 Widget::scrollEvent(p, rel);
181 return true;
182}
183
184void Window::refreshRelativePlacement() {
185 /* Overridden in \ref Popup */
186}
187
188void Window::save(Serializer &s) const {
189 Widget::save(s);
190 s.set("title", mTitle);
191 s.set("modal", mModal);
192}
193
194bool 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
202NAMESPACE_END(nanogui)
203