1// LAF Library
2// Copyright (c) 2021 Igara Studio S.A.
3//
4// This file is released under the terms of the MIT license.
5// Read LICENSE.txt for more information.
6
7#include "gfx/path.h"
8#include "os/os.h"
9
10using Hit = os::Hit;
11
12const int kTitleBarSize = 32;
13const int kButtonSize = 32;
14const int kResizeBorder = 8;
15
16const gfx::Color kTitleBarBase = gfx::rgba(100, 100, 200);
17const gfx::Color kTitleBarHigh = gfx::rgba(200, 200, 220);
18const gfx::Color kTitleBarText = gfx::rgba(25, 25, 50);
19const gfx::Color kContentBase = gfx::rgba(40, 40, 35);
20const gfx::Color kContentHigh = gfx::rgba(50, 55, 45);
21const gfx::Color kContentText = gfx::rgba(105, 115, 85);
22const gfx::Color kContentEdge = gfx::rgba(200, 200, 100);
23
24Hit hit_test(os::Window* window,
25 const gfx::Point& pos)
26{
27 // For a full screen window, we are always in the content area
28 if (window->isFullscreen())
29 return Hit::Content;
30
31 gfx::Rect rc = window->bounds();
32 gfx::Rect rc2 = rc;
33 rc2.shrink(kResizeBorder);
34
35 // Mouse in client area
36 if (!rc.contains(pos)) {
37 return Hit::Content;
38 }
39 // Resize edges
40 else if (!rc2.contains(pos) &&
41 // macOS cannot start the resizing actions (just the window movement)
42 os::instance()->hasCapability(os::Capabilities::CanStartWindowResize)) {
43 if (pos.y < kResizeBorder) {
44 if (pos.x < kResizeBorder) return Hit::TopLeft;
45 else if (pos.x > rc.x2()-kResizeBorder) return Hit::TopRight;
46 else return Hit::Top;
47 }
48 else if (pos.y > rc.y2()-kResizeBorder) {
49 if (pos.x < kResizeBorder) return Hit::BottomLeft;
50 else if (pos.x > rc.x2()-kResizeBorder) return Hit::BottomRight;
51 else return Hit::Bottom;
52 }
53 else {
54 if (pos.x < rc.w/2) return Hit::Left;
55 return Hit::Right;
56 }
57 }
58 else if (pos.y <= kTitleBarSize) {
59 if (pos.x > rc.x2()-kButtonSize) return Hit::CloseButton;
60 else if (pos.x > rc.x2()-kButtonSize*2) return Hit::MaximizeButton;
61 else if (pos.x > rc.x2()-kButtonSize*3) return Hit::MinimizeButton;
62 else return Hit::TitleBar;
63 }
64 else {
65 return Hit::Content;
66 }
67}
68
69void draw_button(os::Surface* surface, int x, Hit button, const Hit hit)
70{
71 os::Paint p;
72 gfx::Rect box(x, 0, kButtonSize, kButtonSize);
73
74 p.color(hit == button ? kTitleBarHigh: kTitleBarBase);
75 p.style(os::Paint::Fill);
76 surface->drawRect(box, p);
77
78 p.color(gfx::rgba(25, 25, 50));
79 p.style(os::Paint::Stroke);
80 surface->drawRect(gfx::Rect(x, 0, 2, kButtonSize), p);
81
82 // Draw icon
83 box.shrink(11);
84 box.inflate(1, 1);
85 p.strokeWidth(1.5f);
86 p.antialias(true);
87 switch (button) {
88 case Hit::MinimizeButton:
89 surface->drawRect(gfx::Rect(box.x, box.y2()-2, box.w, 1), p);
90 break;
91 case Hit::MaximizeButton:
92 surface->drawRect(gfx::Rect(box), p);
93 break;
94 case Hit::CloseButton: {
95 gfx::Path path;
96 path.moveTo(box.x, box.y);
97 path.lineTo(box.x2(), box.y2());
98 path.moveTo(box.x2(), box.y);
99 path.lineTo(box.x, box.y2());
100 surface->drawPath(path, p);
101 break;
102 }
103 }
104}
105
106void draw_window(os::Window* window, const Hit hit)
107{
108 os::Surface* surface = window->surface();
109 os::SurfaceLock lock(surface);
110 gfx::Rect rc = surface->bounds();
111 gfx::Rect rc2 = rc;
112 os::Paint p;
113 p.style(os::Paint::Fill);
114
115 // Draw custom title bar area
116 if (!window->isFullscreen()) {
117 rc2.h = kTitleBarSize;
118
119 p.color(hit == Hit::TitleBar ? kTitleBarHigh: kTitleBarBase);
120 surface->drawRect(gfx::Rect(rc2).inflate(-kButtonSize*3, 0), p);
121
122 rc2.y += kTitleBarSize/2 - 10;
123
124 p.color(kTitleBarText);
125 os::draw_text(surface, nullptr, "Custom Window",
126 rc2.center(), &p, os::TextAlign::Center);
127
128 // Draw buttons
129 draw_button(surface, rc.x2()-kButtonSize, Hit::CloseButton, hit);
130 draw_button(surface, rc.x2()-kButtonSize*2, Hit::MaximizeButton, hit);
131 draw_button(surface, rc.x2()-kButtonSize*3, Hit::MinimizeButton, hit);
132
133 // Client area
134 rc2 = rc;
135 rc2.y += kTitleBarSize;
136 rc2.h -= kTitleBarSize;
137 }
138
139 // Draw client area
140 p.color(hit == Hit::Content ? kContentHigh: kContentBase);
141 surface->drawRect(rc2, p);
142
143 p.style(os::Paint::Style::Stroke);
144 p.color(kContentEdge);
145 surface->drawRect(rc2, p);
146
147 p.style(os::Paint::Style::Fill);
148 p.color(kContentText);
149 os::draw_text(surface, nullptr, "Content Rect",
150 rc2.center(), &p, os::TextAlign::Center);
151
152 if (window->isFullscreen()) {
153 auto pos = rc2.center();
154 pos.y += 24;
155 os::draw_text(surface, nullptr, "(F key or F11 to exit full screen)",
156 pos, &p, os::TextAlign::Center);
157 }
158
159 if (window->isVisible())
160 window->invalidateRegion(gfx::Region(rc));
161 else
162 window->setVisible(true);
163}
164
165bool update_hit(os::Window* window,
166 const os::Event& ev,
167 Hit& hit)
168{
169 Hit newHit = hit_test(window, ev.position());
170 if (newHit != hit) {
171 hit = newHit;
172 return true;
173 }
174 else
175 return false;
176}
177
178os::WindowRef create_window()
179{
180 os::WindowSpec spec;
181 spec.contentRect(gfx::Rect(32, 32, 400, 300));
182 spec.titled(false);
183 spec.borderless(true);
184
185 os::WindowRef window = os::instance()->makeWindow(spec);
186 window->setTitle("Custom Window");
187 window->handleHitTest = hit_test;
188
189 return window;
190}
191
192void handle_mouse_move(os::Window* window,
193 const Hit hit)
194{
195 os::NativeCursor cursor = os::NativeCursor::Arrow;
196 switch (hit) {
197 case Hit::Content: cursor = os::NativeCursor::Arrow; break;
198 case Hit::TitleBar: cursor = os::NativeCursor::Move; break;
199 case Hit::TopLeft: cursor = os::NativeCursor::SizeNW; break;
200 case Hit::Top: cursor = os::NativeCursor::SizeN; break;
201 case Hit::TopRight: cursor = os::NativeCursor::SizeNE; break;
202 case Hit::Left: cursor = os::NativeCursor::SizeW; break;
203 case Hit::Right: cursor = os::NativeCursor::SizeE; break;
204 case Hit::BottomLeft: cursor = os::NativeCursor::SizeSW; break;
205 case Hit::Bottom: cursor = os::NativeCursor::SizeS; break;
206 case Hit::BottomRight: cursor = os::NativeCursor::SizeSE; break;
207 default: break;
208 }
209 window->setCursor(cursor);
210}
211
212bool handle_mouse_down(os::Window* window,
213 const os::Event& ev,
214 const Hit hit)
215{
216 os::NativeCursor cursor = os::NativeCursor::Arrow;
217 os::WindowAction action = os::WindowAction::Move;
218 switch (hit) {
219 case Hit::Content: return true;
220 case Hit::TitleBar: action = os::WindowAction::Move; break;
221 case Hit::TopLeft: action = os::WindowAction::ResizeFromTopLeft; break;
222 case Hit::Top: action = os::WindowAction::ResizeFromTop; break;
223 case Hit::TopRight: action = os::WindowAction::ResizeFromTopRight; break;
224 case Hit::Left: action = os::WindowAction::ResizeFromLeft; break;
225 case Hit::Right: action = os::WindowAction::ResizeFromRight; break;
226 case Hit::BottomLeft: action = os::WindowAction::ResizeFromBottomLeft; break;
227 case Hit::Bottom: action = os::WindowAction::ResizeFromBottom; break;
228 case Hit::BottomRight: action = os::WindowAction::ResizeFromBottomRight; break;
229 case Hit::MinimizeButton: window->minimize(); return true;
230 case Hit::MaximizeButton: window->maximize(); return true;
231 case Hit::CloseButton: return false;
232 }
233 window->performWindowAction(action, &ev);
234 return true;
235}
236
237int app_main(int argc, char* argv[])
238{
239 os::SystemRef system = os::make_system();
240 system->setAppMode(os::AppMode::GUI);
241
242 os::WindowRef window = create_window();
243 Hit hit = Hit::None; // Current area which the mouse cursor hits
244 window->activate();
245
246 system->handleWindowResize = [&](os::Window* w) { draw_window(w, hit); };
247 system->finishLaunching();
248 system->activateApp();
249
250 os::EventQueue* queue = system->eventQueue();
251 bool running = true;
252 bool redraw = true;
253 while (running) {
254 if (redraw) {
255 redraw = false;
256 draw_window(window.get(), hit);
257 }
258
259 os::Event ev;
260 queue->getEvent(ev);
261
262 switch (ev.type()) {
263
264 case os::Event::CloseApp:
265 case os::Event::CloseWindow:
266 running = false;
267 break;
268
269 case os::Event::KeyDown:
270 switch (ev.scancode()) {
271 case os::kKeyEsc:
272 running = false;
273 break;
274 case os::kKeyF:
275 case os::kKeyF11:
276 window->setFullscreen(!window->isFullscreen());
277 break;
278 }
279 break;
280
281 case os::Event::MouseEnter:
282 case os::Event::MouseMove:
283 redraw = update_hit(window.get(), ev, hit);
284 handle_mouse_move(window.get(), hit);
285 break;
286
287 case os::Event::MouseDown:
288 redraw = update_hit(window.get(), ev, hit);
289 if (!handle_mouse_down(window.get(), ev, hit))
290 running = false;
291 break;
292
293 case os::Event::MouseLeave:
294 redraw = update_hit(window.get(), ev, hit);
295 break;
296
297 default:
298 break;
299 }
300 }
301
302 return 0;
303}
304