From 0bb52c916330f238d848e7a8f5793f7c2f9db814 Mon Sep 17 00:00:00 2001 From: claudioluciano Date: Tue, 19 May 2026 22:31:41 +0100 Subject: [PATCH 1/3] Implement CSS cursor handling --- .../color_picker/color_picker_overlay.go | 3 +- .../content_selector_overlay.go | 3 +- src/engine/ui/input.go | 9 +- .../ui/markup/css/properties/css_cursor.go | 96 +++++++------- src/platform/windowing/cocoa_window.h | 7 +- src/platform/windowing/cocoa_window.m | 112 +++++++++++----- src/platform/windowing/cursor.go | 42 ++++++ src/platform/windowing/win32.c | 92 ++++++++++--- src/platform/windowing/win32.h | 6 +- src/platform/windowing/window.darwin.go | 7 +- src/platform/windowing/window.go | 40 ++---- src/platform/windowing/window.x11.go | 26 +--- src/platform/windowing/window_android.go | 6 +- src/platform/windowing/window_windows.go | 23 +--- src/platform/windowing/x11.c | 123 ++++++++++++++---- src/platform/windowing/x11.h | 6 +- 16 files changed, 377 insertions(+), 224 deletions(-) create mode 100644 src/platform/windowing/cursor.go diff --git a/src/editor/editor_overlay/color_picker/color_picker_overlay.go b/src/editor/editor_overlay/color_picker/color_picker_overlay.go index 5dcd1139f..21c6e62f5 100644 --- a/src/editor/editor_overlay/color_picker/color_picker_overlay.go +++ b/src/editor/editor_overlay/color_picker/color_picker_overlay.go @@ -49,6 +49,7 @@ import ( "kaijuengine.com/engine/ui/markup/document" "kaijuengine.com/matrix" "kaijuengine.com/platform/profiler/tracing" + "kaijuengine.com/platform/windowing" ) type ColorPicker struct { @@ -123,7 +124,7 @@ func Show(host *engine.Host, config Config) (*ColorPicker, error) { func (p *ColorPicker) Close() { defer tracing.NewRegion("ColorPicker.Close").End() host := p.uiMan.Host - host.Window.CursorStandard() + host.Window.SetCursor(windowing.CursorKindDefault) p.doc.Destroy() } diff --git a/src/editor/editor_overlay/content_selector/content_selector_overlay.go b/src/editor/editor_overlay/content_selector/content_selector_overlay.go index a7374c124..bc9a2e78e 100644 --- a/src/editor/editor_overlay/content_selector/content_selector_overlay.go +++ b/src/editor/editor_overlay/content_selector/content_selector_overlay.go @@ -48,6 +48,7 @@ import ( "kaijuengine.com/engine/ui/markup/document" "kaijuengine.com/platform/hid" "kaijuengine.com/platform/profiler/tracing" + "kaijuengine.com/platform/windowing" ) type ContentSelector struct { @@ -126,7 +127,7 @@ func (o *ContentSelector) Close() { } func (o *ContentSelector) closeInternal() { - o.uiMan.Host.Window.CursorStandard() + o.uiMan.Host.Window.SetCursor(windowing.CursorKindDefault) o.doc.Destroy() o.uiMan.Host.Window.Keyboard.RemoveKeyCallback(o.keyKb) } diff --git a/src/engine/ui/input.go b/src/engine/ui/input.go index e6ac5cc02..be3909f91 100644 --- a/src/engine/ui/input.go +++ b/src/engine/ui/input.go @@ -49,6 +49,7 @@ import ( "kaijuengine.com/matrix" "kaijuengine.com/platform/hid" "kaijuengine.com/platform/profiler/tracing" + "kaijuengine.com/platform/windowing" "kaijuengine.com/rendering" ) @@ -568,11 +569,11 @@ func (input *Input) onRebuild() { } func (input *Input) onEnter() { - input.man.Value().Host.Window.CursorIbeam() + input.man.Value().Host.Window.SetCursor(windowing.CursorKindText) } func (input *Input) onExit() { - input.man.Value().Host.Window.CursorStandard() + input.man.Value().Host.Window.SetCursor(windowing.CursorKindDefault) } func (input *Input) onDown() { @@ -619,7 +620,7 @@ func (input *Input) detectDoubleClick() bool { func (input *Input) onMiss() { input.RemoveFocus() - input.man.Value().Host.Window.CursorStandard() + input.man.Value().Host.Window.SetCursor(windowing.CursorKindDefault) } func (input *Input) deactivated() { @@ -762,7 +763,7 @@ func (input *Input) RemoveFocus() { } man := input.man.Value() if man != nil { - man.Host.Window.CursorStandard() + man.Host.Window.SetCursor(windowing.CursorKindDefault) man.Group.setFocus(nil) } input.blur() diff --git a/src/engine/ui/markup/css/properties/css_cursor.go b/src/engine/ui/markup/css/properties/css_cursor.go index 29b86111d..c167260e3 100644 --- a/src/engine/ui/markup/css/properties/css_cursor.go +++ b/src/engine/ui/markup/css/properties/css_cursor.go @@ -43,7 +43,7 @@ import ( "kaijuengine.com/engine/ui" "kaijuengine.com/engine/ui/markup/css/rules" "kaijuengine.com/engine/ui/markup/document" - "kaijuengine.com/klib" + "kaijuengine.com/platform/windowing" ) func (p Cursor) Process(panel *ui.Panel, elm *document.Element, values []rules.PropertyValue, host *engine.Host) error { @@ -53,81 +53,85 @@ func (p Cursor) Process(panel *ui.Panel, elm *document.Element, values []rules.P panel.Base().AddEvent(ui.EventTypeEnter, func() { switch values[0].Str { - case "text": - host.Window.CursorIbeam() + case "auto": + host.Window.SetCursor(windowing.CursorKindAuto) case "default": - host.Window.CursorStandard() + host.Window.SetCursor(windowing.CursorKindDefault) + case "none": + host.Window.SetCursor(windowing.CursorKindNone) case "context-menu": - fallthrough - case "help": - fallthrough + host.Window.SetCursor(windowing.CursorKindContextMenu) + case "text": + host.Window.SetCursor(windowing.CursorKindText) + case "vertical-text": + host.Window.SetCursor(windowing.CursorKindVerticalText) case "pointer": - fallthrough + host.Window.SetCursor(windowing.CursorKindPointer) + case "help": + host.Window.SetCursor(windowing.CursorKindHelp) case "progress": - fallthrough + host.Window.SetCursor(windowing.CursorKindProgress) case "wait": - fallthrough + host.Window.SetCursor(windowing.CursorKindWait) case "cell": - fallthrough + host.Window.SetCursor(windowing.CursorKindCell) case "crosshair": - fallthrough - case "vertical-text": - fallthrough + host.Window.SetCursor(windowing.CursorKindCrosshair) case "alias": - fallthrough + host.Window.SetCursor(windowing.CursorKindAlias) case "copy": - fallthrough + host.Window.SetCursor(windowing.CursorKindCopy) case "move": - fallthrough + host.Window.SetCursor(windowing.CursorKindMove) case "no-drop": - fallthrough + host.Window.SetCursor(windowing.CursorKindNoDrop) case "not-allowed": - fallthrough + host.Window.SetCursor(windowing.CursorKindNotAllowed) case "grab": - fallthrough + host.Window.SetCursor(windowing.CursorKindGrab) case "grabbing": - fallthrough + host.Window.SetCursor(windowing.CursorKindGrabbing) case "all-scroll": - fallthrough + host.Window.SetCursor(windowing.CursorKindResizeAll) case "col-resize": - fallthrough + host.Window.SetCursor(windowing.CursorKindResizeCol) + case "e-resize": + host.Window.SetCursor(windowing.CursorKindResizeE) + case "w-resize": + host.Window.SetCursor(windowing.CursorKindResizeW) + case "ew-resize": + host.Window.SetCursor(windowing.CursorKindResizeEW) case "row-resize": - fallthrough + host.Window.SetCursor(windowing.CursorKindResizeRow) case "n-resize": - fallthrough - case "e-resize": - fallthrough + host.Window.SetCursor(windowing.CursorKindResizeN) case "s-resize": - fallthrough - case "w-resize": - fallthrough + host.Window.SetCursor(windowing.CursorKindResizeS) + case "ns-resize": + host.Window.SetCursor(windowing.CursorKindResizeNS) case "ne-resize": - fallthrough - case "nw-resize": - fallthrough - case "se-resize": - fallthrough + host.Window.SetCursor(windowing.CursorKindResizeNE) case "sw-resize": - fallthrough - case "ew-resize": - fallthrough - case "ns-resize": - fallthrough + host.Window.SetCursor(windowing.CursorKindResizeSW) case "nesw-resize": - fallthrough + host.Window.SetCursor(windowing.CursorKindResizeNESW) + case "nw-resize": + host.Window.SetCursor(windowing.CursorKindResizeNW) + case "se-resize": + host.Window.SetCursor(windowing.CursorKindResizeSE) case "nwse-resize": - fallthrough + host.Window.SetCursor(windowing.CursorKindResizeNWSE) case "zoom-in": - fallthrough + host.Window.SetCursor(windowing.CursorKindZoomIn) case "zoom-out": - klib.NotYetImplemented(180) + host.Window.SetCursor(windowing.CursorKindZoomOut) default: - host.Window.CursorStandard() + host.Window.SetCursor(windowing.CursorKindDefault) } }) panel.Base().AddEvent(ui.EventTypeExit, func() { - host.Window.CursorStandard() + host.Window.SetCursor(windowing.CursorKindDefault) }) return nil diff --git a/src/platform/windowing/cocoa_window.h b/src/platform/windowing/cocoa_window.h index bb6cdf7b5..2055a650d 100644 --- a/src/platform/windowing/cocoa_window.h +++ b/src/platform/windowing/cocoa_window.h @@ -41,12 +41,7 @@ void cocoa_set_title(void* nsWindow, const char* title); void cocoa_copy_to_clipboard(const char* text); char* cocoa_clipboard_contents(void); -// Cursor variants -void cocoa_cursor_standard(void); -void cocoa_cursor_ibeam(void); -void cocoa_cursor_size_all(void); -void cocoa_cursor_size_ns(void); -void cocoa_cursor_size_we(void); +void cocoa_set_cursor(int kind); void cocoa_show_cursor(void); void cocoa_hide_cursor(void); diff --git a/src/platform/windowing/cocoa_window.m b/src/platform/windowing/cocoa_window.m index 33c13fb04..10c27ca61 100644 --- a/src/platform/windowing/cocoa_window.m +++ b/src/platform/windowing/cocoa_window.m @@ -560,42 +560,90 @@ void cocoa_copy_to_clipboard(const char* text) { } } -void cocoa_cursor_standard(void) { - dispatch_async(dispatch_get_main_queue(), ^{ - @autoreleasepool { - [[NSCursor arrowCursor] set]; - } - }); -} - -void cocoa_cursor_ibeam(void) { - dispatch_async(dispatch_get_main_queue(), ^{ - @autoreleasepool { - [[NSCursor IBeamCursor] set]; - } - }); -} - -void cocoa_cursor_size_all(void) { - dispatch_async(dispatch_get_main_queue(), ^{ - @autoreleasepool { - [[NSCursor closedHandCursor] set]; - } - }); -} - -void cocoa_cursor_size_ns(void) { - dispatch_async(dispatch_get_main_queue(), ^{ - @autoreleasepool { - [[NSCursor resizeUpDownCursor] set]; - } - }); +static BOOL gCursorHiddenByKaiju = NO; + +static void cocoa_set_cursor_hidden(BOOL hidden) { + if (hidden && !gCursorHiddenByKaiju) { + [NSCursor hide]; + gCursorHiddenByKaiju = YES; + } else if (!hidden && gCursorHiddenByKaiju) { + [NSCursor unhide]; + gCursorHiddenByKaiju = NO; + } } -void cocoa_cursor_size_we(void) { +void cocoa_set_cursor(int kind) { dispatch_async(dispatch_get_main_queue(), ^{ @autoreleasepool { - [[NSCursor resizeLeftRightCursor] set]; + switch (kind) { + case 2: // CursorKindNone + cocoa_set_cursor_hidden(YES); + break; + case 4: // CursorKindText + case 5: // CursorKindVerticalText + cocoa_set_cursor_hidden(NO); + [[NSCursor IBeamCursor] set]; + break; + case 3: // CursorKindContextMenu + case 6: // CursorKindPointer + case 7: // CursorKindHelp + case 12: // CursorKindAlias + case 13: // CursorKindCopy + cocoa_set_cursor_hidden(NO); + [[NSCursor pointingHandCursor] set]; + break; + case 8: // CursorKindWait + case 9: // CursorKindProgress + case 34: // CursorKindZoomIn + case 35: // CursorKindZoomOut + cocoa_set_cursor_hidden(NO); + [[NSCursor arrowCursor] set]; + break; + case 10: // CursorKindCrosshair + case 11: // CursorKindCell + cocoa_set_cursor_hidden(NO); + [[NSCursor crosshairCursor] set]; + break; + case 14: // CursorKindMove + case 18: // CursorKindGrabbing + case 33: // CursorKindResizeAll + cocoa_set_cursor_hidden(NO); + [[NSCursor closedHandCursor] set]; + break; + case 15: // CursorKindNoDrop + case 16: // CursorKindNotAllowed + cocoa_set_cursor_hidden(NO); + [[NSCursor operationNotAllowedCursor] set]; + break; + case 17: // CursorKindGrab + cocoa_set_cursor_hidden(NO); + [[NSCursor openHandCursor] set]; + break; + case 19: // CursorKindResizeN + case 21: // CursorKindResizeS + case 27: // CursorKindResizeNS + case 32: // CursorKindResizeRow + cocoa_set_cursor_hidden(NO); + [[NSCursor resizeUpDownCursor] set]; + break; + case 20: // CursorKindResizeE + case 22: // CursorKindResizeW + case 28: // CursorKindResizeEW + case 31: // CursorKindResizeCol + cocoa_set_cursor_hidden(NO); + [[NSCursor resizeLeftRightCursor] set]; + break; + case 23: // CursorKindResizeNE + case 24: // CursorKindResizeNW + case 25: // CursorKindResizeSE + case 26: // CursorKindResizeSW + case 29: // CursorKindResizeNWSE + case 30: // CursorKindResizeNESW + default: + cocoa_set_cursor_hidden(NO); + [[NSCursor arrowCursor] set]; + break; + } } }); } diff --git a/src/platform/windowing/cursor.go b/src/platform/windowing/cursor.go new file mode 100644 index 000000000..2cf7c705c --- /dev/null +++ b/src/platform/windowing/cursor.go @@ -0,0 +1,42 @@ +package windowing + +type CursorKind int + +const ( + CursorKindAuto CursorKind = iota + CursorKindDefault + CursorKindNone + CursorKindContextMenu + CursorKindText + CursorKindVerticalText + CursorKindPointer + CursorKindHelp + CursorKindWait + CursorKindProgress + CursorKindCrosshair + CursorKindCell + CursorKindAlias + CursorKindCopy + CursorKindMove + CursorKindNoDrop + CursorKindNotAllowed + CursorKindGrab + CursorKindGrabbing + CursorKindResizeN + CursorKindResizeE + CursorKindResizeS + CursorKindResizeW + CursorKindResizeNE + CursorKindResizeNW + CursorKindResizeSE + CursorKindResizeSW + CursorKindResizeNS + CursorKindResizeEW + CursorKindResizeNWSE + CursorKindResizeNESW + CursorKindResizeCol + CursorKindResizeRow + CursorKindResizeAll + CursorKindZoomIn + CursorKindZoomOut +) diff --git a/src/platform/windowing/win32.c b/src/platform/windowing/win32.c index 6bbcdb0ac..2cb3d0154 100644 --- a/src/platform/windowing/win32.c +++ b/src/platform/windowing/win32.c @@ -696,7 +696,7 @@ void window_main(const wchar_t* windowTitle, shared_mem_flush_events(sm); return; } - window_cursor_standard(hwnd); + window_set_cursor(hwnd, 1); // CursorKindDefault sm->windowWidth = width; sm->windowHeight = height; shared_mem_add_event(sm, (WindowEvent) { @@ -896,24 +896,80 @@ void window_destroy(void* hwnd) { free(sm); } -void window_cursor_standard(void* hwnd) { - PostMessageA(hwnd, UWM_SET_CURSOR, CURSOR_ARROW, 0); -} - -void window_cursor_ibeam(void* hwnd) { - PostMessageA(hwnd, UWM_SET_CURSOR, CURSOR_IBEAM, 0); -} - -void window_cursor_size_all(void* hwnd) { - PostMessageA(hwnd, UWM_SET_CURSOR, CURSOR_SIZE_ALL, 0); -} - -void window_cursor_size_ns(void* hwnd) { - PostMessageA(hwnd, UWM_SET_CURSOR, CURSOR_SIZE_NS, 0); -} +void window_set_cursor(void* hwnd, int kind) { + SharedMem* sm = (SharedMem*)GetWindowLongPtrA(hwnd, GWLP_USERDATA); + if (kind == 2) { // CursorKindNone + if (sm != NULL) { + sm->cursorHidden = true; + } + SetCursor(NULL); + while (ShowCursor(FALSE) >= 0) {} + return; + } + if (sm != NULL && sm->cursorHidden) { + sm->cursorHidden = false; + while (ShowCursor(TRUE) < 0) {} + } -void window_cursor_size_we(void* hwnd) { - PostMessageA(hwnd, UWM_SET_CURSOR, CURSOR_SIZE_WE, 0); + int cursor = CURSOR_ARROW; + switch (kind) { + case 4: // CursorKindText + case 5: // CursorKindVerticalText + cursor = CURSOR_IBEAM; + break; + case 6: // CursorKindPointer + case 3: // CursorKindContextMenu + case 12: // CursorKindAlias + case 13: // CursorKindCopy + case 17: // CursorKindGrab + case 18: // CursorKindGrabbing + cursor = CURSOR_HAND; + break; + case 7: // CursorKindHelp + cursor = CURSOR_HELP; + break; + case 8: // CursorKindWait + cursor = CURSOR_WAIT; + break; + case 9: // CursorKindProgress + cursor = CURSOR_APP_STARTING; + break; + case 10: // CursorKindCrosshair + case 11: // CursorKindCell + cursor = CURSOR_CROSS; + break; + case 14: // CursorKindMove + case 33: // CursorKindResizeAll + cursor = CURSOR_SIZE_ALL; + break; + case 15: // CursorKindNoDrop + case 16: // CursorKindNotAllowed + cursor = CURSOR_NO; + break; + case 19: // CursorKindResizeN + case 21: // CursorKindResizeS + case 27: // CursorKindResizeNS + case 32: // CursorKindResizeRow + cursor = CURSOR_SIZE_NS; + break; + case 20: // CursorKindResizeE + case 22: // CursorKindResizeW + case 28: // CursorKindResizeEW + case 31: // CursorKindResizeCol + cursor = CURSOR_SIZE_WE; + break; + case 23: // CursorKindResizeNE + case 26: // CursorKindResizeSW + case 30: // CursorKindResizeNESW + cursor = CURSOR_SIZE_NESW; + break; + case 24: // CursorKindResizeNW + case 25: // CursorKindResizeSE + case 29: // CursorKindResizeNWSE + cursor = CURSOR_SIZE_NWSE; + break; + } + PostMessageA(hwnd, UWM_SET_CURSOR, cursor, 0); } float window_dpi(void* hwnd) { diff --git a/src/platform/windowing/win32.h b/src/platform/windowing/win32.h index d861f3d5a..4d15b0995 100644 --- a/src/platform/windowing/win32.h +++ b/src/platform/windowing/win32.h @@ -49,11 +49,7 @@ void window_show(void* hwnd); void window_poll_controller(void* hwnd); void window_poll(void* hwnd); void window_destroy(void* hwnd); -void window_cursor_standard(void* hwnd); -void window_cursor_ibeam(void* hwnd); -void window_cursor_size_all(void* hwnd); -void window_cursor_size_ns(void* hwnd); -void window_cursor_size_we(void* hwnd); +void window_set_cursor(void* hwnd, int kind); float window_dpi(void* hwnd); int screen_width_mm(void* hwnd); int screen_height_mm(void* hwnd); diff --git a/src/platform/windowing/window.darwin.go b/src/platform/windowing/window.darwin.go index 5c30e7616..5187aa3e4 100644 --- a/src/platform/windowing/window.darwin.go +++ b/src/platform/windowing/window.darwin.go @@ -101,12 +101,7 @@ func (w *Window) poll() { } } -// Cursor variants (private) -func (w *Window) cursorStandard() { C.cocoa_cursor_standard() } -func (w *Window) cursorIbeam() { C.cocoa_cursor_ibeam() } -func (w *Window) cursorSizeAll() { C.cocoa_cursor_size_all() } -func (w *Window) cursorSizeNS() { C.cocoa_cursor_size_ns() } -func (w *Window) cursorSizeWE() { C.cocoa_cursor_size_we() } +func (w *Window) setCursor(kind CursorKind) { C.cocoa_set_cursor(C.int(kind)) } // Clipboard (private) func (w *Window) copyToClipboard(text string) { diff --git a/src/platform/windowing/window.go b/src/platform/windowing/window.go index df8bc13a4..3fbee0afb 100644 --- a/src/platform/windowing/window.go +++ b/src/platform/windowing/window.go @@ -312,37 +312,17 @@ func DPI2PXF(pixels, mm, targetMM float64) float64 { return targetMM * (pixels / mm) } -func (w *Window) CursorStandard() { - w.cursorChangeCount = max(0, w.cursorChangeCount-1) - if w.cursorChangeCount == 0 { - w.cursorStandard() - } -} - -func (w *Window) CursorIbeam() { - if w.canChangeCursor() { - w.cursorIbeam() - } - w.cursorChangeCount++ -} - -func (w *Window) CursorSizeAll() { - if w.canChangeCursor() { - w.cursorSizeAll() - } - w.cursorChangeCount++ -} - -func (w *Window) CursorSizeNS() { - if w.canChangeCursor() { - w.cursorSizeNS() +func (w *Window) SetCursor(kind CursorKind) { + if kind == CursorKindDefault || kind == CursorKindAuto { + w.cursorChangeCount = max(0, w.cursorChangeCount-1) + if w.cursorChangeCount == 0 { + w.setCursor(kind) + } + return } - w.cursorChangeCount++ -} -func (w *Window) CursorSizeWE() { if w.canChangeCursor() { - w.cursorSizeWE() + w.setCursor(kind) } w.cursorChangeCount++ } @@ -371,7 +351,7 @@ func (w *Window) Destroy() { func (w *Window) Focus() { defer tracing.NewRegion("Window.Focus").End() w.focus() - w.cursorStandard() + w.setCursor(CursorKindDefault) } func (w *Window) Position() (x int, y int) { @@ -676,7 +656,7 @@ func (w *Window) becameInactive() { func (w *Window) becameActive() { defer tracing.NewRegion("Window.becameActive").End() - w.cursorStandard() + w.setCursor(CursorKindDefault) idx := -1 for i := range activeWindows { if activeWindows[i] == w { diff --git a/src/platform/windowing/window.x11.go b/src/platform/windowing/window.x11.go index 2427c5d19..782577377 100644 --- a/src/platform/windowing/window.x11.go +++ b/src/platform/windowing/window.x11.go @@ -53,11 +53,7 @@ package windowing #cgo noescape window_set_size #cgo noescape window_width_mm #cgo noescape window_height_mm -#cgo noescape window_cursor_standard -#cgo noescape window_cursor_ibeam -#cgo noescape window_cursor_size_all -#cgo noescape window_cursor_size_ns -#cgo noescape window_cursor_size_we +#cgo noescape window_set_cursor #cgo noescape window_show_cursor #cgo noescape window_hide_cursor #cgo noescape window_dpi @@ -127,24 +123,8 @@ func (w *Window) poll() { C.window_poll(w.handle) } -func (w *Window) cursorStandard() { - C.window_cursor_standard(w.handle) -} - -func (w *Window) cursorIbeam() { - C.window_cursor_ibeam(w.handle) -} - -func (w *Window) cursorSizeAll() { - C.window_cursor_size_all(w.handle) -} - -func (w *Window) cursorSizeNS() { - C.window_cursor_size_ns(w.handle) -} - -func (w *Window) cursorSizeWE() { - C.window_cursor_size_we(w.handle) +func (w *Window) setCursor(kind CursorKind) { + C.window_set_cursor(w.handle, C.int(kind)) } func (w *Window) copyToClipboard(text string) { diff --git a/src/platform/windowing/window_android.go b/src/platform/windowing/window_android.go index 2d748e455..b06ffe6ab 100644 --- a/src/platform/windowing/window_android.go +++ b/src/platform/windowing/window_android.go @@ -165,11 +165,7 @@ func (w *Window) cHandle() unsafe.Pointer { return w.handle } func (w *Window) cInstance() unsafe.Pointer { return w.instance } func (w *Window) showWindow() {} -func (w *Window) cursorStandard() {} -func (w *Window) cursorIbeam() {} -func (w *Window) cursorSizeAll() {} -func (w *Window) cursorSizeNS() {} -func (w *Window) cursorSizeWE() {} +func (w *Window) setCursor(kind CursorKind) {} func (w *Window) focus() {} func (w *Window) position() (x, y int) { return 0, 0 } func (w *Window) setPosition(x, y int) {} diff --git a/src/platform/windowing/window_windows.go b/src/platform/windowing/window_windows.go index a8052461f..8a5ecabcd 100644 --- a/src/platform/windowing/window_windows.go +++ b/src/platform/windowing/window_windows.go @@ -55,8 +55,7 @@ import ( #cgo noescape window_main #cgo noescape window_show #cgo noescape window_destroy -#cgo noescape window_cursor_standard -#cgo noescape window_cursor_ibeam +#cgo noescape window_set_cursor #cgo noescape window_dpi #cgo noescape screen_width_mm #cgo noescape screen_height_mm @@ -135,24 +134,8 @@ func (w *Window) poll() { w.pollEvents() } -func (w *Window) cursorStandard() { - C.window_cursor_standard(w.handle) -} - -func (w *Window) cursorIbeam() { - C.window_cursor_ibeam(w.handle) -} - -func (w *Window) cursorSizeAll() { - C.window_cursor_size_all(w.handle) -} - -func (w *Window) cursorSizeNS() { - C.window_cursor_size_ns(w.handle) -} - -func (w *Window) cursorSizeWE() { - C.window_cursor_size_we(w.handle) +func (w *Window) setCursor(kind CursorKind) { + C.window_set_cursor(w.handle, C.int(kind)) } func (w *Window) copyToClipboard(text string) { diff --git a/src/platform/windowing/x11.c b/src/platform/windowing/x11.c index b7ae9f47a..b40dd8c69 100644 --- a/src/platform/windowing/x11.c +++ b/src/platform/windowing/x11.c @@ -645,34 +645,113 @@ int screen_count(void* state) { return 1; // TODO } -void window_cursor_standard(void* state) { - X11State* s = state; - Cursor c = XcursorLibraryLoadCursor(s->d, "arrow"); - XDefineCursor(s->d, s->w, c); -} - -void window_cursor_ibeam(void* state) { - X11State* s = state; - Cursor c = XcursorLibraryLoadCursor(s->d, "xterm"); - XDefineCursor(s->d, s->w, c); +static Cursor load_cursor(Display* d, const char** names) { + for (int i = 0; names[i] != NULL; i++) { + Cursor c = XcursorLibraryLoadCursor(d, names[i]); + if (c != None) { + return c; + } + } + return XcursorLibraryLoadCursor(d, "arrow"); } -void window_cursor_size_all(void* state) { - X11State* s = state; - Cursor c = XcursorLibraryLoadCursor(s->d, "sizing"); - XDefineCursor(s->d, s->w, c); -} +void window_set_cursor(void* state, int kind) { + if (kind == 2) { // CursorKindNone + window_hide_cursor(state); + return; + } -void window_cursor_size_ns(void* state) { X11State* s = state; - Cursor c = XcursorLibraryLoadCursor(s->d, "sb_v_double_arrow"); - XDefineCursor(s->d, s->w, c); -} + const char* arrow[] = {"default", "arrow", "left_ptr", NULL}; + const char* text[] = {"text", "xterm", NULL}; + const char* hand[] = {"pointer", "hand2", "hand1", NULL}; + const char* help[] = {"help", "question_arrow", "left_ptr", NULL}; + const char* wait[] = {"wait", "watch", NULL}; + const char* progress[] = {"progress", "left_ptr_watch", "watch", NULL}; + const char* crosshair[] = {"crosshair", "cross", NULL}; + const char* move[] = {"move", "fleur", "sizing", NULL}; + const char* no[] = {"not-allowed", "no-drop", "crossed_circle", NULL}; + const char* grab[] = {"grab", "openhand", "hand2", NULL}; + const char* grabbing[] = {"grabbing", "closedhand", "fleur", NULL}; + const char* ns[] = {"ns-resize", "row-resize", "sb_v_double_arrow", NULL}; + const char* ew[] = {"ew-resize", "col-resize", "sb_h_double_arrow", NULL}; + const char* nesw[] = {"nesw-resize", "ne-resize", "sw-resize", "bottom_left_corner", "top_right_corner", NULL}; + const char* nwse[] = {"nwse-resize", "nw-resize", "se-resize", "bottom_right_corner", "top_left_corner", NULL}; + const char* zoom_in[] = {"zoom-in", "plus", NULL}; + const char* zoom_out[] = {"zoom-out", "minus", NULL}; + const char** names = arrow; + + switch (kind) { + case 3: // CursorKindContextMenu + case 6: // CursorKindPointer + case 12: // CursorKindAlias + case 13: // CursorKindCopy + names = hand; + break; + case 4: // CursorKindText + case 5: // CursorKindVerticalText + names = text; + break; + case 7: // CursorKindHelp + names = help; + break; + case 8: // CursorKindWait + names = wait; + break; + case 9: // CursorKindProgress + names = progress; + break; + case 10: // CursorKindCrosshair + case 11: // CursorKindCell + names = crosshair; + break; + case 14: // CursorKindMove + case 33: // CursorKindResizeAll + names = move; + break; + case 15: // CursorKindNoDrop + case 16: // CursorKindNotAllowed + names = no; + break; + case 17: // CursorKindGrab + names = grab; + break; + case 18: // CursorKindGrabbing + names = grabbing; + break; + case 19: // CursorKindResizeN + case 21: // CursorKindResizeS + case 27: // CursorKindResizeNS + case 32: // CursorKindResizeRow + names = ns; + break; + case 20: // CursorKindResizeE + case 22: // CursorKindResizeW + case 28: // CursorKindResizeEW + case 31: // CursorKindResizeCol + names = ew; + break; + case 23: // CursorKindResizeNE + case 26: // CursorKindResizeSW + case 30: // CursorKindResizeNESW + names = nesw; + break; + case 24: // CursorKindResizeNW + case 25: // CursorKindResizeSE + case 29: // CursorKindResizeNWSE + names = nwse; + break; + case 34: // CursorKindZoomIn + names = zoom_in; + break; + case 35: // CursorKindZoomOut + names = zoom_out; + break; + } -void window_cursor_size_we(void* state) { - X11State* s = state; - Cursor c = XcursorLibraryLoadCursor(s->d, "sb_h_double_arrow"); + Cursor c = load_cursor(s->d, names); XDefineCursor(s->d, s->w, c); + XFlush(s->d); } void window_show_cursor(void* state) { diff --git a/src/platform/windowing/x11.h b/src/platform/windowing/x11.h index 566a80072..a01a57605 100644 --- a/src/platform/windowing/x11.h +++ b/src/platform/windowing/x11.h @@ -109,11 +109,7 @@ int window_width_mm(void* state); int window_height_mm(void* state); int screen_count(void* state); void window_invalidate_monitor_cache(void* state); -void window_cursor_standard(void* state); -void window_cursor_ibeam(void* state); -void window_cursor_size_all(void* state); -void window_cursor_size_ns(void* state); -void window_cursor_size_we(void* state); +void window_set_cursor(void* state, int kind); void window_show_cursor(void* state); void window_hide_cursor(void* state); float window_dpi(void* state); From 96c00d14f98ee23aa58ac85eb6469e6abbce2f4e Mon Sep 17 00:00:00 2001 From: claudioluciano Date: Thu, 21 May 2026 00:30:51 +0100 Subject: [PATCH 2/3] Add native and virtual cursor support --- src/editor/editor.go | 33 +++ .../textures/cursors/kenney/LICENSE.txt | 22 ++ .../textures/cursors/kenney/alias.png | Bin 0 -> 407 bytes .../textures/cursors/kenney/cell.png | Bin 0 -> 378 bytes .../textures/cursors/kenney/context_menu.png | Bin 0 -> 394 bytes .../textures/cursors/kenney/copy.png | Bin 0 -> 396 bytes .../textures/cursors/kenney/crosshair.png | Bin 0 -> 409 bytes .../textures/cursors/kenney/default.png | Bin 0 -> 355 bytes .../textures/cursors/kenney/grab.png | Bin 0 -> 501 bytes .../textures/cursors/kenney/grabbing.png | Bin 0 -> 450 bytes .../textures/cursors/kenney/help.png | Bin 0 -> 490 bytes .../textures/cursors/kenney/move.png | Bin 0 -> 380 bytes .../textures/cursors/kenney/no_drop.png | Bin 0 -> 532 bytes .../textures/cursors/kenney/none.png | Bin 0 -> 409 bytes .../textures/cursors/kenney/not_allowed.png | Bin 0 -> 532 bytes .../textures/cursors/kenney/pointer.png | Bin 0 -> 476 bytes .../textures/cursors/kenney/progress.png | Bin 0 -> 529 bytes .../textures/cursors/kenney/resize_all.png | Bin 0 -> 380 bytes .../textures/cursors/kenney/resize_col.png | Bin 0 -> 297 bytes .../textures/cursors/kenney/resize_e.png | Bin 0 -> 297 bytes .../textures/cursors/kenney/resize_ew.png | Bin 0 -> 297 bytes .../textures/cursors/kenney/resize_n.png | Bin 0 -> 332 bytes .../textures/cursors/kenney/resize_ne.png | Bin 0 -> 329 bytes .../textures/cursors/kenney/resize_nesw.png | Bin 0 -> 329 bytes .../textures/cursors/kenney/resize_ns.png | Bin 0 -> 332 bytes .../textures/cursors/kenney/resize_nw.png | Bin 0 -> 323 bytes .../textures/cursors/kenney/resize_nwse.png | Bin 0 -> 323 bytes .../textures/cursors/kenney/resize_row.png | Bin 0 -> 332 bytes .../textures/cursors/kenney/resize_s.png | Bin 0 -> 332 bytes .../textures/cursors/kenney/resize_se.png | Bin 0 -> 323 bytes .../textures/cursors/kenney/resize_sw.png | Bin 0 -> 329 bytes .../textures/cursors/kenney/resize_w.png | Bin 0 -> 297 bytes .../textures/cursors/kenney/text.png | Bin 0 -> 190 bytes .../textures/cursors/kenney/vertical_text.png | Bin 0 -> 180 bytes .../textures/cursors/kenney/wait.png | Bin 0 -> 495 bytes .../textures/cursors/kenney/zoom_in.png | Bin 0 -> 499 bytes .../textures/cursors/kenney/zoom_out.png | Bin 0 -> 495 bytes src/editor/editor_settings/editor_settings.go | 4 + .../settings_workspace/settings_workspace.go | 34 ++- src/engine/ui/cursor.go | 256 ++++++++++++++++++ src/engine/ui/ui.go | 13 +- src/platform/windowing/cursor.go | 44 +++ src/platform/windowing/window.go | 126 +++++++-- 43 files changed, 486 insertions(+), 46 deletions(-) create mode 100644 src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/LICENSE.txt create mode 100644 src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/alias.png create mode 100644 src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/cell.png create mode 100644 src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/context_menu.png create mode 100644 src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/copy.png create mode 100644 src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/crosshair.png create mode 100644 src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/default.png create mode 100644 src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/grab.png create mode 100644 src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/grabbing.png create mode 100644 src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/help.png create mode 100644 src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/move.png create mode 100644 src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/no_drop.png create mode 100644 src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/none.png create mode 100644 src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/not_allowed.png create mode 100644 src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/pointer.png create mode 100644 src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/progress.png create mode 100644 src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/resize_all.png create mode 100644 src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/resize_col.png create mode 100644 src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/resize_e.png create mode 100644 src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/resize_ew.png create mode 100644 src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/resize_n.png create mode 100644 src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/resize_ne.png create mode 100644 src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/resize_nesw.png create mode 100644 src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/resize_ns.png create mode 100644 src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/resize_nw.png create mode 100644 src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/resize_nwse.png create mode 100644 src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/resize_row.png create mode 100644 src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/resize_s.png create mode 100644 src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/resize_se.png create mode 100644 src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/resize_sw.png create mode 100644 src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/resize_w.png create mode 100644 src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/text.png create mode 100644 src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/vertical_text.png create mode 100644 src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/wait.png create mode 100644 src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/zoom_in.png create mode 100644 src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/zoom_out.png create mode 100644 src/engine/ui/cursor.go diff --git a/src/editor/editor.go b/src/editor/editor.go index 53267e822..49272acb9 100644 --- a/src/editor/editor.go +++ b/src/editor/editor.go @@ -62,6 +62,7 @@ import ( "kaijuengine.com/matrix" "kaijuengine.com/platform/hid" "kaijuengine.com/platform/profiler/tracing" + "kaijuengine.com/platform/windowing" "kaijuengine.com/rendering" // Built-in workspace packages register themselves with @@ -109,6 +110,8 @@ type Editor struct { contentPreviewer content_previews.ContentPreviewer updateId engine.UpdateId blurred bool + cursorUI ui.Manager + cursor *ui.Cursor } type globalUI struct { @@ -160,6 +163,28 @@ func (ed *Editor) IsInputFocused() bool { return ed.currentWorkspace.IsFocusedOnInput() } +func (ed *Editor) ensureCursor() { + if ed.cursor != nil { + return + } + + ed.cursorUI.Init(ed.host) + + ed.cursor = ed.cursorUI.Add().ToCursor() + ed.cursor.Init(ui.CursorThemeByName(ed.settings.CursorTheme)) +} + +func cursorModeFromSetting(mode string) windowing.CursorMode { + switch mode { + case "virtual": + return windowing.CursorModeVirtual + case "auto": + return windowing.CursorModeAuto + default: + return windowing.CursorModeNative + } +} + func (ed *Editor) earlyLoadUI() { defer tracing.NewRegion("Editor.earlyLoadUI").End() ed.globalInterfaces.menuBar.Initialize(ed.host, ed) @@ -172,6 +197,14 @@ func (ed *Editor) UpdateSettings() { ed.settings.UIScrollSpeed = 1 } ui.UIScrollSpeed = ed.settings.UIScrollSpeed + + ed.ensureCursor() + ed.cursor.SetTheme(ui.CursorThemeByName(ed.settings.CursorTheme)) + + ed.host.Window.ResetCursor() + ed.host.Window.SetCursorMode(cursorModeFromSetting(ed.settings.CursorMode)) + ed.cursor.SyndWithWindow() + if err := ed.settings.Save(); err != nil { slog.Error("failed to save the editor settings", "error", err) return diff --git a/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/LICENSE.txt b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/LICENSE.txt new file mode 100644 index 000000000..7c3ee641e --- /dev/null +++ b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/LICENSE.txt @@ -0,0 +1,22 @@ + + + Cursor Pack (1.1) + + Created/distributed by Kenney (www.kenney.nl) + Creation date: 05-06-2024 + + ------------------------------ + + License: (Creative Commons Zero, CC0) + http://creativecommons.org/publicdomain/zero/1.0/ + + This content is free to use in personal, educational and commercial projects. + Support us by crediting Kenney or www.kenney.nl (this is not mandatory) + + ------------------------------ + + Donate: http://support.kenney.nl + Patreon: http://patreon.com/kenney/ + + Follow on Twitter for updates: + http://twitter.com/KenneyNL \ No newline at end of file diff --git a/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/alias.png b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/alias.png new file mode 100644 index 0000000000000000000000000000000000000000..ffa8cb2a465f4cf1de6f90ee6677047a8151942c GIT binary patch literal 407 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyEa{HEjtmSN`?>!lvI6-b0X`wF zK$;lf@9%&A{{8y;dVPKU_wV25=jX3qzdk-be*gac=g*(#=jZS5@1H+^{{R2~fu@CC zPVxg%yd^ z-~a!0@&u>$>F@3AJaMvz!|ki{BBM~PM-Hshn;3=uGA!B8=#bL5<_UwZl+iPWz0yxO z*;mvuin4cDIc#WH!^g>gf#qaFrcAYiW<}sCs|H7dqg+&?8NQ)>LmK;G zW{DS!JNT|`(p_WSJdL?A^`Gz^v(xOSr~NRTpdQe|cjhHS*Xuni7B9d5pL>q9Z*gZq Rc@`)*Jzf1=);T3K0RZNImh=Ds literal 0 HcmV?d00001 diff --git a/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/cell.png b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/cell.png new file mode 100644 index 0000000000000000000000000000000000000000..d92384a0eb2db5aa5175eb045c23eef859516a94 GIT binary patch literal 378 zcmV-=0fqjFP)D4Hge@hp0_`9ra2?=-3a!=$5S(+1f(3|G^DMS& z2F`O~5os0HnE!4g)*$X>o zj9p{SE~@tVy*A$_^SvRO<|3jwCpJNxH0CrmW(INU0Z^NFLa4QR85LTs+M?e3O_!0U za2Zwld=(6(E{UQlt!EFiL+OAi2mkgapYd;yU?(rZ4;5tk#rHOFfT;bV!lvI6-b0X`wF zK$;lf@9%&A{{8y;`uO;GeSQ7+@89R==g*%%fBpLP`}glZfBrl_KYxFJ|NsC0fu{Y~ zz_bTQ@sDxD{`_*n!TT$<8w=mXQH`wR-N8e<9?)R?ChHOTRH zNwcrm%Ph*?Vdb!?p-NboaRFx+<7ErJhHD&)O1Cn0XPja!d1v%_v#jQcjKGQyEwSDg z6c60tHc!1K^!b7q|C5^KcB)Da9^0=;`&gU4z7Te=IdywoIVsEg literal 0 HcmV?d00001 diff --git a/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/copy.png b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/copy.png new file mode 100644 index 0000000000000000000000000000000000000000..4e634a0d56d046da492da9d3e5ab8facadd878b4 GIT binary patch literal 396 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyEa{HEjtmSN`?>!lvI6-b0X`wF zK$;lf@9%&A{{8y;`uO;GeSQ7+@89R==g*%%fBpLP`}glZfBrl_KYxFJ|NsC0fu{Y~ zz_bTQ@s= z;$~m5o5_^Dqsn1J1DmigqXO?q23ud{1tLuvq4@`tW)(5+-F@ayo;sINq=?*|#OsPL z1QOP@zBYO-P<&a8|HvPecxM*})jRv+60Gj#DePEVef0dg1czC>Th~c!NXfjF_Ced? z?~!6w68e8cJ^=6u;Ja2Z0pQG~1>rj5V5LCN9H&Ykrb_rWglRZ4v$Zz%Ip_xf&+~jJOdv{KxEsO~ zk8BT2!GMT*A$r2wKEw`)Vc}AUq2UsUVz@Cz6dtsF(KU!tI1#nZoT|1tU4m!`m#m_4 zh|%F)5R&lMMu1-ccK}$|wb&^7@9t}c=yaaPl1{rJ;iDl=!-+_;Y>x{^!#W{>=n5YV z(HpMOFhyG-Zi4WjUWi)`KpHNEIH-6e>saBc5YD;kk<1};Boi#JMTp2N+XE&oys)tx zAQCTADx|2<)m?3aNG>`noZ*MV|6bWrV+iNHpW5jUL!+P=hKT-J00000NkvXXu0mjf DK|-w7 literal 0 HcmV?d00001 diff --git a/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/default.png b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/default.png new file mode 100644 index 0000000000000000000000000000000000000000..827b8696b90e1a26b37b90d84811c714a6a7338b GIT binary patch literal 355 zcmV-p0i6DcP)JA8OZbux_%J;E zKghGI*qxfXNRWBAHg-us*NUDxd! z@Tva`mOHNSht2r%0E7qLvgZW610N66Jay_H>JzM=3i4P~vpfI*002ovPDHLkV1jnZ Bmze+n literal 0 HcmV?d00001 diff --git a/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/grab.png b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/grab.png new file mode 100644 index 0000000000000000000000000000000000000000..5d7a4b15f5f5ecec69bfd0dc9ba4f2a2dbde8aba GIT binary patch literal 501 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyEa{HEjtmSN`?>!lvI6r;{QCO({QP`l=BJWLOV0 zJIt|Za9vsH6d5#6sgN=5s@iTP-3?uQ4Z%C46OA;!HeXfI;%ji!d3Jd^ThyORmdxxI z5-v>itoKq}>Hn0;+~(`*IEe$NSd|up?~7>&T63LY>c$wRlt-Gb+by#i_t@P{x;yLB z#$8UY8T~`_4k+3*)SSMX7P(ob*W-KKh9;?p@x=$uGX1=EN;5*kSUhGaR|b2*ue}%F zFcfo01Vk+`VJ^6o(vxs|!@ea`;>@cQEZL5Etl={*UpG0>`O?vM97nU?y=V2$yyw0u z;#AmzKIYZaH~%vJk}7k{*D0uIZ^o+Xipu?Y!lvI6Eak-ar)@w)6<$2 z1YCTLl=MsG++2@7`oG_3LWh!p`tF%gUzX+1Z(F2s%UtW9#0r<#>rQ6dF3BiJ^3}RD zNbgR#6t#ZFR=M&d=2_1_d`Vuz7A__r$zXnaPU-htzO@-l51MYeL@6>c>^R%ZZwl*lzZ@^_`qpS_6_9+O!p@--DP0h zy5z51&i*vc6-Q3-eNCK_@WU%lQX+M;%cAGIR$q?ZqN}RD{Ws?xEBAA5Nnf5zST$U< znB2s$dUDajU16CSsV|vNd;ju&Bdz{(eZ2m1_W!{lED{!1&wxVS)78&qol`;+0DktO ADgXcg literal 0 HcmV?d00001 diff --git a/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/help.png b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/help.png new file mode 100644 index 0000000000000000000000000000000000000000..85c84f0113394686b929f84b0da79c889f4026b7 GIT binary patch literal 490 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyEa{HEjtmSN`?>!lvI6-D0X`wF zK$;{_UtjO<@2{_~KY#xG`}gn1$H&{-+xPeP@87@w{rmU%`T6VDuRnkOJU>7G|NsAi zCjXOfl?GA*B|(0{{`L9t>*MG1>)*G3-@pGn!#>x`M;RCxB|TjnLo80WPP#p*SwX-h zwl`eF%us5I_y7O#X$-!nLT{{l)aiWQ$9U!scgeZCq}p^?rzreYX%Nd|T4liyVa>G1 zk1)lb>3a?<0tI?D8~G5{=^zV_8+TqH!w``4&D7= z#lu|r6>m2+M}BE;Xnm)ap8hbaMm)8^t|w7#=E?L;)-0xT($*B)Ka^;C&Ytkxitqb9 zt(s*&l-XptraY43ILNfKgJJsw6Jf&z$JbohqyLfh!Y;Wl-@>ok+Gu*qJFRIijSRc9 r$l-8Jzpsb@Yw9U(L3TgunOCHPAA282UUK3dD26;;{an^LB{Ts50 z;2VkBh>q-eEsyw8QQ}T`%C{sj1D^6gu^ltvDbk2ef#(vG!KoMiHXxw@q~q-g;Ah}n af0th|2R5))G_1z}0000!lvI6+SW=ulJAdzn?$hgswyXKkcp5_ydKGFI# zgo!u3YI1Y-3$M(d@?6QEgm-QH`s1m~{71h7vnoP(e2f-;ZhW!v$6B46YYzNWI_B~C z5&xa)^E*{$Ur3SL{_8|xw@OF>p zB`+_ZeeVx{`^xt~Nb%LZozt9BX5MAqc5HKG*hQP?skuunFSy@W{@6wT8~23hu+VQD k8$1Ks{;%|VWB6gEd9_4H^1p3FVdQ&MBb@09H@aDgXcg literal 0 HcmV?d00001 diff --git a/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/none.png b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/none.png new file mode 100644 index 0000000000000000000000000000000000000000..dc2758c56dde6025041b98be5800fd08d84d43e9 GIT binary patch literal 409 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyEa{HEjtmSN`?>!lvI6`vpP%2~-(O!}zkmOJd3kwzd;9tG=kxRP-@kwV{Q2|o@$u`|uYdpky}rIa(9Hk; z|C^+je1=gY_M*FQhsp8q{VKktKeK&dmHE{-7V4tHB(SgtUJvDi*% zJJ7MT#oy=i=jTu3e|(pE$khDEaC){&5pTtbuZBs?UH4@l@EmhKlBYgbFJP_Uvn^LQ zMw!lvI6+SW=ulJAdzn?$hgswyXKkcp5_ydKGFI# zgo!u3YI1Y-3$M(d@?6QEgm-QH`s1m~{71h7vnoP(e2f-;ZhW!v$6B46YYzNWI_B~C z5&xa)^E*{$Ur3SL{_8|xw@OF>p zB`+_ZeeVx{`^xt~Nb%LZozt9BX5MAqc5HKG*hQP?skuunFSy@W{@6wT8~23hu+VQD k8$1Ks{;%|VWB6gEd9_4H^1p3FVdQ&MBb@09H@aDgXcg literal 0 HcmV?d00001 diff --git a/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/pointer.png b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/pointer.png new file mode 100644 index 0000000000000000000000000000000000000000..1ce04eff6f3d5c4c36024debcbf4daefb9ad9d0b GIT binary patch literal 476 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyEa{HEjtmSN`?>!lvI6+9Fo*WbT?|NZ;-^XJdszkff_ z=9U*zx6QZKTca}_~wuF;_7hwHF5KDnajNo?Or)ebcxfc eKl@Kt&-G`!8SyRU#wj~coOrtWxvX!lvI6-D0X`wF zK$>`vpP$dq&p&_u{QLLs%gf8ZfB#-zUw{4j_3`oX{{H^`{r&s*?>~S3e0_bry}kYa z|Nns|Gk<1K1XBDZL4LvV`TqCq_0RXuuiwuf|DHju%uSDhfzi>^#WBR<^xVlWC$%W> zw4^`(Ady&V6Cil${r_pw0U=jbt-bp%vGJ#t%95S6{})j7aWyeW5xd_Om>AydMv2Cdx25HQ~4u7sfC^Eg&E_w@Cjf^V+R!u{V* jQ!h68srBz?h5lFO))SFhg*7|wgCf$?)z4*}Q$iB}{ru0l literal 0 HcmV?d00001 diff --git a/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/resize_all.png b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/resize_all.png new file mode 100644 index 0000000000000000000000000000000000000000..f1c694c79bb6acf5baf01763f7b76abd74b1bf8d GIT binary patch literal 380 zcmV-?0fYXDP) z;2VkBh>q-eEsyw8QQ}T`%C{sj1D^6gu^ltvDbk2ef#(vG!KoMiHXxw@q~q-g;Ah}n af0th|2R5))G_1z}0000g*oeXMitYxIQ0&PvHCSN@X}eli|9C8)H;NR7BJm(@o+Q9zxKG5JDG;yWzbg4r}2H z5{H%o3aIyfF?_;%KR=jR-U1qP4`0e{_-<&jRP zTnv5|+TBW3)hs;&Ph%6<1U7*hfp;#=D2JzU7X;W---`Dw1w5e6x$JEyB9lODDWHIk ze{lxiLaZdeLci7k00004pvf literal 0 HcmV?d00001 diff --git a/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/resize_ne.png b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/resize_ne.png new file mode 100644 index 0000000000000000000000000000000000000000..dcd53f63bb9074ee19056124add8e4263f3cd46a GIT binary patch literal 329 zcmV-P0k-~$P)5QgD5YXA#400&4|2}r1fN;ncmLUDiuq>O~w3l1#H9?q7ZHhaOBOhVFxc~aU5 zaOciHjsQKNfUQcOYd%%;_C?dJ`b+^(z?^fYs7MA~FH`?0{hy ztSE{JAlU&;(=5+GlO3R{R#nyVMpRh=IS@Yps%jyGtOx~6L@b0bA2<=ohIp_scp?!E z;20;RXB=$1;~DRG{{*=mh*xz8vCcfAV-U`{<;2u00MUr$8sEEcS>7VnNdee}NCk*S zyaRlk1eDW5UDqdHbu2w#-uuI!;Oq_nP?qK9TGw?~wAjo%oFeLx0#GpilKs^@+0W~p bJE!vnv!_{M&{B6i00000NkvXXu0mjf)~JV^ literal 0 HcmV?d00001 diff --git a/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/resize_nesw.png b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/resize_nesw.png new file mode 100644 index 0000000000000000000000000000000000000000..dcd53f63bb9074ee19056124add8e4263f3cd46a GIT binary patch literal 329 zcmV-P0k-~$P)5QgD5YXA#400&4|2}r1fN;ncmLUDiuq>O~w3l1#H9?q7ZHhaOBOhVFxc~aU5 zaOciHjsQKNfUQcOYd%%;_C?dJ`b+^(z?^fYs7MA~FH`?0{hy ztSE{JAlU&;(=5+GlO3R{R#nyVMpRh=IS@Yps%jyGtOx~6L@b0bA2<=ohIp_scp?!E z;20;RXB=$1;~DRG{{*=mh*xz8vCcfAV-U`{<;2u00MUr$8sEEcS>7VnNdee}NCk*S zyaRlk1eDW5UDqdHbu2w#-uuI!;Oq_nP?qK9TGw?~wAjo%oFeLx0#GpilKs^@+0W~p bJE!vnv!_{M&{B6i00000NkvXXu0mjf)~JV^ literal 0 HcmV?d00001 diff --git a/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/resize_ns.png b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/resize_ns.png new file mode 100644 index 0000000000000000000000000000000000000000..d0ad7ae36a26a4903c83c849ef4b14f2a22bb08b GIT binary patch literal 332 zcmV-S0ki&zP)g*oeXMitYxIQ0&PvHCSN@X}eli|9C8)H;NR7BJm(@o+Q9zxKG5JDG;yWzbg4r}2H z5{H%o3aIyfF?_;%KR=jR-U1qP4`0e{_-<&jRP zTnv5|+TBW3)hs;&Ph%6<1U7*hfp;#=D2JzU7X;W---`Dw1w5e6x$JEyB9lODDWHIk ze{lxiLaZdeLci7k00004pvf literal 0 HcmV?d00001 diff --git a/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/resize_nw.png b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/resize_nw.png new file mode 100644 index 0000000000000000000000000000000000000000..471c9ce8814d1c3503831563ad89a80b46885698 GIT binary patch literal 323 zcmV-J0lfZ+P)M^~e1s?@!G|Gs?iBGPe_(`Xw5(Q! zBS*d<4j6y{=)xL+t`z(nfi5jmLO=jD#%KsZpA$k*Ywb-Sln@vI_1?e0=iy$3?D2qB zRi)!N{)s?)JG^b%j|R77?Q7_L*U z11#5uOM*w1Y=ti#lnA$z_`rP>S6c)E(}bH%vQ&7Mk*mN1;FItdfmlkxlLSh_R|Rsy zfA)OTG|l|+^o~F*tG~cGH^1@h&aV~d`#$>asS4TCk$%;(r6gPeT-Z3_jvV=Sxd690 VS|9_9y1f7Z002ovPDHLkV1nxjhxY&g literal 0 HcmV?d00001 diff --git a/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/resize_nwse.png b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/resize_nwse.png new file mode 100644 index 0000000000000000000000000000000000000000..471c9ce8814d1c3503831563ad89a80b46885698 GIT binary patch literal 323 zcmV-J0lfZ+P)M^~e1s?@!G|Gs?iBGPe_(`Xw5(Q! zBS*d<4j6y{=)xL+t`z(nfi5jmLO=jD#%KsZpA$k*Ywb-Sln@vI_1?e0=iy$3?D2qB zRi)!N{)s?)JG^b%j|R77?Q7_L*U z11#5uOM*w1Y=ti#lnA$z_`rP>S6c)E(}bH%vQ&7Mk*mN1;FItdfmlkxlLSh_R|Rsy zfA)OTG|l|+^o~F*tG~cGH^1@h&aV~d`#$>asS4TCk$%;(r6gPeT-Z3_jvV=Sxd690 VS|9_9y1f7Z002ovPDHLkV1nxjhxY&g literal 0 HcmV?d00001 diff --git a/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/resize_row.png b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/resize_row.png new file mode 100644 index 0000000000000000000000000000000000000000..d0ad7ae36a26a4903c83c849ef4b14f2a22bb08b GIT binary patch literal 332 zcmV-S0ki&zP)g*oeXMitYxIQ0&PvHCSN@X}eli|9C8)H;NR7BJm(@o+Q9zxKG5JDG;yWzbg4r}2H z5{H%o3aIyfF?_;%KR=jR-U1qP4`0e{_-<&jRP zTnv5|+TBW3)hs;&Ph%6<1U7*hfp;#=D2JzU7X;W---`Dw1w5e6x$JEyB9lODDWHIk ze{lxiLaZdeLci7k00004pvf literal 0 HcmV?d00001 diff --git a/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/resize_s.png b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/resize_s.png new file mode 100644 index 0000000000000000000000000000000000000000..d0ad7ae36a26a4903c83c849ef4b14f2a22bb08b GIT binary patch literal 332 zcmV-S0ki&zP)g*oeXMitYxIQ0&PvHCSN@X}eli|9C8)H;NR7BJm(@o+Q9zxKG5JDG;yWzbg4r}2H z5{H%o3aIyfF?_;%KR=jR-U1qP4`0e{_-<&jRP zTnv5|+TBW3)hs;&Ph%6<1U7*hfp;#=D2JzU7X;W---`Dw1w5e6x$JEyB9lODDWHIk ze{lxiLaZdeLci7k00004pvf literal 0 HcmV?d00001 diff --git a/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/resize_se.png b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/resize_se.png new file mode 100644 index 0000000000000000000000000000000000000000..471c9ce8814d1c3503831563ad89a80b46885698 GIT binary patch literal 323 zcmV-J0lfZ+P)M^~e1s?@!G|Gs?iBGPe_(`Xw5(Q! zBS*d<4j6y{=)xL+t`z(nfi5jmLO=jD#%KsZpA$k*Ywb-Sln@vI_1?e0=iy$3?D2qB zRi)!N{)s?)JG^b%j|R77?Q7_L*U z11#5uOM*w1Y=ti#lnA$z_`rP>S6c)E(}bH%vQ&7Mk*mN1;FItdfmlkxlLSh_R|Rsy zfA)OTG|l|+^o~F*tG~cGH^1@h&aV~d`#$>asS4TCk$%;(r6gPeT-Z3_jvV=Sxd690 VS|9_9y1f7Z002ovPDHLkV1nxjhxY&g literal 0 HcmV?d00001 diff --git a/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/resize_sw.png b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/resize_sw.png new file mode 100644 index 0000000000000000000000000000000000000000..dcd53f63bb9074ee19056124add8e4263f3cd46a GIT binary patch literal 329 zcmV-P0k-~$P)5QgD5YXA#400&4|2}r1fN;ncmLUDiuq>O~w3l1#H9?q7ZHhaOBOhVFxc~aU5 zaOciHjsQKNfUQcOYd%%;_C?dJ`b+^(z?^fYs7MA~FH`?0{hy ztSE{JAlU&;(=5+GlO3R{R#nyVMpRh=IS@Yps%jyGtOx~6L@b0bA2<=ohIp_scp?!E z;20;RXB=$1;~DRG{{*=mh*xz8vCcfAV-U`{<;2u00MUr$8sEEcS>7VnNdee}NCk*S zyaRlk1eDW5UDqdHbu2w#-uuI!;Oq_nP?qK9TGw?~wAjo%oFeLx0#GpilKs^@+0W~p bJE!vnv!_{M&{B6i00000NkvXXu0mjf)~JV^ literal 0 HcmV?d00001 diff --git a/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/resize_w.png b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/resize_w.png new file mode 100644 index 0000000000000000000000000000000000000000..2e1cb9b31f5a9aaa800464a404f02ae8ac893748 GIT binary patch literal 297 zcmV+^0oMMBP)SZ) z(f7ooZ-SuALBW`X$|cPefh(I9xK>U0@3>(3ligw=vC0O_`_A%62t>=scOAYi8O71^J9W!nK#&GXCwhbf%R#Z*?N~0C zR-V&<);b7Y$8j6iD55H(QtILI zE2Yfxcb48q63}UyK5QUO(?N}A(*(EzT1wdqmr~a7DZ&c+Ed>LKosX~|TX2PN3{&n2>TyTzL+wGS5eBKNfMN#}3 zIQo9u@As@$D}Ya6+6Fld+-|oZcpbCZ%*t~%;TZ6uC|n4)0i{$h+?WZybT<=@6bByz l5#xN8)sX=&el_?j`T^aPZ=0RZx^w^l002ovPDHLkV1g-p!lvI6-D0X`wF zK$>`rBv70RAW2{^L{NFz(FF;Y!cIFl4_NPp~rNVz}0>0EcEM271X<^B-NN9J+zpiIH}`0w{ycIaHWlV0t)jagM{*2|5IFEVc|xWm}6okvdMhl2uxh0gU~ z_sS2nYx6bNusE(eZz7!#`?7$uH!1(tK}pjaTX-hidnGh=>#oP8)32(POzNxfpXNLL z(@VeDg!UHg8QfPV-@ATmLWFmp*Rd~R7xzZ$mmCS(@~O?CZ^aGn_-{cXd%{vG_Xz3k zuKZD0!lvI6-D0X`wF zK$>`rBv70}^ta7_ii1+N5d+qbucu&bPRGfa$*vQYq)NpxI{QvK= z9~6%yPU=_iEce~JRyy1&#%b}5+1FSleInUB@=H5UMcn){9cj`Q`gI z9Vy{52-JUa`t6sOnzOrY>SurAig{Yz|J`MO-}B(2Gi}`cplI@R^>bP0l+XkK_KmwV literal 0 HcmV?d00001 diff --git a/src/editor/editor_settings/editor_settings.go b/src/editor/editor_settings/editor_settings.go index 87d72cd21..7e3edb896 100644 --- a/src/editor/editor_settings/editor_settings.go +++ b/src/editor/editor_settings/editor_settings.go @@ -61,6 +61,8 @@ type Settings struct { MeshEditor string AudioEditor string UIScrollSpeed float32 `default:"20" label:"UI Scroll Speed"` + CursorMode string `default:"virtual" label:"Cursor Mode"` + CursorTheme string `default:"kenney" label:"Cursor Theme"` ShowGrid bool `default:"true" label:"Show Viewport Grid"` EditorCamera EditorCameraSettings Snapping SnapSettings @@ -110,6 +112,8 @@ func (s *Settings) setDefaults() { s.RefreshRate = 60 s.CodeEditor = "code" s.UIScrollSpeed = 20 + s.CursorMode = "virtual" + s.CursorTheme = "kenney" s.ShowGrid = true s.EditorCamera.ZoomSpeed = 120 s.EditorCamera.FlySpeed = 10 diff --git a/src/editor/editor_workspace/settings_workspace/settings_workspace.go b/src/editor/editor_workspace/settings_workspace/settings_workspace.go index 42157db00..bbe46651b 100644 --- a/src/editor/editor_workspace/settings_workspace/settings_workspace.go +++ b/src/editor/editor_workspace/settings_workspace/settings_workspace.go @@ -82,18 +82,18 @@ type editorWorkspaceController interface { type SettingsWorkspace struct { common_workspace.CommonWorkspace - projectSettingsBox *document.Element - editorSettingsBox *document.Element - pluginSettingsBox *document.Element + projectSettingsBox *document.Element + editorSettingsBox *document.Element + pluginSettingsBox *document.Element workspaceSettingsBox *document.Element - editor editor_workspace.WorkspaceEditorInterface - editorSettings *editor_settings.Settings - projectSettings *project.Settings - plugins []editor_plugin.PluginInfo - pluginInitStates []bool - reloadRequested bool - recompiling bool - downloadingPlugin bool + editor editor_workspace.WorkspaceEditorInterface + editorSettings *editor_settings.Settings + projectSettings *project.Settings + plugins []editor_plugin.PluginInfo + pluginInitStates []bool + reloadRequested bool + recompiling bool + downloadingPlugin bool } // workspaceRowData is the per-row data the Workspaces panel template loops over. @@ -301,6 +301,7 @@ func (w *SettingsWorkspace) valueChanged(e *document.Element) { defer tracing.NewRegion("SettingsWorkspace.valueChanged").End() if w.editorSettingsBox.UI.Entity().IsActive() { common_workspace.SetObjectValueFromUI(w.editorSettings, e) + w.editor.UpdateSettings() } else if w.projectSettingsBox.UI.Entity().IsActive() { common_workspace.SetObjectValueFromUI(w.projectSettings, e) } @@ -501,7 +502,16 @@ func (w *SettingsWorkspace) uiData() settingsWorkspaceData { for i := range w.plugins { w.pluginInitStates[i] = w.plugins[i].Config.Enabled } - listings := map[string][]ui.SelectOption{} + listings := map[string][]ui.SelectOption{ + "CursorMode": { + {Name: "Native", Value: "native"}, + {Name: "Virtual", Value: "virtual"}, + {Name: "Auto", Value: "auto"}, + }, + "CursorTheme": { + {Name: "Kenney", Value: "kenney"}, + }, + } cache := w.editor.Project().CacheDatabase() return settingsWorkspaceData{ Editor: common_workspace.ReflectUIStructure(cache, diff --git a/src/engine/ui/cursor.go b/src/engine/ui/cursor.go new file mode 100644 index 000000000..7ab9ea336 --- /dev/null +++ b/src/engine/ui/cursor.go @@ -0,0 +1,256 @@ +/******************************************************************************/ +/* cursor.go */ +/******************************************************************************/ +/* This file is part of */ +/* KAIJU ENGINE */ +/* https://kaijuengine.com/ */ +/******************************************************************************/ +/* MIT License */ +/* */ +/* Copyright (c) 2023-present Kaiju Engine authors (AUTHORS.md). */ +/* Copyright (c) 2015-present Brent Farris. */ +/* */ +/* May all those that this source may reach be blessed by the LORD and find */ +/* peace and joy in life. */ +/* Everyone who drinks of this water will be thirsty again; but whoever */ +/* drinks of the water that I will give him shall never thirst; John 4:13-14 */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining a */ +/* copy of this software and associated documentation files (the "Software"), */ +/* to deal in the Software without restriction, including without limitation */ +/* the rights to use, copy, modify, merge, publish, distribute, sublicense, */ +/* and/or sell copies of the Software, and to permit persons to whom the */ +/* Software is furnished to do so, subject to the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be included in */ +/* all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS */ +/* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT */ +/* OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE */ +/* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/******************************************************************************/ + +package ui + +import ( + "log/slog" + "path" + + "kaijuengine.com/matrix" + "kaijuengine.com/platform/profiler/tracing" + "kaijuengine.com/platform/windowing" + "kaijuengine.com/rendering" +) + +type Cursor UI + +type CursorTheme struct { + Name string + BasePath string + Size matrix.Vec2 + Textures map[windowing.CursorKind]string + Hotspots map[windowing.CursorKind]matrix.Vec2 +} + +type cursorData struct { + panelData + theme CursorTheme + lastKind windowing.CursorKind +} + +var KenneyCursorTheme = CursorTheme{ + Name: "kenney", + BasePath: "cursors/kenney", + Size: matrix.NewVec2(32, 32), + Textures: map[windowing.CursorKind]string{ + windowing.CursorKindAuto: "default.png", + windowing.CursorKindDefault: "default.png", + windowing.CursorKindNone: "none.png", + windowing.CursorKindContextMenu: "context_menu.png", + windowing.CursorKindText: "text.png", + windowing.CursorKindVerticalText: "vertical_text.png", + windowing.CursorKindPointer: "pointer.png", + windowing.CursorKindHelp: "help.png", + windowing.CursorKindWait: "wait.png", + windowing.CursorKindProgress: "progress.png", + windowing.CursorKindCrosshair: "crosshair.png", + windowing.CursorKindCell: "cell.png", + windowing.CursorKindAlias: "alias.png", + windowing.CursorKindCopy: "copy.png", + windowing.CursorKindMove: "move.png", + windowing.CursorKindNoDrop: "no_drop.png", + windowing.CursorKindNotAllowed: "not_allowed.png", + windowing.CursorKindGrab: "grab.png", + windowing.CursorKindGrabbing: "grabbing.png", + windowing.CursorKindResizeN: "resize_n.png", + windowing.CursorKindResizeE: "resize_e.png", + windowing.CursorKindResizeS: "resize_s.png", + windowing.CursorKindResizeW: "resize_w.png", + windowing.CursorKindResizeNE: "resize_ne.png", + windowing.CursorKindResizeNW: "resize_nw.png", + windowing.CursorKindResizeSE: "resize_se.png", + windowing.CursorKindResizeSW: "resize_sw.png", + windowing.CursorKindResizeNS: "resize_ns.png", + windowing.CursorKindResizeEW: "resize_ew.png", + windowing.CursorKindResizeNWSE: "resize_nwse.png", + windowing.CursorKindResizeNESW: "resize_nesw.png", + windowing.CursorKindResizeCol: "resize_col.png", + windowing.CursorKindResizeRow: "resize_row.png", + windowing.CursorKindResizeAll: "resize_all.png", + windowing.CursorKindZoomIn: "zoom_in.png", + windowing.CursorKindZoomOut: "zoom_out.png", + }, + Hotspots: map[windowing.CursorKind]matrix.Vec2{ + windowing.CursorKindText: matrix.NewVec2(16, 16), + windowing.CursorKindVerticalText: matrix.NewVec2(16, 16), + windowing.CursorKindWait: matrix.NewVec2(16, 16), + windowing.CursorKindProgress: matrix.NewVec2(16, 16), + windowing.CursorKindCrosshair: matrix.NewVec2(16, 16), + windowing.CursorKindCell: matrix.NewVec2(16, 16), + windowing.CursorKindMove: matrix.NewVec2(16, 16), + windowing.CursorKindGrab: matrix.NewVec2(16, 16), + windowing.CursorKindGrabbing: matrix.NewVec2(16, 16), + windowing.CursorKindResizeN: matrix.NewVec2(16, 16), + windowing.CursorKindResizeE: matrix.NewVec2(16, 16), + windowing.CursorKindResizeS: matrix.NewVec2(16, 16), + windowing.CursorKindResizeW: matrix.NewVec2(16, 16), + windowing.CursorKindResizeNE: matrix.NewVec2(16, 16), + windowing.CursorKindResizeNW: matrix.NewVec2(16, 16), + windowing.CursorKindResizeSE: matrix.NewVec2(16, 16), + windowing.CursorKindResizeSW: matrix.NewVec2(16, 16), + windowing.CursorKindResizeNS: matrix.NewVec2(16, 16), + windowing.CursorKindResizeEW: matrix.NewVec2(16, 16), + windowing.CursorKindResizeNWSE: matrix.NewVec2(16, 16), + windowing.CursorKindResizeNESW: matrix.NewVec2(16, 16), + windowing.CursorKindResizeCol: matrix.NewVec2(16, 16), + windowing.CursorKindResizeRow: matrix.NewVec2(16, 16), + windowing.CursorKindResizeAll: matrix.NewVec2(16, 16), + windowing.CursorKindZoomIn: matrix.NewVec2(16, 16), + windowing.CursorKindZoomOut: matrix.NewVec2(16, 16), + }, +} + +func (u *UI) ToCursor() *Cursor { return (*Cursor)(u) } +func (c *Cursor) Base() *UI { return (*UI)(c) } +func (d *cursorData) innerPanelData() *panelData { return &d.panelData } + +func (c *Cursor) CursorData() *cursorData { + return c.Base().elmData.(*cursorData) +} + +func (c *Cursor) Init(theme CursorTheme) { + c.Base().elmData = &cursorData{ + theme: theme, + lastKind: windowing.CursorKind(-1), + } + + p := c.Base().ToPanel() + p.Init(nil, ElementTypeCursor) + p.AllowClickThrough() + + if p.shaderData != nil { + p.shaderData.BorderLen = matrix.Vec2Zero() + } + + layout := c.Base().Layout() + layout.SetPositioning(PositioningFixed) + layout.SetZ(100) + layout.Scale(theme.Size.X(), theme.Size.Y()) + c.setKind(windowing.CursorKindDefault) +} + +func (c *Cursor) SetTheme(theme CursorTheme) { + data := c.CursorData() + data.theme = theme + data.lastKind = windowing.CursorKind(-1) + c.Base().Layout().Scale(theme.Size.X(), theme.Size.Y()) + c.setKind(c.Base().Host().Window.CursorKind()) +} + +func (c *Cursor) SyndWithWindow() { + base := c.Base() + host := base.Host() + if host == nil { + return + } + if host.Window.CursorMode() != windowing.CursorModeVirtual || + !host.Window.CursorVisible() || + host.Window.CursorKind() == windowing.CursorKindNone { + base.Hide() + return + } + base.Show() +} + +func (theme CursorTheme) TexturePath(kind windowing.CursorKind) string { + if kind == windowing.CursorKindAuto { + kind = windowing.CursorKindDefault + } + if texture, ok := theme.Textures[kind]; ok { + return path.Join(theme.BasePath, texture) + } + return path.Join(theme.BasePath, theme.Textures[windowing.CursorKindDefault]) +} + +func (theme CursorTheme) Hotspot(kind windowing.CursorKind) matrix.Vec2 { + if hotspot, ok := theme.Hotspots[kind]; ok { + return hotspot + } + return matrix.Vec2Zero() +} + +func CursorThemeByName(name string) CursorTheme { + switch name { + case KenneyCursorTheme.Name: + return KenneyCursorTheme + default: + return KenneyCursorTheme + } +} + +func (c *Cursor) setKind(kind windowing.CursorKind) { + data := c.CursorData() + texturePath := data.theme.TexturePath(kind) + tex, err := c.Base().Host().TextureCache().Texture(texturePath, rendering.TextureFilterNearest) + if err != nil { + slog.Error("failed to load virtual cursor texture", + "theme", data.theme.Name, "kind", kind, "texture", texturePath, "error", err) + return + } + c.Base().ToPanel().SetBackground(tex) + data.lastKind = kind +} + +func (c *Cursor) update(deltaTime float64) { + defer tracing.NewRegion("Cursor.update").End() + + base := c.Base() + host := base.Host() + if host == nil { + return + } + + if host.Window.CursorMode() != windowing.CursorModeVirtual || + !host.Window.CursorVisible() || + host.Window.CursorKind() == windowing.CursorKindNone { + base.Hide() + return + } + base.Show() + + kind := host.Window.CursorKind() + data := c.CursorData() + if data.lastKind != kind { + c.setKind(kind) + } + + pos := host.Window.Cursor.ScreenPosition() + hotspot := data.theme.Hotspot(kind) + base.Layout().SetOffset(pos.X()-hotspot.X(), pos.Y()-hotspot.Y()) + + base.ToPanel().update(deltaTime) +} diff --git a/src/engine/ui/ui.go b/src/engine/ui/ui.go index 01bd9e3ee..0a3baedf0 100644 --- a/src/engine/ui/ui.go +++ b/src/engine/ui/ui.go @@ -50,9 +50,11 @@ import ( "kaijuengine.com/rendering" ) -type DirtyType = int -type ElementType = uint8 -type uiBits uint8 +type ( + DirtyType = int + ElementType = uint8 + uiBits uint8 +) const ( DirtyTypeNone = iota @@ -80,6 +82,7 @@ const ( ElementTypeProgressBar ElementTypeSelect ElementTypeSlider + ElementTypeCursor ) const ( @@ -335,7 +338,7 @@ func (ui *UI) GenerateScissor() { for p.PanelData().overflow == OverflowVisible && !p.entity.IsRoot() { p = FirstPanelOnEntity(p.entity.Parent) } - //if !p.entity.IsRoot() { + // if !p.entity.IsRoot() { ps := p.Base().selfScissor() bounds.SetX(max(bounds.X(), ps.X())) bounds.SetY(max(bounds.Y(), ps.Y())) @@ -584,6 +587,8 @@ func (ui *UI) updateFromManager(deltaTime float64) { ui.ToImage().update(deltaTime) case ElementTypeCheckbox: ui.ToPanel().update(deltaTime) + case ElementTypeCursor: + ui.ToCursor().update(deltaTime) } } diff --git a/src/platform/windowing/cursor.go b/src/platform/windowing/cursor.go index 2cf7c705c..53954621f 100644 --- a/src/platform/windowing/cursor.go +++ b/src/platform/windowing/cursor.go @@ -1,3 +1,39 @@ +/******************************************************************************/ +/* cursor.go */ +/******************************************************************************/ +/* This file is part of */ +/* KAIJU ENGINE */ +/* https://kaijuengine.com/ */ +/******************************************************************************/ +/* MIT License */ +/* */ +/* Copyright (c) 2023-present Kaiju Engine authors (AUTHORS.md). */ +/* Copyright (c) 2015-present Brent Farris. */ +/* */ +/* May all those that this source may reach be blessed by the LORD and find */ +/* peace and joy in life. */ +/* Everyone who drinks of this water will be thirsty again; but whoever */ +/* drinks of the water that I will give him shall never thirst; John 4:13-14 */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining a */ +/* copy of this software and associated documentation files (the "Software"), */ +/* to deal in the Software without restriction, including without limitation */ +/* the rights to use, copy, modify, merge, publish, distribute, sublicense, */ +/* and/or sell copies of the Software, and to permit persons to whom the */ +/* Software is furnished to do so, subject to the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be included in */ +/* all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS */ +/* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT */ +/* OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE */ +/* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/******************************************************************************/ + package windowing type CursorKind int @@ -40,3 +76,11 @@ const ( CursorKindZoomIn CursorKindZoomOut ) + +type CursorMode int + +const ( + CursorModeNative CursorMode = iota + CursorModeVirtual + CursorModeAuto +) diff --git a/src/platform/windowing/window.go b/src/platform/windowing/window.go index 3fbee0afb..082625543 100644 --- a/src/platform/windowing/window.go +++ b/src/platform/windowing/window.go @@ -86,7 +86,7 @@ type Window struct { width, height int left, top, right, bottom int // Full window including title and borders resetDragDataInFrames int - cursorChangeCount int + cursorVisible bool cachedScreenSizeWidthMM int cacheScreenSizeHeightMM int windowSync chan struct{} @@ -98,6 +98,8 @@ type Window struct { fatalFromNativeAPI bool resizedFromNativeAPI bool isFullScreen bool + cursorKind CursorKind + cursorMode CursorMode } type FileSearch struct { @@ -132,21 +134,24 @@ func New(windowName string, width, height, x, y int, adb assets.Database, platfo debug.Assert(height > 0, "window height must be greater than zero") debug.Assert(adb != nil, "asset database cannot be nil") w := &Window{ - Keyboard: hid.NewKeyboard(), - Mouse: hid.NewMouse(), - Touch: hid.NewTouch(), - Stylus: hid.NewStylus(), - Controller: hid.NewController(), - width: width, - height: height, - x: x, - y: y, - left: x, - top: y, - right: x + width, - bottom: y + height, - title: windowName, - windowSync: make(chan struct{}), + Keyboard: hid.NewKeyboard(), + Mouse: hid.NewMouse(), + Touch: hid.NewTouch(), + Stylus: hid.NewStylus(), + Controller: hid.NewController(), + width: width, + height: height, + x: x, + y: y, + left: x, + top: y, + right: x + width, + bottom: y + height, + title: windowName, + cursorKind: CursorKindDefault, + cursorMode: CursorModeNative, + cursorVisible: true, + windowSync: make(chan struct{}), } keys := w.checkToggleKeyState() for key, pressed := range keys { @@ -312,19 +317,84 @@ func DPI2PXF(pixels, mm, targetMM float64) float64 { return targetMM * (pixels / mm) } -func (w *Window) SetCursor(kind CursorKind) { - if kind == CursorKindDefault || kind == CursorKindAuto { - w.cursorChangeCount = max(0, w.cursorChangeCount-1) - if w.cursorChangeCount == 0 { - w.setCursor(kind) +func (w *Window) CursorKind() CursorKind { + return w.cursorKind +} + +func (w *Window) CursorMode() CursorMode { + return w.cursorMode +} + +func (w *Window) CursorVisible() bool { + return w.cursorVisible +} + +// SetCursorMode selects the cursor renderer. Native mode delegates cursor +// drawing to the platform; virtual mode hides the platform cursor so UI can +// render it instead. +func (w *Window) SetCursorMode(mode CursorMode) { + if w.cursorMode == mode { + return + } + oldMode := w.cursorMode + w.cursorMode = mode + if oldMode == CursorModeVirtual && w.cursorVisible { + w.showCursor() + } + if mode == CursorModeVirtual { + if w.cursorVisible { + w.hideCursor() } return } + if w.cursorVisible { + w.setCursor(w.cursorKind) + } +} - if w.canChangeCursor() { - w.setCursor(kind) +func (w *Window) ResetCursor() { + w.SetCursor(CursorKindDefault) +} + +func (w *Window) SetCursor(kind CursorKind) { + if kind == CursorKindAuto { + kind = CursorKindDefault } - w.cursorChangeCount++ + w.cursorKind = kind + if w.cursorMode != CursorModeVirtual && w.cursorVisible { + w.setCursor(w.cursorKind) + } +} + +// HideCursor is a visibility override. They do not change the +// current cursor mode or semantic cursor kind. +func (w *Window) HideCursor() { + w.cursorVisible = false + w.hideCursor() +} + +// ShowCursor is a visibility override. They do not change the +// current cursor mode or semantic cursor kind. +func (w *Window) ShowCursor() { + w.cursorVisible = true + if w.cursorMode == CursorModeVirtual { + return + } + w.showCursor() + w.setCursor(w.cursorKind) +} + +func (w *Window) applyCursor() { + if !w.cursorVisible { + w.hideCursor() + return + } + if w.cursorMode == CursorModeVirtual { + w.hideCursor() + return + } + w.showCursor() + w.setCursor(w.cursorKind) } func (w *Window) CopyToClipboard(text string) { w.copyToClipboard(text) } @@ -351,7 +421,7 @@ func (w *Window) Destroy() { func (w *Window) Focus() { defer tracing.NewRegion("Window.Focus").End() w.focus() - w.setCursor(CursorKindDefault) + w.applyCursor() } func (w *Window) Position() (x int, y int) { @@ -377,8 +447,6 @@ func (w *Window) SetSize(width, height int) { func (w *Window) RemoveBorder() { w.removeBorder() } func (w *Window) AddBorder() { w.addBorder() } -func (w *Window) ShowCursor() { w.showCursor() } -func (w *Window) HideCursor() { w.hideCursor() } func (w *Window) IsFullScreen() bool { return w.isFullScreen } func (w *Window) UnlockCursor() { w.unlockCursor() } @@ -470,8 +538,6 @@ func (w *Window) requestSync() { w.syncRequest = true } -func (w *Window) canChangeCursor() bool { return w.cursorChangeCount == 0 } - func (w *Window) processWindowResizeEvent(evt *WindowResizeEvent) { w.width = int(evt.width) w.height = int(evt.height) @@ -656,7 +722,7 @@ func (w *Window) becameInactive() { func (w *Window) becameActive() { defer tracing.NewRegion("Window.becameActive").End() - w.setCursor(CursorKindDefault) + w.applyCursor() idx := -1 for i := range activeWindows { if activeWindows[i] == w { From 3709ebd3b150121cc8a5feac0be73ee9cff6eb73 Mon Sep 17 00:00:00 2001 From: claudioluciano Date: Thu, 21 May 2026 01:18:36 +0100 Subject: [PATCH 3/3] Add support to cursor mode auto --- src/editor/editor.go | 2 +- .../ui/workspace/settings_workspace.go.html | 34 +++++++++++++++ src/engine/ui/cursor.go | 6 +-- src/engine/ui/ui.go | 2 +- src/platform/windowing/cocoa_window.m | 32 +++++++-------- src/platform/windowing/cursor.go | 10 +++++ src/platform/windowing/cursor_android.go | 7 ++++ src/platform/windowing/cursor_darwin.go | 16 ++++++++ src/platform/windowing/cursor_windows.go | 12 ++++++ src/platform/windowing/cursor_x11.go | 7 ++++ src/platform/windowing/window.go | 41 +++++++++---------- 11 files changed, 125 insertions(+), 44 deletions(-) create mode 100644 src/platform/windowing/cursor_android.go create mode 100644 src/platform/windowing/cursor_darwin.go create mode 100644 src/platform/windowing/cursor_windows.go create mode 100644 src/platform/windowing/cursor_x11.go diff --git a/src/editor/editor.go b/src/editor/editor.go index 49272acb9..bdbcb1a33 100644 --- a/src/editor/editor.go +++ b/src/editor/editor.go @@ -203,7 +203,7 @@ func (ed *Editor) UpdateSettings() { ed.host.Window.ResetCursor() ed.host.Window.SetCursorMode(cursorModeFromSetting(ed.settings.CursorMode)) - ed.cursor.SyndWithWindow() + ed.cursor.SyncWithWindow() if err := ed.settings.Save(); err != nil { slog.Error("failed to save the editor settings", "error", err) diff --git a/src/editor/editor_embedded_content/editor_content/editor/ui/workspace/settings_workspace.go.html b/src/editor/editor_embedded_content/editor_content/editor/ui/workspace/settings_workspace.go.html index f3fdc1a2c..3808f043e 100644 --- a/src/editor/editor_embedded_content/editor_content/editor/ui/workspace/settings_workspace.go.html +++ b/src/editor/editor_embedded_content/editor_content/editor/ui/workspace/settings_workspace.go.html @@ -188,6 +188,28 @@

{{.Name}}

.workspaceRequired { color: #cc6; margin-right: 1em; } .workspaceToggle { margin-right: 0.5em; } .workspaceMoveBtn { margin-left: 0.25em; } + .cursorAutoTestGrid { + width: 100%; + margin-top: 1em; + } + .cursorAutoTestBox { + width: 150px; + height: 34px; + margin: 4px; + padding: 8px; + background-color: #313232; + color: white; + border: 1px solid #575757; + } + .cursorAutoDefault { cursor: default; } + .cursorAutoPointer { cursor: pointer; } + .cursorAutoText { cursor: text; } + .cursorAutoWait { cursor: wait; } + .cursorAutoProgress { cursor: progress; } + .cursorAutoResizeNWSE { cursor: nwse-resize; } + .cursorAutoResizeNESW { cursor: nesw-resize; } + .cursorAutoZoomIn { cursor: zoom-in; } + .cursorAutoZoomOut { cursor: zoom-out; } .gitDownloadRow { @@ -226,6 +248,18 @@

Project Settings

Editor Settings

{{template "section" .Editor}} +

Cursor Auto Test

+
+
default
+
pointer
+
text
+
wait
+
progress
+
nwse-resize
+
nesw-resize
+
zoom-in
+
zoom-out
+

Workspaces

diff --git a/src/engine/ui/cursor.go b/src/engine/ui/cursor.go index 7ab9ea336..4022a9a29 100644 --- a/src/engine/ui/cursor.go +++ b/src/engine/ui/cursor.go @@ -171,13 +171,13 @@ func (c *Cursor) SetTheme(theme CursorTheme) { c.setKind(c.Base().Host().Window.CursorKind()) } -func (c *Cursor) SyndWithWindow() { +func (c *Cursor) SyncWithWindow() { base := c.Base() host := base.Host() if host == nil { return } - if host.Window.CursorMode() != windowing.CursorModeVirtual || + if !host.Window.UsesVirtualCursor() || !host.Window.CursorVisible() || host.Window.CursorKind() == windowing.CursorKindNone { base.Hide() @@ -234,7 +234,7 @@ func (c *Cursor) update(deltaTime float64) { return } - if host.Window.CursorMode() != windowing.CursorModeVirtual || + if !host.Window.UsesVirtualCursor() || !host.Window.CursorVisible() || host.Window.CursorKind() == windowing.CursorKindNone { base.Hide() diff --git a/src/engine/ui/ui.go b/src/engine/ui/ui.go index 0a3baedf0..c46adb108 100644 --- a/src/engine/ui/ui.go +++ b/src/engine/ui/ui.go @@ -567,7 +567,7 @@ func (ui *UI) anyChildDirty() bool { func (ui *UI) updateFromManager(deltaTime float64) { defer tracing.NewRegion("UI.updateFromManager").End() - if !ui.IsActive() { + if !ui.IsActive() && ui.elmType != ElementTypeCursor { return } switch ui.elmType { diff --git a/src/platform/windowing/cocoa_window.m b/src/platform/windowing/cocoa_window.m index 10c27ca61..9a79f9f33 100644 --- a/src/platform/windowing/cocoa_window.m +++ b/src/platform/windowing/cocoa_window.m @@ -580,24 +580,28 @@ void cocoa_set_cursor(int kind) { cocoa_set_cursor_hidden(YES); break; case 4: // CursorKindText - case 5: // CursorKindVerticalText cocoa_set_cursor_hidden(NO); [[NSCursor IBeamCursor] set]; break; + case 5: // CursorKindVerticalText + cocoa_set_cursor_hidden(NO); + [[NSCursor IBeamCursorForVerticalLayout] set]; + break; case 3: // CursorKindContextMenu + cocoa_set_cursor_hidden(NO); + [[NSCursor contextualMenuCursor] set]; + break; case 6: // CursorKindPointer - case 7: // CursorKindHelp - case 12: // CursorKindAlias - case 13: // CursorKindCopy cocoa_set_cursor_hidden(NO); [[NSCursor pointingHandCursor] set]; break; - case 8: // CursorKindWait - case 9: // CursorKindProgress - case 34: // CursorKindZoomIn - case 35: // CursorKindZoomOut + case 12: // CursorKindAlias cocoa_set_cursor_hidden(NO); - [[NSCursor arrowCursor] set]; + [[NSCursor dragLinkCursor] set]; + break; + case 13: // CursorKindCopy + cocoa_set_cursor_hidden(NO); + [[NSCursor dragCopyCursor] set]; break; case 10: // CursorKindCrosshair case 11: // CursorKindCell @@ -633,12 +637,6 @@ void cocoa_set_cursor(int kind) { cocoa_set_cursor_hidden(NO); [[NSCursor resizeLeftRightCursor] set]; break; - case 23: // CursorKindResizeNE - case 24: // CursorKindResizeNW - case 25: // CursorKindResizeSE - case 26: // CursorKindResizeSW - case 29: // CursorKindResizeNWSE - case 30: // CursorKindResizeNESW default: cocoa_set_cursor_hidden(NO); [[NSCursor arrowCursor] set]; @@ -651,7 +649,7 @@ void cocoa_set_cursor(int kind) { void cocoa_show_cursor(void) { dispatch_async(dispatch_get_main_queue(), ^{ @autoreleasepool { - [NSCursor unhide]; + cocoa_set_cursor_hidden(NO); } }); } @@ -659,7 +657,7 @@ void cocoa_show_cursor(void) { void cocoa_hide_cursor(void) { dispatch_async(dispatch_get_main_queue(), ^{ @autoreleasepool { - [NSCursor hide]; + cocoa_set_cursor_hidden(YES); } }); } diff --git a/src/platform/windowing/cursor.go b/src/platform/windowing/cursor.go index 53954621f..357264d9b 100644 --- a/src/platform/windowing/cursor.go +++ b/src/platform/windowing/cursor.go @@ -84,3 +84,13 @@ const ( CursorModeVirtual CursorModeAuto ) + +func NativeCursorSupported(kind CursorKind) bool { + switch kind { + case CursorKindAuto: + kind = CursorKindDefault + case CursorKindDefault, CursorKindNone: + return true + } + return nativeCursorSupported(kind) +} diff --git a/src/platform/windowing/cursor_android.go b/src/platform/windowing/cursor_android.go new file mode 100644 index 000000000..64f030f11 --- /dev/null +++ b/src/platform/windowing/cursor_android.go @@ -0,0 +1,7 @@ +//go:build android + +package windowing + +func nativeCursorSupported(CursorKind) bool { + return false +} diff --git a/src/platform/windowing/cursor_darwin.go b/src/platform/windowing/cursor_darwin.go new file mode 100644 index 000000000..57d07d8e8 --- /dev/null +++ b/src/platform/windowing/cursor_darwin.go @@ -0,0 +1,16 @@ +//go:build darwin && !ios + +package windowing + +func nativeCursorSupported(kind CursorKind) bool { + switch kind { + case CursorKindHelp, CursorKindWait, CursorKindProgress, + CursorKindResizeNE, CursorKindResizeNW, + CursorKindResizeSE, CursorKindResizeSW, + CursorKindResizeNWSE, CursorKindResizeNESW, + CursorKindZoomIn, CursorKindZoomOut: + return false + default: + return true + } +} diff --git a/src/platform/windowing/cursor_windows.go b/src/platform/windowing/cursor_windows.go new file mode 100644 index 000000000..1f4a78906 --- /dev/null +++ b/src/platform/windowing/cursor_windows.go @@ -0,0 +1,12 @@ +//go:build windows + +package windowing + +func nativeCursorSupported(kind CursorKind) bool { + switch kind { + case CursorKindZoomIn, CursorKindZoomOut: + return false + default: + return true + } +} diff --git a/src/platform/windowing/cursor_x11.go b/src/platform/windowing/cursor_x11.go new file mode 100644 index 000000000..7e57f1dc1 --- /dev/null +++ b/src/platform/windowing/cursor_x11.go @@ -0,0 +1,7 @@ +//go:build linux && !android + +package windowing + +func nativeCursorSupported(CursorKind) bool { + return true +} diff --git a/src/platform/windowing/window.go b/src/platform/windowing/window.go index 082625543..15b4fc750 100644 --- a/src/platform/windowing/window.go +++ b/src/platform/windowing/window.go @@ -325,6 +325,20 @@ func (w *Window) CursorMode() CursorMode { return w.cursorMode } +func (w *Window) cursorRenderMode() CursorMode { + if w.cursorMode != CursorModeAuto { + return w.cursorMode + } + if NativeCursorSupported(w.cursorKind) { + return CursorModeNative + } + return CursorModeVirtual +} + +func (w *Window) UsesVirtualCursor() bool { + return w.cursorRenderMode() == CursorModeVirtual +} + func (w *Window) CursorVisible() bool { return w.cursorVisible } @@ -334,22 +348,11 @@ func (w *Window) CursorVisible() bool { // render it instead. func (w *Window) SetCursorMode(mode CursorMode) { if w.cursorMode == mode { + w.applyCursor() return } - oldMode := w.cursorMode w.cursorMode = mode - if oldMode == CursorModeVirtual && w.cursorVisible { - w.showCursor() - } - if mode == CursorModeVirtual { - if w.cursorVisible { - w.hideCursor() - } - return - } - if w.cursorVisible { - w.setCursor(w.cursorKind) - } + w.applyCursor() } func (w *Window) ResetCursor() { @@ -361,9 +364,7 @@ func (w *Window) SetCursor(kind CursorKind) { kind = CursorKindDefault } w.cursorKind = kind - if w.cursorMode != CursorModeVirtual && w.cursorVisible { - w.setCursor(w.cursorKind) - } + w.applyCursor() } // HideCursor is a visibility override. They do not change the @@ -377,11 +378,7 @@ func (w *Window) HideCursor() { // current cursor mode or semantic cursor kind. func (w *Window) ShowCursor() { w.cursorVisible = true - if w.cursorMode == CursorModeVirtual { - return - } - w.showCursor() - w.setCursor(w.cursorKind) + w.applyCursor() } func (w *Window) applyCursor() { @@ -389,7 +386,7 @@ func (w *Window) applyCursor() { w.hideCursor() return } - if w.cursorMode == CursorModeVirtual { + if w.UsesVirtualCursor() { w.hideCursor() return }