| 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 | |