From 12d57e8e7b3bb90fad779b0d6e4071df9c887e4d Mon Sep 17 00:00:00 2001 From: Mehmet Ali Date: Mon, 18 Jul 2022 09:44:48 +0300 Subject: [PATCH] sokol: add mouse cursor support (#15111) --- examples/gg/cursor.v | 24 +++ thirdparty/sokol/sokol_app.h | 333 +++++++++++++++++++++++++++---- vlib/sokol/sapp/enums.v | 14 ++ vlib/sokol/sapp/sapp.c.v | 6 + vlib/sokol/sapp/sapp_funcs.c.v | 3 + vlib/sokol/sapp/sapp_structs.c.v | 1 - 6 files changed, 344 insertions(+), 37 deletions(-) create mode 100644 examples/gg/cursor.v diff --git a/examples/gg/cursor.v b/examples/gg/cursor.v new file mode 100644 index 000000000..c883e9b04 --- /dev/null +++ b/examples/gg/cursor.v @@ -0,0 +1,24 @@ +module main + +import gg +import gx +import sokol.sapp + +fn main() { + gg.new_context( + bg_color: gx.white + window_title: 'Cursor' + frame_fn: frame + init_fn: init + ).run() +} + +fn init(mut ctx gg.Context) { + sapp.set_mouse_cursor(.ibeam) +} + +fn frame(mut ctx gg.Context) { + ctx.begin() + ctx.draw_text_def(10, 25, 'Your cursor should be an I shaped beam.') + ctx.end() +} diff --git a/thirdparty/sokol/sokol_app.h b/thirdparty/sokol/sokol_app.h index b46680a54..450b6e3e3 100644 --- a/thirdparty/sokol/sokol_app.h +++ b/thirdparty/sokol/sokol_app.h @@ -131,13 +131,13 @@ SUSPENDED | --- | --- | --- | YES | YES | YES | --- | TODO RESUMED | --- | --- | --- | YES | YES | YES | --- | TODO QUIT_REQUESTED | YES | YES | YES | --- | --- | --- | TODO | YES - UPDATE_CURSOR | YES | YES | TODO | --- | --- | TODO | --- | TODO IME | TODO | TODO? | TODO | ??? | TODO | --- | ??? | ??? key repeat flag | YES | YES | YES | --- | --- | YES | TODO | YES windowed | YES | YES | YES | --- | --- | YES | TODO | YES fullscreen | YES | YES | YES | YES | YES | YES | TODO | --- - mouse hide | YES | YES | YES | --- | --- | YES | TODO | TODO + mouse hide | YES | YES | YES | --- | --- | YES | TODO | YES mouse lock | YES | YES | YES | --- | --- | TODO | TODO | YES + set cursor type | YES | YES | YES | --- | --- | YES | TODO | YES screen keyboard | --- | --- | --- | YES | TODO | TODO | --- | YES swap interval | YES | YES | YES | YES | TODO | --- | TODO | YES high-dpi | YES | YES | TODO | YES | YES | YES | TODO | YES @@ -369,6 +369,23 @@ "Really Quit?" dialog box). Note that the cleanup-callback isn't guaranteed to be called on the web and mobile platforms. + MOUSE CURSOR TYPE AND VISIBILITY + ================================ + You can show and hide the mouse cursor with + void sapp_show_mouse(bool show) + And to get the current shown status: + bool sapp_mouse_shown(void) + NOTE that hiding the mouse cursor is different and independent from + the MOUSE/POINTER LOCK feature which will also hide the mouse pointer when + active (MOUSE LOCK is described below). + To change the mouse cursor to one of several predefined types, call + the function: + void sapp_set_mouse_cursor(sapp_mouse_cursor cursor) + Setting the default mouse cursor SAPP_MOUSECURSOR_DEFAULT will restore + the standard look. + To get the currently active mouse cursor type, call: + sapp_mouse_cursor sapp_get_mouse_cursor(void) + MOUSE LOCK (AKA POINTER LOCK, AKA MOUSE CAPTURE) ================================================ In normal mouse mode, no mouse movement events are reported when the @@ -970,9 +987,6 @@ - sapp_desc needs a bool whether to initialize depth-stencil surface - GL context initialization needs more control (at least what GL version to initialize) - application icon - - the UPDATE_CURSOR event currently behaves differently between Win32 and OSX - (Win32 sends the event each frame when the mouse moves and is inside the window - client area, OSX sends it only once when the mouse enters the client area) - the Android implementation calls cleanup_cb() and destroys the egl context in onDestroy at the latest but should do it earlier, in onStop, as an app is "killable" after onStop on Android Honeycomb and later (it can't be done at the moment as the app may be started @@ -1064,7 +1078,6 @@ typedef enum sapp_event_type { SAPP_EVENTTYPE_UNFOCUSED, SAPP_EVENTTYPE_SUSPENDED, SAPP_EVENTTYPE_RESUMED, - SAPP_EVENTTYPE_UPDATE_CURSOR, SAPP_EVENTTYPE_QUIT_REQUESTED, SAPP_EVENTTYPE_CLIPBOARD_PASTED, SAPP_EVENTTYPE_FILES_DROPPED, @@ -1362,7 +1375,6 @@ typedef struct sapp_desc { bool fullscreen; // whether the window should be created in fullscreen mode bool alpha; // whether the framebuffer should have an alpha channel (ignored on some platforms) const char* window_title; // the window title as UTF-8 encoded string - bool user_cursor; // if true, user is expected to manage cursor image in SAPP_EVENTTYPE_UPDATE_CURSOR bool enable_clipboard; // enable clipboard access, default is false int clipboard_size; // max size of clipboard content in bytes bool enable_dragndrop; // enable file dropping (drag'n'drop), default is false @@ -1413,6 +1425,25 @@ typedef struct sapp_html5_fetch_request { void* user_data; /* optional userdata pointer */ } sapp_html5_fetch_request; +/* + sapp_mouse_cursor + Predefined cursor image definitions, set with sapp_set_mouse_cursor(sapp_mouse_cursor cursor) +*/ +typedef enum sapp_mouse_cursor { + SAPP_MOUSECURSOR_DEFAULT = 0, // equivalent with system default cursor + SAPP_MOUSECURSOR_ARROW, + SAPP_MOUSECURSOR_IBEAM, + SAPP_MOUSECURSOR_CROSSHAIR, + SAPP_MOUSECURSOR_POINTING_HAND, + SAPP_MOUSECURSOR_RESIZE_EW, + SAPP_MOUSECURSOR_RESIZE_NS, + SAPP_MOUSECURSOR_RESIZE_NWSE, + SAPP_MOUSECURSOR_RESIZE_NESW, + SAPP_MOUSECURSOR_RESIZE_ALL, + SAPP_MOUSECURSOR_NOT_ALLOWED, + _SAPP_MOUSECURSOR_NUM, +} sapp_mouse_cursor; + /* user-provided functions */ extern sapp_desc sokol_main(int argc, char* argv[]); @@ -1452,6 +1483,10 @@ SOKOL_APP_API_DECL bool sapp_mouse_shown(void); SOKOL_APP_API_DECL void sapp_lock_mouse(bool lock); /* return true if in mouse-pointer-lock mode (this may toggle a few frames later) */ SOKOL_APP_API_DECL bool sapp_mouse_locked(void); +/* set mouse cursor type */ +SOKOL_APP_API_DECL void sapp_set_mouse_cursor(sapp_mouse_cursor cursor); +/* get current mouse cursor type */ +SOKOL_APP_API_DECL sapp_mouse_cursor sapp_get_mouse_cursor(void); /* return the userdata pointer optionally provided in sapp_desc */ SOKOL_APP_API_DECL void* sapp_userdata(void); /* return a copy of the sapp_desc structure */ @@ -1796,6 +1831,7 @@ inline void sapp_run(const sapp_desc& desc) { return sapp_run(&desc); } #include #include #include + #include /* XC_* font cursors */ #include /* CARD32 */ #include /* dlopen, dlsym, dlclose */ #include /* LONG_MAX */ @@ -2047,6 +2083,7 @@ typedef struct { _sapp_macos_app_delegate* app_dlg; _sapp_macos_window_delegate* win_dlg; _sapp_macos_view* view; + NSCursor* cursors[_SAPP_MOUSECURSOR_NUM]; #if defined(SOKOL_METAL) id mtl_device; #endif @@ -2165,6 +2202,7 @@ typedef struct { HDC dc; HICON big_icon; HICON small_icon; + HCURSOR cursors[_SAPP_MOUSECURSOR_NUM]; UINT orig_codepage; LONG mouse_locked_x, mouse_locked_y; bool is_win10_or_greater; @@ -2386,6 +2424,7 @@ typedef struct { Colormap colormap; Window window; Cursor hidden_cursor; + Cursor cursors[_SAPP_MOUSECURSOR_NUM]; int window_state; float dpi; unsigned char error_code; @@ -2487,6 +2526,7 @@ typedef struct { bool shown; bool locked; bool pos_valid; + sapp_mouse_cursor current_cursor; } _sapp_mouse_t; typedef struct { @@ -3094,6 +3134,28 @@ _SOKOL_PRIVATE void _sapp_macos_discard_state(void) { _SAPP_OBJC_RELEASE(_sapp.macos.window); } +// undocumented methods for creating cursors (see GLFW 3.4 and imgui_impl_osx.mm) +@interface NSCursor() ++ (id)_windowResizeNorthWestSouthEastCursor; ++ (id)_windowResizeNorthEastSouthWestCursor; ++ (id)_windowResizeNorthSouthCursor; ++ (id)_windowResizeEastWestCursor; +@end + +_SOKOL_PRIVATE void _sapp_macos_init_cursors(void) { + _sapp.macos.cursors[SAPP_MOUSECURSOR_DEFAULT] = nil; // not a bug + _sapp.macos.cursors[SAPP_MOUSECURSOR_ARROW] = [NSCursor arrowCursor]; + _sapp.macos.cursors[SAPP_MOUSECURSOR_IBEAM] = [NSCursor IBeamCursor]; + _sapp.macos.cursors[SAPP_MOUSECURSOR_CROSSHAIR] = [NSCursor crosshairCursor]; + _sapp.macos.cursors[SAPP_MOUSECURSOR_POINTING_HAND] = [NSCursor pointingHandCursor]; + _sapp.macos.cursors[SAPP_MOUSECURSOR_RESIZE_EW] = [NSCursor respondsToSelector:@selector(_windowResizeEastWestCursor)] ? [NSCursor _windowResizeEastWestCursor] : [NSCursor resizeLeftRightCursor]; + _sapp.macos.cursors[SAPP_MOUSECURSOR_RESIZE_NS] = [NSCursor respondsToSelector:@selector(_windowResizeNorthSouthCursor)] ? [NSCursor _windowResizeNorthSouthCursor] : [NSCursor resizeUpDownCursor]; + _sapp.macos.cursors[SAPP_MOUSECURSOR_RESIZE_NWSE] = [NSCursor respondsToSelector:@selector(_windowResizeNorthWestSouthEastCursor)] ? [NSCursor _windowResizeNorthWestSouthEastCursor] : [NSCursor closedHandCursor]; + _sapp.macos.cursors[SAPP_MOUSECURSOR_RESIZE_NESW] = [NSCursor respondsToSelector:@selector(_windowResizeNorthEastSouthWestCursor)] ? [NSCursor _windowResizeNorthEastSouthWestCursor] : [NSCursor closedHandCursor]; + _sapp.macos.cursors[SAPP_MOUSECURSOR_RESIZE_ALL] = [NSCursor closedHandCursor]; + _sapp.macos.cursors[SAPP_MOUSECURSOR_NOT_ALLOWED] = [NSCursor operationNotAllowedCursor]; +} + _SOKOL_PRIVATE void _sapp_macos_run(const sapp_desc* desc) { _sapp_init_state(desc); _sapp_macos_init_keytable(); @@ -3296,14 +3358,34 @@ _SOKOL_PRIVATE void _sapp_macos_lock_mouse(bool lock) { */ if (_sapp.mouse.locked) { CGAssociateMouseAndMouseCursorPosition(NO); - CGDisplayHideCursor(kCGDirectMainDisplay); + [NSCursor hide]; } else { - CGDisplayShowCursor(kCGDirectMainDisplay); + [NSCursor unhide]; CGAssociateMouseAndMouseCursorPosition(YES); } } +_SOKOL_PRIVATE void _sapp_macos_update_cursor(sapp_mouse_cursor cursor, bool shown) { + // show/hide cursor only if visibility status has changed (required because show/hide stacks) + if (shown != _sapp.mouse.shown) { + if (shown) { + [NSCursor unhide]; + } + else { + [NSCursor hide]; + } + } + // update cursor type + SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM)); + if (_sapp.macos.cursors[cursor]) { + [_sapp.macos.cursors[cursor] set]; + } + else { + [[NSCursor arrowCursor] set]; + } +} + _SOKOL_PRIVATE void _sapp_macos_set_icon(const sapp_icon_desc* icon_desc, int num_images) { NSDockTile* dock_tile = NSApp.dockTile; const int wanted_width = (int) dock_tile.size.width; @@ -3351,6 +3433,7 @@ _SOKOL_PRIVATE void _sapp_macos_frame(void) { @implementation _sapp_macos_app_delegate - (void)applicationDidFinishLaunching:(NSNotification*)aNotification { _SOKOL_UNUSED(aNotification); + _sapp_macos_init_cursors(); if (_sapp.fullscreen) { NSRect screen_rect = NSScreen.mainScreen.frame; _sapp.window_width = screen_rect.size.width; @@ -3911,12 +3994,6 @@ _SOKOL_PRIVATE void _sapp_macos_poll_input_events() { _sapp_macos_mods(event)); } } -- (void)cursorUpdate:(NSEvent*)event { - _SOKOL_UNUSED(event); - if (_sapp.desc.user_cursor) { - _sapp_macos_app_event(SAPP_EVENTTYPE_UPDATE_CURSOR); - } -} @end #endif /* MacOS */ @@ -4599,6 +4676,36 @@ _SOKOL_PRIVATE void _sapp_emsc_update_mouse_lock_state(void) { } } +// set mouse cursor type +EM_JS(void, sapp_js_set_cursor, (int cursor_type, int shown), { + if (Module.sapp_emsc_target) { + var cursor; + if (shown === 0) { + cursor = "none"; + } + else switch (cursor_type) { + case 0: cursor = "auto"; break; // SAPP_MOUSECURSOR_DEFAULT + case 1: cursor = "default"; break; // SAPP_MOUSECURSOR_ARROW + case 2: cursor = "text"; break; // SAPP_MOUSECURSOR_IBEAM + case 3: cursor = "crosshair"; break; // SAPP_MOUSECURSOR_CROSSHAIR + case 4: cursor = "pointer"; break; // SAPP_MOUSECURSOR_POINTING_HAND + case 5: cursor = "ew-resize"; break; // SAPP_MOUSECURSOR_RESIZE_EW + case 6: cursor = "ns-resize"; break; // SAPP_MOUSECURSOR_RESIZE_NS + case 7: cursor = "nwse-resize"; break; // SAPP_MOUSECURSOR_RESIZE_NWSE + case 8: cursor = "nesw-resize"; break; // SAPP_MOUSECURSOR_RESIZE_NESW + case 9: cursor = "all-scroll"; break; // SAPP_MOUSECURSOR_RESIZE_ALL + case 10: cursor = "not-allowed"; break; // SAPP_MOUSECURSOR_NOT_ALLOWED + default: cursor = "auto"; break; + } + Module.sapp_emsc_target.style.cursor = cursor; + } +}); + +_SOKOL_PRIVATE void _sapp_emsc_update_cursor(sapp_mouse_cursor cursor, bool shown) { + SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM)); + sapp_js_set_cursor((int)cursor, shown ? 1 : 0); +} + /* JS helper functions to update browser tab favicon */ EM_JS(void, sapp_js_clear_favicon, (void), { var link = document.getElementById('sokol-app-favicon'); @@ -6211,9 +6318,73 @@ _SOKOL_PRIVATE void _sapp_win32_toggle_fullscreen(void) { SetWindowPos(_sapp.win32.hwnd, HWND_TOP, mr.left + rect.left, mr.top + rect.top, win_width, win_height, SWP_SHOWWINDOW | SWP_FRAMECHANGED); } -_SOKOL_PRIVATE void _sapp_win32_show_mouse(bool visible) { - /* NOTE: this function is only called when the mouse visibility actually changes */ - ShowCursor((BOOL)visible); +_SOKOL_PRIVATE void _sapp_win32_init_cursor(sapp_mouse_cursor cursor) { + SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM)); + // NOTE: the OCR_* constants are only defined if OEMRESOURCE is defined + // before windows.h is included, but we can't guarantee that because + // the sokol_app.h implementation may be included with other implementations + // in the same compilation unit + int id = 0; + switch (cursor) { + case SAPP_MOUSECURSOR_ARROW: id = 32512; break; // OCR_NORMAL + case SAPP_MOUSECURSOR_IBEAM: id = 32513; break; // OCR_IBEAM + case SAPP_MOUSECURSOR_CROSSHAIR: id = 32515; break; // OCR_CROSS + case SAPP_MOUSECURSOR_POINTING_HAND: id = 32649; break; // OCR_HAND + case SAPP_MOUSECURSOR_RESIZE_EW: id = 32644; break; // OCR_SIZEWE + case SAPP_MOUSECURSOR_RESIZE_NS: id = 32645; break; // OCR_SIZENS + case SAPP_MOUSECURSOR_RESIZE_NWSE: id = 32642; break; // OCR_SIZENWSE + case SAPP_MOUSECURSOR_RESIZE_NESW: id = 32643; break; // OCR_SIZENESW + case SAPP_MOUSECURSOR_RESIZE_ALL: id = 32646; break; // OCR_SIZEALL + case SAPP_MOUSECURSOR_NOT_ALLOWED: id = 32648; break; // OCR_NO + default: break; + } + if (id != 0) { + _sapp.win32.cursors[cursor] = (HCURSOR)LoadImageW(NULL, MAKEINTRESOURCEW(id), IMAGE_CURSOR, 0, 0, LR_DEFAULTSIZE|LR_SHARED); + } + // fallback: default cursor + if (0 == _sapp.win32.cursors[cursor]) { + // 32512 => IDC_ARROW + _sapp.win32.cursors[cursor] = LoadCursorW(NULL, MAKEINTRESOURCEW(32512)); + } + SOKOL_ASSERT(0 != _sapp.win32.cursors[cursor]); +} + +_SOKOL_PRIVATE void _sapp_win32_init_cursors(void) { + for (int i = 0; i < _SAPP_MOUSECURSOR_NUM; i++) { + _sapp_win32_init_cursor((sapp_mouse_cursor)i); + } +} + +_SOKOL_PRIVATE bool _sapp_win32_cursor_in_content_area(void) { + POINT pos; + if (!GetCursorPos(&pos)) { + return false; + } + if (WindowFromPoint(pos) != _sapp.win32.hwnd) { + return false; + } + RECT area; + GetClientRect(_sapp.win32.hwnd, &area); + ClientToScreen(_sapp.win32.hwnd, (POINT*)&area.left); + ClientToScreen(_sapp.win32.hwnd, (POINT*)&area.right); + return PtInRect(&area, pos); +} + +_SOKOL_PRIVATE void _sapp_win32_update_cursor(sapp_mouse_cursor cursor, bool shown, bool skip_area_test) { + // NOTE: when called from WM_SETCURSOR, the area test would be redundant + if (!skip_area_test) { + if (!_sapp_win32_cursor_in_content_area()) { + return; + } + } + if (!shown) { + SetCursor(NULL); + } + else { + SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM)); + SOKOL_ASSERT(0 != _sapp.win32.cursors[cursor]); + SetCursor(_sapp.win32.cursors[cursor]); + } } _SOKOL_PRIVATE void _sapp_win32_capture_mouse(uint8_t btn_mask) { @@ -6517,11 +6688,9 @@ _SOKOL_PRIVATE LRESULT CALLBACK _sapp_win32_wndproc(HWND hWnd, UINT uMsg, WPARAM _sapp_win32_uwp_app_event(SAPP_EVENTTYPE_UNFOCUSED); break; case WM_SETCURSOR: - if (_sapp.desc.user_cursor) { - if (LOWORD(lParam) == HTCLIENT) { - _sapp_win32_uwp_app_event(SAPP_EVENTTYPE_UPDATE_CURSOR); - return 1; - } + if (LOWORD(lParam) == HTCLIENT) { + _sapp_win32_update_cursor(_sapp.mouse.current_cursor, _sapp.mouse.shown, true); + return TRUE; } break; case WM_LBUTTONDOWN: @@ -6997,6 +7166,7 @@ _SOKOL_PRIVATE void _sapp_win32_run(const sapp_desc* desc) { _sapp_win32_uwp_init_keytable(); _sapp_win32_uwp_utf8_to_wide(_sapp.window_title, _sapp.window_title_wide, sizeof(_sapp.window_title_wide)); _sapp_win32_init_dpi(); + _sapp_win32_init_cursors(); _sapp_win32_create_window(); sapp_set_icon(&desc->icon); #if defined(SOKOL_D3D11) @@ -7136,13 +7306,26 @@ _SOKOL_PRIVATE void _sapp_uwp_configure_dpi(float monitor_dpi) { _sapp.dpi_scale = _sapp.uwp.dpi.content_scale; } -_SOKOL_PRIVATE void _sapp_uwp_show_mouse(bool visible) { +_SOKOL_PRIVATE void _sapp_uwp_update_cursor(sapp_mouse_cursor cursor, bool shown) { using namespace winrt::Windows::UI::Core; - /* NOTE: this function is only called when the mouse visibility actually changes */ - CoreWindow::GetForCurrentThread().PointerCursor(visible ? - CoreCursor(CoreCursorType::Arrow, 0) : - CoreCursor(nullptr)); + CoreCursor uwp_cursor(nullptr); + if (shown) { + switch (cursor) { + case SAPP_MOUSECURSOR_ARROW: uwp_cursor = CoreCursor(CoreCursorType::Arrow, 0); break; + case SAPP_MOUSECURSOR_IBEAM: uwp_cursor = CoreCursor(CoreCursorType::IBeam, 0); break; + case SAPP_MOUSECURSOR_CROSSHAIR: uwp_cursor = CoreCursor(CoreCursorType::Cross, 0); break; + case SAPP_MOUSECURSOR_POINTING_HAND: uwp_cursor = CoreCursor(CoreCursorType::Hand, 0); break; + case SAPP_MOUSECURSOR_RESIZE_EW: uwp_cursor = CoreCursor(CoreCursorType::SizeWestEast, 0); break; + case SAPP_MOUSECURSOR_RESIZE_NS: uwp_cursor = CoreCursor(CoreCursorType::SizeNorthSouth, 0); break; + case SAPP_MOUSECURSOR_RESIZE_NWSE: uwp_cursor = CoreCursor(CoreCursorType::SizeNorthwestSoutheast, 0); break; + case SAPP_MOUSECURSOR_RESIZE_NESW: uwp_cursor = CoreCursor(CoreCursorType::SizeNortheastSouthwest, 0); break; + case SAPP_MOUSECURSOR_RESIZE_ALL: uwp_cursor = CoreCursor(CoreCursorType::SizeAll, 0); break; + case SAPP_MOUSECURSOR_NOT_ALLOWED: uwp_cursor = CoreCursor(CoreCursorType::UniversalNo, 0); break; + default: uwp_cursor = CoreCursor(CoreCursorType::Arrow, 0); break; + } + } + CoreWindow::GetForCurrentThread().PointerCursor(uwp_cursor); } _SOKOL_PRIVATE uint32_t _sapp_uwp_mods(winrt::Windows::UI::Core::CoreWindow const& sender_window) { @@ -10022,19 +10205,72 @@ _SOKOL_PRIVATE void _sapp_x11_create_hidden_cursor(void) { XcursorImageDestroy(img); } +_SOKOL_PRIVATE void _sapp_x11_create_standard_cursor(sapp_mouse_cursor cursor, const char* name, const char* theme, int size, uint32_t fallback_native) { + SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM)); + SOKOL_ASSERT(_sapp.x11.display); + if (theme) { + XcursorImage* img = XcursorLibraryLoadImage(name, theme, size); + if (img) { + _sapp.x11.cursors[cursor] = XcursorImageLoadCursor(_sapp.x11.display, img); + XcursorImageDestroy(img); + } + } + if (0 == _sapp.x11.cursors[cursor]) { + _sapp.x11.cursors[cursor] = XCreateFontCursor(_sapp.x11.display, fallback_native); + } +} + +_SOKOL_PRIVATE void _sapp_x11_create_cursors(void) { + SOKOL_ASSERT(_sapp.x11.display); + const char* cursor_theme = XcursorGetTheme(_sapp.x11.display); + const int size = XcursorGetDefaultSize(_sapp.x11.display); + _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_ARROW, "default", cursor_theme, size, XC_left_ptr); + _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_IBEAM, "text", cursor_theme, size, XC_xterm); + _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_CROSSHAIR, "crosshair", cursor_theme, size, XC_crosshair); + _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_POINTING_HAND, "pointer", cursor_theme, size, XC_hand2); + _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_RESIZE_EW, "ew-resize", cursor_theme, size, XC_sb_h_double_arrow); + _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_RESIZE_NS, "ns-resize", cursor_theme, size, XC_sb_v_double_arrow); + _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_RESIZE_NWSE, "nwse-resize", cursor_theme, size, 0); + _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_RESIZE_NESW, "nesw-resize", cursor_theme, size, 0); + _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_RESIZE_ALL, "all-scroll", cursor_theme, size, XC_fleur); + _sapp_x11_create_standard_cursor(SAPP_MOUSECURSOR_NOT_ALLOWED, "no-allowed", cursor_theme, size, 0); + _sapp_x11_create_hidden_cursor(); +} + +_SOKOL_PRIVATE void _sapp_x11_destroy_cursors(void) { + SOKOL_ASSERT(_sapp.x11.display); + if (_sapp.x11.hidden_cursor) { + XFreeCursor(_sapp.x11.display, _sapp.x11.hidden_cursor); + _sapp.x11.hidden_cursor = 0; + } + for (int i = 0; i < _SAPP_MOUSECURSOR_NUM; i++) { + if (_sapp.x11.cursors[i]) { + XFreeCursor(_sapp.x11.display, _sapp.x11.cursors[i]); + _sapp.x11.cursors[i] = 0; + } + } +} + _SOKOL_PRIVATE void _sapp_x11_toggle_fullscreen(void) { _sapp.fullscreen = !_sapp.fullscreen; _sapp_x11_set_fullscreen(_sapp.fullscreen); _sapp_x11_query_window_size(); } -_SOKOL_PRIVATE void _sapp_x11_show_mouse(bool show) { - if (show) { - XUndefineCursor(_sapp.x11.display, _sapp.x11.window); +_SOKOL_PRIVATE void _sapp_x11_update_cursor(sapp_mouse_cursor cursor, bool shown) { + SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM)); + if (shown) { + if (_sapp.x11.cursors[cursor]) { + XDefineCursor(_sapp.x11.display, _sapp.x11.window, _sapp.x11.cursors[cursor]); + } + else { + XUndefineCursor(_sapp.x11.display, _sapp.x11.window); + } } else { XDefineCursor(_sapp.x11.display, _sapp.x11.window, _sapp.x11.hidden_cursor); } + XFlush(_sapp.x11.display); } _SOKOL_PRIVATE void _sapp_x11_lock_mouse(bool lock) { @@ -10927,7 +11163,7 @@ _SOKOL_PRIVATE void _sapp_linux_run(const sapp_desc* desc) { _sapp_x11_query_system_dpi(); _sapp.dpi_scale = _sapp.x11.dpi / 96.0f; _sapp_x11_init_extensions(); - _sapp_x11_create_hidden_cursor(); + _sapp_x11_create_cursors(); _sapp_glx_init(); Visual* visual = 0; int depth = 0; @@ -10968,6 +11204,7 @@ _SOKOL_PRIVATE void _sapp_linux_run(const sapp_desc* desc) { _sapp_call_cleanup(); _sapp_glx_destroy_context(); _sapp_x11_destroy_window(); + _sapp_x11_destroy_cursors(); XCloseDisplay(_sapp.x11.display); _sapp_discard_state(); } @@ -11128,13 +11365,15 @@ SOKOL_API_IMPL void sapp_toggle_fullscreen(void) { SOKOL_API_IMPL void sapp_show_mouse(bool show) { if (_sapp.mouse.shown != show) { #if defined(_SAPP_MACOS) - _sapp_macos_show_mouse(show); + _sapp_macos_update_cursor(_sapp.mouse.current_cursor, show); #elif defined(_SAPP_WIN32) - _sapp_win32_show_mouse(show); + _sapp_win32_update_cursor(_sapp.mouse.current_cursor, show, false); #elif defined(_SAPP_LINUX) - _sapp_x11_show_mouse(show); + _sapp_x11_update_cursor(_sapp.mouse.current_cursor, show); #elif defined(_SAPP_UWP) - _sapp_uwp_show_mouse(show); + _sapp_uwp_update_cursor(_sapp.mouse.current_cursor, show); + #elif defined(_SAPP_EMSCRIPTEN) + _sapp_emsc_update_cursor(_sapp.mouse.current_cursor, show); #endif _sapp.mouse.shown = show; } @@ -11162,6 +11401,28 @@ SOKOL_API_IMPL bool sapp_mouse_locked(void) { return _sapp.mouse.locked; } +SOKOL_API_IMPL void sapp_set_mouse_cursor(sapp_mouse_cursor cursor) { + SOKOL_ASSERT((cursor >= 0) && (cursor < _SAPP_MOUSECURSOR_NUM)); + if (_sapp.mouse.current_cursor != cursor) { + #if defined(_SAPP_MACOS) + _sapp_macos_update_cursor(cursor, _sapp.mouse.shown); + #elif defined(_SAPP_WIN32) + _sapp_win32_update_cursor(cursor, _sapp.mouse.shown, false); + #elif defined(_SAPP_LINUX) + _sapp_x11_update_cursor(cursor, _sapp.mouse.shown); + #elif defined(_SAPP_UWP) + _sapp_uwp_update_cursor(cursor, _sapp.mouse.shown); + #elif defined(_SAPP_EMSCRIPTEN) + _sapp_emsc_update_cursor(cursor, _sapp.mouse.shown); + #endif + _sapp.mouse.current_cursor = cursor; + } +} + +SOKOL_API_IMPL sapp_mouse_cursor sapp_get_mouse_cursor(void) { + return _sapp.mouse.current_cursor; +} + SOKOL_API_IMPL void sapp_request_quit(void) { _sapp.quit_requested = true; } diff --git a/vlib/sokol/sapp/enums.v b/vlib/sokol/sapp/enums.v index ac885a4f1..290d0f67f 100644 --- a/vlib/sokol/sapp/enums.v +++ b/vlib/sokol/sapp/enums.v @@ -36,6 +36,20 @@ pub enum MouseButton { middle = 2 } +pub enum MouseCursor { + default = C.SAPP_MOUSECURSOR_DEFAULT + arrow = C.SAPP_MOUSECURSOR_ARROW + ibeam = C.SAPP_MOUSECURSOR_IBEAM + crosshair = C.SAPP_MOUSECURSOR_CROSSHAIR + pointing_hand = C.SAPP_MOUSECURSOR_POINTING_HAND + resize_ew = C.SAPP_MOUSECURSOR_RESIZE_EW + resize_ns = C.SAPP_MOUSECURSOR_RESIZE_NS + resize_nwse = C.SAPP_MOUSECURSOR_RESIZE_NWSE + resize_nesw = C.SAPP_MOUSECURSOR_RESIZE_NESW + resize_all = C.SAPP_MOUSECURSOR_RESIZE_ALL + not_allowed = C.SAPP_MOUSECURSOR_NOT_ALLOWED +} + pub enum Modifier { shift = 1 //(1<<0) ctrl = 2 //(1<<1) diff --git a/vlib/sokol/sapp/sapp.c.v b/vlib/sokol/sapp/sapp.c.v index b8b940035..384446406 100644 --- a/vlib/sokol/sapp/sapp.c.v +++ b/vlib/sokol/sapp/sapp.c.v @@ -97,6 +97,12 @@ pub fn show_mouse(visible bool) { C.sapp_show_mouse(visible) } +// set mouse cursor +[inline] +pub fn set_mouse_cursor(cursor MouseCursor) { + C.sapp_set_mouse_cursor(cursor) +} + // show or hide the mouse cursor [inline] pub fn mouse_shown() bool { diff --git a/vlib/sokol/sapp/sapp_funcs.c.v b/vlib/sokol/sapp/sapp_funcs.c.v index ab6024d30..ff878a740 100644 --- a/vlib/sokol/sapp/sapp_funcs.c.v +++ b/vlib/sokol/sapp/sapp_funcs.c.v @@ -44,6 +44,9 @@ fn C.sapp_show_mouse(visible bool) // return true if the mouse cursor is shown fn C.sapp_mouse_shown() bool +// set mouse cursor +fn C.sapp_set_mouse_cursor(cursor MouseCursor) + // lock or unlock the mouse cursor fn C.sapp_lock_mouse(locked bool) diff --git a/vlib/sokol/sapp/sapp_structs.c.v b/vlib/sokol/sapp/sapp_structs.c.v index 460b4ab16..07e5582bf 100644 --- a/vlib/sokol/sapp/sapp_structs.c.v +++ b/vlib/sokol/sapp/sapp_structs.c.v @@ -54,7 +54,6 @@ pub: fullscreen bool // whether the window should be created in fullscreen mode alpha bool // whether the framebuffer should have an alpha channel (ignored on some platforms) window_title &char // the window title as UTF-8 encoded string - user_cursor bool // if true, user is expected to manage cursor image in SAPP_EVENTTYPE_UPDATE_CURSOR enable_clipboard bool // enable clipboard access, default is false clipboard_size int // max size of clipboard content in bytes enable_dragndrop bool // enable file dropping (drag'n'drop), default is false -- 2.30.2