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
32namespace 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.)
36base::thread::native_id_type main_gui_thread;
37
38// Multiple displays (create one os::Window for each ui::Window)
39bool multi_displays = false;
40
41// Current mouse cursor type.
42static CursorType mouse_cursor_type = kOutsideDisplay;
43static const Cursor* mouse_cursor_custom = nullptr;
44static const Cursor* mouse_cursor = nullptr;
45static Display* mouse_display = nullptr;
46static OverlayRef mouse_cursor_overlay = nullptr;
47static bool use_native_mouse_cursor = true;
48static bool support_native_custom_cursor = false;
49
50// Mouse information
51static int mouse_cursor_scale = 1;
52static int mouse_scares = 0;
53
54static 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
81static 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
106static 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
123static 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
136static 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
207static UISystem* g_instance = nullptr;
208
209// static
210UISystem* UISystem::instance()
211{
212 return g_instance;
213}
214
215UISystem::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
232UISystem::~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
249void _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
258void set_multiple_displays(bool multi)
259{
260 multi_displays = multi;
261}
262
263bool get_multiple_displays()
264{
265 return multi_displays;
266}
267
268void 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
276bool 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
286void 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
299void set_use_native_cursors(bool state)
300{
301 use_native_mouse_cursor = state;
302 update_mouse_cursor();
303}
304
305CursorType get_mouse_cursor()
306{
307 return mouse_cursor_type;
308}
309
310void 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
321void set_mouse_cursor_scale(const int newScale)
322{
323 mouse_cursor_scale = newScale;
324 update_mouse_cursor();
325}
326
327void set_mouse_cursor_reset_info()
328{
329 mouse_cursor_type = kCustomCursor;
330 mouse_cursor_custom = nullptr;
331}
332
333void hide_mouse_cursor()
334{
335 ASSERT(mouse_scares >= 0);
336 mouse_scares++;
337}
338
339void show_mouse_cursor()
340{
341 ASSERT(mouse_scares > 0);
342 mouse_scares--;
343
344 if (mouse_scares == 0)
345 update_mouse_cursor();
346}
347
348void _internal_no_mouse_position()
349{
350 if (!update_custom_native_cursor(nullptr))
351 update_mouse_overlay(nullptr);
352}
353
354gfx::Point get_mouse_position()
355{
356 return os::instance()->mousePosition();
357}
358
359void 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
371void 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
380bool is_ui_thread()
381{
382 return (main_gui_thread == base::this_thread::native_id());
383}
384
385#ifdef _DEBUG
386void assert_ui_thread()
387{
388 ASSERT(is_ui_thread());
389}
390#endif
391
392} // namespace ui
393