diff --git a/src/editor/editor.go b/src/editor/editor.go
index 53267e822..bdbcb1a33 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.SyncWithWindow()
+
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/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/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 000000000..ffa8cb2a4
Binary files /dev/null and b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/alias.png differ
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 000000000..d92384a0e
Binary files /dev/null and b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/cell.png differ
diff --git a/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/context_menu.png b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/context_menu.png
new file mode 100644
index 000000000..a9877c498
Binary files /dev/null and b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/context_menu.png differ
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 000000000..4e634a0d5
Binary files /dev/null and b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/copy.png differ
diff --git a/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/crosshair.png b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/crosshair.png
new file mode 100644
index 000000000..72b014a65
Binary files /dev/null and b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/crosshair.png differ
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 000000000..827b8696b
Binary files /dev/null and b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/default.png differ
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 000000000..5d7a4b15f
Binary files /dev/null and b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/grab.png differ
diff --git a/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/grabbing.png b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/grabbing.png
new file mode 100644
index 000000000..c5d066ee6
Binary files /dev/null and b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/grabbing.png differ
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 000000000..85c84f011
Binary files /dev/null and b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/help.png differ
diff --git a/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/move.png b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/move.png
new file mode 100644
index 000000000..f1c694c79
Binary files /dev/null and b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/move.png differ
diff --git a/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/no_drop.png b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/no_drop.png
new file mode 100644
index 000000000..0ce55c6b0
Binary files /dev/null and b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/no_drop.png differ
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 000000000..dc2758c56
Binary files /dev/null and b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/none.png differ
diff --git a/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/not_allowed.png b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/not_allowed.png
new file mode 100644
index 000000000..0ce55c6b0
Binary files /dev/null and b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/not_allowed.png differ
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 000000000..1ce04eff6
Binary files /dev/null and b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/pointer.png differ
diff --git a/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/progress.png b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/progress.png
new file mode 100644
index 000000000..aadbd45c7
Binary files /dev/null and b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/progress.png differ
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 000000000..f1c694c79
Binary files /dev/null and b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/resize_all.png differ
diff --git a/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/resize_col.png b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/resize_col.png
new file mode 100644
index 000000000..2e1cb9b31
Binary files /dev/null and b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/resize_col.png differ
diff --git a/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/resize_e.png b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/resize_e.png
new file mode 100644
index 000000000..2e1cb9b31
Binary files /dev/null and b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/resize_e.png differ
diff --git a/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/resize_ew.png b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/resize_ew.png
new file mode 100644
index 000000000..2e1cb9b31
Binary files /dev/null and b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/resize_ew.png differ
diff --git a/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/resize_n.png b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/resize_n.png
new file mode 100644
index 000000000..d0ad7ae36
Binary files /dev/null and b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/resize_n.png differ
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 000000000..dcd53f63b
Binary files /dev/null and b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/resize_ne.png differ
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 000000000..dcd53f63b
Binary files /dev/null and b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/resize_nesw.png differ
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 000000000..d0ad7ae36
Binary files /dev/null and b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/resize_ns.png differ
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 000000000..471c9ce88
Binary files /dev/null and b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/resize_nw.png differ
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 000000000..471c9ce88
Binary files /dev/null and b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/resize_nwse.png differ
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 000000000..d0ad7ae36
Binary files /dev/null and b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/resize_row.png differ
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 000000000..d0ad7ae36
Binary files /dev/null and b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/resize_s.png differ
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 000000000..471c9ce88
Binary files /dev/null and b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/resize_se.png differ
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 000000000..dcd53f63b
Binary files /dev/null and b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/resize_sw.png differ
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 000000000..2e1cb9b31
Binary files /dev/null and b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/resize_w.png differ
diff --git a/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/text.png b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/text.png
new file mode 100644
index 000000000..88d13fc96
Binary files /dev/null and b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/text.png differ
diff --git a/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/vertical_text.png b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/vertical_text.png
new file mode 100644
index 000000000..f988f5c46
Binary files /dev/null and b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/vertical_text.png differ
diff --git a/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/wait.png b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/wait.png
new file mode 100644
index 000000000..bf2aa0283
Binary files /dev/null and b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/wait.png differ
diff --git a/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/zoom_in.png b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/zoom_in.png
new file mode 100644
index 000000000..a3c636e4b
Binary files /dev/null and b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/zoom_in.png differ
diff --git a/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/zoom_out.png b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/zoom_out.png
new file mode 100644
index 000000000..f7deba027
Binary files /dev/null and b/src/editor/editor_embedded_content/editor_content/textures/cursors/kenney/zoom_out.png differ
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/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..4022a9a29
--- /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) SyncWithWindow() {
+ base := c.Base()
+ host := base.Host()
+ if host == nil {
+ return
+ }
+ if !host.Window.UsesVirtualCursor() ||
+ !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.UsesVirtualCursor() ||
+ !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/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/engine/ui/ui.go b/src/engine/ui/ui.go
index 01bd9e3ee..c46adb108 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()))
@@ -564,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 {
@@ -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/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..9a79f9f33 100644
--- a/src/platform/windowing/cocoa_window.m
+++ b/src/platform/windowing/cocoa_window.m
@@ -560,42 +560,88 @@ 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
+ 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
+ cocoa_set_cursor_hidden(NO);
+ [[NSCursor pointingHandCursor] set];
+ break;
+ case 12: // CursorKindAlias
+ cocoa_set_cursor_hidden(NO);
+ [[NSCursor dragLinkCursor] set];
+ break;
+ case 13: // CursorKindCopy
+ cocoa_set_cursor_hidden(NO);
+ [[NSCursor dragCopyCursor] 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;
+ default:
+ cocoa_set_cursor_hidden(NO);
+ [[NSCursor arrowCursor] set];
+ break;
+ }
}
});
}
@@ -603,7 +649,7 @@ void cocoa_cursor_size_we(void) {
void cocoa_show_cursor(void) {
dispatch_async(dispatch_get_main_queue(), ^{
@autoreleasepool {
- [NSCursor unhide];
+ cocoa_set_cursor_hidden(NO);
}
});
}
@@ -611,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
new file mode 100644
index 000000000..357264d9b
--- /dev/null
+++ b/src/platform/windowing/cursor.go
@@ -0,0 +1,96 @@
+/******************************************************************************/
+/* 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
+
+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
+)
+
+type CursorMode int
+
+const (
+ CursorModeNative CursorMode = iota
+ 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/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..15b4fc750 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,39 +317,81 @@ 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) CursorKind() CursorKind {
+ return w.cursorKind
+}
+
+func (w *Window) CursorMode() CursorMode {
+ return w.cursorMode
}
-func (w *Window) CursorIbeam() {
- if w.canChangeCursor() {
- w.cursorIbeam()
+func (w *Window) cursorRenderMode() CursorMode {
+ if w.cursorMode != CursorModeAuto {
+ return w.cursorMode
}
- w.cursorChangeCount++
+ 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
}
-func (w *Window) CursorSizeAll() {
- if w.canChangeCursor() {
- w.cursorSizeAll()
+// 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 {
+ w.applyCursor()
+ return
}
- w.cursorChangeCount++
+ w.cursorMode = mode
+ w.applyCursor()
}
-func (w *Window) CursorSizeNS() {
- if w.canChangeCursor() {
- w.cursorSizeNS()
+func (w *Window) ResetCursor() {
+ w.SetCursor(CursorKindDefault)
+}
+
+func (w *Window) SetCursor(kind CursorKind) {
+ if kind == CursorKindAuto {
+ kind = CursorKindDefault
}
- w.cursorChangeCount++
+ w.cursorKind = kind
+ w.applyCursor()
+}
+
+// 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
+ w.applyCursor()
}
-func (w *Window) CursorSizeWE() {
- if w.canChangeCursor() {
- w.cursorSizeWE()
+func (w *Window) applyCursor() {
+ if !w.cursorVisible {
+ w.hideCursor()
+ return
+ }
+ if w.UsesVirtualCursor() {
+ w.hideCursor()
+ return
}
- w.cursorChangeCount++
+ w.showCursor()
+ w.setCursor(w.cursorKind)
}
func (w *Window) CopyToClipboard(text string) { w.copyToClipboard(text) }
@@ -371,7 +418,7 @@ func (w *Window) Destroy() {
func (w *Window) Focus() {
defer tracing.NewRegion("Window.Focus").End()
w.focus()
- w.cursorStandard()
+ w.applyCursor()
}
func (w *Window) Position() (x int, y int) {
@@ -397,8 +444,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() }
@@ -490,8 +535,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)
@@ -676,7 +719,7 @@ func (w *Window) becameInactive() {
func (w *Window) becameActive() {
defer tracing.NewRegion("Window.becameActive").End()
- w.cursorStandard()
+ w.applyCursor()
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);