1 | // Aseprite UI Library |
2 | // Copyright (C) 2018-2021 Igara Studio S.A. |
3 | // Copyright (C) 2001-2018 David Capello |
4 | // |
5 | // This file is released under the terms of the MIT license. |
6 | // Read LICENSE.txt for more information. |
7 | |
8 | #ifdef HAVE_CONFIG_H |
9 | #include "config.h" |
10 | #endif |
11 | |
12 | #include "ui/system.h" |
13 | |
14 | #include "base/thread.h" |
15 | #include "gfx/point.h" |
16 | #include "os/event.h" |
17 | #include "os/event_queue.h" |
18 | #include "os/surface.h" |
19 | #include "os/system.h" |
20 | #include "os/window.h" |
21 | #include "ui/clipboard_delegate.h" |
22 | #include "ui/cursor.h" |
23 | #include "ui/intern.h" |
24 | #include "ui/manager.h" |
25 | #include "ui/message.h" |
26 | #include "ui/overlay.h" |
27 | #include "ui/overlay_manager.h" |
28 | #include "ui/scale.h" |
29 | #include "ui/theme.h" |
30 | #include "ui/widget.h" |
31 | |
32 | namespace ui { |
33 | |
34 | // This is used to check if calls to UI layer are made from the non-UI |
35 | // thread. (Which might be catastrofic.) |
36 | base::thread::native_id_type main_gui_thread; |
37 | |
38 | // Multiple displays (create one os::Window for each ui::Window) |
39 | bool multi_displays = false; |
40 | |
41 | // Current mouse cursor type. |
42 | static CursorType mouse_cursor_type = kOutsideDisplay; |
43 | static const Cursor* mouse_cursor_custom = nullptr; |
44 | static const Cursor* mouse_cursor = nullptr; |
45 | static Display* mouse_display = nullptr; |
46 | static OverlayRef mouse_cursor_overlay = nullptr; |
47 | static bool use_native_mouse_cursor = true; |
48 | static bool support_native_custom_cursor = false; |
49 | |
50 | // Mouse information |
51 | static int mouse_cursor_scale = 1; |
52 | static int mouse_scares = 0; |
53 | |
54 | static void update_mouse_overlay(const Cursor* cursor) |
55 | { |
56 | mouse_cursor = cursor; |
57 | |
58 | if (mouse_cursor && mouse_scares == 0) { |
59 | if (!mouse_cursor_overlay) { |
60 | ASSERT(mouse_display); |
61 | mouse_cursor_overlay = base::make_ref<Overlay>( |
62 | mouse_display, |
63 | mouse_cursor->surface(), |
64 | mouse_display->nativeWindow()->pointFromScreen(get_mouse_position()), |
65 | Overlay::MouseZOrder); |
66 | |
67 | OverlayManager::instance()->addOverlay(mouse_cursor_overlay); |
68 | } |
69 | else { |
70 | mouse_cursor_overlay->setSurface(mouse_cursor->surface()); |
71 | update_cursor_overlay(); |
72 | } |
73 | } |
74 | else if (mouse_cursor_overlay) { |
75 | OverlayManager::instance()->removeOverlay(mouse_cursor_overlay); |
76 | mouse_cursor_overlay->setSurface(nullptr); |
77 | mouse_cursor_overlay.reset(); |
78 | } |
79 | } |
80 | |
81 | static bool set_native_cursor_on_all_displays(Display* display, |
82 | const Cursor* cursor) |
83 | { |
84 | bool result = false; |
85 | while (display) { |
86 | os::Window* nativeWindow = display->nativeWindow(); |
87 | |
88 | if (cursor && cursor->surface()) { |
89 | // The cursor surface is already scaled by guiscale(), we scale |
90 | // the cursor by the os::Window scale and mouse scale. |
91 | const int scale = nativeWindow->scale() * mouse_cursor_scale; |
92 | if (auto osCursor = cursor->nativeCursor(scale)) |
93 | result |= nativeWindow->setCursor(osCursor); |
94 | } |
95 | else if (mouse_cursor_type == kOutsideDisplay) { |
96 | result |= nativeWindow->setCursor(os::NativeCursor::Arrow); |
97 | } |
98 | else { |
99 | result |= nativeWindow->setCursor(os::NativeCursor::Hidden); |
100 | } |
101 | display = display->parentDisplay(); |
102 | } |
103 | return result; |
104 | } |
105 | |
106 | static bool set_native_cursor_on_all_displays(Display* display, |
107 | const os::NativeCursor cursor) |
108 | { |
109 | bool result = false; |
110 | while (display) { |
111 | os::Window* nativeWindow = display->nativeWindow(); |
112 | if (mouse_cursor_type == kOutsideDisplay) { |
113 | result |= nativeWindow->setCursor(os::NativeCursor::Arrow); |
114 | } |
115 | else { |
116 | result |= nativeWindow->setCursor(cursor); |
117 | } |
118 | display = display->parentDisplay(); |
119 | } |
120 | return result; |
121 | } |
122 | |
123 | static bool update_custom_native_cursor(const Cursor* cursor) |
124 | { |
125 | bool result = false; |
126 | |
127 | // Check if we can use a custom native mouse in this platform |
128 | if (support_native_custom_cursor && |
129 | mouse_display) { |
130 | result = set_native_cursor_on_all_displays(mouse_display, cursor); |
131 | } |
132 | |
133 | return result; |
134 | } |
135 | |
136 | static void update_mouse_cursor() |
137 | { |
138 | os::NativeCursor nativeCursor = os::NativeCursor::Hidden; |
139 | const Cursor* cursor = nullptr; |
140 | |
141 | if (use_native_mouse_cursor || |
142 | mouse_cursor_type == kOutsideDisplay) { |
143 | switch (mouse_cursor_type) { |
144 | case ui::kOutsideDisplay: |
145 | nativeCursor = os::NativeCursor::Arrow; |
146 | break; |
147 | case ui::kNoCursor: break; |
148 | case ui::kArrowCursor: |
149 | case ui::kArrowPlusCursor: |
150 | nativeCursor = os::NativeCursor::Arrow; |
151 | break; |
152 | case ui::kCrosshairCursor: |
153 | nativeCursor = os::NativeCursor::Crosshair; |
154 | break; |
155 | case ui::kForbiddenCursor: |
156 | nativeCursor = os::NativeCursor::Forbidden; |
157 | break; |
158 | case ui::kHandCursor: |
159 | nativeCursor = os::NativeCursor::Link; |
160 | break; |
161 | case ui::kScrollCursor: |
162 | case ui::kMoveCursor: |
163 | nativeCursor = os::NativeCursor::Move; |
164 | break; |
165 | case ui::kSizeNSCursor: nativeCursor = os::NativeCursor::SizeNS; break; |
166 | case ui::kSizeWECursor: nativeCursor = os::NativeCursor::SizeWE; break; |
167 | case ui::kSizeNCursor: nativeCursor = os::NativeCursor::SizeN; break; |
168 | case ui::kSizeNECursor: nativeCursor = os::NativeCursor::SizeNE; break; |
169 | case ui::kSizeECursor: nativeCursor = os::NativeCursor::SizeE; break; |
170 | case ui::kSizeSECursor: nativeCursor = os::NativeCursor::SizeSE; break; |
171 | case ui::kSizeSCursor: nativeCursor = os::NativeCursor::SizeS; break; |
172 | case ui::kSizeSWCursor: nativeCursor = os::NativeCursor::SizeSW; break; |
173 | case ui::kSizeWCursor: nativeCursor = os::NativeCursor::SizeW; break; |
174 | case ui::kSizeNWCursor: nativeCursor = os::NativeCursor::SizeNW; break; |
175 | } |
176 | } |
177 | |
178 | // Set native cursor |
179 | if (mouse_display) { |
180 | bool ok = set_native_cursor_on_all_displays(mouse_display, nativeCursor); |
181 | |
182 | // It looks like the specific native cursor is not supported, |
183 | // so we can should use the internal overlay (even when we |
184 | // have use_native_mouse_cursor flag enabled). |
185 | if (!ok) |
186 | nativeCursor = os::NativeCursor::Hidden; |
187 | } |
188 | |
189 | // Use a custom cursor |
190 | if (nativeCursor == os::NativeCursor::Hidden && |
191 | mouse_cursor_type != ui::kOutsideDisplay) { |
192 | if (get_theme() && mouse_cursor_type != ui::kCustomCursor) |
193 | cursor = get_theme()->getStandardCursor(mouse_cursor_type); |
194 | else |
195 | cursor = mouse_cursor_custom; |
196 | } |
197 | |
198 | // Try to use a custom native cursor if it's possible |
199 | if (mouse_display && |
200 | nativeCursor == os::NativeCursor::Hidden && |
201 | !update_custom_native_cursor(cursor)) { |
202 | // Or an overlay as last resource |
203 | update_mouse_overlay(cursor); |
204 | } |
205 | } |
206 | |
207 | static UISystem* g_instance = nullptr; |
208 | |
209 | // static |
210 | UISystem* UISystem::instance() |
211 | { |
212 | return g_instance; |
213 | } |
214 | |
215 | UISystem::UISystem() |
216 | : m_clipboardDelegate(nullptr) |
217 | { |
218 | ASSERT(!g_instance); |
219 | g_instance = this; |
220 | |
221 | main_gui_thread = base::this_thread::native_id(); |
222 | mouse_cursor_type = kOutsideDisplay; |
223 | support_native_custom_cursor = |
224 | ((os::instance() && |
225 | (int(os::instance()->capabilities()) & |
226 | int(os::Capabilities::CustomMouseCursor))) ? |
227 | true: false); |
228 | |
229 | details::initWidgets(); |
230 | } |
231 | |
232 | UISystem::~UISystem() |
233 | { |
234 | // finish theme |
235 | set_theme(nullptr, guiscale()); |
236 | |
237 | details::exitWidgets(); |
238 | |
239 | _internal_set_mouse_display(nullptr); |
240 | if (!update_custom_native_cursor(nullptr)) |
241 | update_mouse_overlay(nullptr); |
242 | |
243 | OverlayManager::destroyInstance(); |
244 | |
245 | ASSERT(g_instance == this); |
246 | g_instance = nullptr; |
247 | } |
248 | |
249 | void _internal_set_mouse_display(Display* display) |
250 | { |
251 | if (display != mouse_display) { |
252 | mouse_display = display; |
253 | if (mouse_display) |
254 | update_mouse_cursor(); |
255 | } |
256 | } |
257 | |
258 | void set_multiple_displays(bool multi) |
259 | { |
260 | multi_displays = multi; |
261 | } |
262 | |
263 | bool get_multiple_displays() |
264 | { |
265 | return multi_displays; |
266 | } |
267 | |
268 | void set_clipboard_text(const std::string& text) |
269 | { |
270 | ASSERT(g_instance); |
271 | ClipboardDelegate* delegate = g_instance->clipboardDelegate(); |
272 | if (delegate) |
273 | delegate->setClipboardText(text); |
274 | } |
275 | |
276 | bool get_clipboard_text(std::string& text) |
277 | { |
278 | ASSERT(g_instance); |
279 | ClipboardDelegate* delegate = g_instance->clipboardDelegate(); |
280 | if (delegate) |
281 | return delegate->getClipboardText(text); |
282 | else |
283 | return false; |
284 | } |
285 | |
286 | void update_cursor_overlay() |
287 | { |
288 | if (mouse_cursor_overlay != nullptr && mouse_scares == 0) { |
289 | gfx::Point newPos = |
290 | mouse_display->nativeWindow()->pointFromScreen(get_mouse_position()) |
291 | - mouse_cursor->focus(); |
292 | |
293 | if (newPos != mouse_cursor_overlay->position()) { |
294 | mouse_cursor_overlay->moveOverlay(newPos); |
295 | } |
296 | } |
297 | } |
298 | |
299 | void set_use_native_cursors(bool state) |
300 | { |
301 | use_native_mouse_cursor = state; |
302 | update_mouse_cursor(); |
303 | } |
304 | |
305 | CursorType get_mouse_cursor() |
306 | { |
307 | return mouse_cursor_type; |
308 | } |
309 | |
310 | void set_mouse_cursor(CursorType type, const Cursor* cursor) |
311 | { |
312 | if (mouse_cursor_type == type && |
313 | mouse_cursor_custom == cursor) |
314 | return; |
315 | |
316 | mouse_cursor_type = type; |
317 | mouse_cursor_custom = cursor; |
318 | update_mouse_cursor(); |
319 | } |
320 | |
321 | void set_mouse_cursor_scale(const int newScale) |
322 | { |
323 | mouse_cursor_scale = newScale; |
324 | update_mouse_cursor(); |
325 | } |
326 | |
327 | void set_mouse_cursor_reset_info() |
328 | { |
329 | mouse_cursor_type = kCustomCursor; |
330 | mouse_cursor_custom = nullptr; |
331 | } |
332 | |
333 | void hide_mouse_cursor() |
334 | { |
335 | ASSERT(mouse_scares >= 0); |
336 | mouse_scares++; |
337 | } |
338 | |
339 | void show_mouse_cursor() |
340 | { |
341 | ASSERT(mouse_scares > 0); |
342 | mouse_scares--; |
343 | |
344 | if (mouse_scares == 0) |
345 | update_mouse_cursor(); |
346 | } |
347 | |
348 | void _internal_no_mouse_position() |
349 | { |
350 | if (!update_custom_native_cursor(nullptr)) |
351 | update_mouse_overlay(nullptr); |
352 | } |
353 | |
354 | gfx::Point get_mouse_position() |
355 | { |
356 | return os::instance()->mousePosition(); |
357 | } |
358 | |
359 | void set_mouse_position(const gfx::Point& newPos, |
360 | Display* display) |
361 | { |
362 | if (display && display != mouse_display) |
363 | _internal_set_mouse_display(display); |
364 | |
365 | if (display) |
366 | display->nativeWindow()->setMousePosition(newPos); |
367 | else |
368 | os::instance()->setMousePosition(newPos); |
369 | } |
370 | |
371 | void execute_from_ui_thread(std::function<void()>&& func) |
372 | { |
373 | // Queue the event |
374 | os::Event ev; |
375 | ev.setType(os::Event::Callback); |
376 | ev.setCallback(std::move(func)); |
377 | os::queue_event(ev); |
378 | } |
379 | |
380 | bool is_ui_thread() |
381 | { |
382 | return (main_gui_thread == base::this_thread::native_id()); |
383 | } |
384 | |
385 | #ifdef _DEBUG |
386 | void assert_ui_thread() |
387 | { |
388 | ASSERT(is_ui_thread()); |
389 | } |
390 | #endif |
391 | |
392 | } // namespace ui |
393 | |