mirror of
https://github.com/wayvr-org/wayvr.git
synced 2026-10-28 13:02:53 +01:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
00bc179655 | ||
|
|
a4115f5e4b | ||
|
|
6c896bb956 | ||
|
|
d9c8689c18 | ||
|
|
5df6ce93f8 | ||
|
|
ee4bb5ca29 | ||
|
|
b89ae4726c | ||
|
|
b0bbc25083 | ||
|
|
635df298fe | ||
|
|
786847cc2f | ||
|
|
d9f89faf33 | ||
|
|
769978560e | ||
|
|
bebc39e766 | ||
|
|
bb15a4e867 | ||
|
|
84015c7972 | ||
|
|
f2c34fba8f | ||
|
|
1a74eb1507 | ||
|
|
6b3f8a7b42 | ||
|
|
ad78fdab24 | ||
|
|
12ceebec6d | ||
|
|
6f9ca5b3b7 | ||
|
|
c54b211872 | ||
|
|
cb899235b4 | ||
|
|
a83af3d833 | ||
|
|
e8c8a67fc0 | ||
|
|
2b4a0b6a0d | ||
|
|
5ffa5e59bf | ||
|
|
ed8ca1e068 | ||
|
|
f21776b123 | ||
|
|
ab3f873a38 | ||
|
|
de6047107e | ||
|
|
39152b8548 | ||
|
|
b517afc379 | ||
|
|
b84d565b1e | ||
|
|
46f3a54203 | ||
|
|
4afaa6d048 | ||
|
|
26f078d512 | ||
|
|
124d817752 | ||
|
|
2ddb419676 | ||
|
|
841241a7e6 | ||
|
|
c902f0cd44 | ||
|
|
756c42609d | ||
|
|
4fc439abe2 | ||
|
|
9685576272 | ||
|
|
d2f01c06ec | ||
|
|
94c36076af |
No files matched your search
@@ -0,0 +1 @@
|
||||
open_collective: wlx-team
|
||||
Generated
+16
-45
@@ -143,16 +143,6 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "annotate-snippets"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccaf7e9dfbb6ab22c82e473cd1a8a7bd313c19a5b7e40970f3d89ef5a5c9e81e"
|
||||
dependencies = [
|
||||
"unicode-width 0.1.14",
|
||||
"yansi-term",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.21"
|
||||
@@ -683,21 +673,18 @@ checksum = "f09c37bc0e9f0924b7dae9988265ef3c76c88538f41a3b06caf4bed07cee5226"
|
||||
|
||||
[[package]]
|
||||
name = "bindgen"
|
||||
version = "0.69.5"
|
||||
version = "0.72.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088"
|
||||
checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895"
|
||||
dependencies = [
|
||||
"annotate-snippets",
|
||||
"bitflags 2.10.0",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"itertools 0.12.1",
|
||||
"lazy_static",
|
||||
"lazycell",
|
||||
"itertools 0.10.5",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"rustc-hash 1.1.0",
|
||||
"rustc-hash 2.1.1",
|
||||
"shlex",
|
||||
"syn 2.0.113",
|
||||
]
|
||||
@@ -932,6 +919,7 @@ dependencies = [
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"pure-rust-locales",
|
||||
"wasm-bindgen",
|
||||
"windows-link 0.2.1",
|
||||
]
|
||||
@@ -2665,15 +2653,6 @@ dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.13.0"
|
||||
@@ -2816,12 +2795,6 @@ version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "lazycell"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
|
||||
|
||||
[[package]]
|
||||
name = "lebe"
|
||||
version = "0.5.3"
|
||||
@@ -2909,7 +2882,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "libspa"
|
||||
version = "0.8.0"
|
||||
source = "git+https://gitlab.freedesktop.org/galister/pipewire-rs.git?rev=ba32202c3c391004c3bb533b58fa75a50e47ff57#ba32202c3c391004c3bb533b58fa75a50e47ff57"
|
||||
source = "git+https://gitlab.freedesktop.org/galister/pipewire-rs.git?rev=6ef2cd1af41e5e8844c63c37ea9a01f0758079b0#6ef2cd1af41e5e8844c63c37ea9a01f0758079b0"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"cc",
|
||||
@@ -2925,7 +2898,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "libspa-sys"
|
||||
version = "0.8.0"
|
||||
source = "git+https://gitlab.freedesktop.org/galister/pipewire-rs.git?rev=ba32202c3c391004c3bb533b58fa75a50e47ff57#ba32202c3c391004c3bb533b58fa75a50e47ff57"
|
||||
source = "git+https://gitlab.freedesktop.org/galister/pipewire-rs.git?rev=6ef2cd1af41e5e8844c63c37ea9a01f0758079b0#6ef2cd1af41e5e8844c63c37ea9a01f0758079b0"
|
||||
dependencies = [
|
||||
"bindgen",
|
||||
"cc",
|
||||
@@ -4002,7 +3975,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pipewire"
|
||||
version = "0.8.0"
|
||||
source = "git+https://gitlab.freedesktop.org/galister/pipewire-rs.git?rev=ba32202c3c391004c3bb533b58fa75a50e47ff57#ba32202c3c391004c3bb533b58fa75a50e47ff57"
|
||||
source = "git+https://gitlab.freedesktop.org/galister/pipewire-rs.git?rev=6ef2cd1af41e5e8844c63c37ea9a01f0758079b0#6ef2cd1af41e5e8844c63c37ea9a01f0758079b0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.10.0",
|
||||
@@ -4018,7 +3991,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "pipewire-sys"
|
||||
version = "0.8.0"
|
||||
source = "git+https://gitlab.freedesktop.org/galister/pipewire-rs.git?rev=ba32202c3c391004c3bb533b58fa75a50e47ff57#ba32202c3c391004c3bb533b58fa75a50e47ff57"
|
||||
source = "git+https://gitlab.freedesktop.org/galister/pipewire-rs.git?rev=6ef2cd1af41e5e8844c63c37ea9a01f0758079b0#6ef2cd1af41e5e8844c63c37ea9a01f0758079b0"
|
||||
dependencies = [
|
||||
"bindgen",
|
||||
"libspa-sys",
|
||||
@@ -4194,6 +4167,12 @@ dependencies = [
|
||||
"syn 2.0.113",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pure-rust-locales"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "869675ad2d7541aea90c6d88c81f46a7f4ea9af8cd0395d38f11a95126998a0d"
|
||||
|
||||
[[package]]
|
||||
name = "pxfm"
|
||||
version = "0.1.27"
|
||||
@@ -6314,6 +6293,7 @@ dependencies = [
|
||||
"mint",
|
||||
"openxr",
|
||||
"ovr_overlay",
|
||||
"pure-rust-locales",
|
||||
"regex",
|
||||
"rosc",
|
||||
"rust-embed",
|
||||
@@ -7122,15 +7102,6 @@ version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
|
||||
|
||||
[[package]]
|
||||
name = "yansi-term"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe5c30ade05e61656247b2e334a031dfd0cc466fadef865bdcdea8d537951bf1"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yazi"
|
||||
version = "0.2.1"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||

|
||||

|
||||
|
||||
# WayVR (previously WlxOverlay-S)
|
||||
|
||||
@@ -8,13 +8,13 @@ WayVR lets you access your desktop screens while in VR, and even launch apps dir
|
||||
|
||||
In comparison to similar overlays, WayVR aims to run alongside VR games and experiences while having as little performance impact as possible. The UI appearance and rendering techniques are kept as simple and efficient as possible, while still allowing a high degree of customizability.
|
||||
|
||||

|
||||

|
||||
|
||||
## Join the Linux VR Community
|
||||
|
||||
We are available on either **Discord** or **Matrix space**:
|
||||
|
||||
[](https://discord.gg/EHAYe3tTYa) [](https://matrix.to/#/#linux-vr-adventures:matrix.org)
|
||||
[](https://discord.gg/EHAYe3tTYa) [](https://matrix.to/#/#linux-vr-adventures:matrix.org)
|
||||
|
||||
Questions/issues specific to WayVR will be handled in the `wayvr` chat room. Feel free to ask anything.
|
||||
|
||||
@@ -24,10 +24,10 @@ Questions/issues specific to WayVR will be handled in the `wayvr` chat room. Fee
|
||||
|
||||
There are multiple ways to install WayVR:
|
||||
|
||||
1. AppImage: Download from [Releases](https://github.com/wlx-team/wayvr/releases)
|
||||
1. AUR package: [wayvr](https://aur.archlinux.org/packages/wayvr) or [wayvr-git](https://aur.archlinux.org/packages/wayvr-git)
|
||||
1. AppImage: Download from [Releases](https://github.com/wayvr-org/wayvr/releases)
|
||||
1. AUR package: [wayvr](https://aur.archlinux.org/packages/wayvr) or [wayvr-git](https://aur.archlinux.org/packages/wayvr-git)
|
||||
1. Nix package: [wayvr](https://search.nixos.org/packages?channel=unstable&show=wayvr&query=wayvr) or [unstable package from nixpkgs-xr](https://github.com/nix-community/nixpkgs-xr)
|
||||
1. [Building from source](https://github.com/wlx-team/wayvr/wiki/Building-from-Source).
|
||||
1. [Docs: Building from source](https://wayvr.org/docs/basics/building-from-source/).
|
||||
|
||||
### General Setup
|
||||
|
||||
@@ -40,7 +40,7 @@ There are multiple ways to install WayVR:
|
||||
|
||||
For users specifically running **SteamVR via Steam Flatpak**, follow these steps:
|
||||
|
||||
1. Grab the latest AppImage from [Releases](https://github.com/wlx-team/wayvr/releases).
|
||||
1. Grab the latest AppImage from [Releases](https://github.com/wayvr-org/wayvr/releases).
|
||||
1. `WayVR-*.AppImage --appimage-extract`
|
||||
1. `chmod +x squashfs-root/AppRun`
|
||||
1. Move the newly created `squashfs-root` folder to a location accessible by the Steam Flatpak.
|
||||
@@ -64,7 +64,7 @@ In case screens were selected in the wrong order:
|
||||
**Envision users**: Go to the Plugins menu and select the WayVR plugin. This will download and run the AppImage version of the overlay.
|
||||
To run a standalone installation (for instance, from the AUR), create a bash script containing `wayvr --openxr --show` and then set this bash script as a custom Envision plugin.
|
||||
|
||||
This will show a home environment with headset passthrough enabled by default or a [customizable background](https://github.com/wlx-team/wayvr/wiki/OpenXR-Skybox)!
|
||||
This will show a home environment with headset passthrough enabled by default or a [customizable background](https://wayvr.org/docs/various/openxr-skybox/)!
|
||||
|
||||
**SteamVR users**: WayVR will register itself for auto-start, so there is no need to start it every time. Disclaimer: SteamVR will sometimes disregard this and not start WayVR anyway.
|
||||
|
||||
@@ -131,26 +131,42 @@ Typing
|
||||
- While using the ORANGE laser, all keystrokes will have SHIFT applied.
|
||||
- Purple laser is customizable via the settings, no modifier by default.
|
||||
|
||||
**Modifier Keys** are sticky. They will remain pressed until a non-modifier key is pressed, the modifier gets toggled off, or the keyboard gets hidden.
|
||||
**Modifier Keys are sticky**. They will remain pressed until either:
|
||||
|
||||
- a non-modifier key is pressed
|
||||
- the modifier is toggled off by clicking again
|
||||
- the keyboard is hidden (including via show-hide)
|
||||
|
||||
### Default Bindings
|
||||
|
||||

|
||||

|
||||
|
||||

|
||||

|
||||
|
||||
To customize bindings on OpenXR, refer to the [OpenXR Bindings wiki page](https://github.com/wlx-team/wayvr/wiki/OpenXR-Bindings).
|
||||
### Changing Bindings
|
||||
|
||||
If your bindings are not supported, please reach out. \
|
||||
SteamVR: Simply change the bindings from the SteamVR bindings section. If WayVR doesn't show up on the list, select any other title and then press back on the top left. (SteamVR is weird like that sometimes)
|
||||
|
||||
OpenXR (Monado/WiVRn): See [Docs: OpenXR Bindings](https://wayvr.org/docs/various/openxr-bindings/)
|
||||
|
||||
If your controllers are not supported, please reach out. \
|
||||
We would like to work with you and include additional bindings.
|
||||
|
||||
## Customization
|
||||
|
||||
See these relevant wiki pages:
|
||||
|
||||
- For all available config options, check [Docs: Configuration](https://wayvr.org/docs/basics/configuration/)
|
||||
- Looking to customize look & feel, or add functionality? See [Docs: Customization](https://wayvr.org/docs/basics/customization/)
|
||||
- Looking to change the OpenXR background? See [Docs: OpenXR Skybox](https://wayvr.org/docs/various/openxr-skybox/)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
When an error is detected, we often print tips for fixing it into the log file.
|
||||
|
||||
Logs will be at `/tmp/wayvr.log` for most distros.
|
||||
|
||||
Check [here](https://github.com/wlx-team/wayvr/wiki/Troubleshooting) for tips.
|
||||
Check [here](https://wayvr.org/docs/various/troubleshooting/) for tips.
|
||||
|
||||
## Known Issues
|
||||
|
||||
|
||||
@@ -93,19 +93,18 @@
|
||||
position="absolute" width="100%" height="100%"
|
||||
gradient="radial" color="#44BBFF11" color2="#00000000" />
|
||||
|
||||
<div
|
||||
id="content"
|
||||
flex_direction="column"
|
||||
overflow_x="scroll"
|
||||
overflow_y="scroll"
|
||||
padding="16"
|
||||
gap="8"
|
||||
width="100%"
|
||||
min_height="100%"
|
||||
>
|
||||
<!-- filled-in at runtime -->
|
||||
<div overflow_x="scroll" overflow_y="scroll" width="100%" height="100%">
|
||||
<div
|
||||
id="content"
|
||||
flex_direction="column"
|
||||
padding="16"
|
||||
gap="8"
|
||||
width="100%"
|
||||
height="100%"
|
||||
>
|
||||
<!-- filled-in at runtime -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div position="absolute" id="popup_manager" width="100%" height="100%" />
|
||||
</rectangle>
|
||||
<!-- BOTTOM PANEL -->
|
||||
|
||||
@@ -44,7 +44,6 @@
|
||||
flex_direction="row"
|
||||
flex_wrap="wrap"
|
||||
gap="4"
|
||||
overflow_y="scroll"
|
||||
/>
|
||||
</elements>
|
||||
</layout>
|
||||
@@ -1,6 +1,6 @@
|
||||
<layout>
|
||||
<elements>
|
||||
<div id="running_games_list_parent" align_self="center" />
|
||||
<div id="game_list_parent" align_items="center" flex_direction="column" gap="8" overflow_y="scroll" />
|
||||
<div id="game_list_parent" align_items="center" flex_direction="column" gap="8" />
|
||||
</elements>
|
||||
</layout>
|
||||
@@ -10,7 +10,6 @@
|
||||
"APP_SETTINGS": {
|
||||
"HIDE_USERNAME": "Benutzernamen ausblenden",
|
||||
"OPAQUE_BACKGROUND": "Undurchsichtiger Hintergrund",
|
||||
"WLX": {},
|
||||
"LOOK_AND_FEEL": "Aussehen und Verhalten",
|
||||
"HIDE_GRAB_HELP": "Greif-Hilfe ausblenden",
|
||||
"ANIMATION_SPEED": "UI-Animationsgeschwindigkeit",
|
||||
@@ -89,7 +88,11 @@
|
||||
"RESET_PLAYSPACE": "Spielbereich zurücksetzen",
|
||||
"RESET_PLAYSPACE_HELP": "Den Abstand des Spielbereichs zurücksetzen.",
|
||||
"BLOCK_POSES_ON_KBD_INTERACTION": "Posen beim Interagieren mit der Tastatur blockieren",
|
||||
"BLOCK_POSES_ON_KBD_INTERACTION_HELP": "Verhindert, dass das Spiel Posen empfängt, wenn die Tastatur angefahren wird und „Spieleingabe blockieren“ aktiviert ist"
|
||||
"BLOCK_POSES_ON_KBD_INTERACTION_HELP": "Verhindert, dass das Spiel Posen empfängt, wenn die Tastatur angefahren wird und „Spieleingabe blockieren“ aktiviert ist",
|
||||
"LANGUAGE": "Sprache",
|
||||
"REQUIRES_RESTART": "Erfordert Neustart",
|
||||
"GRID_OPACITY": "Bodenraster-Undurchsichtigkeit",
|
||||
"GRID_OPACITY_HELP": "Undurchsichtigkeit des Bodenrasters, wenn der Skybox aktiviert ist"
|
||||
},
|
||||
"HELLO": "Hallo!",
|
||||
"AUDIO": {
|
||||
|
||||
@@ -63,6 +63,7 @@
|
||||
"KEYBOARD_MIDDLE_CLICK": "Keyboard middle click",
|
||||
"KEYBOARD_MIDDLE_CLICK_HELP": "Modifier to use when typing\nwith purple laser",
|
||||
"KEYBOARD_SOUND_ENABLED": "Keyboard sounds",
|
||||
"LANGUAGE": "Language",
|
||||
"LEFT_HANDED_MOUSE": "Left-handed mouse",
|
||||
"LEFT_HANDED_MOUSE_HELP": "Use this if mouse buttons are swapped",
|
||||
"LONG_PRESS_DURATION": "Long press duration",
|
||||
@@ -74,10 +75,10 @@
|
||||
"OPTION": {
|
||||
"AUTO": "Automatic",
|
||||
"AUTO_HELP": "ScreenCopy GPU if supported,\notherwise PipeWire GPU.",
|
||||
"EYE_PINCH": "Eye + pinch",
|
||||
"HMD_PINCH": "HMD + pinch",
|
||||
"EYE_ONLY": "Eye only",
|
||||
"EYE_PINCH": "Eye + pinch",
|
||||
"HMD_ONLY": "HMD only",
|
||||
"HMD_PINCH": "HMD + pinch",
|
||||
"NONE": "None",
|
||||
"PIPEWIRE_HELP": "Fast GPU capture,\nstandard on all desktops.",
|
||||
"PW_FALLBACK_HELP": "Slow method with high CPU usage.\nTry in case PipeWire GPU doesn't work",
|
||||
@@ -85,6 +86,7 @@
|
||||
"SCREENCOPY_HELP": "Slow, no screen share popups.\nWorks on: Hyprland, Niri, River, Sway"
|
||||
},
|
||||
"POINTER_LERP_FACTOR": "Pointer smoothing",
|
||||
"REQUIRES_RESTART": "Requires restart",
|
||||
"RESET_PLAYSPACE": "Reset playspace",
|
||||
"RESET_PLAYSPACE_HELP": "Clear the stage space offset.",
|
||||
"RESTART_SOFTWARE": "Restart software",
|
||||
@@ -105,6 +107,8 @@
|
||||
"USE_PASSTHROUGH_HELP": "Allow passthrough if the XR runtime supports it",
|
||||
"USE_SKYBOX": "Enable skybox",
|
||||
"USE_SKYBOX_HELP": "Show a skybox if there's no scene app or passthrough",
|
||||
"GRID_OPACITY": "Floor grid opacity",
|
||||
"GRID_OPACITY_HELP": "Opacity of the floor grid when the skybox is enabled",
|
||||
"XR_CLICK_SENSITIVITY": "XR click sensitivity",
|
||||
"XR_CLICK_SENSITIVITY_HELP": "Analog trigger sensitivity",
|
||||
"XR_CLICK_SENSITIVITY_RELEASE": "XR release sensitivity",
|
||||
@@ -148,8 +152,8 @@
|
||||
"RESOLUTION": "Resolution"
|
||||
},
|
||||
"PROCESS": {
|
||||
"STOP": "Stop",
|
||||
"FORCE_KILL": "Force-kill"
|
||||
"FORCE_KILL": "Force-kill",
|
||||
"STOP": "Stop"
|
||||
},
|
||||
"PROCESS_LIST": "Process list",
|
||||
"REFRESH": "Refresh",
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
"APP_SETTINGS": {
|
||||
"HIDE_USERNAME": "Ocultar nombre de usuario",
|
||||
"OPAQUE_BACKGROUND": "Fondo opaco",
|
||||
"WLX": {},
|
||||
"LOOK_AND_FEEL": "Apariencia y estilo",
|
||||
"HIDE_GRAB_HELP": "Ocultar ayuda para agarrar",
|
||||
"ANIMATION_SPEED": "Velocidad de animación de la IU",
|
||||
@@ -89,7 +88,11 @@
|
||||
"RESET_PLAYSPACE": "Restablecer espacio de juego",
|
||||
"RESET_PLAYSPACE_HELP": "Borrar el desplazamiento del espacio de juego.",
|
||||
"BLOCK_POSES_ON_KBD_INTERACTION": "Bloquear poses al interactuar con el teclado",
|
||||
"BLOCK_POSES_ON_KBD_INTERACTION_HELP": "Bloquea que el juego reciba poses cuando el teclado está sobre él y 'Bloquear entrada del juego' está habilitado"
|
||||
"BLOCK_POSES_ON_KBD_INTERACTION_HELP": "Bloquea que el juego reciba poses cuando el teclado está sobre él y 'Bloquear entrada del juego' está habilitado",
|
||||
"LANGUAGE": "Idioma",
|
||||
"REQUIRES_RESTART": "Requiere reinicio",
|
||||
"GRID_OPACITY": "Opacidad de la cuadrícula del suelo",
|
||||
"GRID_OPACITY_HELP": "Opacidad de la cuadrícula del suelo cuando el skybox está habilitado"
|
||||
},
|
||||
"HELLO": "¡Hola!",
|
||||
"AUDIO": {
|
||||
|
||||
@@ -109,7 +109,11 @@
|
||||
"RESET_PLAYSPACE": "Ripristina playspace",
|
||||
"RESET_PLAYSPACE_HELP": "Cancella l'offset dello spazio di gioco.",
|
||||
"BLOCK_POSES_ON_KBD_INTERACTION": "Blocca le pose durante l'interazione con la tastiera",
|
||||
"BLOCK_POSES_ON_KBD_INTERACTION_HELP": "Impedisce al gioco di ricevere pose quando la tastiera è evidenziata e 'Blocca input di gioco' è abilitato"
|
||||
"BLOCK_POSES_ON_KBD_INTERACTION_HELP": "Impedisce al gioco di ricevere pose quando la tastiera è evidenziata e 'Blocca input di gioco' è abilitato",
|
||||
"LANGUAGE": "Lingua",
|
||||
"REQUIRES_RESTART": "Richiede riavvio",
|
||||
"GRID_OPACITY": "Opacità della griglia del pavimento",
|
||||
"GRID_OPACITY_HELP": "Opacità della griglia del pavimento quando lo skybox è abilitato"
|
||||
},
|
||||
"APPLICATION_LAUNCHER": "Lanciatore applicazioni",
|
||||
"APPLICATION_STARTED": "Applicazione avviata",
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
"APP_SETTINGS": {
|
||||
"HIDE_USERNAME": "ユーザー名を非表示",
|
||||
"OPAQUE_BACKGROUND": "不透明な背景",
|
||||
"WLX": {},
|
||||
"LOOK_AND_FEEL": "外観",
|
||||
"HIDE_GRAB_HELP": "グリップ動作中にのヘルプを非表示",
|
||||
"ANIMATION_SPEED": "UIアニメーション速度",
|
||||
@@ -89,7 +88,11 @@
|
||||
"RESET_PLAYSPACE": "プレイエリアをリセット",
|
||||
"RESET_PLAYSPACE_HELP": "プレイエリアのオフセットをクリアします。",
|
||||
"BLOCK_POSES_ON_KBD_INTERACTION": "キーボード操作時のポーズをブロック",
|
||||
"BLOCK_POSES_ON_KBD_INTERACTION_HELP": "キーボードがホバーされ、「ゲーム入力をブロック」が有効になっている場合、ゲームがポーズを受信することをブロックします"
|
||||
"BLOCK_POSES_ON_KBD_INTERACTION_HELP": "キーボードがホバーされ、「ゲーム入力をブロック」が有効になっている場合、ゲームがポーズを受信することをブロックします",
|
||||
"LANGUAGE": "言語",
|
||||
"REQUIRES_RESTART": "再起動が必要です",
|
||||
"GRID_OPACITY": "フロアグリッドの不透明度",
|
||||
"GRID_OPACITY_HELP": "スカイボックスが有効なときの床グリッドの不透明度"
|
||||
},
|
||||
"HELLO": "こんにちは!",
|
||||
"AUDIO": {
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
"APP_SETTINGS": {
|
||||
"HIDE_USERNAME": "Ukryj nazwę użytkownika",
|
||||
"OPAQUE_BACKGROUND": "Nieprzezroczyste tło",
|
||||
"WLX": {},
|
||||
"LOOK_AND_FEEL": "Wygląd i działanie",
|
||||
"HIDE_GRAB_HELP": "Ukryj pomoc dotyczącą chwytania",
|
||||
"ANIMATION_SPEED": "Prędkość animacji UI",
|
||||
@@ -34,7 +33,6 @@
|
||||
"XR_CLICK_SENSITIVITY": "Czułość kliknięć XR",
|
||||
"XR_CLICK_SENSITIVITY_RELEASE": "Czułość zwalniania XR",
|
||||
"CLICK_FREEZE_TIME_MS": "Czas zamrożenia po kliknięciu (ms)",
|
||||
"MISC": "Różne",
|
||||
"XWAYLAND_BY_DEFAULT": "Uruchamiaj aplikacje domyślnie w trybie kompatybilności",
|
||||
"UPRIGHT_SCREEN_FIX": "Naprawa pozycji ekranu",
|
||||
"DOUBLE_CURSOR_FIX": "Naprawa podwójnego kursora",
|
||||
@@ -55,7 +53,7 @@
|
||||
"CLEAR_SAVED_STATE": "Wyczyść zapisany stan",
|
||||
"CLEAR_PIPEWIRE_TOKENS": "Wyczyść tokeny PipeWire",
|
||||
"DELETE_ALL_CONFIGS": "Wyczyść konfigurację",
|
||||
"RESTART_SOFTWARE": "Uruchom ponownie oprogramowanie",
|
||||
"RESTART_SOFTWARE": "Restartuj WayVR",
|
||||
"CLEAR_SAVED_STATE_HELP": "Zresetuj zestawy i pozycje nakładek",
|
||||
"CLEAR_PIPEWIRE_TOKENS_HELP": "Zapytaj o wybór ekranu przy następnym uruchomieniu",
|
||||
"DELETE_ALL_CONFIGS_HELP": "Usuń wszystkie pliki konfiguracyjne z katalogu conf.d",
|
||||
@@ -72,19 +70,24 @@
|
||||
"SCREENCOPY_GPU_HELP": "Szybkie działanie, brak wyskakujących okien z informacją o udostępnianiu ekranu.\nDziała na: Hyprland, Niri, River, Sway",
|
||||
"SCREENCOPY_HELP": "Wolne, bez wyskakujących okienek udostępniania ekranu.\nDziała na: Hyprland, Niri, River, Sway",
|
||||
"NONE": "Brak",
|
||||
"HMD_PINCH": "HMD + szczyknięcie",
|
||||
"HMD_PINCH": "HMD + ściśnięcie placami",
|
||||
"EYE_PINCH": "Ściśnięcie palcami + oko",
|
||||
"EYE_ONLY": "Tylko oko",
|
||||
"HMD_ONLY": "Tylko HMD"
|
||||
},
|
||||
"AUTOSTART_APPS": "Aplikacje do uruchomienia przy starcie",
|
||||
"AUTOSTART_APPS": "Aplikacje auto-start",
|
||||
"HANDSFREE_POINTER": "Tryb bez użycia rąk",
|
||||
"HANDSFREE_POINTER_HELP": "Wejście do użycia, gdy kontrolery ruchu\nsą niedostępne. Lewy szczyptak to chwyt,\nprawy to kliknięcie.",
|
||||
"UI_GRADIENT_INTENSITY": "Intensywność gradientu UI",
|
||||
"RESET_PLAYSPACE": "Zresetuj przestrzeń gry",
|
||||
"RESET_PLAYSPACE_HELP": "Wyczyść przesunięcie przestrzeni gry.",
|
||||
"BLOCK_POSES_ON_KBD_INTERACTION": "Blokuj pozy podczas interakcji z klawiaturą",
|
||||
"BLOCK_POSES_ON_KBD_INTERACTION_HELP": "Blokuje odbieranie póz przez grę, gdy kursor myszy znajduje się nad klawiaturą i włączona jest opcja 'Blokuj dane wejściowe z gry'"
|
||||
"BLOCK_POSES_ON_KBD_INTERACTION_HELP": "Blokuje odbieranie póz przez grę, gdy kursor myszy znajduje się nad klawiaturą i włączona jest opcja 'Blokuj dane wejściowe z gry'",
|
||||
"LANGUAGE": "Język",
|
||||
"REQUIRES_RESTART": "Wymaga restartu",
|
||||
"MISC": "Różne",
|
||||
"GRID_OPACITY": "Przezroczystość siatki podłogowej",
|
||||
"GRID_OPACITY_HELP": "Przezroczystość siatki podłogowej, gdy włączony jest skybox"
|
||||
},
|
||||
"APPLICATION_LAUNCHER": "Uruchamiacz aplikacji",
|
||||
"APPLICATIONS": "Aplikacje",
|
||||
|
||||
@@ -109,7 +109,11 @@
|
||||
"RESET_PLAYSPACE": "重置游戏空间",
|
||||
"RESET_PLAYSPACE_HELP": "清除舞台空间偏移。",
|
||||
"BLOCK_POSES_ON_KBD_INTERACTION": "与键盘交互时阻止姿势",
|
||||
"BLOCK_POSES_ON_KBD_INTERACTION_HELP": "当键盘悬停且启用“阻止游戏输入”时,阻止游戏接收姿势"
|
||||
"BLOCK_POSES_ON_KBD_INTERACTION_HELP": "当键盘悬停且启用“阻止游戏输入”时,阻止游戏接收姿势",
|
||||
"LANGUAGE": "语言",
|
||||
"REQUIRES_RESTART": "需要重启",
|
||||
"GRID_OPACITY": "地面网格不透明度",
|
||||
"GRID_OPACITY_HELP": "启用天空盒时地板网格的不透明度"
|
||||
},
|
||||
"APPLICATION_LAUNCHER": "应用启动器",
|
||||
"APPLICATION_STARTED": "应用已启动",
|
||||
|
||||
@@ -12,12 +12,14 @@ use wgui::{
|
||||
parser::{Fetchable, ParseDocumentParams, ParserState},
|
||||
renderer_vk::text::custom_glyph::CustomGlyphData,
|
||||
task::Tasks,
|
||||
theme::WguiTheme,
|
||||
widget::{label::WidgetLabel, rectangle::WidgetRectangle, sprite::WidgetSprite},
|
||||
windowing::window::{WguiWindow, WguiWindowParams, WguiWindowParamsExtra, WguiWindowPlacement},
|
||||
};
|
||||
use wlx_common::{
|
||||
audio,
|
||||
dash_interface::{BoxDashInterface, RecenterMode},
|
||||
locale::WayVRLangProvider,
|
||||
timestep::{self, Timestep},
|
||||
};
|
||||
|
||||
@@ -81,9 +83,11 @@ pub struct FrontendUpdateResult {
|
||||
pub sounds_to_play: Vec<SoundType>,
|
||||
}
|
||||
|
||||
pub struct InitParams<T> {
|
||||
pub struct InitParams<'a, T> {
|
||||
pub interface: BoxDashInterface<T>,
|
||||
pub lang_provider: &'a WayVRLangProvider,
|
||||
pub has_monado: bool,
|
||||
pub theme: Rc<WguiTheme>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -104,12 +108,11 @@ pub enum FrontendTask {
|
||||
RecenterPlayspace,
|
||||
PushToast(Translation),
|
||||
PlaySound(SoundType),
|
||||
UpdateWguiDefaultsFromConfig,
|
||||
HideDashboard,
|
||||
}
|
||||
|
||||
impl<T: 'static> Frontend<T> {
|
||||
pub fn new(mut params: InitParams<T>, data: &mut T) -> anyhow::Result<Frontend<T>> {
|
||||
pub fn new(params: InitParams<T>) -> anyhow::Result<Frontend<T>> {
|
||||
let mut assets = Box::new(assets::Asset {});
|
||||
|
||||
let font_binary_bold = assets.load_from_path_gzip("Quicksand-Bold.ttf.gz")?;
|
||||
@@ -118,7 +121,7 @@ impl<T: 'static> Frontend<T> {
|
||||
|
||||
let globals = WguiGlobals::new(
|
||||
assets,
|
||||
wgui::globals::Defaults::default(),
|
||||
params.lang_provider,
|
||||
&WguiFontConfig {
|
||||
binaries: vec![&font_binary_regular, &font_binary_bold, &font_binary_light],
|
||||
family_name_sans_serif: "Quicksand",
|
||||
@@ -128,15 +131,16 @@ impl<T: 'static> Frontend<T> {
|
||||
PathBuf::new(), //FIXME: pass from somewhere else
|
||||
)?;
|
||||
|
||||
Frontend::update_defaults_from_config(&globals, &mut params.interface, data);
|
||||
|
||||
let (layout, state) = wgui::parser::new_layout_from_assets(
|
||||
&ParseDocumentParams {
|
||||
globals: globals.clone(),
|
||||
path: AssetPath::BuiltIn("gui/dashboard.xml"),
|
||||
extra: Default::default(),
|
||||
},
|
||||
&LayoutParams { resize_to_parent: true },
|
||||
LayoutParams {
|
||||
resize_to_parent: true,
|
||||
theme: params.theme,
|
||||
},
|
||||
)?;
|
||||
|
||||
let id_popup_manager = state.get_widget_id("popup_manager")?;
|
||||
@@ -182,7 +186,6 @@ impl<T: 'static> Frontend<T> {
|
||||
// init some things first
|
||||
frontend.tasks.push(FrontendTask::RefreshBackground);
|
||||
frontend.tasks.push(FrontendTask::RefreshClock);
|
||||
frontend.tasks.push(FrontendTask::UpdateWguiDefaultsFromConfig);
|
||||
|
||||
Frontend::register_widgets(&mut frontend)?;
|
||||
|
||||
@@ -193,16 +196,6 @@ impl<T: 'static> Frontend<T> {
|
||||
self.sounds_to_play.push(sound_type);
|
||||
}
|
||||
|
||||
fn update_defaults_from_config(globals: &WguiGlobals, interface: &mut BoxDashInterface<T>, data: &mut T) {
|
||||
let config = interface.general_config(data);
|
||||
|
||||
let mut globals = globals.get();
|
||||
let defaults = &mut globals.defaults;
|
||||
|
||||
defaults.animation_mult = 1.0 / config.ui_animation_speed;
|
||||
defaults.gradient_intensity = config.ui_gradient_intensity;
|
||||
}
|
||||
|
||||
fn play_sound(&mut self, audio_system: &mut audio::AudioSystem, sound_type: SoundType) -> anyhow::Result<()> {
|
||||
let mut assets = self.globals.assets_builtin();
|
||||
|
||||
@@ -270,7 +263,7 @@ impl<T: 'static> Frontend<T> {
|
||||
{
|
||||
// always 30 times per second
|
||||
while self.timestep.on_tick() {
|
||||
self.toast_manager.tick(&self.globals, &mut self.layout)?;
|
||||
self.toast_manager.tick(&mut self.layout)?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -371,9 +364,6 @@ impl<T: 'static> Frontend<T> {
|
||||
FrontendTask::PushToast(content) => self.toast_manager.push(content),
|
||||
FrontendTask::PlaySound(sound_type) => self.queue_play_sound(sound_type),
|
||||
FrontendTask::HideDashboard => self.action_hide_dashboard(params.data),
|
||||
FrontendTask::UpdateWguiDefaultsFromConfig => {
|
||||
Frontend::update_defaults_from_config(&self.globals, &mut self.interface, params.data)
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -10,14 +10,20 @@ use wgui::{
|
||||
slider::ComponentSlider,
|
||||
tabs::ComponentTabs,
|
||||
},
|
||||
drawing,
|
||||
event::{CallbackDataCommon, EventAlterables},
|
||||
globals::WguiGlobals,
|
||||
i18n::Translation,
|
||||
layout::{Layout, WidgetID},
|
||||
log::LogErr,
|
||||
parser::{Fetchable, ParseDocumentParams, ParserState},
|
||||
renderer_vk::text::{FontWeight, TextStyle},
|
||||
taffy::{self, prelude::length},
|
||||
task::Tasks,
|
||||
widget::label::WidgetLabel,
|
||||
widget::{
|
||||
div::WidgetDiv,
|
||||
label::{WidgetLabel, WidgetLabelParams},
|
||||
},
|
||||
windowing::context_menu::{self, Blueprint, ContextMenu, TickResult},
|
||||
};
|
||||
use wlx_common::{config::GeneralConfig, config_io::ConfigRoot, dash_interface::RecenterMode};
|
||||
@@ -157,7 +163,7 @@ impl<T> Tab<T> for TabSettings<T> {
|
||||
}
|
||||
Task::SettingUpdated(setting) => match setting {
|
||||
SettingType::UiAnimationSpeed | SettingType::UiGradientIntensity | SettingType::UiRoundMultiplier => {
|
||||
frontend.tasks.push(FrontendTask::UpdateWguiDefaultsFromConfig);
|
||||
// todo: currently, wayvr restart is required to apply these changes (WguiTheme is Rc)
|
||||
}
|
||||
_ => { /* do nothing */ }
|
||||
},
|
||||
@@ -202,46 +208,49 @@ impl<T> Tab<T> for TabSettings<T> {
|
||||
}
|
||||
}
|
||||
|
||||
// Sorted alphabetically
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
#[derive(Clone, Copy, AsRefStr, EnumString)]
|
||||
enum SettingType {
|
||||
UiAnimationSpeed,
|
||||
UiGradientIntensity,
|
||||
UiRoundMultiplier,
|
||||
InvertScrollDirectionX,
|
||||
InvertScrollDirectionY,
|
||||
ScrollSpeed,
|
||||
LongPressDuration,
|
||||
NotificationsEnabled,
|
||||
NotificationsSoundEnabled,
|
||||
KeyboardSoundEnabled,
|
||||
UprightScreenFix,
|
||||
DoubleCursorFix,
|
||||
SetsOnWatch,
|
||||
HideGrabHelp,
|
||||
XrClickSensitivity,
|
||||
XrClickSensitivityRelease,
|
||||
AllowSliding,
|
||||
ClickFreezeTimeMs,
|
||||
FocusFollowsMouseMode,
|
||||
LeftHandedMouse,
|
||||
BlockGameInput,
|
||||
BlockGameInputIgnoreWatch,
|
||||
BlockPosesOnKbdInteraction,
|
||||
SpaceDragMultiplier,
|
||||
UseSkybox,
|
||||
UsePassthrough,
|
||||
ScreenRenderDown,
|
||||
CaptureMethod,
|
||||
ClickFreezeTimeMs,
|
||||
Clock12h,
|
||||
DoubleCursorFix,
|
||||
FocusFollowsMouseMode,
|
||||
HandsfreePointer,
|
||||
HideGrabHelp,
|
||||
HideUsername,
|
||||
InvertScrollDirectionX,
|
||||
InvertScrollDirectionY,
|
||||
KeyboardMiddleClick,
|
||||
KeyboardSoundEnabled,
|
||||
Language,
|
||||
LeftHandedMouse,
|
||||
LongPressDuration,
|
||||
NotificationsEnabled,
|
||||
NotificationsSoundEnabled,
|
||||
OpaqueBackground,
|
||||
PointerLerpFactor,
|
||||
ScreenRenderDown,
|
||||
ScrollSpeed,
|
||||
SetsOnWatch,
|
||||
SpaceDragMultiplier,
|
||||
SpaceDragUnlocked,
|
||||
SpaceRotateUnlocked,
|
||||
Clock12h,
|
||||
HideUsername,
|
||||
OpaqueBackground,
|
||||
UiAnimationSpeed,
|
||||
UiGradientIntensity,
|
||||
UiRoundMultiplier,
|
||||
UprightScreenFix,
|
||||
UsePassthrough,
|
||||
UseSkybox,
|
||||
GridOpacity,
|
||||
XrClickSensitivity,
|
||||
XrClickSensitivityRelease,
|
||||
XwaylandByDefault,
|
||||
CaptureMethod,
|
||||
KeyboardMiddleClick,
|
||||
HandsfreePointer,
|
||||
}
|
||||
|
||||
impl SettingType {
|
||||
@@ -286,6 +295,7 @@ impl SettingType {
|
||||
Self::XrClickSensitivityRelease => &mut config.xr_click_sensitivity_release,
|
||||
Self::SpaceDragMultiplier => &mut config.space_drag_multiplier,
|
||||
Self::PointerLerpFactor => &mut config.pointer_lerp_factor,
|
||||
Self::GridOpacity => &mut config.grid_opacity,
|
||||
_ => panic!("Requested f32 for non-f32 SettingType"),
|
||||
}
|
||||
}
|
||||
@@ -309,6 +319,9 @@ impl SettingType {
|
||||
Self::HandsfreePointer => {
|
||||
config.handsfree_pointer = wlx_common::config::HandsfreePointer::from_str(value).expect("Invalid enum value!")
|
||||
}
|
||||
Self::Language => {
|
||||
config.language = Some(wlx_common::locale::Language::from_str(value).expect("Invalid enum value!"))
|
||||
}
|
||||
_ => panic!("Requested enum for non-enum SettingType"),
|
||||
}
|
||||
}
|
||||
@@ -318,6 +331,10 @@ impl SettingType {
|
||||
Self::CaptureMethod => Self::get_enum_title_inner(config.capture_method),
|
||||
Self::KeyboardMiddleClick => Self::get_enum_title_inner(config.keyboard_middle_click_mode),
|
||||
Self::HandsfreePointer => Self::get_enum_title_inner(config.handsfree_pointer),
|
||||
Self::Language => match &config.language {
|
||||
Some(lang) => Self::get_enum_title_inner(*lang),
|
||||
None => Translation::from_translation_key("APP_SETTINGS.OPTION.AUTO"),
|
||||
},
|
||||
_ => panic!("Requested enum for non-enum SettingType"),
|
||||
}
|
||||
}
|
||||
@@ -341,80 +358,85 @@ impl SettingType {
|
||||
}
|
||||
|
||||
/// Ok is translation, Err is raw text
|
||||
/// `match` sorted alphabetically
|
||||
fn get_translation(self) -> Result<&'static str, &'static str> {
|
||||
match self {
|
||||
Self::UiAnimationSpeed => Ok("APP_SETTINGS.ANIMATION_SPEED"),
|
||||
Self::UiGradientIntensity => Ok("APP_SETTINGS.UI_GRADIENT_INTENSITY"),
|
||||
Self::UiRoundMultiplier => Ok("APP_SETTINGS.ROUND_MULTIPLIER"),
|
||||
Self::InvertScrollDirectionX => Ok("APP_SETTINGS.INVERT_SCROLL_DIRECTION_X"),
|
||||
Self::InvertScrollDirectionY => Ok("APP_SETTINGS.INVERT_SCROLL_DIRECTION_Y"),
|
||||
Self::ScrollSpeed => Ok("APP_SETTINGS.SCROLL_SPEED"),
|
||||
Self::LongPressDuration => Ok("APP_SETTINGS.LONG_PRESS_DURATION"),
|
||||
Self::NotificationsEnabled => Ok("APP_SETTINGS.NOTIFICATIONS_ENABLED"),
|
||||
Self::NotificationsSoundEnabled => Ok("APP_SETTINGS.NOTIFICATIONS_SOUND_ENABLED"),
|
||||
Self::KeyboardSoundEnabled => Ok("APP_SETTINGS.KEYBOARD_SOUND_ENABLED"),
|
||||
Self::UprightScreenFix => Ok("APP_SETTINGS.UPRIGHT_SCREEN_FIX"),
|
||||
Self::DoubleCursorFix => Ok("APP_SETTINGS.DOUBLE_CURSOR_FIX"),
|
||||
Self::SetsOnWatch => Ok("APP_SETTINGS.SETS_ON_WATCH"),
|
||||
Self::HideGrabHelp => Ok("APP_SETTINGS.HIDE_GRAB_HELP"),
|
||||
Self::XrClickSensitivity => Ok("APP_SETTINGS.XR_CLICK_SENSITIVITY"),
|
||||
Self::XrClickSensitivityRelease => Ok("APP_SETTINGS.XR_CLICK_SENSITIVITY_RELEASE"),
|
||||
Self::AllowSliding => Ok("APP_SETTINGS.ALLOW_SLIDING"),
|
||||
Self::ClickFreezeTimeMs => Ok("APP_SETTINGS.CLICK_FREEZE_TIME_MS"),
|
||||
Self::FocusFollowsMouseMode => Ok("APP_SETTINGS.FOCUS_FOLLOWS_MOUSE_MODE"),
|
||||
Self::LeftHandedMouse => Ok("APP_SETTINGS.LEFT_HANDED_MOUSE"),
|
||||
Self::BlockGameInput => Ok("APP_SETTINGS.BLOCK_GAME_INPUT"),
|
||||
Self::BlockGameInputIgnoreWatch => Ok("APP_SETTINGS.BLOCK_GAME_INPUT_IGNORE_WATCH"),
|
||||
Self::BlockPosesOnKbdInteraction => Ok("APP_SETTINGS.BLOCK_POSES_ON_KBD_INTERACTION"),
|
||||
Self::SpaceDragMultiplier => Ok("APP_SETTINGS.SPACE_DRAG_MULTIPLIER"),
|
||||
Self::UseSkybox => Ok("APP_SETTINGS.USE_SKYBOX"),
|
||||
Self::UsePassthrough => Ok("APP_SETTINGS.USE_PASSTHROUGH"),
|
||||
Self::ScreenRenderDown => Ok("APP_SETTINGS.SCREEN_RENDER_DOWN"),
|
||||
Self::CaptureMethod => Ok("APP_SETTINGS.CAPTURE_METHOD"),
|
||||
Self::ClickFreezeTimeMs => Ok("APP_SETTINGS.CLICK_FREEZE_TIME_MS"),
|
||||
Self::Clock12h => Ok("APP_SETTINGS.CLOCK_12H"),
|
||||
Self::DoubleCursorFix => Ok("APP_SETTINGS.DOUBLE_CURSOR_FIX"),
|
||||
Self::FocusFollowsMouseMode => Ok("APP_SETTINGS.FOCUS_FOLLOWS_MOUSE_MODE"),
|
||||
Self::GridOpacity => Ok("APP_SETTINGS.GRID_OPACITY"),
|
||||
Self::HandsfreePointer => Ok("APP_SETTINGS.HANDSFREE_POINTER"),
|
||||
Self::HideGrabHelp => Ok("APP_SETTINGS.HIDE_GRAB_HELP"),
|
||||
Self::HideUsername => Ok("APP_SETTINGS.HIDE_USERNAME"),
|
||||
Self::InvertScrollDirectionX => Ok("APP_SETTINGS.INVERT_SCROLL_DIRECTION_X"),
|
||||
Self::InvertScrollDirectionY => Ok("APP_SETTINGS.INVERT_SCROLL_DIRECTION_Y"),
|
||||
Self::KeyboardMiddleClick => Ok("APP_SETTINGS.KEYBOARD_MIDDLE_CLICK"),
|
||||
Self::KeyboardSoundEnabled => Ok("APP_SETTINGS.KEYBOARD_SOUND_ENABLED"),
|
||||
Self::Language => Ok("APP_SETTINGS.LANGUAGE"),
|
||||
Self::LeftHandedMouse => Ok("APP_SETTINGS.LEFT_HANDED_MOUSE"),
|
||||
Self::LongPressDuration => Ok("APP_SETTINGS.LONG_PRESS_DURATION"),
|
||||
Self::NotificationsEnabled => Ok("APP_SETTINGS.NOTIFICATIONS_ENABLED"),
|
||||
Self::NotificationsSoundEnabled => Ok("APP_SETTINGS.NOTIFICATIONS_SOUND_ENABLED"),
|
||||
Self::OpaqueBackground => Ok("APP_SETTINGS.OPAQUE_BACKGROUND"),
|
||||
Self::PointerLerpFactor => Ok("APP_SETTINGS.POINTER_LERP_FACTOR"),
|
||||
Self::ScreenRenderDown => Ok("APP_SETTINGS.SCREEN_RENDER_DOWN"),
|
||||
Self::ScrollSpeed => Ok("APP_SETTINGS.SCROLL_SPEED"),
|
||||
Self::SetsOnWatch => Ok("APP_SETTINGS.SETS_ON_WATCH"),
|
||||
Self::SpaceDragMultiplier => Ok("APP_SETTINGS.SPACE_DRAG_MULTIPLIER"),
|
||||
Self::SpaceDragUnlocked => Ok("APP_SETTINGS.SPACE_DRAG_UNLOCKED"),
|
||||
Self::SpaceRotateUnlocked => Ok("APP_SETTINGS.SPACE_ROTATE_UNLOCKED"),
|
||||
Self::Clock12h => Ok("APP_SETTINGS.CLOCK_12H"),
|
||||
Self::HideUsername => Ok("APP_SETTINGS.HIDE_USERNAME"),
|
||||
Self::OpaqueBackground => Ok("APP_SETTINGS.OPAQUE_BACKGROUND"),
|
||||
Self::UiAnimationSpeed => Ok("APP_SETTINGS.ANIMATION_SPEED"),
|
||||
Self::UiGradientIntensity => Ok("APP_SETTINGS.UI_GRADIENT_INTENSITY"),
|
||||
Self::UiRoundMultiplier => Ok("APP_SETTINGS.ROUND_MULTIPLIER"),
|
||||
Self::UprightScreenFix => Ok("APP_SETTINGS.UPRIGHT_SCREEN_FIX"),
|
||||
Self::UsePassthrough => Ok("APP_SETTINGS.USE_PASSTHROUGH"),
|
||||
Self::UseSkybox => Ok("APP_SETTINGS.USE_SKYBOX"),
|
||||
Self::XrClickSensitivity => Ok("APP_SETTINGS.XR_CLICK_SENSITIVITY"),
|
||||
Self::XrClickSensitivityRelease => Ok("APP_SETTINGS.XR_CLICK_SENSITIVITY_RELEASE"),
|
||||
Self::XwaylandByDefault => Ok("APP_SETTINGS.XWAYLAND_BY_DEFAULT"),
|
||||
Self::CaptureMethod => Ok("APP_SETTINGS.CAPTURE_METHOD"),
|
||||
Self::KeyboardMiddleClick => Ok("APP_SETTINGS.KEYBOARD_MIDDLE_CLICK"),
|
||||
Self::HandsfreePointer => Ok("APP_SETTINGS.HANDSFREE_POINTER"),
|
||||
}
|
||||
}
|
||||
|
||||
/// `match` sorted alphabetically
|
||||
fn get_tooltip(self) -> Option<&'static str> {
|
||||
match self {
|
||||
Self::UprightScreenFix => Some("APP_SETTINGS.UPRIGHT_SCREEN_FIX_HELP"),
|
||||
Self::DoubleCursorFix => Some("APP_SETTINGS.DOUBLE_CURSOR_FIX_HELP"),
|
||||
Self::XrClickSensitivity => Some("APP_SETTINGS.XR_CLICK_SENSITIVITY_HELP"),
|
||||
Self::XrClickSensitivityRelease => Some("APP_SETTINGS.XR_CLICK_SENSITIVITY_RELEASE_HELP"),
|
||||
Self::FocusFollowsMouseMode => Some("APP_SETTINGS.FOCUS_FOLLOWS_MOUSE_MODE_HELP"),
|
||||
Self::LeftHandedMouse => Some("APP_SETTINGS.LEFT_HANDED_MOUSE_HELP"),
|
||||
Self::BlockGameInput => Some("APP_SETTINGS.BLOCK_GAME_INPUT_HELP"),
|
||||
Self::BlockGameInputIgnoreWatch => Some("APP_SETTINGS.BLOCK_GAME_INPUT_IGNORE_WATCH_HELP"),
|
||||
Self::BlockPosesOnKbdInteraction => Some("APP_SETTINGS.BLOCK_POSES_ON_KBD_INTERACTION_HELP"),
|
||||
Self::UseSkybox => Some("APP_SETTINGS.USE_SKYBOX_HELP"),
|
||||
Self::UsePassthrough => Some("APP_SETTINGS.USE_PASSTHROUGH_HELP"),
|
||||
Self::ScreenRenderDown => Some("APP_SETTINGS.SCREEN_RENDER_DOWN_HELP"),
|
||||
Self::CaptureMethod => Some("APP_SETTINGS.CAPTURE_METHOD_HELP"),
|
||||
Self::KeyboardMiddleClick => Some("APP_SETTINGS.KEYBOARD_MIDDLE_CLICK_HELP"),
|
||||
Self::DoubleCursorFix => Some("APP_SETTINGS.DOUBLE_CURSOR_FIX_HELP"),
|
||||
Self::GridOpacity => Some("APP_SETTINGS.GRID_OPACITY_HELP"),
|
||||
Self::HandsfreePointer => Some("APP_SETTINGS.HANDSFREE_POINTER_HELP"),
|
||||
Self::KeyboardMiddleClick => Some("APP_SETTINGS.KEYBOARD_MIDDLE_CLICK_HELP"),
|
||||
Self::LeftHandedMouse => Some("APP_SETTINGS.LEFT_HANDED_MOUSE_HELP"),
|
||||
Self::ScreenRenderDown => Some("APP_SETTINGS.SCREEN_RENDER_DOWN_HELP"),
|
||||
Self::UprightScreenFix => Some("APP_SETTINGS.UPRIGHT_SCREEN_FIX_HELP"),
|
||||
Self::UsePassthrough => Some("APP_SETTINGS.USE_PASSTHROUGH_HELP"),
|
||||
Self::UseSkybox => Some("APP_SETTINGS.USE_SKYBOX_HELP"),
|
||||
Self::XrClickSensitivity => Some("APP_SETTINGS.XR_CLICK_SENSITIVITY_HELP"),
|
||||
Self::XrClickSensitivityRelease => Some("APP_SETTINGS.XR_CLICK_SENSITIVITY_RELEASE_HELP"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: incorporate this
|
||||
fn requires_restart(self) -> bool {
|
||||
match self {
|
||||
matches!(
|
||||
self,
|
||||
Self::UiAnimationSpeed
|
||||
| Self::UiRoundMultiplier
|
||||
| Self::UprightScreenFix
|
||||
| Self::DoubleCursorFix
|
||||
| Self::ScreenRenderDown
|
||||
| Self::CaptureMethod => true,
|
||||
_ => false,
|
||||
}
|
||||
| Self::UiRoundMultiplier
|
||||
| Self::UiGradientIntensity
|
||||
| Self::UprightScreenFix
|
||||
| Self::DoubleCursorFix
|
||||
| Self::ScreenRenderDown
|
||||
| Self::Language
|
||||
| Self::CaptureMethod
|
||||
)
|
||||
}
|
||||
|
||||
fn get_frontend_task(self) -> Option<FrontendTask> {
|
||||
@@ -426,6 +448,42 @@ impl SettingType {
|
||||
}
|
||||
}
|
||||
|
||||
// creates a simple div with horizontal, centered flow
|
||||
fn horiz_cell(layout: &mut Layout, parent: WidgetID) -> anyhow::Result<WidgetID> {
|
||||
let (pair, _) = layout.add_child(
|
||||
parent,
|
||||
WidgetDiv::create(),
|
||||
taffy::Style {
|
||||
flex_direction: taffy::FlexDirection::Row,
|
||||
align_items: Some(taffy::AlignItems::Center),
|
||||
gap: length(8.0),
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
Ok(pair.id)
|
||||
}
|
||||
|
||||
fn mount_requires_restart(layout: &mut Layout, parent: WidgetID) -> anyhow::Result<()> {
|
||||
let content = Translation::from_translation_key("APP_SETTINGS.REQUIRES_RESTART");
|
||||
let label = WidgetLabel::create(
|
||||
&mut layout.state,
|
||||
WidgetLabelParams {
|
||||
content,
|
||||
style: TextStyle {
|
||||
wrap: false,
|
||||
color: Some(drawing::Color::new(1.0, 0.5, 0.5, 1.0)),
|
||||
weight: Some(FontWeight::Bold),
|
||||
size: Some(10.0),
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
layout.add_child(parent, label, Default::default())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
macro_rules! category {
|
||||
($pe:expr, $root:expr, $translation:expr, $icon:expr) => {{
|
||||
let id = $pe.idx.to_string();
|
||||
@@ -464,9 +522,15 @@ macro_rules! checkbox {
|
||||
let checked = if *$setting.mut_bool($mp.config) { "1" } else { "0" };
|
||||
params.insert(Rc::from("checked"), Rc::from(checked));
|
||||
|
||||
let id_cell = horiz_cell($mp.layout, $root)?;
|
||||
|
||||
$mp
|
||||
.parser_state
|
||||
.instantiate_template($mp.doc_params, "CheckBoxSetting", $mp.layout, $root, params)?;
|
||||
.instantiate_template($mp.doc_params, "CheckBoxSetting", $mp.layout, id_cell, params)?;
|
||||
|
||||
if $setting.requires_restart() {
|
||||
mount_requires_restart($mp.layout, id_cell)?;
|
||||
}
|
||||
|
||||
let checkbox = $mp.parser_state.fetch_component_as::<ComponentCheckbox>(&id)?;
|
||||
checkbox.on_toggle(Box::new({
|
||||
@@ -502,9 +566,15 @@ macro_rules! slider_f32 {
|
||||
params.insert(Rc::from("max"), Rc::from($max.to_string()));
|
||||
params.insert(Rc::from("step"), Rc::from($step.to_string()));
|
||||
|
||||
let id_cell = horiz_cell($mp.layout, $root)?;
|
||||
|
||||
$mp
|
||||
.parser_state
|
||||
.instantiate_template($mp.doc_params, "SliderSetting", $mp.layout, $root, params)?;
|
||||
.instantiate_template($mp.doc_params, "SliderSetting", $mp.layout, id_cell, params)?;
|
||||
|
||||
if $setting.requires_restart() {
|
||||
mount_requires_restart($mp.layout, id_cell)?;
|
||||
}
|
||||
|
||||
let slider = $mp.parser_state.fetch_component_as::<ComponentSlider>(&id)?;
|
||||
slider.on_value_changed(Box::new({
|
||||
@@ -534,6 +604,8 @@ macro_rules! slider_i32 {
|
||||
params.insert(Rc::from("tooltip"), Rc::from(tooltip));
|
||||
}
|
||||
|
||||
let id_cell = horiz_cell($mp.layout, $root)?;
|
||||
|
||||
let value = $setting.mut_i32($mp.config).to_string();
|
||||
params.insert(Rc::from("value"), Rc::from(value));
|
||||
params.insert(Rc::from("min"), Rc::from($min.to_string()));
|
||||
@@ -542,7 +614,11 @@ macro_rules! slider_i32 {
|
||||
|
||||
$mp
|
||||
.parser_state
|
||||
.instantiate_template($mp.doc_params, "SliderSetting", $mp.layout, $root, params)?;
|
||||
.instantiate_template($mp.doc_params, "SliderSetting", $mp.layout, id_cell, params)?;
|
||||
|
||||
if $setting.requires_restart() {
|
||||
mount_requires_restart($mp.layout, id_cell)?;
|
||||
}
|
||||
|
||||
let slider = $mp.parser_state.fetch_component_as::<ComponentSlider>(&id)?;
|
||||
slider.on_value_changed(Box::new({
|
||||
@@ -556,7 +632,7 @@ macro_rules! slider_i32 {
|
||||
}
|
||||
|
||||
macro_rules! dropdown {
|
||||
($mp:expr, $root:expr, $setting:expr, $options:expr) => {
|
||||
($mp:expr /* `MacroParams` struct */, $root:expr, $setting:expr, $options:expr) => {
|
||||
let id = $mp.idx.to_string();
|
||||
$mp.idx += 1;
|
||||
|
||||
@@ -572,9 +648,15 @@ macro_rules! dropdown {
|
||||
params.insert(Rc::from("tooltip"), Rc::from(tooltip));
|
||||
}
|
||||
|
||||
let id_cell = horiz_cell($mp.layout, $root)?;
|
||||
|
||||
$mp
|
||||
.parser_state
|
||||
.instantiate_template($mp.doc_params, "DropdownButton", $mp.layout, $root, params)?;
|
||||
.instantiate_template($mp.doc_params, "DropdownButton", $mp.layout, id_cell, params)?;
|
||||
|
||||
if $setting.requires_restart() {
|
||||
mount_requires_restart($mp.layout, id_cell)?;
|
||||
}
|
||||
|
||||
let setting_str = $setting.as_ref();
|
||||
let title = $setting.get_enum_title($mp.config);
|
||||
@@ -709,6 +791,7 @@ impl<T> TabSettings<T> {
|
||||
match name {
|
||||
TabNameEnum::LookAndFeel => {
|
||||
let c = category!(mp, root, "APP_SETTINGS.LOOK_AND_FEEL", "dashboard/palette.svg")?;
|
||||
dropdown!(mp, c, SettingType::Language, wlx_common::locale::Language::VARIANTS);
|
||||
checkbox!(mp, c, SettingType::OpaqueBackground);
|
||||
checkbox!(mp, c, SettingType::HideUsername);
|
||||
checkbox!(mp, c, SettingType::HideGrabHelp);
|
||||
@@ -717,6 +800,7 @@ impl<T> TabSettings<T> {
|
||||
slider_f32!(mp, c, SettingType::UiRoundMultiplier, 0.5, 5.0, 0.1);
|
||||
checkbox!(mp, c, SettingType::SetsOnWatch);
|
||||
checkbox!(mp, c, SettingType::UseSkybox);
|
||||
slider_f32!(mp, c, SettingType::GridOpacity, 0.0, 1.0, 0.05); // min, max, step
|
||||
checkbox!(mp, c, SettingType::UsePassthrough);
|
||||
checkbox!(mp, c, SettingType::Clock12h);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ use wgui::{
|
||||
animation::{Animation, AnimationEasing},
|
||||
components::tooltip::{TOOLTIP_BORDER_COLOR, TOOLTIP_COLOR},
|
||||
drawing::Color,
|
||||
globals::WguiGlobals,
|
||||
i18n::Translation,
|
||||
layout::{Layout, LayoutTask, LayoutTasks, WidgetID},
|
||||
renderer_vk::{
|
||||
@@ -61,15 +60,7 @@ impl ToastManager {
|
||||
}
|
||||
}
|
||||
|
||||
fn mount_toast(
|
||||
&self,
|
||||
globals: &WguiGlobals,
|
||||
layout: &mut Layout,
|
||||
state: &mut State,
|
||||
content: Translation,
|
||||
) -> anyhow::Result<()> {
|
||||
let mut globals = globals.get();
|
||||
|
||||
fn mount_toast(&self, layout: &mut Layout, state: &mut State, content: Translation) -> anyhow::Result<()> {
|
||||
let (root, _) = layout.add_topmost_child(
|
||||
WidgetDiv::create(),
|
||||
taffy::Style {
|
||||
@@ -110,27 +101,24 @@ impl ToastManager {
|
||||
},
|
||||
)?;
|
||||
|
||||
let (label, _) = layout.add_child(
|
||||
rect.id,
|
||||
WidgetLabel::create(
|
||||
&mut globals,
|
||||
WidgetLabelParams {
|
||||
content,
|
||||
style: TextStyle {
|
||||
weight: Some(FontWeight::Bold),
|
||||
align: Some(HorizontalAlign::Center),
|
||||
wrap: true,
|
||||
..Default::default()
|
||||
},
|
||||
let label = WidgetLabel::create(
|
||||
&mut layout.state,
|
||||
WidgetLabelParams {
|
||||
content,
|
||||
style: TextStyle {
|
||||
weight: Some(FontWeight::Bold),
|
||||
align: Some(HorizontalAlign::Center),
|
||||
wrap: true,
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
taffy::Style { ..Default::default() },
|
||||
)?;
|
||||
},
|
||||
);
|
||||
let (label, _) = layout.add_child(rect.id, label, taffy::Style { ..Default::default() })?;
|
||||
|
||||
// show-up animation
|
||||
layout.animations.add(Animation::new(
|
||||
rect.id,
|
||||
(TOAST_DURATION_TICKS as f32 * globals.defaults.animation_mult) as u32,
|
||||
(TOAST_DURATION_TICKS as f32 * layout.state.theme.animation_mult) as u32,
|
||||
AnimationEasing::Linear,
|
||||
Box::new(move |common, data| {
|
||||
let pos_showup = AnimationEasing::OutQuint.interpolate((data.pos * 4.0).min(1.0));
|
||||
@@ -161,7 +149,7 @@ impl ToastManager {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn tick(&mut self, globals: &WguiGlobals, layout: &mut Layout) -> anyhow::Result<()> {
|
||||
pub fn tick(&mut self, layout: &mut Layout) -> anyhow::Result<()> {
|
||||
let mut state = self.state.borrow_mut();
|
||||
if state.timeout > 0 {
|
||||
state.timeout -= 1;
|
||||
@@ -176,7 +164,7 @@ impl ToastManager {
|
||||
state.timeout = TOAST_DURATION_TICKS;
|
||||
// mount next
|
||||
if let Some(content) = state.queue.pop_front() {
|
||||
self.mount_toast(globals, layout, &mut state, content)?;
|
||||
self.mount_toast(layout, &mut state, content)?;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,9 +5,10 @@ use wgui::{
|
||||
widget::label::{WidgetLabel, WidgetLabelParams},
|
||||
};
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn create_label(layout: &mut Layout, parent: WidgetID, content: Translation) -> anyhow::Result<()> {
|
||||
let label = WidgetLabel::create(
|
||||
&mut layout.state.globals.get(),
|
||||
&mut layout.state,
|
||||
WidgetLabelParams {
|
||||
content,
|
||||
style: TextStyle {
|
||||
|
||||
@@ -50,7 +50,6 @@ enum CompositorMode {
|
||||
enum Task {
|
||||
SetCompositor(CompositorMode),
|
||||
SetRes(ResMode),
|
||||
SetPos(PosMode), // TODO?
|
||||
SetOrientation(OrientationMode),
|
||||
SetAutoStart(bool),
|
||||
Launch,
|
||||
@@ -295,7 +294,6 @@ impl View {
|
||||
match task {
|
||||
Task::SetCompositor(mode) => self.compositor_mode = mode,
|
||||
Task::SetRes(mode) => self.res_mode = mode,
|
||||
Task::SetPos(mode) => self.pos_mode = mode,
|
||||
Task::SetOrientation(mode) => self.orientation_mode = mode,
|
||||
Task::SetAutoStart(auto_start) => self.auto_start = auto_start,
|
||||
Task::Launch => self.action_launch(interface, data),
|
||||
|
||||
@@ -110,8 +110,7 @@ struct MultiSelectorParams<'a> {
|
||||
}
|
||||
|
||||
fn mount_multi_selector(params: MultiSelectorParams) -> anyhow::Result<()> {
|
||||
let globals = params.ess.layout.state.globals.clone();
|
||||
let accent_color = globals.defaults().accent_color;
|
||||
let accent_color = params.ess.layout.state.theme.accent_color;
|
||||
|
||||
for cell in params.cells {
|
||||
let highlighted = cell.key == params.def_cell;
|
||||
@@ -654,8 +653,6 @@ impl View {
|
||||
}
|
||||
|
||||
fn update_button_highlights(&self, layout: &mut Layout) -> anyhow::Result<()> {
|
||||
let defaults = self.globals.defaults();
|
||||
|
||||
let mut c = layout.start_common();
|
||||
let mut common = c.common();
|
||||
|
||||
@@ -667,14 +664,12 @@ impl View {
|
||||
};
|
||||
|
||||
let mut perform = |btn_num: u8, btn: &Rc<ComponentButton>| {
|
||||
btn.set_color(
|
||||
&mut common,
|
||||
if num == btn_num {
|
||||
defaults.accent_color
|
||||
} else {
|
||||
defaults.button_color
|
||||
},
|
||||
);
|
||||
let color = if num == btn_num {
|
||||
common.state.theme.accent_color
|
||||
} else {
|
||||
common.state.theme.button_color
|
||||
};
|
||||
btn.set_color(&mut common, color);
|
||||
};
|
||||
|
||||
perform(0, &self.btn_sinks);
|
||||
|
||||
@@ -98,15 +98,9 @@ impl View {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn mount_placeholder_text(
|
||||
&self,
|
||||
globals: &WguiGlobals,
|
||||
layout: &mut Layout,
|
||||
parent: WidgetID,
|
||||
text: &str,
|
||||
) -> anyhow::Result<()> {
|
||||
fn mount_placeholder_text(&self, layout: &mut Layout, parent: WidgetID, text: &str) -> anyhow::Result<()> {
|
||||
let label = WidgetLabel::create(
|
||||
&mut globals.get(),
|
||||
&mut layout.state,
|
||||
WidgetLabelParams {
|
||||
content: Translation::from_raw_text(text),
|
||||
style: TextStyle {
|
||||
@@ -153,7 +147,7 @@ impl View {
|
||||
// mount placeholder
|
||||
let img = view_common.get_placeholder_image()?.clone();
|
||||
self.mount_image(layout, &img)?;
|
||||
self.mount_placeholder_text(&view_common.globals, layout, self.id_image_parent, &self.app_name)?;
|
||||
self.mount_placeholder_text(layout, self.id_image_parent, &self.app_name)?;
|
||||
} else {
|
||||
// mount image
|
||||
let path = format!("app:{:?}", self.app_id);
|
||||
|
||||
@@ -256,17 +256,14 @@ impl View {
|
||||
)?;
|
||||
|
||||
if let Some(text) = text.take() {
|
||||
layout.add_child(
|
||||
self.id_list_parent,
|
||||
WidgetLabel::create(
|
||||
&mut self.globals.get(),
|
||||
WidgetLabelParams {
|
||||
content: text,
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
Default::default(),
|
||||
)?;
|
||||
let label = WidgetLabel::create(
|
||||
&mut layout.state,
|
||||
WidgetLabelParams {
|
||||
content: text,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
layout.add_child(self.id_list_parent, label, Default::default())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -59,10 +59,18 @@
|
||||
</div>
|
||||
</rectangle>
|
||||
|
||||
<rectangle macro="rect">
|
||||
<label text="Context menu test" />
|
||||
<Button id="button_context_menu" text="Show context menu" />
|
||||
</rectangle>
|
||||
<div>
|
||||
<rectangle macro="rect">
|
||||
<label text="Context menu test" />
|
||||
<Button id="button_context_menu" text="Show context menu" />
|
||||
</rectangle>
|
||||
<rectangle macro="rect">
|
||||
<label text="Editbox test" />
|
||||
<EditBox text="Initial text. Test, abcdef." width="400" />
|
||||
|
||||
<EditBox margin_left="42" margin_top="42" margin_bottom="40" width="200" />
|
||||
</rectangle>
|
||||
</div>
|
||||
|
||||
<rectangle macro="rect">
|
||||
<label text="visibility test" weight="bold" />
|
||||
|
||||
+50
-37
@@ -1,25 +1,25 @@
|
||||
use glam::{vec2, Vec2};
|
||||
use std::sync::Arc;
|
||||
use testbed::{testbed_any::TestbedAny, Testbed};
|
||||
use glam::{Vec2, vec2};
|
||||
use std::{rc::Rc, sync::Arc};
|
||||
use testbed::{Testbed, testbed_any::TestbedAny};
|
||||
use tracing_subscriber::EnvFilter;
|
||||
use tracing_subscriber::filter::LevelFilter;
|
||||
use tracing_subscriber::layer::SubscriberExt;
|
||||
use tracing_subscriber::util::SubscriberInitExt;
|
||||
use tracing_subscriber::EnvFilter;
|
||||
use vulkan::init_window;
|
||||
use vulkano::{
|
||||
Validated, VulkanError,
|
||||
command_buffer::CommandBufferUsage,
|
||||
format::Format,
|
||||
image::{view::ImageView, ImageUsage},
|
||||
image::{ImageUsage, view::ImageView},
|
||||
swapchain::{
|
||||
acquire_next_image, ColorSpace, CompositeAlpha, PresentMode, Surface, SurfaceInfo, Swapchain,
|
||||
SwapchainCreateInfo, SwapchainPresentInfo,
|
||||
ColorSpace, CompositeAlpha, PresentMode, Surface, SurfaceInfo, Swapchain, SwapchainCreateInfo,
|
||||
SwapchainPresentInfo, acquire_next_image,
|
||||
},
|
||||
sync::GpuFuture,
|
||||
Validated, VulkanError,
|
||||
};
|
||||
use wgui::{
|
||||
event::{MouseButtonIndex, MouseDownEvent, MouseMotionEvent, MouseUpEvent, MouseWheelEvent},
|
||||
gfx::{cmd::WGfxClearMode, WGfx},
|
||||
event::{MouseButtonEvent, MouseButtonIndex, MouseMotionEvent, MouseWheelEvent},
|
||||
gfx::{WGfx, cmd::WGfxClearMode},
|
||||
renderer_vk::{self},
|
||||
};
|
||||
use winit::{
|
||||
@@ -32,7 +32,7 @@ use wlx_common::{audio, timestep::Timestep};
|
||||
use crate::{
|
||||
rate_limiter::RateLimiter,
|
||||
testbed::{
|
||||
testbed_dashboard::TestbedDashboard, testbed_generic::TestbedGeneric, TestbedUpdateParams,
|
||||
TestbedUpdateParams, testbed_dashboard::TestbedDashboard, testbed_generic::TestbedGeneric,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -174,7 +174,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
testbed
|
||||
.layout()
|
||||
.push_event(
|
||||
&wgui::event::Event::MouseDown(MouseDownEvent {
|
||||
&wgui::event::Event::MouseDown(MouseButtonEvent {
|
||||
pos: mouse / scale,
|
||||
index: MouseButtonIndex::Left,
|
||||
device: 0,
|
||||
@@ -187,7 +187,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
testbed
|
||||
.layout()
|
||||
.push_event(
|
||||
&wgui::event::Event::MouseUp(MouseUpEvent {
|
||||
&wgui::event::Event::MouseUp(MouseButtonEvent {
|
||||
pos: mouse / scale,
|
||||
index: MouseButtonIndex::Left,
|
||||
device: 0,
|
||||
@@ -221,33 +221,46 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
..
|
||||
} => {
|
||||
if event.state == ElementState::Pressed {
|
||||
if event.physical_key == PhysicalKey::Code(KeyCode::F10) {
|
||||
debug_draw_enabled = !debug_draw_enabled;
|
||||
if debug_draw_enabled {
|
||||
log::info!(
|
||||
"Debug draw enabled\n\tAqua: widget boundary\n\tMagenta: Scissoring (separate render pass)"
|
||||
);
|
||||
if !event.repeat {
|
||||
if event.physical_key == PhysicalKey::Code(KeyCode::F10) {
|
||||
debug_draw_enabled = !debug_draw_enabled;
|
||||
if debug_draw_enabled {
|
||||
log::info!(
|
||||
"Debug draw enabled\n\tAqua: widget boundary\n\tMagenta: Scissoring (separate render pass)"
|
||||
);
|
||||
}
|
||||
testbed.layout().mark_redraw();
|
||||
}
|
||||
|
||||
if event.physical_key == PhysicalKey::Code(KeyCode::F11) {
|
||||
testbed.layout().print_tree();
|
||||
}
|
||||
|
||||
if event.physical_key == PhysicalKey::Code(KeyCode::Equal) {
|
||||
scale *= 1.25;
|
||||
render_context
|
||||
.update_viewport(&mut shared_context, swapchain_size, scale)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
if event.physical_key == PhysicalKey::Code(KeyCode::Minus) {
|
||||
scale *= 0.75;
|
||||
render_context
|
||||
.update_viewport(&mut shared_context, swapchain_size, scale)
|
||||
.unwrap();
|
||||
}
|
||||
testbed.layout().mark_redraw();
|
||||
}
|
||||
|
||||
if event.physical_key == PhysicalKey::Code(KeyCode::F11) {
|
||||
testbed.layout().print_tree();
|
||||
}
|
||||
|
||||
if event.physical_key == PhysicalKey::Code(KeyCode::Equal) {
|
||||
scale *= 1.25;
|
||||
render_context
|
||||
.update_viewport(&mut shared_context, swapchain_size, scale)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
if event.physical_key == PhysicalKey::Code(KeyCode::Minus) {
|
||||
scale *= 0.75;
|
||||
render_context
|
||||
.update_viewport(&mut shared_context, swapchain_size, scale)
|
||||
.unwrap();
|
||||
}
|
||||
testbed
|
||||
.layout()
|
||||
.push_event(
|
||||
&wgui::event::Event::TextInput(wgui::event::TextInputEvent {
|
||||
text: event.text.map(|text| Rc::from(text.as_str())),
|
||||
}),
|
||||
&mut (),
|
||||
&mut (),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
Event::WindowEvent {
|
||||
|
||||
@@ -12,6 +12,7 @@ use wgui::{
|
||||
layout::{Layout, LayoutParams, LayoutUpdateParams},
|
||||
parser::{ParseDocumentParams, ParserState},
|
||||
};
|
||||
use wlx_common::locale::WayVRLangProvider;
|
||||
|
||||
pub struct TestbedAny {
|
||||
pub layout: Layout,
|
||||
@@ -28,9 +29,11 @@ impl TestbedAny {
|
||||
AssetPath::BuiltIn(&format!("gui/{name}.xml"))
|
||||
};
|
||||
|
||||
let lang_provider = WayVRLangProvider::default();
|
||||
|
||||
let globals = WguiGlobals::new(
|
||||
assets,
|
||||
wgui::globals::Defaults::default(),
|
||||
&lang_provider,
|
||||
&WguiFontConfig::default(),
|
||||
PathBuf::new(), // cwd
|
||||
)?;
|
||||
@@ -41,7 +44,7 @@ impl TestbedAny {
|
||||
path,
|
||||
extra: Default::default(),
|
||||
},
|
||||
&LayoutParams::default(),
|
||||
LayoutParams::default(),
|
||||
)?;
|
||||
Ok(Self { layout, state })
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::testbed::{Testbed, TestbedUpdateParams};
|
||||
use dash_frontend::frontend::{self, FrontendUpdateParams};
|
||||
use wgui::layout::Layout;
|
||||
use wlx_common::dash_interface_emulated::DashInterfaceEmulated;
|
||||
use wgui::{layout::Layout, theme::WguiTheme};
|
||||
use wlx_common::{dash_interface_emulated::DashInterfaceEmulated, locale::WayVRLangProvider};
|
||||
|
||||
pub struct TestbedDashboard {
|
||||
frontend: frontend::Frontend<()>,
|
||||
@@ -10,14 +12,14 @@ pub struct TestbedDashboard {
|
||||
impl TestbedDashboard {
|
||||
pub fn new() -> anyhow::Result<Self> {
|
||||
let interface = DashInterfaceEmulated::new();
|
||||
let lang_provider = WayVRLangProvider::default();
|
||||
|
||||
let frontend = frontend::Frontend::new(
|
||||
frontend::InitParams {
|
||||
interface: Box::new(interface),
|
||||
has_monado: true,
|
||||
},
|
||||
&mut (),
|
||||
)?;
|
||||
let frontend = frontend::Frontend::new(frontend::InitParams {
|
||||
interface: Box::new(interface),
|
||||
has_monado: true,
|
||||
lang_provider: &lang_provider,
|
||||
theme: Rc::new(WguiTheme::default()),
|
||||
})?;
|
||||
Ok(Self { frontend })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,12 +21,14 @@ use wgui::{
|
||||
parser::{Fetchable, ParseDocumentExtra, ParseDocumentParams, ParserState},
|
||||
taffy::{self, prelude::length},
|
||||
task::Tasks,
|
||||
theme::WguiTheme,
|
||||
widget::{div::WidgetDiv, label::WidgetLabel, rectangle::WidgetRectangle},
|
||||
windowing::{
|
||||
context_menu::{self, TickResult},
|
||||
window::{WguiWindow, WguiWindowParams, WguiWindowParamsExtra},
|
||||
},
|
||||
};
|
||||
use wlx_common::locale::WayVRLangProvider;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum TestbedTask {
|
||||
@@ -85,9 +87,11 @@ impl TestbedGeneric {
|
||||
}
|
||||
|
||||
pub fn new(assets: Box<assets::Asset>) -> anyhow::Result<Self> {
|
||||
let lang_provider = WayVRLangProvider::default();
|
||||
|
||||
let globals = WguiGlobals::new(
|
||||
assets,
|
||||
wgui::globals::Defaults::default(),
|
||||
&lang_provider,
|
||||
&WguiFontConfig::default(),
|
||||
PathBuf::new(), // cwd
|
||||
)?;
|
||||
@@ -121,8 +125,9 @@ impl TestbedGeneric {
|
||||
|
||||
let (layout, parser_state) = wgui::parser::new_layout_from_assets(
|
||||
&TestbedGeneric::doc_params(&globals, extra),
|
||||
&LayoutParams {
|
||||
LayoutParams {
|
||||
resize_to_parent: true,
|
||||
theme: Rc::new(WguiTheme::default()),
|
||||
},
|
||||
)?;
|
||||
|
||||
|
||||
+3
-3
@@ -2,8 +2,8 @@ use anyhow::Context;
|
||||
use std::sync::{Arc, OnceLock};
|
||||
use vulkano::{
|
||||
device::{
|
||||
physical::{PhysicalDevice, PhysicalDeviceType},
|
||||
Device, DeviceCreateInfo, DeviceExtensions, DeviceFeatures, Queue, QueueCreateInfo, QueueFlags,
|
||||
physical::{PhysicalDevice, PhysicalDeviceType},
|
||||
},
|
||||
instance::{Instance, InstanceCreateInfo},
|
||||
swapchain::ColorSpace,
|
||||
@@ -96,8 +96,8 @@ pub fn init_window(
|
||||
let (format, color_space) = physical_device
|
||||
.surface_formats(&surface, vulkano::swapchain::SurfaceInfo::default())?
|
||||
.into_iter()
|
||||
.next()
|
||||
.context("Could not read surface formats for PhysicalDevice")?;
|
||||
.find(|(format, _)| !format.components().iter().any(|b| *b > 8))
|
||||
.context("Could not read or no suitable surface formats for PhysicalDevice")?;
|
||||
|
||||
let (device, queues) = Device::new(
|
||||
physical_device,
|
||||
|
||||
+1
-5
@@ -2,8 +2,4 @@
|
||||
|
||||
This repository contains the IPC specification and client implementation for the WayVR protocol. The primary purpose is to enable communication between applications and the WayVR server, offering a range of API functions.
|
||||
|
||||
[WayVR Server README](https://github.com/galister/wlx-overlay-s/tree/main/contrib/wayvr)
|
||||
|
||||
## Usage
|
||||
|
||||
[Example WayVR IPC Client usage in WayVR Dashboard (Legacy)](https://github.com/olekolek1000/wayvr-dashboard/blob/master/src-tauri/src/frontend_ipc.rs)
|
||||
[Wayvrctl Documentation](https://wayvr.org/docs/various/integrations/wayvr-ipc/)
|
||||
+2
-1
@@ -44,7 +44,8 @@ xdg.workspace = true
|
||||
|
||||
ash = "^0.38.0" # must match vulkano
|
||||
bytes = { version = "1.11.1" }
|
||||
chrono = "0.4.42"
|
||||
chrono = { version = "0.4.42", features = [ "unstable-locales"]}
|
||||
pure-rust-locales = "0.8.2"
|
||||
chrono-tz = "0.10.4"
|
||||
config = "0.15.19"
|
||||
dbus = { version = "0.9.9" }
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
<p align="center">
|
||||
<img src="https://raw.githubusercontent.com/galister/wlx-overlay-s/refs/heads/guide/wayvr/logo.svg" height="120"/>
|
||||
</p>
|
||||
|
||||
# Overview
|
||||
|
||||
### Features
|
||||
|
||||
- Display Wayland applications without GPU overhead (zero-copy via dma-buf)
|
||||
- Mouse and keyboard input, with precision scrolling support
|
||||
- Tested on AMD and Nvidia
|
||||
|
||||
### Supported software
|
||||
|
||||
- Basically all Qt and GTK applications (they work out of the box)
|
||||
- Most XWayland applications via `cage`
|
||||
|
||||
### Launching external apps inside WayVR
|
||||
|
||||
To launch your app externally:
|
||||
|
||||
```sh
|
||||
DISPLAY= WAYLAND_DISPLAY=wayland-$(cat $XDG_RUNTIME_DIR/wayvr.disp) yourapp
|
||||
```
|
||||
|
||||
or (in the most cases):
|
||||
|
||||
```
|
||||
DISPLAY= WAYLAND_DISPLAY=wayland-20 yourapp
|
||||
```
|
||||
|
||||
Setting `DISPLAY` to an empty string forces various apps to use Wayland instead of X11.
|
||||
|
||||
# Troubleshooting
|
||||
|
||||
### My application doesn't launch but others do!
|
||||
|
||||
Even though some applications support Wayland, some still check for the `DISPLAY` environment variable and an available X11 server, throwing an error. This can also be fixed by running `cage` on top of them.
|
||||
|
||||
### Floating windows
|
||||
|
||||
Context menus are not functional in most cases yet, including drag & drop support.
|
||||
@@ -109,9 +109,10 @@
|
||||
"LAST_EXISTING_SET": "Dies ist das letzte vorhandene Set.",
|
||||
"EMPTY_SET": "Leeres Set!",
|
||||
"LETS_ADD_OVERLAYS": "Lass uns ein paar Overlays von der Uhr hinzufügen!",
|
||||
"FIXING_FLOOR": "Boden wird in 5 Sekunden fixiert...",
|
||||
"ONE_CONTROLLER_ON_FLOOR": "Lege einen Controller auf den Boden!",
|
||||
"CANNOT_ADD_SET": "Satz kann nicht hinzugefügt werden!",
|
||||
"MAXIMUM_SETS_REACHED": "Maximale Anzahl an Sets erreicht."
|
||||
}
|
||||
"MAXIMUM_SETS_REACHED": "Maximale Anzahl an Sets erreicht.",
|
||||
"FIXING_FLOOR_IN_X_SECS": "Boden wird in {SECONDS} Sekunde(n) repariert..."
|
||||
},
|
||||
"DONE": "Erledigt"
|
||||
}
|
||||
@@ -4,41 +4,56 @@
|
||||
},
|
||||
"BAR": {
|
||||
"ADD_MIRROR": "Add a new mirror overlay",
|
||||
"EDIT_MODE_TOGGLE": "Toggle edit mode",
|
||||
"ADD_NEW_SET": "Add new set",
|
||||
"DELETE_CURRENT_SET": "Delete current set",
|
||||
"TOGGLE_VISIBILITY": "Toggle visibility",
|
||||
"RESET_POSITION": "Reset position",
|
||||
"RELOAD_FROM_DISK": "Reload XML from disk",
|
||||
"CLOSE_MIRROR": "Close mirror",
|
||||
"CLOSE_APP": "Close app",
|
||||
"CLOSE_MIRROR": "Close mirror",
|
||||
"DELETE_CURRENT_SET": "Delete current set",
|
||||
"EDIT_MODE_TOGGLE": "Toggle edit mode",
|
||||
"FORCE_CLOSE_APP": "Force close app",
|
||||
"HANDSFREE": {
|
||||
"TITLE": "Handsfree mode",
|
||||
"NONE": "Off",
|
||||
"HMD": "HMD + pinch",
|
||||
"EYE_ONLY": "Eye only",
|
||||
"EYE_TRACKING": "Eye + pinch",
|
||||
"HMD": "HMD + pinch",
|
||||
"HMD_ONLY": "HMD only",
|
||||
"EYE_ONLY": "Eye only"
|
||||
}
|
||||
"NONE": "Off",
|
||||
"TITLE": "Handsfree mode"
|
||||
},
|
||||
"RELOAD_FROM_DISK": "Reload XML from disk",
|
||||
"RESET_POSITION": "Reset position",
|
||||
"TOGGLE_VISIBILITY": "Toggle visibility"
|
||||
},
|
||||
"DEFAULT": "Default",
|
||||
"DISABLED": "Disabled",
|
||||
"DONE": "Done",
|
||||
"EDIT_MODE": {
|
||||
"ADJUST_CURVATURE": "Adjust curvature",
|
||||
"ALIGN_TO_HMD": "Align to HMD",
|
||||
"ALPHA_BLEND_MODE": "Alpha blend mode",
|
||||
"BLENDING_ADDITIVE": "Additive blending",
|
||||
"ANGLE_FADE": "Angle fade",
|
||||
"ANGLE_FADE_HELP": "Fade when not facing HMD",
|
||||
"BLENDING_ADDITIVE": "Additive blending",
|
||||
"BLOCK_INPUT": "Block game input",
|
||||
"CURVATURE": "Curvature",
|
||||
"DELETE": "Long press to remove from current set",
|
||||
"DISABLE_GRAB": "Disable grab",
|
||||
"GLOBAL": "Always visible",
|
||||
"HINT_POINT_WINDOW": "Point at a window to change its parameters.\nOnce done, leave edit mode using the button on the right.",
|
||||
"INTERPOLATION": "Interpolation",
|
||||
"ALIGN_TO_HMD": "Align to HMD",
|
||||
"KEYBOARD": "Keyboard",
|
||||
"LEAVE": "Leave edit mode",
|
||||
"LOCK_INTERACTION": "Lock interaction",
|
||||
"MOUSE": {
|
||||
"FLIP180": "Flipped 180°",
|
||||
"FLIP270": "Flipped 270°",
|
||||
"FLIP90": "Flipped 90°",
|
||||
"FLIPPED": "Flipped",
|
||||
"NORMAL": "Normal",
|
||||
"ROTATE180": "Rotated 180°",
|
||||
"ROTATE270": "Rotated 270°",
|
||||
"ROTATE90": "Rotated 90°",
|
||||
"TITLE": "Mouse fixes",
|
||||
"WRONG_SCREEN_SELECTION_HELP": "If the cursor moves on a completely different screen,\nthe screens were likely selected wrong. See readme."
|
||||
},
|
||||
"MOVE_PRESS_AND_DRAG": "Move (press & drag)",
|
||||
"OPACITY": "Opacity",
|
||||
"POS_ANCHORED": "Anchored: Moves with center marker. Default.",
|
||||
@@ -48,56 +63,42 @@
|
||||
"POS_HMD": "Follow the HMD.",
|
||||
"POS_STATIC": "Static: Not part of any set, no recenter.",
|
||||
"POSITIONING": "Positioning",
|
||||
"GLOBAL": "Always visible",
|
||||
"BLOCK_INPUT": "Block game input",
|
||||
"RESIZE_PRESS_AND_DRAG": "Resize (press & drag)",
|
||||
"STEREO_3D_MODE": {
|
||||
"ADJUST_MOUSE": "Adjust mouse",
|
||||
"FULL_FRAME": "Full 3D",
|
||||
"FULL_FRAME_BAT": "Full-BAT",
|
||||
"FULL_FRAME_SBS": "Full-SBS",
|
||||
"FULL_FRAME_TAB": "Full-TAB",
|
||||
"SPLIT_BOTTOM_TOP": "BOTTOM→TOP",
|
||||
"SPLIT_LEFT_RIGHT": "LEFT→RIGHT",
|
||||
"SPLIT_RIGHT_LEFT": "RIGHT→LEFT",
|
||||
"SPLIT_TOP_BOTTOM": "TOP→BOTTOM",
|
||||
"TITLE": "3D Stereo Mode",
|
||||
"FULL_FRAME": "Full 3D",
|
||||
"FULL_FRAME_SBS": "Full-SBS",
|
||||
"FULL_FRAME_TAB": "Full-TAB",
|
||||
"FULL_FRAME_BAT": "Full-BAT"
|
||||
},
|
||||
"MOUSE": {
|
||||
"TITLE": "Mouse fixes",
|
||||
"WRONG_SCREEN_SELECTION_HELP": "If the cursor moves on a completely different screen,\nthe screens were likely selected wrong. See readme.",
|
||||
"NORMAL": "Normal",
|
||||
"ROTATE90": "Rotated 90°",
|
||||
"ROTATE180": "Rotated 180°",
|
||||
"ROTATE270": "Rotated 270°",
|
||||
"FLIPPED": "Flipped",
|
||||
"FLIP90": "Flipped 90°",
|
||||
"FLIP180": "Flipped 180°",
|
||||
"FLIP270": "Flipped 270°"
|
||||
"TITLE": "3D Stereo Mode"
|
||||
}
|
||||
},
|
||||
"GRAB": {
|
||||
"ADJUST_DISTANCE": "Adjust distance",
|
||||
"ADJUST_SIZE": "Adjust size",
|
||||
"UNRESTRICTED_MOVEMENT": "Unrestricted movement",
|
||||
"GRABBING_WATCH": "To swap hands, move the watch in front and grab it with the other hand.",
|
||||
"GRABBING_STATIC": "This overlay is Static and will stay in place, ignoring recenter.",
|
||||
"GRABBING_ANCHORED": "Anchored overlays all move together. Separate a single window by grabbing it with the other hand while still grabbing the anchor.",
|
||||
"GRABBING_ANCHORED_EDIT": "This overlay will stay anchored to the center marker.",
|
||||
"GRABBING_FLOATING": "This overlay is Floating and will stay in place, unless recentered.",
|
||||
"GRABBING_FOLLOW": "This overlay will follow the device it is attached to."
|
||||
"GRABBING_FOLLOW": "This overlay will follow the device it is attached to.",
|
||||
"GRABBING_STATIC": "This overlay is Static and will stay in place, ignoring recenter.",
|
||||
"GRABBING_WATCH": "To swap hands, move the watch in front and grab it with the other hand.",
|
||||
"UNRESTRICTED_MOVEMENT": "Unrestricted movement"
|
||||
},
|
||||
"TOAST": {
|
||||
"DEFAULT_TITLE": "Notification",
|
||||
"ERROR": "Error",
|
||||
"CANNOT_ADD_SET": "Cannot add set!",
|
||||
"MAXIMUM_SETS_REACHED": "Maximum number of sets reached.",
|
||||
"CANNOT_REMOVE_SET": "Cannot remove set!",
|
||||
"NO_SET_SELECTED": "No set is selected.",
|
||||
"LAST_EXISTING_SET": "This is the last existing set.",
|
||||
"DEFAULT_TITLE": "Notification",
|
||||
"EMPTY_SET": "Empty set!",
|
||||
"ERROR": "Error",
|
||||
"FIXING_FLOOR_IN_X_SECS": "Fixing floor in {SECONDS} second(s)...",
|
||||
"LAST_EXISTING_SET": "This is the last existing set.",
|
||||
"LETS_ADD_OVERLAYS": "Let's add some overlays from the watch!",
|
||||
"FIXING_FLOOR": "Fixing floor in 5 seconds...",
|
||||
"MAXIMUM_SETS_REACHED": "Maximum number of sets reached.",
|
||||
"NO_SET_SELECTED": "No set is selected.",
|
||||
"ONE_CONTROLLER_ON_FLOOR": "Place one controller on the floor!"
|
||||
},
|
||||
"WATCH": {
|
||||
|
||||
@@ -109,9 +109,10 @@
|
||||
"LAST_EXISTING_SET": "Este es el último conjunto existente.",
|
||||
"EMPTY_SET": "¡Conjunto vacío!",
|
||||
"LETS_ADD_OVERLAYS": "¡Añadamos algunos overlays desde el reloj!",
|
||||
"FIXING_FLOOR": "Fijando el suelo en 5 segundos...",
|
||||
"ONE_CONTROLLER_ON_FLOOR": "¡Coloca un mando en el suelo!",
|
||||
"CANNOT_ADD_SET": "¡No se puede agregar el conjunto!",
|
||||
"MAXIMUM_SETS_REACHED": "Se ha alcanzado el número máximo de sets."
|
||||
}
|
||||
"MAXIMUM_SETS_REACHED": "Se ha alcanzado el número máximo de sets.",
|
||||
"FIXING_FLOOR_IN_X_SECS": "Corrigiendo el suelo en {SECONDS} segundo(s)..."
|
||||
},
|
||||
"DONE": "Hecho"
|
||||
}
|
||||
@@ -97,8 +97,8 @@
|
||||
"LAST_EXISTING_SET": "Questo è l'ultimo set esistente.",
|
||||
"EMPTY_SET": "Set vuoto!",
|
||||
"LETS_ADD_OVERLAYS": "Aggiungiamo degli overlay dal Watch!",
|
||||
"FIXING_FLOOR": "Rifissaggio del pavimento tra 5 secondi...",
|
||||
"ONE_CONTROLLER_ON_FLOOR": "Posiziona un controller a terra!"
|
||||
"ONE_CONTROLLER_ON_FLOOR": "Posiziona un controller a terra!",
|
||||
"FIXING_FLOOR_IN_X_SECS": "Correzione del pavimento in {SECONDS} secondo/i..."
|
||||
},
|
||||
"WATCH": {
|
||||
"ADD_NEW_SET": "Aggiungi un nuovo set",
|
||||
@@ -111,5 +111,6 @@
|
||||
"RECENTER": "Recentra l'area di gioco",
|
||||
"SWITCH_TO_SET": "Passa a set",
|
||||
"TOGGLE_FOR_CURRENT_SET": "Attiva/disattiva per il set corrente"
|
||||
}
|
||||
},
|
||||
"DONE": "Fatto"
|
||||
}
|
||||
@@ -107,9 +107,10 @@
|
||||
"LAST_EXISTING_SET": "これが最後の設定です。",
|
||||
"EMPTY_SET": "空のセットです!",
|
||||
"LETS_ADD_OVERLAYS": "ウォッチからオーバーレイを追加しましょう!",
|
||||
"FIXING_FLOOR": "5秒後にフロアを固定します...",
|
||||
"ONE_CONTROLLER_ON_FLOOR": "コントローラーを床に置いてください!",
|
||||
"CANNOT_ADD_SET": "セットを追加できません!",
|
||||
"MAXIMUM_SETS_REACHED": "最大セット数に達しました。"
|
||||
}
|
||||
"MAXIMUM_SETS_REACHED": "最大セット数に達しました。",
|
||||
"FIXING_FLOOR_IN_X_SECS": "{SECONDS}秒で床を固定中..."
|
||||
},
|
||||
"DONE": "完了"
|
||||
}
|
||||
@@ -107,9 +107,10 @@
|
||||
"LAST_EXISTING_SET": "To jest ostatni istniejący zestaw.",
|
||||
"EMPTY_SET": "Pusty zestaw!",
|
||||
"LETS_ADD_OVERLAYS": "Dodajmy kilka nakładek z zegarka!",
|
||||
"FIXING_FLOOR": "Naprawianie podłogi za 5 sekund...",
|
||||
"ONE_CONTROLLER_ON_FLOOR": "Umieść jeden kontroler na podłodze!",
|
||||
"CANNOT_ADD_SET": "Nie można dodać zestawu!",
|
||||
"MAXIMUM_SETS_REACHED": "Osiągnięto maksymalną liczbę zestawów."
|
||||
}
|
||||
"MAXIMUM_SETS_REACHED": "Osiągnięto maksymalną liczbę zestawów.",
|
||||
"FIXING_FLOOR_IN_X_SECS": "Naprawianie podłogi za {SECONDS} sekund(y)..."
|
||||
},
|
||||
"DONE": "Gotowe"
|
||||
}
|
||||
@@ -97,8 +97,8 @@
|
||||
"LAST_EXISTING_SET": "这是最后一个现有的集合。",
|
||||
"EMPTY_SET": "空集合!",
|
||||
"LETS_ADD_OVERLAYS": "让我们从手表添加一些覆盖层吧!",
|
||||
"FIXING_FLOOR": "将在 5 秒内修复地面...",
|
||||
"ONE_CONTROLLER_ON_FLOOR": "请将一个控制器放在地面上!"
|
||||
"ONE_CONTROLLER_ON_FLOOR": "请将一个控制器放在地面上!",
|
||||
"FIXING_FLOOR_IN_X_SECS": "{SECONDS}秒内固定地面..."
|
||||
},
|
||||
"WATCH": {
|
||||
"ADD_NEW_SET": "添加新集合",
|
||||
@@ -111,5 +111,6 @@
|
||||
"RECENTER": "重置游玩区中心 (Recenter)",
|
||||
"SWITCH_TO_SET": "切换到集合",
|
||||
"TOGGLE_FOR_CURRENT_SET": "切换当前集合"
|
||||
}
|
||||
},
|
||||
"DONE": "完成"
|
||||
}
|
||||
@@ -40,6 +40,7 @@ const PATH_SCROLL: &str = "/actions/default/in/Scroll";
|
||||
const PATH_SHOW_HIDE: &str = "/actions/default/in/ShowHide";
|
||||
const PATH_SPACE_DRAG: &str = "/actions/default/in/SpaceDrag";
|
||||
const PATH_SPACE_ROTATE: &str = "/actions/default/in/SpaceRotate";
|
||||
const PATH_SPACE_RESET: &str = "/actions/default/in/SpaceReset";
|
||||
const PATH_TOGGLE_DASHBOARD: &str = "/actions/default/in/ToggleDashboard";
|
||||
|
||||
const INPUT_ANY: InputValueHandle = InputValueHandle(ovr_overlay::sys::k_ulInvalidInputValueHandle);
|
||||
@@ -55,6 +56,7 @@ pub(super) struct OpenVrInputSource {
|
||||
toggle_dashboard_hnd: ActionHandle,
|
||||
space_drag_hnd: ActionHandle,
|
||||
space_rotate_hnd: ActionHandle,
|
||||
space_reset_hnd: ActionHandle,
|
||||
click_modifier_right_hnd: ActionHandle,
|
||||
click_modifier_middle_hnd: ActionHandle,
|
||||
move_mouse_hnd: ActionHandle,
|
||||
@@ -80,6 +82,7 @@ impl OpenVrInputSource {
|
||||
let toggle_dashboard_hnd = input.get_action_handle(PATH_TOGGLE_DASHBOARD)?;
|
||||
let space_drag_hnd = input.get_action_handle(PATH_SPACE_DRAG)?;
|
||||
let space_rotate_hnd = input.get_action_handle(PATH_SPACE_ROTATE)?;
|
||||
let space_reset_hnd = input.get_action_handle(PATH_SPACE_RESET)?;
|
||||
let click_modifier_right_hnd = input.get_action_handle(PATH_CLICK_MODIFIER_RIGHT)?;
|
||||
let click_modifier_middle_hnd = input.get_action_handle(PATH_CLICK_MODIFIER_MIDDLE)?;
|
||||
let move_mouse_hnd = input.get_action_handle(PATH_MOVE_MOUSE)?;
|
||||
@@ -118,6 +121,7 @@ impl OpenVrInputSource {
|
||||
toggle_dashboard_hnd,
|
||||
space_drag_hnd,
|
||||
space_rotate_hnd,
|
||||
space_reset_hnd,
|
||||
click_modifier_right_hnd,
|
||||
click_modifier_middle_hnd,
|
||||
move_mouse_hnd,
|
||||
@@ -243,6 +247,11 @@ impl OpenVrInputSource {
|
||||
.map(|x| x.0.bState)
|
||||
.unwrap_or(false);
|
||||
|
||||
app_hand.now.space_reset = input
|
||||
.get_digital_action_data(self.space_reset_hnd, hand.input_hnd)
|
||||
.map(|x| x.0.bState)
|
||||
.unwrap_or(false);
|
||||
|
||||
app_hand.now.click_modifier_right = input
|
||||
.get_digital_action_data(self.click_modifier_right_hnd, hand.input_hnd)
|
||||
.map(|x| x.0.bState)
|
||||
|
||||
@@ -178,6 +178,14 @@ impl PlayspaceMover {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (_, pointer) in app.input_state.pointers.iter().enumerate() {
|
||||
if pointer.now.space_reset && !pointer.before.space_reset {
|
||||
self.reset_offset(chaperone_mgr, &app.input_state);
|
||||
log::info!("Space reset");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset_offset(&mut self, chaperone_mgr: &mut ChaperoneSetupManager, input: &InputState) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use libmonado::{BlockFlags, ClientLogic, ClientState, Monado, Version};
|
||||
use log::{trace, warn};
|
||||
use log::trace;
|
||||
use wgui::log::LogErr;
|
||||
|
||||
use crate::state::AppState;
|
||||
|
||||
@@ -18,6 +19,10 @@ impl InputBlocker {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unblock(&self, monado: &mut Monado) {
|
||||
self.block_inputs(monado, false, false);
|
||||
}
|
||||
|
||||
pub fn update(&mut self, app: &mut AppState) {
|
||||
let Some(monado) = &mut app.monado else {
|
||||
return; // monado not available
|
||||
@@ -53,47 +58,38 @@ impl InputBlocker {
|
||||
}
|
||||
|
||||
fn block_inputs(&self, monado: &mut Monado, block_inputs: bool, block_poses: bool) {
|
||||
match monado.clients() {
|
||||
Ok(clients) => {
|
||||
for mut client in clients {
|
||||
match client.name() {
|
||||
Ok(n) => {
|
||||
if n == "wayvr" {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Failed to get client name: {e}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let Ok(clients) = monado
|
||||
.clients()
|
||||
.log_warn("Failed to get clients from Monado")
|
||||
else {
|
||||
return;
|
||||
};
|
||||
for mut client in clients {
|
||||
let Ok(name) = client.name().log_warn("Failed to get client name") else {
|
||||
continue;
|
||||
};
|
||||
if name == "wayvr" {
|
||||
continue;
|
||||
}
|
||||
|
||||
let Ok(state) = client.state().log_warn("Failed to get client state") else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let state = match client.state() {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
warn!("Failed to get client state: {e}");
|
||||
continue;
|
||||
}
|
||||
if state.contains(ClientState::ClientSessionActive | ClientState::ClientSessionVisible)
|
||||
{
|
||||
let _ = if self.use_io_blocks {
|
||||
let flags = match (block_inputs, block_poses) {
|
||||
(true, true) => BlockFlags::BlockPoses | BlockFlags::BlockInputs,
|
||||
(true, false) => BlockFlags::BlockInputs.into(),
|
||||
(false, _) => BlockFlags::None.into(),
|
||||
};
|
||||
|
||||
if state.contains(ClientState::ClientSessionVisible) {
|
||||
let r = if self.use_io_blocks {
|
||||
let flags = match (block_inputs, block_poses) {
|
||||
(true, true) => BlockFlags::BlockPoses | BlockFlags::BlockInputs,
|
||||
(true, false) => BlockFlags::BlockInputs.into(),
|
||||
(false, _) => BlockFlags::None.into(),
|
||||
};
|
||||
client.set_io_blocks(flags)
|
||||
} else {
|
||||
client.set_io_active(!block_inputs)
|
||||
};
|
||||
if let Err(e) = r {
|
||||
warn!("Failed to set io active for client: {e}");
|
||||
}
|
||||
}
|
||||
client.set_io_blocks(flags)
|
||||
} else {
|
||||
client.set_io_active(!block_inputs)
|
||||
}
|
||||
.log_warn("Failed to set IO active for client");
|
||||
}
|
||||
Err(e) => warn!("Failed to get clients from Monado: {e}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,18 @@ use glam::{Affine3A, Quat, Vec3, Vec3A};
|
||||
use openxr::{self as xr, SessionCreateFlags, Version, sys::Handle};
|
||||
use xr::OverlaySessionCreateFlagsEXTX;
|
||||
|
||||
macro_rules! next_chain_insert {
|
||||
($layer:expr, $payload:expr) => {{
|
||||
let payload_ptr = $payload.as_mut() as *mut _ as *mut xr::sys::BaseInStructure;
|
||||
let new_elem = payload_ptr.as_mut().unwrap();
|
||||
let mut raw = $layer.into_raw();
|
||||
new_elem.next = raw.next as _;
|
||||
raw.next = payload_ptr as *const _;
|
||||
raw
|
||||
}};
|
||||
}
|
||||
pub(crate) use next_chain_insert;
|
||||
|
||||
pub(super) fn init_xr() -> Result<(xr::Instance, xr::SystemId), anyhow::Error> {
|
||||
let entry = xr::Entry::linked();
|
||||
|
||||
|
||||
@@ -499,6 +499,10 @@ pub fn openxr_run(show_by_default: bool, headless: bool) -> Result<(), BackendEr
|
||||
watch.config.active_state.as_mut().unwrap().transform = watch_transform;
|
||||
} // main_loop
|
||||
|
||||
if let (Some(blocker), Some(monado)) = (blocker, app.monado.as_mut()) {
|
||||
blocker.unblock(monado);
|
||||
}
|
||||
|
||||
overlays.persist_layout(&mut app);
|
||||
if let Err(e) = save_state(&app.session.config) {
|
||||
log::error!("Could not save state: {e:?}");
|
||||
|
||||
@@ -214,8 +214,11 @@
|
||||
left: "/user/hand/left/input/trackpad/x",
|
||||
right: "/user/hand/right/input/trackpad/x"
|
||||
},
|
||||
toggle_dashboard: {
|
||||
right: "/user/hand/right/input/system/click",
|
||||
},
|
||||
show_hide: {
|
||||
left: "/user/hand/left/input/menu/click",
|
||||
left: "/user/hand/left/input/system/click",
|
||||
},
|
||||
space_drag: {
|
||||
right: "/user/hand/right/input/menu/click",
|
||||
@@ -258,11 +261,11 @@
|
||||
right: "/user/hand/right/input/thumbstick/x"
|
||||
},
|
||||
show_hide: {
|
||||
left: "/user/hand/left/input/menu/click",
|
||||
left: "/user/hand/left/input/system/click",
|
||||
},
|
||||
space_drag: {
|
||||
double_click: true,
|
||||
right: "/user/hand/left/input/menu/click",
|
||||
right: "/user/hand/left/input/system/click",
|
||||
},
|
||||
click_modifier_right: {
|
||||
left: "/user/hand/left/input/trackpad/dpad_up",
|
||||
@@ -302,11 +305,11 @@
|
||||
right: "/user/hand/right/input/thumbstick/x"
|
||||
},
|
||||
show_hide: {
|
||||
left: "/user/hand/left/input/menu/click",
|
||||
left: "/user/hand/left/input/system/click",
|
||||
},
|
||||
space_drag: {
|
||||
double_click: true,
|
||||
right: "/user/hand/left/input/menu/click",
|
||||
right: "/user/hand/left/input/system/click",
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
@@ -6,7 +6,10 @@ use xr::EyeVisibility;
|
||||
|
||||
use super::{CompositionLayer, XrState, helpers, swapchain::WlxSwapchain};
|
||||
use crate::{
|
||||
backend::openxr::swapchain::{SwapchainOpts, WlxSwapchainImage, create_swapchain},
|
||||
backend::openxr::{
|
||||
helpers::next_chain_insert,
|
||||
swapchain::{SwapchainOpts, WlxSwapchainImage, create_swapchain},
|
||||
},
|
||||
state::AppState,
|
||||
windowing::window::OverlayWindowData,
|
||||
};
|
||||
@@ -20,17 +23,6 @@ pub struct OpenXrOverlayData {
|
||||
color_bias_khr: Option<Box<xr::sys::CompositionLayerColorScaleBiasKHR>>,
|
||||
}
|
||||
|
||||
macro_rules! next_chain_insert {
|
||||
($layer:expr, $payload:expr) => {{
|
||||
let payload_ptr = $payload.as_mut() as *mut _ as *mut xr::sys::BaseInStructure;
|
||||
let new_elem = payload_ptr.as_mut().unwrap();
|
||||
let mut raw = $layer.into_raw();
|
||||
new_elem.next = raw.next as _;
|
||||
raw.next = payload_ptr as *const _;
|
||||
raw
|
||||
}};
|
||||
}
|
||||
|
||||
impl OverlayWindowData<OpenXrOverlayData> {
|
||||
pub(super) fn ensure_swapchain_acquire<'a>(
|
||||
&'a mut self,
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
use std::{
|
||||
f32::consts::PI,
|
||||
fs::File,
|
||||
sync::{Arc, LazyLock},
|
||||
};
|
||||
use std::{f32::consts::PI, fs::File, sync::Arc};
|
||||
|
||||
use glam::{Quat, Vec3A};
|
||||
use openxr as xr;
|
||||
@@ -14,7 +10,10 @@ use wgui::gfx::{cmd::WGfxClearMode, pipeline::WPipelineCreateInfo};
|
||||
use wlx_common::config_io;
|
||||
|
||||
use crate::{
|
||||
backend::openxr::{helpers::translation_rotation_to_posef, swapchain::SwapchainOpts},
|
||||
backend::openxr::{
|
||||
helpers::{next_chain_insert, translation_rotation_to_posef},
|
||||
swapchain::SwapchainOpts,
|
||||
},
|
||||
graphics::{ExtentExt, GpuFutures, dds::WlxCommandBufferDds},
|
||||
state::AppState,
|
||||
};
|
||||
@@ -28,10 +27,12 @@ pub(super) struct Skybox {
|
||||
view: Arc<ImageView>,
|
||||
sky: Option<WlxSwapchain>,
|
||||
grid: Option<WlxSwapchain>,
|
||||
grid_pose: xr::Posef,
|
||||
grid_color_scale_bias_khr: Option<Box<xr::sys::CompositionLayerColorScaleBiasKHR>>,
|
||||
}
|
||||
|
||||
impl Skybox {
|
||||
pub fn new(app: &AppState) -> anyhow::Result<Self> {
|
||||
pub fn new(xr: &XrState, app: &AppState) -> anyhow::Result<Self> {
|
||||
let mut command_buffer = app
|
||||
.gfx
|
||||
.create_xfer_command_buffer(CommandBufferUsage::OneTimeSubmit)?;
|
||||
@@ -74,10 +75,30 @@ impl Skybox {
|
||||
|
||||
let view = ImageView::new_default(maybe_image.unwrap())?; // safe unwrap
|
||||
|
||||
let grid_color_scale_bias_khr = xr
|
||||
.instance
|
||||
.exts()
|
||||
.khr_composition_layer_color_scale_bias
|
||||
.map(|_| {
|
||||
Box::new(xr::sys::CompositionLayerColorScaleBiasKHR {
|
||||
ty: xr::StructureType::COMPOSITION_LAYER_COLOR_SCALE_BIAS_KHR,
|
||||
next: std::ptr::null(),
|
||||
color_bias: Default::default(),
|
||||
color_scale: xr::Color4f {
|
||||
r: 1.0,
|
||||
g: 1.0,
|
||||
b: 1.0,
|
||||
a: 1.0,
|
||||
},
|
||||
})
|
||||
});
|
||||
|
||||
Ok(Self {
|
||||
view,
|
||||
sky: None,
|
||||
grid: None,
|
||||
grid_pose: translation_rotation_to_posef(Vec3A::ZERO, Quat::from_rotation_x(PI * -0.5)),
|
||||
grid_color_scale_bias_khr: grid_color_scale_bias_khr,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -205,10 +226,6 @@ impl Skybox {
|
||||
const HI_VERT_ANGLE: f32 = 0.5 * PI;
|
||||
const LO_VERT_ANGLE: f32 = -0.5 * PI;
|
||||
|
||||
static GRID_POSE: LazyLock<xr::Posef> = LazyLock::new(|| {
|
||||
translation_rotation_to_posef(Vec3A::ZERO, Quat::from_rotation_x(PI * -0.5))
|
||||
});
|
||||
|
||||
let pose = xr::Posef {
|
||||
orientation: xr::Quaternionf::IDENTITY,
|
||||
position: xr::Vector3f {
|
||||
@@ -232,9 +249,12 @@ impl Skybox {
|
||||
.lower_vertical_angle(LO_VERT_ANGLE);
|
||||
|
||||
self.grid.as_mut().unwrap().ensure_image_released()?;
|
||||
let grid = xr::CompositionLayerQuad::new()
|
||||
.layer_flags(xr::CompositionLayerFlags::BLEND_TEXTURE_SOURCE_ALPHA)
|
||||
.pose(*GRID_POSE)
|
||||
|
||||
let flags = xr::CompositionLayerFlags::BLEND_TEXTURE_SOURCE_ALPHA
|
||||
| xr::CompositionLayerFlags::UNPREMULTIPLIED_ALPHA;
|
||||
let mut grid = xr::CompositionLayerQuad::new()
|
||||
.layer_flags(flags)
|
||||
.pose(self.grid_pose)
|
||||
.size(xr::Extent2Df {
|
||||
width: 10.0,
|
||||
height: 10.0,
|
||||
@@ -243,6 +263,15 @@ impl Skybox {
|
||||
.eye_visibility(xr::EyeVisibility::BOTH)
|
||||
.space(&xr.stage);
|
||||
|
||||
if let Some(grid_color_scale_bias_khr) = self.grid_color_scale_bias_khr.as_mut() {
|
||||
let grid_opacity = app.session.config.grid_opacity;
|
||||
grid_color_scale_bias_khr.color_scale.a = (grid_opacity * grid_opacity).clamp(0.0, 1.0);
|
||||
unsafe {
|
||||
let raw = next_chain_insert!(grid, grid_color_scale_bias_khr);
|
||||
grid = xr::CompositionLayerQuad::from_raw(raw);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(vec like `%H:%M:%S`.
|
||||
|
||||
See the Custom Timezones section for more info on timezones. Skip `_timezone` to use local time.
|
||||
|
||||
```xml
|
||||
<label _source="clock" _display="time" _timezone="0" [...] />
|
||||
```
|
||||
|
||||
#### Timer label
|
||||
|
||||
Instead of a clock, this label shows the amount of time since program start, (aka time in VR).
|
||||
|
||||
Use `_format` to arrange `%h` hours, `%m` minutes, and `%s` seconds.
|
||||
|
||||
```xml
|
||||
<label _source="timer" _format="%h:%m:%s" [...] />
|
||||
```
|
||||
|
||||
#### Battery label
|
||||
|
||||
This is a label type that's used internally to display battery states.
|
||||
|
||||
```xml
|
||||
<label _source="battery" _device="0" [...] />
|
||||
```
|
||||
|
||||
#### IPD
|
||||
|
||||
Displays IPD value in millimeters. Not parametrizable.
|
||||
|
||||
Format: `ipd`
|
||||
|
||||
```xml
|
||||
<label _source="ipd" [...] />
|
||||
```
|
||||
|
||||
### Buttons
|
||||
|
||||
Buttons consist of a label component and one or more actions to handle press and/or release events.
|
||||
|
||||
Supported events:
|
||||
|
||||
```xml
|
||||
<button _press="..." _release="..." />
|
||||
```
|
||||
|
||||
Laser-color-specific variants are also available
|
||||
- `_press_left` & `_release_left` for blue laser
|
||||
- `_press_right` & `_release_right` for orange laser
|
||||
- `_press_middle` & `_release_middle` for purple laser
|
||||
|
||||
Release after short/long press (length controlled by config `long_press_duration`)
|
||||
- `_short_release` & `_long_release` for any laser
|
||||
- `_short_release_left` & `_long_release_left` for blue laser
|
||||
- `_short_release_right` & `_long_release_right` for orange laser
|
||||
- `_short_release_middle` & `_long_release_middle` for purple laser
|
||||
|
||||
#### Supported button actions
|
||||
|
||||
##### `::ShellExec <command> [args ..]`
|
||||
|
||||
This button action executes a shell script using the `sh` shell.
|
||||
|
||||
- Long-running processes are allowed, but a new execution will not be triggered until the previous process has exited.
|
||||
- If triggered again while the previous process is still running, SIGUSR1 will be sent to that child process.
|
||||
|
||||
```xml
|
||||
<button _press="::ShellExec $HOME/myscript.sh test-argument" [...] />
|
||||
```
|
||||
|
||||
##### `::OscSend <path>` `::OscSend <path> <args ..>`
|
||||
|
||||
Send an OSC message. The target port comes from the `osc_out_port` configuration setting.
|
||||
|
||||
There are two formats; here is an example for both formats writing a message to the VRChat Chatbox:
|
||||
```xml
|
||||
<!-- parameter form - OSC arguments are listed as parameters labelled `_arg<n>` where `n` is 0-indexed -->
|
||||
<Button _press="::OscSend /chatbox/input" _arg0="Hello World! I am WayVR." _arg1="true" _arg2="true"> </Button>
|
||||
<!-- will send: ("Hello World! I am WayVR.", True, True) to /chatbox/input -->
|
||||
```
|
||||
|
||||
```xml
|
||||
<!-- shorthand form - OSC arguments are space-separated in one string. note that single strings cannot contain spaces -->
|
||||
<Button _press="::OscSend /chatbox/input Hello_World!_I_am_WayVR. true true"> </Button>
|
||||
<!-- will send: ("Hello_World!_I_am_WayVR.", True, True) to /chatbox/input -->
|
||||
```
|
||||
|
||||
The two can be combined; parameter-form arguments will be appended after shorthand-form arguments:
|
||||
```xml
|
||||
<!-- combined-form - rectangle bounds with a name -->
|
||||
<Button _press="::OscSend /graphthing/rectangle 0i32 0i32 50i32 200i32" _arg0="tall rectangle"> </Button>
|
||||
<!-- will send: (0, 0, 50, 200, "tall rectangle") to /graphthing/rectangle -->
|
||||
```
|
||||
|
||||
Available argument value types (case insensitive):
|
||||
- Bool: `true` or `false`
|
||||
- Nil: `nil`
|
||||
- Inf: `inf`
|
||||
- Int: suffix `i32` (`-1i32`, `1i32`, etc)
|
||||
- Long: suffix `i64` (`-1i64`, `1i64`, etc)
|
||||
- Float: suffix `f32` (`1f32`, `1.0f32`, etc)
|
||||
- Double: suffix `f64` (`1f64`, `1.0f64`, etc)
|
||||
- String: any other value
|
||||
- Shorthand form will treat Strings with spaces as multiple arguments. Use parameter form if you need spaces.
|
||||
|
||||
##### `::SendKey <VirtualKey> <UP|DOWN>`
|
||||
|
||||
Sends a key using the virtual keyboard. If WayVR is focused, the key is sent to the WayVR app.
|
||||
|
||||
Supported VirtualKey values are listed [here](https://github.com/galister/wlx-overlay-s/blob/f2bd169c2217d51cd2de862a6429444bf326f471/wlx-overlay-s/src/subsystem/hid/mod.rs#L336).
|
||||
|
||||
##### `::PlayspaceReset`
|
||||
|
||||
Resets the STAGE space to (0,0,0) with identity rotation.
|
||||
|
||||
##### `::PlayspaceRecenter`
|
||||
|
||||
Recenters the STAGE space position so that the HMD is in the center. Does not modify floor level.
|
||||
|
||||
##### `::PlayspaceFixFloor`
|
||||
|
||||
Adjusts the level of floor for STAGE and LOCAL_FLOOR spaces.
|
||||
|
||||
The user is asked to place one controller on the floor.
|
||||
|
||||
##### `::ContextMenuOpen <name>`
|
||||
|
||||
Opens the `<context_menu>` of the given name at the location of the click.
|
||||
|
||||
##### `::ContextMenuClose`
|
||||
|
||||
Closes any active context menus.
|
||||
|
||||
##### `::ElementSetDisplay <id> <none|flex|block|grid>`
|
||||
|
||||
Sets the visiblitity of the element with `id`.
|
||||
|
||||
##### `::SetToggle <index>`
|
||||
|
||||
If the given set is visible, it will be hidden.
|
||||
|
||||
Otherwise, it will be set visible.
|
||||
|
||||
##### `::SetSwitch <index>`
|
||||
|
||||
Switch to the given set. If the set is already visible, nothing happens.
|
||||
|
||||
##### `::AddSet`
|
||||
|
||||
Add a new set and switch to it. The keyboard will be set to visible.
|
||||
|
||||
##### `::DeleteSet`
|
||||
|
||||
Hides the current set, then deletes it.
|
||||
|
||||
##### `::DashToggle`
|
||||
|
||||
Toggle the dashboard
|
||||
|
||||
##### `::EditToggle`
|
||||
|
||||
Toggle edit mode
|
||||
|
||||
##### `::NewMirror`
|
||||
|
||||
Opens a new PipeWire mirror (Wayland-only)
|
||||
|
||||
##### `::CleanupMirrors`
|
||||
|
||||
Destroys all mirrors that are not currently visible (including those that are in a different set).
|
||||
|
||||
##### `::Restart`
|
||||
|
||||
Restarts WayVR, reloading all settings.
|
||||
|
||||
##### `::ShutDown`
|
||||
|
||||
Gracefully shuts down WayVR.
|
||||
|
||||
##### `::OverlayReset <overlay_name>`
|
||||
|
||||
Resets the position of the given overlay and makes it visible.
|
||||
|
||||
##### `::OverlayToggle <overlay_name>`
|
||||
|
||||
Toggle the visibility of this overlay for the current set
|
||||
|
||||
##### `::OverlayDrop <overlay_name>`
|
||||
|
||||
Destroys the overlay permanently. Mostly useful for mirrors.
|
||||
|
||||
##### `::CustomOverlayReload <overlay_name>`
|
||||
|
||||
If this is a custom overlay, reloads its XML from disk.
|
||||
|
||||
##### `::WvrOverlayCloseWindow <overlay_name>`
|
||||
|
||||
If this is an application, send a close window request to its wl_surface (e.a. X'ing the window)
|
||||
|
||||
##### `::WvrOverlayTermProcess <overlay_name>`
|
||||
|
||||
If the overlay belongs to an application, sends SIGTERM (graceful exit request) to its process.
|
||||
|
||||
##### `::WvrOverlayKillProcess <overlay_name>`
|
||||
|
||||
If the overlay belongs to an application, sends SIGKILL (forced exit request) to its process.
|
||||
|
||||
Also destroys all overlays belonging to the process.
|
||||
|
||||
@@ -577,19 +577,37 @@ pub(super) fn setup_custom_button<S: 'static>(
|
||||
return Ok(EventResult::Pass);
|
||||
}
|
||||
|
||||
Toast::new(
|
||||
ToastTopic::System,
|
||||
"TOAST.FIXING_FLOOR".into(),
|
||||
"TOAST.ONE_CONTROLLER_ON_FLOOR".into(),
|
||||
)
|
||||
.with_timeout(5.)
|
||||
.with_sound(true)
|
||||
.submit(app);
|
||||
let globals = app.wgui_globals.clone();
|
||||
|
||||
app.tasks.enqueue_at(
|
||||
TaskType::Playspace(PlayspaceTask::FixFloor),
|
||||
Instant::now() + Duration::from_secs(5),
|
||||
);
|
||||
let duration_secs = 5;
|
||||
let now = Instant::now();
|
||||
|
||||
for i in 0..duration_secs {
|
||||
Toast::new(
|
||||
ToastTopic::System,
|
||||
globals.i18n().translate_and_replace(
|
||||
"TOAST.FIXING_FLOOR_IN_X_SECS",
|
||||
("{SECONDS}", &format!("{}", duration_secs - i)),
|
||||
),
|
||||
"TOAST.ONE_CONTROLLER_ON_FLOOR".into(),
|
||||
)
|
||||
.with_timeout(1.0)
|
||||
.with_lerp_amount(1.0)
|
||||
.with_sound(i == 0)
|
||||
.submit_at(app, now + Duration::from_secs(i as _));
|
||||
}
|
||||
|
||||
app.audio_sample_player
|
||||
.play_sample(&mut app.audio_system, "fix_floor");
|
||||
|
||||
let deadline = now + Duration::from_secs(duration_secs);
|
||||
|
||||
app.tasks
|
||||
.enqueue_at(TaskType::Playspace(PlayspaceTask::FixFloor), deadline);
|
||||
|
||||
Toast::new(ToastTopic::System, "DONE".into(), String::new())
|
||||
.with_timeout(2.0)
|
||||
.submit_at(app, deadline);
|
||||
Ok(EventResult::Consumed)
|
||||
}),
|
||||
"::Shutdown" => Box::new(move |_common, data, app, _| {
|
||||
|
||||
@@ -130,6 +130,14 @@ pub(super) fn setup_custom_label<S: 'static>(
|
||||
.ok()
|
||||
}),
|
||||
format: format.into(),
|
||||
locale: {
|
||||
let i18n = app.wgui_globals.i18n();
|
||||
let locale = i18n.get_locale();
|
||||
match pure_rust_locales::Locale::try_from(locale.to_string().as_str()) {
|
||||
Ok(loc) => loc,
|
||||
Err(_) => pure_rust_locales::Locale::en_US,
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
Box::new(move |common, data, _, _| {
|
||||
@@ -213,6 +221,7 @@ fn battery_on_tick(
|
||||
struct ClockLabelState {
|
||||
timezone: Option<Tz>,
|
||||
format: Rc<str>,
|
||||
locale: chrono::Locale,
|
||||
}
|
||||
|
||||
fn clock_on_tick(
|
||||
@@ -221,8 +230,20 @@ fn clock_on_tick(
|
||||
data: &mut event::CallbackData,
|
||||
) {
|
||||
let date_time = state.timezone.as_ref().map_or_else(
|
||||
|| format!("{}", Local::now().format(&state.format)),
|
||||
|tz| format!("{}", Local::now().with_timezone(tz).format(&state.format)),
|
||||
|| {
|
||||
format!(
|
||||
"{}",
|
||||
Local::now().format_localized(&state.format, state.locale)
|
||||
)
|
||||
},
|
||||
|tz| {
|
||||
format!(
|
||||
"{}",
|
||||
Local::now()
|
||||
.with_timezone(tz)
|
||||
.format_localized(&state.format, state.locale)
|
||||
)
|
||||
},
|
||||
);
|
||||
|
||||
let label = data.obj.get_as_mut::<WidgetLabel>().unwrap();
|
||||
|
||||
@@ -14,8 +14,8 @@ use wgui::{
|
||||
drawing,
|
||||
event::{
|
||||
CallbackDataCommon, Event as WguiEvent, EventAlterables, EventCallback, EventListenerID,
|
||||
EventListenerKind, InternalStateChangeEvent, MouseButtonIndex, MouseDownEvent,
|
||||
MouseLeaveEvent, MouseMotionEvent, MouseUpEvent, MouseWheelEvent,
|
||||
EventListenerKind, InternalStateChangeEvent, MouseButtonEvent, MouseButtonIndex,
|
||||
MouseLeaveEvent, MouseMotionEvent, MouseWheelEvent,
|
||||
},
|
||||
gfx::cmd::WGfxClearMode,
|
||||
i18n::Translation,
|
||||
@@ -147,8 +147,9 @@ impl<S: 'static> GuiPanel<S> {
|
||||
|
||||
let (mut layout, mut parser_state) = wgui::parser::new_layout_from_assets(
|
||||
&doc_params,
|
||||
&LayoutParams {
|
||||
LayoutParams {
|
||||
resize_to_parent: params.resize_to_parent,
|
||||
theme: app.wgui_theme.clone(),
|
||||
},
|
||||
)?;
|
||||
|
||||
@@ -416,13 +417,13 @@ impl<S: 'static> OverlayBackend for GuiPanel<S> {
|
||||
};
|
||||
|
||||
let e = if pressed {
|
||||
WguiEvent::MouseDown(MouseDownEvent {
|
||||
WguiEvent::MouseDown(MouseButtonEvent {
|
||||
pos: hit.uv * self.layout.content_size,
|
||||
index,
|
||||
device: hit.pointer,
|
||||
})
|
||||
} else {
|
||||
WguiEvent::MouseUp(MouseUpEvent {
|
||||
WguiEvent::MouseUp(MouseButtonEvent {
|
||||
pos: hit.uv * self.layout.content_size,
|
||||
index,
|
||||
device: hit.pointer,
|
||||
|
||||
+1
-1
@@ -192,7 +192,7 @@ fn auto_run(args: Args, used_backend: &mut Option<XrBackend>) {
|
||||
|
||||
let instructions = format!("Could not connect to runtime.\n{instructions}");
|
||||
|
||||
let _ = DbusConnector::notify_send("WayVR", &instructions, 1, 0, 0, false);
|
||||
let _ = DbusConnector::notify_send("WayVR", &instructions, 1, 30000, 0, false);
|
||||
|
||||
#[cfg(not(any(feature = "openvr", feature = "openxr")))]
|
||||
compile_error!("No VR support! Enable either openvr or openxr features!");
|
||||
|
||||
@@ -8,8 +8,8 @@ use wayvr_ipc::{
|
||||
};
|
||||
use wgui::{
|
||||
event::{
|
||||
Event as WguiEvent, MouseButtonIndex, MouseDownEvent, MouseLeaveEvent, MouseMotionEvent,
|
||||
MouseUpEvent, MouseWheelEvent,
|
||||
Event as WguiEvent, MouseButtonEvent, MouseButtonIndex, MouseLeaveEvent, MouseMotionEvent,
|
||||
MouseWheelEvent,
|
||||
},
|
||||
gfx::cmd::WGfxClearMode,
|
||||
renderer_vk::context::Context as WguiContext,
|
||||
@@ -17,6 +17,7 @@ use wgui::{
|
||||
};
|
||||
use wlx_common::{
|
||||
dash_interface::{self, DashInterface, RecenterMode},
|
||||
locale::WayVRLangProvider,
|
||||
overlays::{BackendAttrib, BackendAttribValue},
|
||||
};
|
||||
use wlx_common::{
|
||||
@@ -73,16 +74,15 @@ impl DashFrontend {
|
||||
let mut interface = DashInterfaceLive::new();
|
||||
|
||||
for p in app.session.config.autostart_apps.clone() {
|
||||
let _ = interface.process_launch(app, false, p)?;
|
||||
let _ = interface.process_launch(app, false, p);
|
||||
}
|
||||
|
||||
let frontend = frontend::Frontend::new(
|
||||
frontend::InitParams {
|
||||
interface: Box::new(interface),
|
||||
has_monado: matches!(app.xr_backend, XrBackend::OpenXR),
|
||||
},
|
||||
app,
|
||||
)?;
|
||||
let frontend = frontend::Frontend::new(frontend::InitParams {
|
||||
interface: Box::new(interface),
|
||||
lang_provider: &WayVRLangProvider::from_config(&app.session.config),
|
||||
has_monado: matches!(app.xr_backend, XrBackend::OpenXR),
|
||||
theme: app.wgui_theme.clone(),
|
||||
})?;
|
||||
|
||||
frontend
|
||||
.tasks
|
||||
@@ -245,13 +245,13 @@ impl OverlayBackend for DashFrontend {
|
||||
};
|
||||
|
||||
let e = if pressed {
|
||||
WguiEvent::MouseDown(MouseDownEvent {
|
||||
WguiEvent::MouseDown(MouseButtonEvent {
|
||||
pos: hit.uv * self.inner.layout.content_size,
|
||||
index,
|
||||
device: hit.pointer,
|
||||
})
|
||||
} else {
|
||||
WguiEvent::MouseUp(MouseUpEvent {
|
||||
WguiEvent::MouseUp(MouseButtonEvent {
|
||||
pos: hit.uv * self.inner.layout.content_size,
|
||||
index,
|
||||
device: hit.pointer,
|
||||
|
||||
@@ -11,7 +11,7 @@ use wgui::{
|
||||
widget::rectangle::WidgetRectangle,
|
||||
};
|
||||
|
||||
use crate::{backend::task::ModifyOverlayTask, overlays::edit::EditModeWrapPanel, state::AppState};
|
||||
use crate::{backend::task::ModifyOverlayTask, overlays::edit::EditModeWrapPanel};
|
||||
|
||||
#[derive(Default)]
|
||||
pub(super) struct InteractLockHandler {
|
||||
@@ -53,21 +53,18 @@ impl InteractLockHandler {
|
||||
button.set_sticky_state(common, !interactable);
|
||||
}
|
||||
|
||||
let globals = common.state.globals.get();
|
||||
if interactable {
|
||||
set_anim_color(&mut rect, 0.0, self.color, globals.defaults.danger_color);
|
||||
set_anim_color(&mut rect, 0.0, self.color, common.state.theme.danger_color);
|
||||
} else {
|
||||
set_anim_color(&mut rect, 0.2, self.color, globals.defaults.danger_color);
|
||||
set_anim_color(&mut rect, 0.2, self.color, common.state.theme.danger_color);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toggle(
|
||||
&mut self,
|
||||
common: &mut CallbackDataCommon,
|
||||
app: &mut AppState,
|
||||
anim_mult: f32,
|
||||
) -> Box<ModifyOverlayTask> {
|
||||
let defaults = app.wgui_globals.defaults().clone();
|
||||
let rect_color = self.color;
|
||||
|
||||
self.interactable = !self.interactable;
|
||||
@@ -86,7 +83,7 @@ impl InteractLockHandler {
|
||||
rect,
|
||||
0.2 - (data.pos * 0.2),
|
||||
rect_color,
|
||||
defaults.danger_color,
|
||||
common.state.theme.danger_color,
|
||||
);
|
||||
common.alterables.mark_redraw();
|
||||
}),
|
||||
@@ -98,7 +95,12 @@ impl InteractLockHandler {
|
||||
AnimationEasing::OutBack,
|
||||
Box::new(move |common, data| {
|
||||
let rect = data.obj.get_as_mut::<WidgetRectangle>().unwrap();
|
||||
set_anim_color(rect, data.pos * 0.2, rect_color, defaults.danger_color);
|
||||
set_anim_color(
|
||||
rect,
|
||||
data.pos * 0.2,
|
||||
rect_color,
|
||||
common.state.theme.danger_color,
|
||||
);
|
||||
common.alterables.mark_redraw();
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -272,7 +272,7 @@ fn make_edit_panel(app: &mut AppState) -> anyhow::Result<EditModeWrapPanel> {
|
||||
mouse: SpriteTabHandler::default(),
|
||||
};
|
||||
|
||||
let anim_mult = app.wgui_globals.defaults().animation_mult;
|
||||
let anim_mult = app.wgui_theme.animation_mult;
|
||||
|
||||
let on_custom_attrib: OnCustomAttribFunc = Box::new(move |layout, parser, attribs, _app| {
|
||||
let Ok(button) =
|
||||
@@ -302,7 +302,7 @@ fn make_edit_panel(app: &mut AppState) -> anyhow::Result<EditModeWrapPanel> {
|
||||
}
|
||||
|
||||
let sel = OverlaySelector::Id(*state.id.borrow());
|
||||
let task = state.lock.toggle(common, app, anim_mult);
|
||||
let task = state.lock.toggle(common, anim_mult);
|
||||
app.tasks
|
||||
.enqueue(TaskType::Overlay(OverlayTask::Modify(sel, task)));
|
||||
Ok(EventResult::Consumed)
|
||||
|
||||
@@ -53,11 +53,9 @@ pub(super) fn create_keyboard_panel(
|
||||
|
||||
let doc_params = new_doc_params(&mut panel);
|
||||
|
||||
let globals = app.wgui_globals.clone();
|
||||
|
||||
let (accent_color, anim_mult) = {
|
||||
let def = globals.defaults();
|
||||
(def.accent_color, def.animation_mult)
|
||||
let theme = &app.wgui_theme;
|
||||
(theme.accent_color, theme.animation_mult)
|
||||
};
|
||||
|
||||
let root = panel
|
||||
|
||||
@@ -32,7 +32,7 @@ use regex::Regex;
|
||||
use slotmap::{SlotMap, new_key_type};
|
||||
use wgui::{
|
||||
drawing,
|
||||
event::{InternalStateChangeEvent, MouseButton, MouseButtonIndex},
|
||||
event::{InternalStateChangeEvent, MouseButtonEvent, MouseButtonIndex},
|
||||
};
|
||||
use wlx_common::windowing::{OverlayWindowState, Positioning};
|
||||
use wlx_common::{
|
||||
@@ -390,7 +390,7 @@ fn handle_press(
|
||||
app: &mut AppState,
|
||||
key: &KeyState,
|
||||
keyboard: &mut KeyboardState,
|
||||
button: MouseButton,
|
||||
button: MouseButtonEvent,
|
||||
) {
|
||||
match &key.button_state {
|
||||
KeyButtonData::Key { vk, pressed } => {
|
||||
|
||||
@@ -95,7 +95,8 @@ pub(super) fn select_pw_screen(
|
||||
task::Poll::Pending => {
|
||||
if Instant::now() >= print_at {
|
||||
log::info!("{instructions}");
|
||||
if let Ok(id) = DbusConnector::notify_send(instructions, "", 1, 60, 0, true)
|
||||
if let Ok(id) =
|
||||
DbusConnector::notify_send(instructions, "", 1, 30000, 0, true)
|
||||
{
|
||||
notify = Some(id);
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ pub struct Toast {
|
||||
pub body: String,
|
||||
pub opacity: f32,
|
||||
pub timeout: f32,
|
||||
pub lerp_amount: f32,
|
||||
pub sound: bool,
|
||||
pub topic: ToastTopic,
|
||||
}
|
||||
@@ -41,6 +42,7 @@ impl Toast {
|
||||
title,
|
||||
body,
|
||||
opacity: 1.0,
|
||||
lerp_amount: 0.1,
|
||||
timeout: 3.0,
|
||||
sound: false,
|
||||
topic,
|
||||
@@ -50,6 +52,10 @@ impl Toast {
|
||||
self.timeout = timeout;
|
||||
self
|
||||
}
|
||||
pub const fn with_lerp_amount(mut self, lerp: f32) -> Self {
|
||||
self.lerp_amount = lerp;
|
||||
self
|
||||
}
|
||||
pub const fn with_opacity(mut self, opacity: f32) -> Self {
|
||||
self.opacity = opacity;
|
||||
self
|
||||
@@ -118,7 +124,9 @@ fn new_toast(toast: Toast, app: &mut AppState) -> Option<OverlayWindowConfig> {
|
||||
ToastDisplayMethod::Center => (
|
||||
vec3(0., -0.2, -0.5),
|
||||
Quat::IDENTITY,
|
||||
Positioning::FollowHead { lerp: 0.1 },
|
||||
Positioning::FollowHead {
|
||||
lerp: toast.lerp_amount,
|
||||
},
|
||||
),
|
||||
ToastDisplayMethod::Watch => {
|
||||
let relative_to = Positioning::FollowHand {
|
||||
|
||||
@@ -50,6 +50,11 @@
|
||||
"type": "boolean",
|
||||
"requirement": "optional"
|
||||
},
|
||||
{
|
||||
"name": "/actions/default/in/SpaceReset",
|
||||
"type": "boolean",
|
||||
"requirement": "optional"
|
||||
},
|
||||
{
|
||||
"name": "/actions/default/in/LeftHand",
|
||||
"type": "pose",
|
||||
|
||||
@@ -125,6 +125,9 @@
|
||||
## If disabled, there will be no background rendered (solid black).
|
||||
#use_skybox: true
|
||||
|
||||
## Monado/WiVRn only. Opacity of the floor grid if use_skybox is enabled.
|
||||
#grid_opacity: true
|
||||
|
||||
## Hide the help popup that appears on your hand while grabbing an overlay
|
||||
#hide_grab_help: false
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -16,4 +16,3 @@ void main()
|
||||
}
|
||||
out_color = vec4(1.0, 1.0, 1.0, grid * fade);
|
||||
}
|
||||
|
||||
+28
-13
@@ -1,12 +1,15 @@
|
||||
use glam::Affine3A;
|
||||
use idmap::IdMap;
|
||||
use smallvec::{SmallVec, smallvec};
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use wgui::log::LogErr;
|
||||
use wgui::theme::WguiTheme;
|
||||
use wgui::{
|
||||
drawing, font_config::WguiFontConfig, gfx::WGfx, globals::WguiGlobals, parser::parse_color_hex,
|
||||
renderer_vk::context::SharedContext as WSharedContext,
|
||||
};
|
||||
use wlx_common::locale::WayVRLangProvider;
|
||||
use wlx_common::{
|
||||
audio,
|
||||
config::GeneralConfig,
|
||||
@@ -47,6 +50,7 @@ pub struct AppState {
|
||||
pub anchor_grabbed: bool,
|
||||
|
||||
pub wgui_globals: WguiGlobals,
|
||||
pub wgui_theme: Rc<WguiTheme>,
|
||||
|
||||
pub dbus: DbusConnector,
|
||||
|
||||
@@ -89,12 +93,12 @@ impl AppState {
|
||||
let osc_sender = crate::subsystem::osc::OscSender::new(session.config.osc_out_port).ok();
|
||||
|
||||
let wgui_shared = WSharedContext::new(gfx.clone())?;
|
||||
let theme = session.config.theme_path.clone();
|
||||
let theme_path = session.config.theme_path.clone();
|
||||
|
||||
let mut audio_sample_player = audio::SamplePlayer::new();
|
||||
audio_sample_player.register_sample(
|
||||
"key_click",
|
||||
audio::AudioSample::from_mp3(&*audio::AudioSample::bytes_from_config_or_default(
|
||||
audio::AudioSample::from_mp3(&audio::AudioSample::bytes_from_config_or_default(
|
||||
"sound/key_click.mp3",
|
||||
include_bytes!("res/key_click.mp3"),
|
||||
))?,
|
||||
@@ -102,16 +106,24 @@ impl AppState {
|
||||
|
||||
audio_sample_player.register_sample(
|
||||
"toast",
|
||||
audio::AudioSample::from_mp3(&*audio::AudioSample::bytes_from_config_or_default(
|
||||
audio::AudioSample::from_mp3(&audio::AudioSample::bytes_from_config_or_default(
|
||||
"sound/toast.mp3",
|
||||
include_bytes!("res/toast.mp3"),
|
||||
))?,
|
||||
)?;
|
||||
|
||||
audio_sample_player.register_sample(
|
||||
"fix_floor",
|
||||
audio::AudioSample::from_mp3(&audio::AudioSample::bytes_from_config_or_default(
|
||||
"sound/fix_floor.mp3",
|
||||
include_bytes!("res/fix_floor.mp3"),
|
||||
))?,
|
||||
)?;
|
||||
|
||||
let mut assets = Box::new(gui::asset::GuiAsset {});
|
||||
audio_sample_player.register_wgui_samples(assets.as_mut())?;
|
||||
|
||||
let mut defaults = wgui::globals::Defaults::default();
|
||||
let mut theme = WguiTheme::default();
|
||||
|
||||
{
|
||||
#[allow(clippy::ref_option)]
|
||||
@@ -121,15 +133,15 @@ impl AppState {
|
||||
}
|
||||
}
|
||||
|
||||
apply_color(&mut defaults.text_color, &session.config.color_text);
|
||||
apply_color(&mut defaults.accent_color, &session.config.color_accent);
|
||||
apply_color(&mut defaults.danger_color, &session.config.color_danger);
|
||||
apply_color(&mut defaults.faded_color, &session.config.color_faded);
|
||||
apply_color(&mut defaults.bg_color, &session.config.color_background);
|
||||
apply_color(&mut theme.text_color, &session.config.color_text);
|
||||
apply_color(&mut theme.accent_color, &session.config.color_accent);
|
||||
apply_color(&mut theme.danger_color, &session.config.color_danger);
|
||||
apply_color(&mut theme.faded_color, &session.config.color_faded);
|
||||
apply_color(&mut theme.bg_color, &session.config.color_background);
|
||||
}
|
||||
|
||||
defaults.animation_mult = 1. / session.config.ui_animation_speed;
|
||||
defaults.rounding_mult = session.config.ui_round_multiplier;
|
||||
theme.animation_mult = 1. / session.config.ui_animation_speed;
|
||||
theme.rounding_mult = session.config.ui_round_multiplier;
|
||||
|
||||
let dbus = DbusConnector::default();
|
||||
|
||||
@@ -138,6 +150,8 @@ impl AppState {
|
||||
let mut desktop_finder = DesktopFinder::new();
|
||||
desktop_finder.refresh();
|
||||
|
||||
let lang_provider = WayVRLangProvider::from_config(&session.config);
|
||||
|
||||
Ok(Self {
|
||||
session,
|
||||
tasks,
|
||||
@@ -153,10 +167,11 @@ impl AppState {
|
||||
anchor_grabbed: false,
|
||||
wgui_globals: WguiGlobals::new(
|
||||
assets,
|
||||
defaults,
|
||||
&lang_provider,
|
||||
&WguiFontConfig::default(),
|
||||
get_config_file_path(&theme),
|
||||
get_config_file_path(&theme_path),
|
||||
)?,
|
||||
wgui_theme: Rc::new(theme),
|
||||
dbus,
|
||||
xr_backend,
|
||||
ipc_server,
|
||||
|
||||
@@ -94,7 +94,7 @@ impl DbusConnector {
|
||||
summary: &str,
|
||||
body: &str,
|
||||
urgency: u8,
|
||||
timeout: i32,
|
||||
timeout_ms: i32,
|
||||
replaces_id: u32,
|
||||
transient: bool,
|
||||
) -> anyhow::Result<u32> {
|
||||
@@ -118,7 +118,7 @@ impl DbusConnector {
|
||||
body,
|
||||
vec![],
|
||||
hints,
|
||||
timeout,
|
||||
timeout_ms,
|
||||
)?;
|
||||
Ok(retval)
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ use std::{
|
||||
|
||||
use anyhow::Context;
|
||||
use glam::{Affine3A, Vec3, Vec3A};
|
||||
use slotmap::{HopSlotMap, Key, SecondaryMap};
|
||||
use slotmap::{Key, SecondaryMap, SlotMap};
|
||||
use wgui::log::LogErr;
|
||||
use wlx_common::{
|
||||
astr_containers::{AStrMap, AStrMapExt},
|
||||
@@ -42,7 +42,7 @@ pub const MAX_OVERLAY_SETS: usize = 6;
|
||||
|
||||
pub struct OverlayWindowManager<T> {
|
||||
wrappers: EditWrapperManager,
|
||||
overlays: HopSlotMap<OverlayID, OverlayWindowData<T>>,
|
||||
overlays: SlotMap<OverlayID, OverlayWindowData<T>>,
|
||||
sets: Vec<OverlayWindowSet>,
|
||||
global_set: OverlayWindowSet,
|
||||
/// The set that is currently visible.
|
||||
@@ -65,7 +65,7 @@ where
|
||||
pub fn new(app: &mut AppState, headless: bool) -> anyhow::Result<Self> {
|
||||
let mut me = Self {
|
||||
wrappers: EditWrapperManager::default(),
|
||||
overlays: HopSlotMap::with_key(),
|
||||
overlays: SlotMap::<OverlayID, OverlayWindowData<T>>::with_key(),
|
||||
current_set: Some(0),
|
||||
restore_set: 0,
|
||||
sets: vec![OverlayWindowSet::default()],
|
||||
|
||||
@@ -249,7 +249,7 @@ enum Subcommands {
|
||||
/// Name for the overlay
|
||||
#[arg(short, long, default_value = "")]
|
||||
name: String,
|
||||
/// Enviroment variables, separated by comma
|
||||
/// Environment variables, separated by comma
|
||||
#[arg(short, long, default_value = "")]
|
||||
env: String,
|
||||
/// Executable to run
|
||||
|
||||
@@ -7,3 +7,5 @@
|
||||
an experimental gui library for WayVR and friends
|
||||
|
||||
powered by Vulkan
|
||||
|
||||
Wgui documentation available at [wayvr.org](https://wayvr.org/docs/wgui/) or [here](https://github.com/wayvr-org/website).
|
||||
+80
-2
@@ -6,7 +6,7 @@
|
||||
|
||||
## [Built-in components](#components)
|
||||
|
||||
[Button](#button-component), [Slider](#slider-component), [CheckBox](#checkbox-component)
|
||||
[Button](#button-component), [Slider](#slider-component), [CheckBox](#checkbox-component), [Tabs](#tabs-component) ([Tab](#tab-component)), [EditBox](#editbox-component)
|
||||
|
||||
## [Examples](#examples)
|
||||
|
||||
@@ -60,7 +60,7 @@ _They can be used in any widget/component._
|
||||
|
||||
`width`, `height`: **float** | **percent**
|
||||
|
||||
### Advanced attributes
|
||||
## Advanced attributes
|
||||
|
||||
`interactable`: "1" | "0"
|
||||
|
||||
@@ -74,6 +74,32 @@ _Used in case of overlapping pop-ups or windows, most notably applied to various
|
||||
|
||||
_Set to 1 if you want to render overlapping pop-ups to properly render your widgets in order. Wgui renders with as few Vulkan drawcalls as possible, so this is your responsibility._
|
||||
|
||||
## Default Colors
|
||||
|
||||
_These colors can be defined by the user to control the color scheme. They always exists and can be used in any place a normal color could be used._
|
||||
|
||||
`~color_text`: default: white(#FFFFFF)
|
||||
`~color_accent`: default: light blue(#21ADFF)
|
||||
`~color_danger`: default: red(#E60000)
|
||||
`~color_faded`: default: grey(#ABBDCC)
|
||||
`~color_bg`: default: black(#00121ABF) (color_background in config)
|
||||
|
||||
_Only the default colors can be changed with the following suffixes._
|
||||
|
||||
`_translucent`
|
||||
|
||||
_Halves the alpha of the color._
|
||||
|
||||
`_10`
|
||||
`_20`
|
||||
`_30`
|
||||
`_40`
|
||||
`_50`
|
||||
|
||||
_Darkens the color by multiplying it with a percentage. `~color_accent_50` Would be half of the normal brightness but the same alpha. You can't combine the suffixes_
|
||||
|
||||
### Color
|
||||
|
||||
# Widgets
|
||||
|
||||
## div widget
|
||||
@@ -330,6 +356,8 @@ _Translated by key_
|
||||
|
||||
`box_size`: **float** (default: 24)
|
||||
|
||||
`color_checked`: #FFAABB | #FFAABBCC
|
||||
|
||||
`value`: **string**
|
||||
|
||||
_optional value that will be sent with internal events_
|
||||
@@ -370,6 +398,56 @@ _optional value that will be set as the RadioGroup's value_
|
||||
|
||||
### A radio group. Place `<RadioBox>` components inside this.
|
||||
|
||||
## Tabs component
|
||||
|
||||
### `<Tabs>`
|
||||
|
||||
### A tab bar component
|
||||
|
||||
#### Parameters
|
||||
|
||||
`id`: **string**
|
||||
|
||||
_Unique identifier for the tab group_
|
||||
|
||||
Only `<Tab>` components should be placed inside `<Tabs>`.
|
||||
|
||||
---
|
||||
|
||||
## Tab component
|
||||
|
||||
### `<Tab>`
|
||||
|
||||
### A single tab inside a `<Tabs>` component
|
||||
|
||||
#### Parameters
|
||||
|
||||
`name`: **string**
|
||||
|
||||
_Unique identifier for the tab_
|
||||
|
||||
`translation`: **string**
|
||||
|
||||
_Translated by key_
|
||||
|
||||
`sprite_src` | `sprite_src_ext` | `sprite_src_builtin` | `sprite_src_internal`
|
||||
|
||||
_Image path (see [sprite](#sprite-widget)) for src descriptions_
|
||||
|
||||
---
|
||||
|
||||
## EditBox component
|
||||
|
||||
### `<EditBox>`
|
||||
|
||||
### A single-line text input field.
|
||||
|
||||
#### Parameters
|
||||
|
||||
`text`: **string**
|
||||
|
||||
_Initial text content_
|
||||
|
||||
---
|
||||
|
||||
# Examples
|
||||
|
||||
@@ -3,6 +3,8 @@ use std::ffi::OsStr;
|
||||
use std::io::Read;
|
||||
use std::path::{Component, Path, PathBuf};
|
||||
|
||||
use crate::i18n::LangsList;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum AssetPath<'a> {
|
||||
WguiInternal(&'a str), // tied to internal wgui AssetProvider. Used internally
|
||||
@@ -83,6 +85,11 @@ impl Default for AssetPathOwned {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait LangProvider {
|
||||
fn langs_list(&self) -> &dyn LangsList;
|
||||
fn forced_lang(&self) -> Option<&str>;
|
||||
}
|
||||
|
||||
pub trait AssetProvider {
|
||||
fn load_from_path(&mut self, path: &str) -> anyhow::Result<Vec<u8>>;
|
||||
fn load_from_path_gzip(&mut self, path: &str) -> anyhow::Result<Vec<u8>> {
|
||||
|
||||
@@ -157,7 +157,7 @@ impl ComponentButton {
|
||||
}
|
||||
|
||||
pub fn set_color(&self, common: &mut CallbackDataCommon, color: Color) {
|
||||
let gradient_intensity = common.defaults().gradient_intensity;
|
||||
let gradient_intensity = common.state.theme.gradient_intensity;
|
||||
|
||||
let Some(mut rect) = common.state.widgets.get_as::<WidgetRectangle>(self.data.id_rect) else {
|
||||
return;
|
||||
@@ -194,8 +194,8 @@ impl ComponentButton {
|
||||
}
|
||||
|
||||
let (anim_mult, gradient_intensity) = {
|
||||
let defaults = common.state.globals.defaults();
|
||||
(defaults.animation_mult, defaults.gradient_intensity)
|
||||
let theme = &common.state.theme;
|
||||
(theme.animation_mult, theme.gradient_intensity)
|
||||
};
|
||||
|
||||
let anim_ticks = if sticky_down { 5. } else { 10. };
|
||||
@@ -247,7 +247,7 @@ fn anim_hover(
|
||||
|
||||
let bgcolor = init_color.lerp(&colors.hover_color, mult);
|
||||
|
||||
let gradient_intensity = common.globals().defaults.gradient_intensity;
|
||||
let gradient_intensity = common.state.theme.gradient_intensity;
|
||||
|
||||
//let t = Mat4::from_scale(Vec3::splat(1.0 + pos * 0.5)) * Mat4::from_rotation_z(pos * 1.0);
|
||||
|
||||
@@ -349,6 +349,7 @@ fn register_event_mouse_press(state: Rc<RefCell<State>>, listeners: &mut EventLi
|
||||
common.alterables.trigger_haptics();
|
||||
common.alterables.play_sound(WguiSoundType::ButtonPress);
|
||||
common.alterables.mark_redraw();
|
||||
common.alterables.unfocus();
|
||||
|
||||
if state.hovered {
|
||||
state.down = true;
|
||||
@@ -415,8 +416,8 @@ fn register_event_mouse_release(
|
||||
}
|
||||
|
||||
pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Result<(WidgetPair, Rc<ComponentButton>)> {
|
||||
let globals = ess.layout.state.globals.clone();
|
||||
let mut style = params.style;
|
||||
let theme = &ess.layout.state.theme;
|
||||
|
||||
// force-override style
|
||||
style.align_items = Some(AlignItems::Center);
|
||||
@@ -427,31 +428,26 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul
|
||||
style.overflow.y = taffy::Overflow::Hidden;
|
||||
|
||||
// update colors to default ones if they are not specified
|
||||
let color = if let Some(color) = params.color {
|
||||
color
|
||||
} else {
|
||||
globals.defaults().button_color
|
||||
};
|
||||
let color = params.color.unwrap_or(theme.button_color);
|
||||
|
||||
let border_color = if let Some(border_color) = params.border_color {
|
||||
border_color
|
||||
} else {
|
||||
Color::new(color.r, color.g, color.b, color.a + 0.25)
|
||||
};
|
||||
let border_color = params
|
||||
.border_color
|
||||
.unwrap_or_else(|| Color::new(color.r, color.g, color.b, color.a + 0.25));
|
||||
|
||||
let hover_color = if let Some(hover_color) = params.hover_color {
|
||||
hover_color
|
||||
} else {
|
||||
Color::new(color.r + 0.25, color.g + 0.25, color.g + 0.25, color.a + 0.15)
|
||||
};
|
||||
let hover_color = params
|
||||
.hover_color
|
||||
.unwrap_or_else(|| Color::new(color.r + 0.25, color.g + 0.25, color.g + 0.25, color.a + 0.15));
|
||||
|
||||
let hover_border_color = if let Some(hover_border_color) = params.hover_border_color {
|
||||
hover_border_color
|
||||
} else {
|
||||
Color::new(color.r + 0.5, color.g + 0.5, color.g + 0.5, color.a + 0.5)
|
||||
};
|
||||
let hover_border_color = params
|
||||
.hover_border_color
|
||||
.unwrap_or_else(|| Color::new(color.r + 0.5, color.g + 0.5, color.g + 0.5, color.a + 0.5));
|
||||
|
||||
let gradient_intensity = ess.layout.state.globals.defaults().gradient_intensity;
|
||||
let gradient_intensity = theme.gradient_intensity;
|
||||
|
||||
let light_text = {
|
||||
let mult = if theme.dark_mode { color.a } else { 1.0 - color.a };
|
||||
(color.r + color.g + color.b) * mult < 1.5
|
||||
};
|
||||
|
||||
let (root, _) = ess.layout.add_child(
|
||||
ess.parent,
|
||||
@@ -468,15 +464,6 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul
|
||||
|
||||
let id_rect = root.id;
|
||||
|
||||
let light_text = {
|
||||
let mult = if globals.defaults().dark_mode {
|
||||
color.a
|
||||
} else {
|
||||
1.0 - color.a
|
||||
};
|
||||
(color.r + color.g + color.b) * mult < 1.5
|
||||
};
|
||||
|
||||
let default_margin = taffy::Rect {
|
||||
top: length(4.0),
|
||||
bottom: length(4.0),
|
||||
@@ -486,7 +473,7 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul
|
||||
|
||||
if let Some(sprite_path) = params.sprite_src {
|
||||
let sprite = WidgetSprite::create(WidgetSpriteParams {
|
||||
glyph_data: Some(CustomGlyphData::from_assets(&globals, sprite_path)?),
|
||||
glyph_data: Some(CustomGlyphData::from_assets(&ess.layout.state.globals, sprite_path)?),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
@@ -505,23 +492,25 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul
|
||||
}
|
||||
|
||||
let id_label = if let Some(content) = params.text {
|
||||
let widget_label = WidgetLabel::create(
|
||||
&mut ess.layout.state,
|
||||
WidgetLabelParams {
|
||||
content,
|
||||
style: TextStyle {
|
||||
weight: Some(FontWeight::Bold),
|
||||
color: Some(if light_text {
|
||||
Color::new(1.0, 1.0, 1.0, 1.0)
|
||||
} else {
|
||||
Color::new(0.0, 0.0, 0.0, 1.0)
|
||||
}),
|
||||
..params.text_style
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
let (label, _node_label) = ess.layout.add_child(
|
||||
id_rect,
|
||||
WidgetLabel::create(
|
||||
&mut globals.get(),
|
||||
WidgetLabelParams {
|
||||
content,
|
||||
style: TextStyle {
|
||||
weight: Some(FontWeight::Bold),
|
||||
color: Some(if light_text {
|
||||
Color::new(1.0, 1.0, 1.0, 1.0)
|
||||
} else {
|
||||
Color::new(0.0, 0.0, 0.0, 1.0)
|
||||
}),
|
||||
..params.text_style
|
||||
},
|
||||
},
|
||||
),
|
||||
widget_label,
|
||||
taffy::Style {
|
||||
margin: default_margin,
|
||||
..Default::default()
|
||||
@@ -556,25 +545,19 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul
|
||||
let base = ComponentBase {
|
||||
id: root.id,
|
||||
lhandles: {
|
||||
let mut widget = ess.layout.state.widgets.get(id_rect).unwrap().state();
|
||||
let anim_mult = ess.layout.state.globals.defaults().animation_mult;
|
||||
let listeners = &mut root.widget.state().event_listeners;
|
||||
let anim_mult = ess.layout.state.theme.animation_mult;
|
||||
vec![
|
||||
register_event_mouse_enter(
|
||||
data.clone(),
|
||||
state.clone(),
|
||||
&mut widget.event_listeners,
|
||||
params.tooltip,
|
||||
anim_mult,
|
||||
),
|
||||
register_event_mouse_leave(state.clone(), &mut widget.event_listeners, anim_mult),
|
||||
register_event_mouse_press(state.clone(), &mut widget.event_listeners),
|
||||
register_event_mouse_release(data.clone(), state.clone(), &mut widget.event_listeners),
|
||||
register_event_mouse_enter(data.clone(), state.clone(), listeners, params.tooltip, anim_mult),
|
||||
register_event_mouse_leave(state.clone(), listeners, anim_mult),
|
||||
register_event_mouse_press(state.clone(), listeners),
|
||||
register_event_mouse_release(data.clone(), state.clone(), listeners),
|
||||
]
|
||||
},
|
||||
};
|
||||
|
||||
let button = Rc::new(ComponentButton { base, data, state });
|
||||
|
||||
ess.layout.register_component_refresh(Component(button.clone()));
|
||||
ess.layout.register_component_refresh(&Component(button.clone()));
|
||||
Ok((root, button))
|
||||
}
|
||||
@@ -31,6 +31,7 @@ use crate::{
|
||||
pub struct Params {
|
||||
pub text: Translation,
|
||||
pub style: taffy::Style,
|
||||
pub color_checked: Option<Color>,
|
||||
pub box_size: f32,
|
||||
pub checked: bool,
|
||||
pub radio_group: Option<Rc<ComponentRadioGroup>>,
|
||||
@@ -43,6 +44,7 @@ impl Default for Params {
|
||||
Self {
|
||||
text: Translation::from_raw_text(""),
|
||||
style: Default::default(),
|
||||
color_checked: None,
|
||||
box_size: 24.0,
|
||||
checked: false,
|
||||
radio_group: None,
|
||||
@@ -84,6 +86,8 @@ struct Data {
|
||||
id_label: WidgetID, // Label, parent of container
|
||||
value: Option<Rc<str>>, // arbitrary value assigned to the element
|
||||
radio_group: Option<Weak<ComponentRadioGroup>>,
|
||||
|
||||
color_checked: Color,
|
||||
}
|
||||
|
||||
pub struct ComponentCheckbox {
|
||||
@@ -92,6 +96,8 @@ pub struct ComponentCheckbox {
|
||||
state: Rc<RefCell<State>>,
|
||||
}
|
||||
|
||||
const COLOR_UNCHECKED: Color = Color::new(0.0, 0.0, 0.0, 0.0);
|
||||
|
||||
impl ComponentTrait for ComponentCheckbox {
|
||||
fn base(&self) -> &ComponentBase {
|
||||
&self.base
|
||||
@@ -106,12 +112,9 @@ impl ComponentTrait for ComponentCheckbox {
|
||||
}
|
||||
}
|
||||
|
||||
const COLOR_CHECKED: Color = Color::new(0.1, 0.5, 1.0, 1.0);
|
||||
const COLOR_UNCHECKED: Color = Color::new(0.1, 0.5, 1.0, 0.0);
|
||||
|
||||
fn set_box_checked(widgets: &layout::WidgetMap, data: &Data, checked: bool) {
|
||||
widgets.call(data.id_inner_box, |rect: &mut WidgetRectangle| {
|
||||
rect.params.color = if checked { COLOR_CHECKED } else { COLOR_UNCHECKED }
|
||||
rect.params.color = if checked { data.color_checked } else { COLOR_UNCHECKED }
|
||||
});
|
||||
}
|
||||
|
||||
@@ -248,6 +251,7 @@ fn register_event_mouse_press(state: Rc<RefCell<State>>, listeners: &mut EventLi
|
||||
|
||||
common.alterables.trigger_haptics();
|
||||
common.alterables.mark_redraw();
|
||||
common.alterables.unfocus();
|
||||
|
||||
if state.hovered {
|
||||
state.down = true;
|
||||
@@ -314,6 +318,7 @@ fn register_event_mouse_release(
|
||||
|
||||
pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Result<(WidgetPair, Rc<ComponentCheckbox>)> {
|
||||
let mut style = params.style;
|
||||
let theme = &ess.layout.state.theme;
|
||||
|
||||
// force-override style
|
||||
style.flex_wrap = taffy::FlexWrap::NoWrap;
|
||||
@@ -342,7 +347,7 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul
|
||||
(WLength::Units(5.0), WLength::Units(8.0))
|
||||
};
|
||||
|
||||
let globals = ess.layout.state.globals.clone();
|
||||
let color_checked = params.color_checked.unwrap_or(theme.accent_color);
|
||||
|
||||
let (root, _) = ess.layout.add_child(
|
||||
ess.parent,
|
||||
@@ -384,7 +389,7 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul
|
||||
outer_box.id,
|
||||
WidgetRectangle::create(WidgetRectangleParams {
|
||||
round: round_5,
|
||||
color: if params.checked { COLOR_CHECKED } else { COLOR_UNCHECKED },
|
||||
color: if params.checked { color_checked } else { COLOR_UNCHECKED },
|
||||
..Default::default()
|
||||
}),
|
||||
taffy::Style {
|
||||
@@ -396,20 +401,17 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul
|
||||
},
|
||||
)?;
|
||||
|
||||
let (label, _node_label) = ess.layout.add_child(
|
||||
id_container,
|
||||
WidgetLabel::create(
|
||||
&mut globals.get(),
|
||||
WidgetLabelParams {
|
||||
content: params.text,
|
||||
style: TextStyle {
|
||||
weight: Some(FontWeight::Bold),
|
||||
..Default::default()
|
||||
},
|
||||
let widget_label = WidgetLabel::create(
|
||||
&mut ess.layout.state,
|
||||
WidgetLabelParams {
|
||||
content: params.text,
|
||||
style: TextStyle {
|
||||
weight: Some(FontWeight::Bold),
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
Default::default(),
|
||||
)?;
|
||||
},
|
||||
);
|
||||
let (label, _node_label) = ess.layout.add_child(id_container, widget_label, Default::default())?;
|
||||
|
||||
let data = Rc::new(Data {
|
||||
id_container,
|
||||
@@ -417,6 +419,7 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul
|
||||
id_label: label.id,
|
||||
value: params.value,
|
||||
radio_group: params.radio_group.as_ref().map(Rc::downgrade),
|
||||
color_checked,
|
||||
});
|
||||
|
||||
let state = Rc::new(RefCell::new(State {
|
||||
@@ -431,13 +434,13 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul
|
||||
let base = ComponentBase {
|
||||
id: root.id,
|
||||
lhandles: {
|
||||
let mut widget = ess.layout.state.widgets.get(id_container).unwrap().state();
|
||||
let anim_mult = ess.layout.state.globals.defaults().animation_mult;
|
||||
let listeners = &mut root.widget.state().event_listeners;
|
||||
let anim_mult = ess.layout.state.theme.animation_mult;
|
||||
vec![
|
||||
register_event_mouse_enter(state.clone(), &mut widget.event_listeners, params.tooltip, anim_mult),
|
||||
register_event_mouse_leave(state.clone(), &mut widget.event_listeners, anim_mult),
|
||||
register_event_mouse_press(state.clone(), &mut widget.event_listeners),
|
||||
register_event_mouse_release(data.clone(), state.clone(), &mut widget.event_listeners),
|
||||
register_event_mouse_enter(state.clone(), listeners, params.tooltip, anim_mult),
|
||||
register_event_mouse_leave(state.clone(), listeners, anim_mult),
|
||||
register_event_mouse_press(state.clone(), listeners),
|
||||
register_event_mouse_release(data.clone(), state.clone(), listeners),
|
||||
]
|
||||
},
|
||||
};
|
||||
|
||||
@@ -0,0 +1,418 @@
|
||||
use std::{
|
||||
cell::{Ref, RefCell},
|
||||
rc::{Rc, Weak},
|
||||
};
|
||||
|
||||
use glam::FloatExt;
|
||||
use taffy::prelude::{auto, length, percent};
|
||||
|
||||
use crate::{
|
||||
animation::{Animation, AnimationEasing},
|
||||
components::{Component, ComponentBase, ComponentTrait, FocusChangeData, RefreshData},
|
||||
drawing::{self, Color},
|
||||
event::{self, CallbackDataCommon, CallbackMetadata, EventListenerCollection, EventListenerKind, StyleSetRequest},
|
||||
i18n::Translation,
|
||||
layout::{WidgetID, WidgetPair},
|
||||
renderer_vk::text::{TextShadow, TextStyle},
|
||||
widget::{
|
||||
ConstructEssentials, EventResult,
|
||||
div::WidgetDiv,
|
||||
label::{WidgetLabel, WidgetLabelParams},
|
||||
rectangle::{WidgetRectangle, WidgetRectangleParams},
|
||||
util::WLength,
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Params {
|
||||
pub style: taffy::Style,
|
||||
pub initial_text: String,
|
||||
}
|
||||
|
||||
struct State {
|
||||
text: String,
|
||||
hovered: bool,
|
||||
focused: bool,
|
||||
focused_prev: bool,
|
||||
first_refresh: bool,
|
||||
self_ref: Weak<ComponentEditBox>,
|
||||
}
|
||||
|
||||
#[allow(clippy::struct_field_names)]
|
||||
struct Data {
|
||||
#[allow(dead_code)]
|
||||
id_rect_container: WidgetID,
|
||||
id_rect_bottom: WidgetID,
|
||||
id_rect_cursor: WidgetID,
|
||||
id_label: WidgetID,
|
||||
}
|
||||
|
||||
pub struct ComponentEditBox {
|
||||
base: ComponentBase,
|
||||
data: Rc<Data>,
|
||||
state: Rc<RefCell<State>>,
|
||||
}
|
||||
|
||||
fn anim_bottom_rect(
|
||||
common: &mut CallbackDataCommon,
|
||||
accent_color: drawing::Color,
|
||||
id_rect: WidgetID,
|
||||
anim_mult: f32,
|
||||
focused: bool,
|
||||
) {
|
||||
common.alterables.animate(Animation::new(
|
||||
id_rect,
|
||||
(10.0 * anim_mult) as _,
|
||||
AnimationEasing::OutQuad,
|
||||
{
|
||||
Box::new(move |common, data| {
|
||||
let rect = data.obj.get_as_mut::<WidgetRectangle>().unwrap();
|
||||
let pos_bidir = if focused { data.pos } else { 1.0 - data.pos };
|
||||
|
||||
rect.set_color(
|
||||
common,
|
||||
accent_color.lerp(&drawing::Color::new(1.0, 1.0, 1.0, 1.0), pos_bidir),
|
||||
);
|
||||
|
||||
common.alterables.set_style(
|
||||
data.widget_id,
|
||||
StyleSetRequest::Size(taffy::Size {
|
||||
width: percent(0.95.lerp(1.0, pos_bidir)),
|
||||
height: length(1.0 + pos_bidir),
|
||||
}),
|
||||
);
|
||||
|
||||
common.alterables.set_style(
|
||||
data.widget_id,
|
||||
StyleSetRequest::Margin(taffy::Rect {
|
||||
bottom: length(pos_bidir),
|
||||
left: auto(),
|
||||
right: auto(),
|
||||
top: auto(),
|
||||
}),
|
||||
);
|
||||
|
||||
common.alterables.mark_redraw();
|
||||
})
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
fn refresh_all(common: &mut CallbackDataCommon, data: &Data, state: &mut State) -> Option<()> {
|
||||
let theme = &common.state.theme;
|
||||
let editbox_color = theme.editbox_color;
|
||||
let anim_mult = theme.animation_mult;
|
||||
let accent_color = theme.accent_color;
|
||||
|
||||
let (rect_color, border_color) = if state.focused {
|
||||
(editbox_color.add_rgb(0.15), editbox_color.add_rgb(0.15 + 0.25))
|
||||
} else if state.hovered {
|
||||
(editbox_color.add_rgb(0.1), editbox_color.add_rgb(0.1 + 0.15))
|
||||
} else {
|
||||
(editbox_color, editbox_color.add_rgb(0.15))
|
||||
};
|
||||
|
||||
// update background color
|
||||
let mut rect = common.state.widgets.get_as::<WidgetRectangle>(data.id_rect_container)?;
|
||||
rect.params.border_color = border_color;
|
||||
rect.set_color(common, rect_color);
|
||||
|
||||
if state.focused_prev != state.focused || state.first_refresh {
|
||||
anim_bottom_rect(common, accent_color, data.id_rect_bottom, anim_mult, state.focused);
|
||||
state.focused_prev = state.focused;
|
||||
}
|
||||
|
||||
// Cursor
|
||||
common.alterables.set_style(
|
||||
data.id_rect_cursor,
|
||||
StyleSetRequest::Display(if state.focused {
|
||||
taffy::Display::Flex
|
||||
} else {
|
||||
taffy::Display::None
|
||||
}),
|
||||
);
|
||||
|
||||
state.first_refresh = false;
|
||||
|
||||
Some(())
|
||||
}
|
||||
|
||||
impl ComponentTrait for ComponentEditBox {
|
||||
fn base(&self) -> &ComponentBase {
|
||||
&self.base
|
||||
}
|
||||
|
||||
fn base_mut(&mut self) -> &mut ComponentBase {
|
||||
&mut self.base
|
||||
}
|
||||
|
||||
fn refresh(&self, data: &mut RefreshData) {
|
||||
let mut state = self.state.borrow_mut();
|
||||
let res = refresh_all(data.common, &self.data, &mut state);
|
||||
debug_assert!(res.is_some());
|
||||
}
|
||||
|
||||
fn on_focus_change(&self, data: &mut FocusChangeData) {
|
||||
let mut state = self.state.borrow_mut();
|
||||
state.focused = data.focused;
|
||||
data.common.alterables.refresh_component_once(&state.self_ref);
|
||||
}
|
||||
}
|
||||
|
||||
fn update_text(common: &mut CallbackDataCommon, state: &mut State, data: &Data, text: String) {
|
||||
let Some(mut label) = common.state.widgets.get_as::<WidgetLabel>(data.id_label) else {
|
||||
return;
|
||||
};
|
||||
|
||||
label.set_text(common, Translation::from_raw_text(&text));
|
||||
|
||||
common.alterables.refresh_component_once(&state.self_ref);
|
||||
|
||||
state.text = text;
|
||||
}
|
||||
|
||||
impl ComponentEditBox {
|
||||
pub fn set_text(&self, common: &mut CallbackDataCommon, text: &str) {
|
||||
let mut state = self.state.borrow_mut();
|
||||
update_text(common, &mut state, &self.data, String::from(text));
|
||||
}
|
||||
|
||||
pub fn get_text(&self) -> Ref<'_, String> {
|
||||
Ref::map(self.state.borrow(), |x| &x.text)
|
||||
}
|
||||
}
|
||||
|
||||
fn register_event_text_input(
|
||||
state: Rc<RefCell<State>>,
|
||||
data: Rc<Data>,
|
||||
listeners: &mut EventListenerCollection,
|
||||
) -> event::EventListenerID {
|
||||
listeners.register(
|
||||
EventListenerKind::TextInput,
|
||||
Box::new(move |common, evt, (), ()| {
|
||||
let mut state = state.borrow_mut();
|
||||
if !state.focused {
|
||||
return Ok(EventResult::Pass);
|
||||
}
|
||||
|
||||
let CallbackMetadata::TextInput(input) = &evt.metadata else {
|
||||
unreachable!();
|
||||
};
|
||||
|
||||
let Some(input_text) = &input.text else {
|
||||
return Ok(EventResult::Pass); // nothing to do
|
||||
};
|
||||
|
||||
let Some(ch) = input_text.chars().next() else {
|
||||
return Ok(EventResult::Pass); // ???
|
||||
};
|
||||
|
||||
let mut new_text = std::mem::take(&mut state.text);
|
||||
|
||||
if ch == '\x08' {
|
||||
// Backspace
|
||||
new_text.pop();
|
||||
} else {
|
||||
let printable = !input_text.chars().any(char::is_control);
|
||||
if printable {
|
||||
new_text.push_str(input_text);
|
||||
}
|
||||
}
|
||||
|
||||
update_text(common, &mut state, &data, new_text);
|
||||
|
||||
Ok(EventResult::Consumed)
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
fn register_event_mouse_enter(
|
||||
state: Rc<RefCell<State>>,
|
||||
listeners: &mut EventListenerCollection,
|
||||
) -> event::EventListenerID {
|
||||
listeners.register(
|
||||
EventListenerKind::MouseEnter,
|
||||
Box::new(move |common, _evt, (), ()| {
|
||||
let mut state = state.borrow_mut();
|
||||
state.hovered = true;
|
||||
common.alterables.trigger_haptics();
|
||||
common.alterables.refresh_component_once(&state.self_ref);
|
||||
Ok(EventResult::Pass)
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
fn register_event_mouse_press(
|
||||
state: Rc<RefCell<State>>,
|
||||
listeners: &mut EventListenerCollection,
|
||||
) -> event::EventListenerID {
|
||||
listeners.register(
|
||||
EventListenerKind::MousePress,
|
||||
Box::new(move |common, _evt, (), ()| {
|
||||
let state = state.borrow_mut();
|
||||
common.alterables.focus(&state.self_ref);
|
||||
common.alterables.trigger_haptics();
|
||||
common.alterables.refresh_component_once(&state.self_ref);
|
||||
Ok(EventResult::Pass)
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
fn register_event_mouse_leave(
|
||||
state: Rc<RefCell<State>>,
|
||||
listeners: &mut EventListenerCollection,
|
||||
) -> event::EventListenerID {
|
||||
listeners.register(
|
||||
EventListenerKind::MouseLeave,
|
||||
Box::new(move |common, _evt, (), ()| {
|
||||
let mut state = state.borrow_mut();
|
||||
state.hovered = false;
|
||||
common.alterables.trigger_haptics();
|
||||
common.alterables.refresh_component_once(&state.self_ref);
|
||||
Ok(EventResult::Pass)
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn construct(
|
||||
ess: &mut ConstructEssentials,
|
||||
mut params: Params,
|
||||
) -> anyhow::Result<(WidgetPair, Rc<ComponentEditBox>)> {
|
||||
let text_color = ess.layout.state.theme.text_color;
|
||||
|
||||
if params.style.size.width.is_auto() {
|
||||
params.style.size.width = length(128.0);
|
||||
}
|
||||
|
||||
if params.style.size.height.is_auto() {
|
||||
params.style.size.height = length(32.0);
|
||||
}
|
||||
|
||||
// override style
|
||||
params.style.align_items = Some(taffy::AlignItems::Center);
|
||||
params.style.position = taffy::Position::Relative;
|
||||
params.style.overflow = taffy::Point {
|
||||
x: taffy::Overflow::Scroll,
|
||||
y: taffy::Overflow::Visible,
|
||||
};
|
||||
params.style.min_size = params.style.max_size;
|
||||
|
||||
let (root, _) = ess.layout.add_child(
|
||||
ess.parent,
|
||||
WidgetRectangle::create(WidgetRectangleParams {
|
||||
border: 2.0,
|
||||
round: WLength::Units(3.0),
|
||||
..Default::default()
|
||||
}),
|
||||
params.style,
|
||||
)?;
|
||||
|
||||
// for centering
|
||||
let (rect_bottom_parent, _) = ess.layout.add_child(
|
||||
root.id,
|
||||
WidgetDiv::create(),
|
||||
taffy::Style {
|
||||
position: taffy::Position::Absolute,
|
||||
flex_direction: taffy::FlexDirection::Column,
|
||||
align_content: Some(taffy::AlignContent::Center),
|
||||
align_items: Some(taffy::AlignItems::Center),
|
||||
size: taffy::Size {
|
||||
width: percent(1.0),
|
||||
height: percent(1.0),
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
let (rect_bottom, _) = ess.layout.add_child(
|
||||
rect_bottom_parent.id,
|
||||
WidgetRectangle::create(Default::default()),
|
||||
Default::default(),
|
||||
)?;
|
||||
|
||||
let id_container = root.id;
|
||||
|
||||
let (label_parent, _) = ess.layout.add_child(
|
||||
root.id,
|
||||
WidgetDiv::create(),
|
||||
taffy::Style {
|
||||
padding: taffy::Rect::length(8.0),
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
let widget_label = WidgetLabel::create(
|
||||
&mut ess.layout.state,
|
||||
WidgetLabelParams {
|
||||
content: Translation::from_raw_text(¶ms.initial_text),
|
||||
style: TextStyle {
|
||||
shadow: Some(TextShadow {
|
||||
x: 1.0,
|
||||
y: 1.0,
|
||||
color: Color::new(0.0, 0.0, 0.0, 1.0),
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
);
|
||||
let (label, _node_label) = ess.layout.add_child(
|
||||
label_parent.id,
|
||||
widget_label,
|
||||
taffy::Style {
|
||||
position: taffy::Position::Relative,
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
let (rect_cursor, _) = ess.layout.add_child(
|
||||
label_parent.id,
|
||||
WidgetRectangle::create(WidgetRectangleParams {
|
||||
color: text_color.with_alpha(0.75),
|
||||
..Default::default()
|
||||
}),
|
||||
taffy::Style {
|
||||
align_self: Some(taffy::AlignSelf::Center),
|
||||
justify_self: Some(taffy::JustifySelf::End),
|
||||
min_size: taffy::Size {
|
||||
width: length(2.0),
|
||||
height: length(16.0),
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
let data = Rc::new(Data {
|
||||
id_rect_container: id_container,
|
||||
id_label: label.id,
|
||||
id_rect_bottom: rect_bottom.id,
|
||||
id_rect_cursor: rect_cursor.id,
|
||||
});
|
||||
|
||||
let state = Rc::new(RefCell::new(State {
|
||||
self_ref: Weak::new(),
|
||||
text: params.initial_text,
|
||||
hovered: false,
|
||||
focused: false,
|
||||
focused_prev: false,
|
||||
first_refresh: true,
|
||||
}));
|
||||
|
||||
let base = ComponentBase {
|
||||
id: root.id,
|
||||
lhandles: {
|
||||
let mut root_state = root.widget.state();
|
||||
vec![
|
||||
register_event_mouse_enter(state.clone(), &mut root_state.event_listeners),
|
||||
register_event_mouse_leave(state.clone(), &mut root_state.event_listeners),
|
||||
register_event_mouse_press(state.clone(), &mut root_state.event_listeners),
|
||||
register_event_text_input(state.clone(), data.clone(), &mut root_state.event_listeners),
|
||||
]
|
||||
},
|
||||
};
|
||||
|
||||
let editbox = Rc::new(ComponentEditBox { base, data, state });
|
||||
editbox.state.borrow_mut().self_ref = Rc::downgrade(&editbox);
|
||||
|
||||
ess.layout.defer_component_refresh(Component(editbox.clone()));
|
||||
Ok((root, editbox))
|
||||
}
|
||||
@@ -9,6 +9,7 @@ use crate::{
|
||||
|
||||
pub mod button;
|
||||
pub mod checkbox;
|
||||
pub mod editbox;
|
||||
pub mod radio_group;
|
||||
pub mod slider;
|
||||
pub mod tabs;
|
||||
@@ -18,6 +19,11 @@ pub struct RefreshData<'a> {
|
||||
pub common: &'a mut CallbackDataCommon<'a>,
|
||||
}
|
||||
|
||||
pub struct FocusChangeData<'a> {
|
||||
pub common: &'a mut CallbackDataCommon<'a>,
|
||||
pub focused: bool,
|
||||
}
|
||||
|
||||
// common component data
|
||||
#[derive(Default)]
|
||||
pub struct ComponentBase {
|
||||
@@ -36,6 +42,7 @@ pub trait ComponentTrait: AnyTrait {
|
||||
fn base(&self) -> &ComponentBase;
|
||||
fn base_mut(&mut self) -> &mut ComponentBase;
|
||||
fn refresh(&self, data: &mut RefreshData);
|
||||
fn on_focus_change(&self, _data: &mut FocusChangeData) {}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
||||
@@ -324,7 +324,6 @@ fn register_event_mouse_enter(
|
||||
common.alterables.trigger_haptics();
|
||||
state.borrow_mut().hovered = true;
|
||||
on_enter_anim(common, data.slider_handle_rect_id, anim_mult);
|
||||
|
||||
ComponentTooltip::register_hover_in(common, &tooltip_info, event_data.widget_id, state.clone());
|
||||
|
||||
Ok(EventResult::Pass)
|
||||
@@ -388,6 +387,7 @@ fn register_event_mouse_press(
|
||||
EventListenerKind::MousePress,
|
||||
Box::new(move |common, event_data, (), ()| {
|
||||
common.alterables.trigger_haptics();
|
||||
common.alterables.unfocus();
|
||||
let mut state = state.borrow_mut();
|
||||
|
||||
let CallbackMetadata::MouseButton(btn) = event_data.metadata else {
|
||||
@@ -498,26 +498,20 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul
|
||||
active_tooltip: None,
|
||||
};
|
||||
|
||||
let globals = ess.layout.state.globals.clone();
|
||||
|
||||
let slider_text: Option<(WidgetPair, taffy::NodeId)> = if params.show_value {
|
||||
let pair = ess.layout.add_child(
|
||||
slider_handle.id,
|
||||
WidgetLabel::create(
|
||||
&mut globals.get(),
|
||||
WidgetLabelParams {
|
||||
content: Translation::default(),
|
||||
style: TextStyle {
|
||||
color: Some(drawing::Color::new(0.0, 0.0, 0.0, 1.0)), // always black
|
||||
weight: Some(FontWeight::Bold),
|
||||
align: Some(HorizontalAlign::Center),
|
||||
..Default::default()
|
||||
},
|
||||
let label = WidgetLabel::create(
|
||||
&mut ess.layout.state,
|
||||
WidgetLabelParams {
|
||||
content: Translation::default(),
|
||||
style: TextStyle {
|
||||
color: Some(drawing::Color::new(0.0, 0.0, 0.0, 1.0)), // always black
|
||||
weight: Some(FontWeight::Bold),
|
||||
align: Some(HorizontalAlign::Center),
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
Default::default(),
|
||||
)?;
|
||||
Some(pair)
|
||||
},
|
||||
);
|
||||
Some(ess.layout.add_child(slider_handle.id, label, Default::default())?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@@ -534,26 +528,20 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul
|
||||
let base = ComponentBase {
|
||||
id: root.id,
|
||||
lhandles: {
|
||||
let mut widget = ess.layout.state.widgets.get(body_id).unwrap().state();
|
||||
let anim_mult = ess.layout.state.globals.defaults().animation_mult;
|
||||
let listeners = &mut root.widget.state().event_listeners;
|
||||
let anim_mult = ess.layout.state.theme.animation_mult;
|
||||
vec![
|
||||
register_event_mouse_enter(
|
||||
data.clone(),
|
||||
state.clone(),
|
||||
&mut widget.event_listeners,
|
||||
params.tooltip,
|
||||
anim_mult,
|
||||
),
|
||||
register_event_mouse_leave(data.clone(), state.clone(), &mut widget.event_listeners, anim_mult),
|
||||
register_event_mouse_motion(data.clone(), state.clone(), &mut widget.event_listeners),
|
||||
register_event_mouse_press(data.clone(), state.clone(), &mut widget.event_listeners),
|
||||
register_event_mouse_release(state.clone(), &mut widget.event_listeners),
|
||||
register_event_mouse_enter(data.clone(), state.clone(), listeners, params.tooltip, anim_mult),
|
||||
register_event_mouse_leave(data.clone(), state.clone(), listeners, anim_mult),
|
||||
register_event_mouse_motion(data.clone(), state.clone(), listeners),
|
||||
register_event_mouse_press(data.clone(), state.clone(), listeners),
|
||||
register_event_mouse_release(state.clone(), listeners),
|
||||
]
|
||||
},
|
||||
};
|
||||
|
||||
let slider = Rc::new(ComponentSlider { base, data, state });
|
||||
|
||||
ess.layout.register_component_refresh(Component(slider.clone()));
|
||||
ess.layout.register_component_refresh(&Component(slider.clone()));
|
||||
Ok((root, slider))
|
||||
}
|
||||
@@ -67,8 +67,8 @@ impl ComponentTrait for ComponentTabs {
|
||||
impl State {
|
||||
fn select_entry(&mut self, common: &mut CallbackDataCommon, name: &Rc<str>) {
|
||||
let (color_accent, color_button) = {
|
||||
let def = common.state.globals.defaults();
|
||||
(def.accent_color, def.button_color)
|
||||
let theme = &common.state.theme;
|
||||
(theme.accent_color, theme.button_color)
|
||||
};
|
||||
|
||||
for entry in &self.mounted_entries {
|
||||
|
||||
@@ -163,8 +163,6 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul
|
||||
),
|
||||
};
|
||||
|
||||
let globals = ess.layout.state.globals.clone();
|
||||
|
||||
let (div, _) = ess.layout.add_child(
|
||||
ess.parent,
|
||||
WidgetDiv::create(),
|
||||
@@ -211,20 +209,17 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul
|
||||
},
|
||||
)?;
|
||||
|
||||
let (label, _) = ess.layout.add_child(
|
||||
rect.id,
|
||||
WidgetLabel::create(
|
||||
&mut globals.get(),
|
||||
WidgetLabelParams {
|
||||
content: params.info.text,
|
||||
style: TextStyle {
|
||||
weight: Some(FontWeight::Bold),
|
||||
..Default::default()
|
||||
},
|
||||
let label = WidgetLabel::create(
|
||||
&mut ess.layout.state,
|
||||
WidgetLabelParams {
|
||||
content: params.info.text,
|
||||
style: TextStyle {
|
||||
weight: Some(FontWeight::Bold),
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
Default::default(),
|
||||
)?;
|
||||
},
|
||||
);
|
||||
let (label, _) = ess.layout.add_child(rect.id, label, Default::default())?;
|
||||
|
||||
let data = Rc::new(Data { id_root: div.id });
|
||||
|
||||
@@ -246,7 +241,7 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul
|
||||
TooltipSide::Bottom => Vec2::new(0.0, 1.0),
|
||||
};
|
||||
|
||||
let anim_mult = ess.layout.state.globals.defaults().animation_mult;
|
||||
let anim_mult = ess.layout.state.theme.animation_mult;
|
||||
ess.layout.animations.add(Animation::new(
|
||||
rect.id,
|
||||
(10.0 * anim_mult) as u32,
|
||||
|
||||
+34
-17
@@ -19,10 +19,6 @@ use crate::{
|
||||
|
||||
use super::{layout::Layout, widget::DrawState};
|
||||
|
||||
pub struct ImageHandle {
|
||||
// to be implemented, will contain pixel data (RGB or RGBA) loaded via "ImageBank" or something by the gui
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq)]
|
||||
pub struct Boundary {
|
||||
pub pos: Vec2,
|
||||
@@ -254,14 +250,19 @@ pub fn has_overflow_clip(style: &taffy::Style) -> bool {
|
||||
style.overflow.x != taffy::Overflow::Visible || style.overflow.y != taffy::Overflow::Visible
|
||||
}
|
||||
|
||||
fn primitive_debug_rect(boundary: &Boundary, transform: &Mat4, color: drawing::Color) -> drawing::RenderPrimitive {
|
||||
fn primitive_debug_rect(
|
||||
boundary: &Boundary,
|
||||
transform: &Mat4,
|
||||
color: drawing::Color,
|
||||
border: f32,
|
||||
) -> drawing::RenderPrimitive {
|
||||
drawing::RenderPrimitive::Rectangle(
|
||||
PrimitiveExtent {
|
||||
boundary: *boundary,
|
||||
transform: *transform,
|
||||
},
|
||||
Rectangle {
|
||||
border: 1.0,
|
||||
border,
|
||||
border_color: color,
|
||||
color: Color::new(0.0, 0.0, 0.0, 0.0),
|
||||
..Default::default()
|
||||
@@ -277,12 +278,16 @@ pub fn push_transform_stack(
|
||||
) {
|
||||
let raw_dim = Vec2::new(l.size.width, l.size.height);
|
||||
let visual_dim = raw_dim + scroll_shift;
|
||||
let content_dim = Vec2::new(l.content_box_width(), l.content_box_height());
|
||||
let content_rel_pos = Vec2::new(l.content_box_x(), l.content_box_y());
|
||||
|
||||
transform_stack.push(stack::Transform {
|
||||
rel_pos: Vec2::new(l.location.x, l.location.y) - scroll_shift,
|
||||
transform: widget_state.data.transform,
|
||||
raw_dim,
|
||||
visual_dim,
|
||||
content_dim,
|
||||
content_rel_pos,
|
||||
abs_pos: Default::default(),
|
||||
transform_rel: Default::default(),
|
||||
});
|
||||
@@ -301,29 +306,33 @@ impl PushScissorStackResult {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if scissor has been pushed.
|
||||
/// Returns Some() if scissor has been pushed.
|
||||
pub fn push_scissor_stack(
|
||||
transform_stack: &mut TransformStack,
|
||||
scissor_stack: &mut ScissorStack,
|
||||
scroll_shift: Vec2,
|
||||
info: &Option<ScrollbarInfo>,
|
||||
style: &taffy::Style,
|
||||
) -> PushScissorStackResult {
|
||||
) -> Option<PushScissorStackResult> {
|
||||
let mut boundary_absolute = drawing::Boundary::construct_absolute(transform_stack);
|
||||
boundary_absolute.pos += scroll_shift;
|
||||
|
||||
let do_clip = info.is_some() && has_overflow_clip(style);
|
||||
|
||||
if !do_clip {
|
||||
return None; // Don't care
|
||||
}
|
||||
|
||||
scissor_stack.push(ScissorBoundary(boundary_absolute));
|
||||
|
||||
if scissor_stack.is_out_of_bounds() {
|
||||
return PushScissorStackResult::OutOfBounds;
|
||||
return Some(PushScissorStackResult::OutOfBounds);
|
||||
}
|
||||
|
||||
if do_clip {
|
||||
PushScissorStackResult::VisibleAndClip
|
||||
Some(PushScissorStackResult::VisibleAndClip)
|
||||
} else {
|
||||
PushScissorStackResult::VisibleDontClip
|
||||
Some(PushScissorStackResult::VisibleDontClip)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -368,14 +377,16 @@ fn draw_widget(
|
||||
&boundary,
|
||||
&state.transform_stack.get().transform,
|
||||
Color::new(0.0, 1.0, 1.0, 0.5),
|
||||
1.0,
|
||||
));
|
||||
}
|
||||
|
||||
let starting_scissor_set_count = internal.scissor_set_count;
|
||||
|
||||
let scissor_result = push_scissor_stack(state.transform_stack, state.scissor_stack, scroll_shift, &info, style);
|
||||
|
||||
if scissor_result == PushScissorStackResult::VisibleAndClip {
|
||||
if let Some(scissor_result) = &scissor_result
|
||||
&& *scissor_result == PushScissorStackResult::VisibleAndClip
|
||||
{
|
||||
if params.debug_draw {
|
||||
let mut boundary_relative = drawing::Boundary::construct_relative(state.transform_stack);
|
||||
boundary_relative.pos += scroll_shift;
|
||||
@@ -383,6 +394,7 @@ fn draw_widget(
|
||||
&boundary_relative,
|
||||
&state.transform_stack.get().transform,
|
||||
Color::new(1.0, 0.0, 1.0, 1.0),
|
||||
3.0,
|
||||
));
|
||||
}
|
||||
state
|
||||
@@ -397,12 +409,17 @@ fn draw_widget(
|
||||
style,
|
||||
};
|
||||
|
||||
if scissor_result.should_display() {
|
||||
if scissor_result
|
||||
.as_ref()
|
||||
.is_none_or(PushScissorStackResult::should_display)
|
||||
{
|
||||
widget_state.draw_all(state, &draw_params);
|
||||
draw_children(params, state, node_id, internal, false);
|
||||
}
|
||||
|
||||
state.scissor_stack.pop();
|
||||
if scissor_result.is_some() {
|
||||
state.scissor_stack.pop();
|
||||
}
|
||||
|
||||
let current_scissor_set_count = internal.scissor_set_count;
|
||||
|
||||
@@ -412,12 +429,12 @@ fn draw_widget(
|
||||
.push(drawing::RenderPrimitive::ScissorSet(*state.scissor_stack.get()));
|
||||
}
|
||||
|
||||
state.transform_stack.pop();
|
||||
|
||||
if let Some(info) = &info {
|
||||
widget_state.draw_scrollbars(state, &draw_params, info);
|
||||
}
|
||||
|
||||
state.transform_stack.pop();
|
||||
|
||||
if wants_redraw {
|
||||
state.alterables.mark_redraw();
|
||||
}
|
||||
|
||||
+36
-27
@@ -1,7 +1,8 @@
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
cell::{Ref, RefMut},
|
||||
cell::RefMut,
|
||||
collections::HashSet,
|
||||
rc::{Rc, Weak},
|
||||
};
|
||||
|
||||
use glam::Vec2;
|
||||
@@ -9,9 +10,10 @@ use slotmap::{DenseSlotMap, new_key_type};
|
||||
|
||||
use crate::{
|
||||
animation::{self, Animation},
|
||||
components::{ComponentTrait, ComponentWeak},
|
||||
globals,
|
||||
i18n::I18n,
|
||||
layout::{LayoutState, LayoutTask, WidgetID},
|
||||
layout::{LayoutDispatchFunc, LayoutState, LayoutTask, WidgetID},
|
||||
sound::WguiSoundType,
|
||||
stack::{ScissorStack, Transform, TransformStack},
|
||||
widget::{EventResult, WidgetData, WidgetObj},
|
||||
@@ -25,7 +27,7 @@ pub enum MouseButtonIndex {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct MouseButton {
|
||||
pub struct MouseButtonEvent {
|
||||
pub index: MouseButtonIndex,
|
||||
pub pos: Vec2,
|
||||
pub device: usize,
|
||||
@@ -37,12 +39,6 @@ pub struct MousePosition {
|
||||
pub device: usize,
|
||||
}
|
||||
|
||||
pub struct MouseDownEvent {
|
||||
pub pos: Vec2,
|
||||
pub index: MouseButtonIndex,
|
||||
pub device: usize,
|
||||
}
|
||||
|
||||
pub struct MouseLeaveEvent {
|
||||
pub device: usize,
|
||||
}
|
||||
@@ -52,29 +48,29 @@ pub struct MouseMotionEvent {
|
||||
pub device: usize,
|
||||
}
|
||||
|
||||
pub struct MouseUpEvent {
|
||||
pub pos: Vec2,
|
||||
pub index: MouseButtonIndex,
|
||||
pub device: usize,
|
||||
}
|
||||
|
||||
pub struct MouseWheelEvent {
|
||||
pub pos: Vec2, /* mouse position */
|
||||
pub delta: Vec2, /* wheel delta */
|
||||
pub device: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TextInputEvent {
|
||||
pub text: Option<Rc<str>>,
|
||||
}
|
||||
|
||||
pub struct InternalStateChangeEvent {
|
||||
pub metadata: usize,
|
||||
}
|
||||
|
||||
pub enum Event {
|
||||
InternalStateChange(InternalStateChangeEvent),
|
||||
MouseDown(MouseDownEvent),
|
||||
MouseDown(MouseButtonEvent),
|
||||
MouseLeave(MouseLeaveEvent),
|
||||
MouseMotion(MouseMotionEvent),
|
||||
MouseUp(MouseUpEvent),
|
||||
MouseUp(MouseButtonEvent),
|
||||
MouseWheel(MouseWheelEvent),
|
||||
TextInput(TextInputEvent),
|
||||
}
|
||||
|
||||
impl Event {
|
||||
@@ -101,12 +97,14 @@ pub enum StyleSetRequest {
|
||||
Margin(taffy::Rect<taffy::LengthPercentageAuto>),
|
||||
Width(taffy::Dimension),
|
||||
Height(taffy::Dimension),
|
||||
Size(taffy::Size<taffy::Dimension>),
|
||||
}
|
||||
|
||||
// alterables which will be dispatched in the next loop iteration phase
|
||||
#[derive(Default)]
|
||||
pub struct EventAlterables {
|
||||
pub dirty_widgets: Vec<WidgetID>,
|
||||
pub components_to_refresh_once: Vec<ComponentWeak>,
|
||||
pub style_set_requests: Vec<(WidgetID, StyleSetRequest)>,
|
||||
pub animations: Vec<animation::Animation>,
|
||||
pub widgets_to_tick: HashSet<WidgetID>, // widgets which needs to be ticked in the next `Layout::update()` fn
|
||||
@@ -147,8 +145,20 @@ impl EventAlterables {
|
||||
self.tasks.push(LayoutTask::PlaySound(sound_type));
|
||||
}
|
||||
|
||||
pub fn dispatch(&mut self, func: Box<dyn FnOnce(&mut CallbackDataCommon) -> anyhow::Result<()>>) {
|
||||
self.tasks.push(LayoutTask::Dispatch(func))
|
||||
pub fn dispatch(&mut self, func: LayoutDispatchFunc) {
|
||||
self.tasks.push(LayoutTask::Dispatch(func));
|
||||
}
|
||||
|
||||
pub fn focus<ComponentType: ComponentTrait>(&mut self, component: &Weak<ComponentType>) {
|
||||
self.tasks.push(LayoutTask::SetFocus(component.clone()));
|
||||
}
|
||||
|
||||
pub fn unfocus(&mut self) {
|
||||
self.tasks.push(LayoutTask::Unfocus);
|
||||
}
|
||||
|
||||
pub fn refresh_component_once<ComponentType: ComponentTrait>(&mut self, component: &Weak<ComponentType>) {
|
||||
self.components_to_refresh_once.push(component.clone());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,10 +181,6 @@ impl CallbackDataCommon<'_> {
|
||||
pub fn globals(&self) -> RefMut<'_, globals::Globals> {
|
||||
self.state.globals.get()
|
||||
}
|
||||
|
||||
pub fn defaults(&self) -> Ref<'_, globals::Defaults> {
|
||||
self.state.globals.defaults()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CallbackData<'a> {
|
||||
@@ -187,8 +193,9 @@ pub struct CallbackData<'a> {
|
||||
|
||||
pub enum CallbackMetadata {
|
||||
None,
|
||||
MouseButton(MouseButton),
|
||||
MouseButton(MouseButtonEvent),
|
||||
MousePosition(MousePosition),
|
||||
TextInput(TextInputEvent),
|
||||
Custom(usize),
|
||||
}
|
||||
|
||||
@@ -197,9 +204,10 @@ impl CallbackMetadata {
|
||||
|
||||
pub const fn get_mouse_pos_absolute(&self) -> Option<Vec2> {
|
||||
match *self {
|
||||
Self::MouseButton(b) => Some(b.pos),
|
||||
Self::MousePosition(b) => Some(b.pos),
|
||||
Self::Custom(_) | Self::None => None,
|
||||
CallbackMetadata::MouseButton(b) => Some(b.pos),
|
||||
CallbackMetadata::MousePosition(b) => Some(b.pos),
|
||||
CallbackMetadata::Custom(_) | CallbackMetadata::None => None,
|
||||
CallbackMetadata::TextInput(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,6 +224,7 @@ pub enum EventListenerKind {
|
||||
MouseEnter,
|
||||
MouseMotion,
|
||||
MouseLeave,
|
||||
TextInput,
|
||||
InternalStateChange,
|
||||
}
|
||||
|
||||
|
||||
+5
-44
@@ -1,5 +1,5 @@
|
||||
use std::{
|
||||
cell::{Ref, RefCell, RefMut},
|
||||
cell::{RefCell, RefMut},
|
||||
io::Read,
|
||||
path::PathBuf,
|
||||
rc::Rc,
|
||||
@@ -10,52 +10,18 @@ use anyhow::Context;
|
||||
use regex::Regex;
|
||||
|
||||
use crate::{
|
||||
assets::{AssetPath, AssetProvider},
|
||||
assets_internal, drawing,
|
||||
assets::{AssetPath, AssetProvider, LangProvider},
|
||||
assets_internal,
|
||||
font_config::{WguiFontConfig, WguiFontSystem},
|
||||
i18n::I18n,
|
||||
renderer_vk::text::custom_glyph::CustomGlyphCache,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Defaults {
|
||||
pub dark_mode: bool,
|
||||
pub text_color: drawing::Color,
|
||||
pub button_color: drawing::Color,
|
||||
pub accent_color: drawing::Color,
|
||||
pub danger_color: drawing::Color,
|
||||
pub faded_color: drawing::Color,
|
||||
pub bg_color: drawing::Color,
|
||||
pub translucent_alpha: f32,
|
||||
pub animation_mult: f32,
|
||||
pub rounding_mult: f32,
|
||||
pub gradient_intensity: f32, // currently used for buttons
|
||||
}
|
||||
|
||||
impl Default for Defaults {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
dark_mode: true,
|
||||
text_color: drawing::Color::new(1.0, 1.0, 1.0, 1.0),
|
||||
button_color: drawing::Color::new(1.0, 1.0, 1.0, 0.02),
|
||||
accent_color: drawing::Color::new(0.13, 0.68, 1.0, 1.0),
|
||||
danger_color: drawing::Color::new(0.9, 0.0, 0.0, 1.0),
|
||||
faded_color: drawing::Color::new(0.67, 0.74, 0.80, 1.0),
|
||||
bg_color: drawing::Color::new(0.0, 0.07, 0.1, 0.75),
|
||||
translucent_alpha: 0.5,
|
||||
animation_mult: 1.0,
|
||||
rounding_mult: 1.0,
|
||||
gradient_intensity: 0.3,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Globals {
|
||||
pub assets_internal: Box<dyn AssetProvider>,
|
||||
pub assets_builtin: Box<dyn AssetProvider>,
|
||||
pub asset_folder: PathBuf,
|
||||
pub i18n_builtin: I18n,
|
||||
pub defaults: Defaults,
|
||||
pub font_system: WguiFontSystem,
|
||||
pub custom_glyph_cache: CustomGlyphCache,
|
||||
}
|
||||
@@ -66,17 +32,16 @@ pub struct WguiGlobals(Rc<RefCell<Globals>>);
|
||||
impl WguiGlobals {
|
||||
pub fn new(
|
||||
mut assets_builtin: Box<dyn AssetProvider>,
|
||||
defaults: Defaults,
|
||||
lang_provider: &dyn LangProvider,
|
||||
font_config: &WguiFontConfig,
|
||||
asset_folder: PathBuf,
|
||||
) -> anyhow::Result<Self> {
|
||||
let i18n_builtin = I18n::new(&mut assets_builtin)?;
|
||||
let i18n_builtin = I18n::new(assets_builtin.as_mut(), lang_provider)?;
|
||||
let assets_internal = Box::new(assets_internal::AssetInternal {});
|
||||
|
||||
Ok(Self(Rc::new(RefCell::new(Globals {
|
||||
assets_internal,
|
||||
assets_builtin,
|
||||
defaults,
|
||||
asset_folder,
|
||||
font_system: WguiFontSystem::new(font_config, i18n_builtin.get_locale()),
|
||||
i18n_builtin,
|
||||
@@ -125,10 +90,6 @@ impl WguiGlobals {
|
||||
RefMut::map(self.0.borrow_mut(), |x| &mut x.i18n_builtin)
|
||||
}
|
||||
|
||||
pub fn defaults(&self) -> Ref<'_, Defaults> {
|
||||
Ref::map(self.0.borrow(), |x| &x.defaults)
|
||||
}
|
||||
|
||||
pub fn assets_internal(&self) -> RefMut<'_, Box<dyn AssetProvider>> {
|
||||
RefMut::map(self.0.borrow_mut(), |x| &mut x.assets_internal)
|
||||
}
|
||||
|
||||
+51
-34
@@ -2,7 +2,7 @@ use std::{fmt::Display, rc::Rc, str};
|
||||
|
||||
use anyhow::Context;
|
||||
|
||||
use crate::assets::AssetProvider;
|
||||
use crate::assets::{AssetProvider, LangProvider};
|
||||
|
||||
// a string which optionally has translation key in it
|
||||
// it will hopefully support dynamic language changing soon
|
||||
@@ -63,50 +63,70 @@ pub struct Locale {
|
||||
matched: String,
|
||||
}
|
||||
|
||||
pub trait LangsList {
|
||||
fn all_locale(&self) -> &'static [&'static str]; // "en", "de", "es" (...)
|
||||
fn default_lang(&self) -> &'static str; // "en"
|
||||
}
|
||||
|
||||
impl Locale {
|
||||
pub fn all_locale() -> &'static [&'static str] {
|
||||
&["de", "en", "es", "ja", "it", "pl", "zh_CN"]
|
||||
}
|
||||
pub fn default_lang() -> &'static str {
|
||||
"en"
|
||||
}
|
||||
fn match_locale<'o>(lang: &str, region: Option<&str>, all_locales: &[&'o str]) -> &'o str {
|
||||
fn match_locale<'o>(
|
||||
default_lang: &'static str,
|
||||
lang: &str,
|
||||
region: Option<&str>,
|
||||
all_locales: &[&'o str],
|
||||
) -> &'o str {
|
||||
if let Some(region) = region {
|
||||
let locale_str = format!("{lang}_{region}");
|
||||
if let Some(locale) = all_locales.iter().find(|&&l| l == locale_str) {
|
||||
return locale;
|
||||
}
|
||||
log::warn!("Unsupported locale \"{locale_str}\", trying \"{lang}\".");
|
||||
};
|
||||
}
|
||||
|
||||
if let Some(locale) = all_locales.iter().find(|&&l| l == lang) {
|
||||
return locale;
|
||||
}
|
||||
|
||||
|
||||
let prefix = format!("{lang}_");
|
||||
if let Some(locale) = all_locales.iter().find(|&&l| l.starts_with(&prefix)) {
|
||||
return locale;
|
||||
}
|
||||
|
||||
let locale = Self::default_lang();
|
||||
log::warn!("Unsupported language \"{lang}\", defaulting to \"{locale}\".");
|
||||
locale
|
||||
log::warn!("Unsupported language \"{lang}\", defaulting to \"{default_lang}\".");
|
||||
default_lang
|
||||
}
|
||||
pub fn new(lang: String, region: Option<String>) -> Self {
|
||||
let matched = Self::match_locale(&lang, region.as_deref(), Self::all_locale()).to_string();
|
||||
|
||||
pub fn new(langs_list: &dyn LangsList, lang: String, region: Option<String>) -> Self {
|
||||
let matched = Self::match_locale(
|
||||
langs_list.default_lang(),
|
||||
&lang,
|
||||
region.as_deref(),
|
||||
langs_list.all_locale(),
|
||||
)
|
||||
.to_string();
|
||||
Self { lang, region, matched }
|
||||
}
|
||||
pub fn parse_str(locale: &str) -> Self {
|
||||
let base = locale.split(|c| c == '.' || c == '@').next().unwrap_or(locale);
|
||||
let parts: Vec<&str> = base.split(|c| c == '_' || c == '-').collect();
|
||||
|
||||
pub fn parse_str(langs_list: &dyn LangsList, locale: &str) -> Self {
|
||||
let base = locale.split(['.', '@']).next().unwrap_or(locale);
|
||||
let parts: Vec<&str> = base.split(['_', '-']).collect();
|
||||
// Ensures the format is lang_REGION
|
||||
match parts.as_slice() {
|
||||
[lang, region, ..] => Self::new(lang.to_lowercase(), Some(region.to_uppercase())),
|
||||
[lang] if !lang.is_empty() => Self::new(lang.to_lowercase(), None),
|
||||
_ => Self::new("en".to_string(), None),
|
||||
[lang, region, ..] => Self::new(langs_list, lang.to_lowercase(), Some(region.to_uppercase())),
|
||||
[lang] if !lang.is_empty() => Self::new(langs_list, lang.to_lowercase(), None),
|
||||
_ => Self::new(langs_list, langs_list.default_lang().to_string(), None),
|
||||
}
|
||||
}
|
||||
pub fn from_env() -> Self {
|
||||
|
||||
pub fn from_env(lang_provider: &dyn LangProvider) -> Self {
|
||||
let default_lang = lang_provider.langs_list().default_lang();
|
||||
|
||||
// check if forced language is set
|
||||
if let Some(forced_lang) = lang_provider.forced_lang() {
|
||||
return Self::parse_str(lang_provider.langs_list(), forced_lang);
|
||||
}
|
||||
|
||||
// fallback to environment variables
|
||||
use std::env;
|
||||
let vars = ["LC_ALL", "LC_MESSAGES", "LANG"];
|
||||
let full_locale = vars
|
||||
@@ -114,13 +134,10 @@ impl Locale {
|
||||
.find_map(|&v| env::var(v).ok())
|
||||
.filter(|v| !v.is_empty() && v != "C" && v != "POSIX")
|
||||
.unwrap_or_else(|| {
|
||||
log::warn!(
|
||||
"LC_ALL/LC_MESSAGES/LANG is not set, defaulting to \"{}\"",
|
||||
Self::default_lang()
|
||||
);
|
||||
Self::default_lang().to_string()
|
||||
log::warn!("LC_ALL/LC_MESSAGES/LANG is not set, defaulting to \"{default_lang}\"");
|
||||
default_lang.to_string()
|
||||
});
|
||||
Self::parse_str(&full_locale)
|
||||
Self::parse_str(lang_provider.langs_list(), &full_locale)
|
||||
}
|
||||
pub fn get_matched(&self) -> &str {
|
||||
&self.matched
|
||||
@@ -155,13 +172,13 @@ fn find_translation<'a>(translation: &str, mut val: &'a serde_json::Value) -> Op
|
||||
}
|
||||
|
||||
impl I18n {
|
||||
pub fn new(provider: &mut Box<dyn AssetProvider>) -> anyhow::Result<Self> {
|
||||
let locale = Locale::from_env();
|
||||
pub fn new(asset_provider: &mut dyn AssetProvider, lang_provider: &dyn LangProvider) -> anyhow::Result<Self> {
|
||||
let locale = Locale::from_env(lang_provider);
|
||||
log::info!("Guessed system language: {locale}");
|
||||
|
||||
let data_english = provider.load_from_path("lang/en.json")?;
|
||||
let data_english = asset_provider.load_from_path("lang/en.json")?;
|
||||
let path = format!("lang/{}.json", locale.get_matched());
|
||||
let data_translated = provider
|
||||
let data_translated = asset_provider
|
||||
.load_from_path(&path)
|
||||
.with_context(|| path.clone())
|
||||
.context("Could not load translation file")?;
|
||||
@@ -189,7 +206,7 @@ impl I18n {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_locale(&self) -> &Locale {
|
||||
pub const fn get_locale(&self) -> &Locale {
|
||||
&self.locale
|
||||
}
|
||||
|
||||
@@ -202,7 +219,7 @@ impl I18n {
|
||||
}
|
||||
|
||||
if let Some(translated_fallback) = find_translation(translation_key, &self.json_root_fallback) {
|
||||
log::warn!("missing translation for key \"{translation_key}\", using \"en\" instead");
|
||||
log::warn!("missing translation for key \"{translation_key}\", using fallback instead");
|
||||
return Rc::from(format_translated(translated_fallback, sections));
|
||||
}
|
||||
|
||||
|
||||
+87
-26
@@ -7,18 +7,21 @@ use std::{
|
||||
|
||||
use crate::{
|
||||
animation::Animations,
|
||||
components::{Component, RefreshData},
|
||||
drawing::{self, ANSI_BOLD_CODE, ANSI_RESET_CODE, Boundary, push_scissor_stack, push_transform_stack},
|
||||
components::{self, Component, ComponentWeak, FocusChangeData, RefreshData},
|
||||
drawing::{
|
||||
self, ANSI_BOLD_CODE, ANSI_RESET_CODE, Boundary, PushScissorStackResult, push_scissor_stack, push_transform_stack,
|
||||
},
|
||||
event::{self, CallbackDataCommon, EventAlterables},
|
||||
globals::WguiGlobals,
|
||||
sound::WguiSoundType,
|
||||
task::Tasks,
|
||||
theme::WguiTheme,
|
||||
widget::{self, EventParams, EventResult, WidgetObj, WidgetState, WidgetStateFlags, div::WidgetDiv},
|
||||
};
|
||||
|
||||
use anyhow::Context;
|
||||
use glam::{Vec2, vec2};
|
||||
use slotmap::{HopSlotMap, SecondaryMap, new_key_type};
|
||||
use slotmap::{SecondaryMap, SlotMap, new_key_type};
|
||||
use taffy::{NodeId, TaffyTree, TraversePartialTree};
|
||||
|
||||
new_key_type! {
|
||||
@@ -57,7 +60,7 @@ impl WeakWidget {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WidgetMap(HopSlotMap<WidgetID, Widget>);
|
||||
pub struct WidgetMap(SlotMap<WidgetID, Widget>);
|
||||
pub type WidgetNodeMap = SecondaryMap<WidgetID, taffy::NodeId>;
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -68,7 +71,7 @@ pub struct WidgetPair {
|
||||
|
||||
impl WidgetMap {
|
||||
fn new() -> Self {
|
||||
Self(HopSlotMap::with_key())
|
||||
Self(SlotMap::<WidgetID, Widget>::with_key())
|
||||
}
|
||||
|
||||
pub fn get_as<T: 'static>(&self, handle: WidgetID) -> Option<RefMut<'_, T>> {
|
||||
@@ -116,6 +119,7 @@ impl WidgetMap {
|
||||
|
||||
pub struct LayoutState {
|
||||
pub globals: WguiGlobals,
|
||||
pub theme: Rc<WguiTheme>,
|
||||
pub widgets: WidgetMap,
|
||||
pub nodes: WidgetNodeMap,
|
||||
pub tree: taffy::tree::TaffyTree<WidgetID>,
|
||||
@@ -134,14 +138,17 @@ pub struct LayoutUpdateResult {
|
||||
pub sounds_to_play: Vec<WguiSoundType>,
|
||||
}
|
||||
|
||||
pub type ModifyLayoutStateFunc = Box<dyn FnOnce(ModifyLayoutStateData) -> anyhow::Result<()>>;
|
||||
pub type LayoutModifyStateFunc = Box<dyn FnOnce(ModifyLayoutStateData) -> anyhow::Result<()>>;
|
||||
pub type LayoutDispatchFunc = Box<dyn FnOnce(&mut CallbackDataCommon) -> anyhow::Result<()>>;
|
||||
|
||||
pub enum LayoutTask {
|
||||
RemoveWidget(WidgetID),
|
||||
SetWidgetStyle(WidgetID, event::StyleSetRequest),
|
||||
ModifyLayoutState(ModifyLayoutStateFunc),
|
||||
ModifyLayoutState(LayoutModifyStateFunc),
|
||||
PlaySound(WguiSoundType),
|
||||
Dispatch(Box<dyn FnOnce(&mut CallbackDataCommon) -> anyhow::Result<()>>),
|
||||
Dispatch(LayoutDispatchFunc),
|
||||
SetFocus(ComponentWeak),
|
||||
Unfocus,
|
||||
}
|
||||
|
||||
pub type LayoutTasks = Tasks<LayoutTask>;
|
||||
@@ -152,8 +159,9 @@ pub struct Layout {
|
||||
pub tasks: LayoutTasks,
|
||||
|
||||
components_to_refresh_once: HashSet<Component>,
|
||||
registered_components_to_refresh: HashMap<taffy::NodeId, Component>,
|
||||
registered_components_to_refresh: HashMap<taffy::NodeId, ComponentWeak>,
|
||||
sounds_to_play_once: Vec<WguiSoundType>,
|
||||
focused_component: Option<ComponentWeak>,
|
||||
|
||||
pub widgets_to_tick: Vec<WidgetID>,
|
||||
|
||||
@@ -179,6 +187,7 @@ pub struct Layout {
|
||||
#[derive(Default)]
|
||||
pub struct LayoutParams {
|
||||
pub resize_to_parent: bool,
|
||||
pub theme: Rc<WguiTheme>,
|
||||
}
|
||||
|
||||
fn add_child_internal(
|
||||
@@ -358,14 +367,14 @@ impl Layout {
|
||||
}
|
||||
|
||||
// call ComponentTrait::refresh() *every time time* the layout is dirty
|
||||
pub fn register_component_refresh(&mut self, component: Component) {
|
||||
pub fn register_component_refresh(&mut self, component: &Component) {
|
||||
let widget_id = component.0.base().get_id();
|
||||
let Some(node_id) = self.state.nodes.get(widget_id) else {
|
||||
debug_assert!(false);
|
||||
return;
|
||||
};
|
||||
|
||||
self.registered_components_to_refresh.insert(*node_id, component);
|
||||
self.registered_components_to_refresh.insert(*node_id, component.weak());
|
||||
}
|
||||
|
||||
/// Convenience function to avoid repeated `WidgetID` → `WidgetState` lookups.
|
||||
@@ -455,7 +464,10 @@ impl Layout {
|
||||
style,
|
||||
);
|
||||
|
||||
if scissor_result.should_display() {
|
||||
if scissor_result
|
||||
.as_ref()
|
||||
.is_none_or(PushScissorStackResult::should_display)
|
||||
{
|
||||
// check children first
|
||||
self.push_event_children(node_id, event, event_result, alterables, user_data)?;
|
||||
|
||||
@@ -472,7 +484,9 @@ impl Layout {
|
||||
}
|
||||
}
|
||||
|
||||
alterables.scissor_stack.pop();
|
||||
if scissor_result.is_some() {
|
||||
alterables.scissor_stack.pop();
|
||||
}
|
||||
alterables.transform_stack.pop();
|
||||
|
||||
Ok(())
|
||||
@@ -515,12 +529,13 @@ impl Layout {
|
||||
Ok(event_result)
|
||||
}
|
||||
|
||||
pub fn new(globals: WguiGlobals, params: &LayoutParams) -> anyhow::Result<Self> {
|
||||
pub fn new(globals: WguiGlobals, params: LayoutParams) -> anyhow::Result<Self> {
|
||||
let mut state = LayoutState {
|
||||
tree: TaffyTree::new(),
|
||||
widgets: WidgetMap::new(),
|
||||
nodes: WidgetNodeMap::default(),
|
||||
globals,
|
||||
theme: params.theme,
|
||||
};
|
||||
|
||||
let size = if params.resize_to_parent {
|
||||
@@ -581,6 +596,7 @@ impl Layout {
|
||||
widgets_to_tick: Vec::new(),
|
||||
tasks: LayoutTasks::new(),
|
||||
sounds_to_play_once: Vec::new(),
|
||||
focused_component: None,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -590,8 +606,10 @@ impl Layout {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(component) = self.registered_components_to_refresh.get(&node_id) {
|
||||
to_refresh.push(component.clone());
|
||||
if let Some(component) = self.registered_components_to_refresh.get(&node_id)
|
||||
&& let Some(c) = component.upgrade()
|
||||
{
|
||||
to_refresh.push(Component(c));
|
||||
}
|
||||
|
||||
for child_id in self.state.tree.child_ids(node_id) {
|
||||
@@ -708,15 +726,48 @@ impl Layout {
|
||||
c.finish()?;
|
||||
}
|
||||
LayoutTask::SetWidgetStyle(widget_id, style_request) => {
|
||||
self.set_style_request(widget_id, style_request);
|
||||
self.set_style_request(widget_id, &style_request);
|
||||
}
|
||||
LayoutTask::SetFocus(weak) => {
|
||||
if let Some(c) = weak.upgrade() {
|
||||
self.set_focus(Some(&components::Component(c)))?;
|
||||
}
|
||||
}
|
||||
LayoutTask::Unfocus => self.set_focus(None)?,
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_style_request(&mut self, widget_id: WidgetID, style_request: event::StyleSetRequest) {
|
||||
pub fn set_focus(&mut self, to_focus: Option<&Component>) -> anyhow::Result<()> {
|
||||
let mut c = self.start_common();
|
||||
|
||||
if let Some(focused) = &c.layout.focused_component
|
||||
&& let Some(focused) = focused.upgrade()
|
||||
{
|
||||
// Unfocus
|
||||
focused.on_focus_change(&mut FocusChangeData {
|
||||
common: &mut c.common(),
|
||||
focused: false,
|
||||
});
|
||||
c.layout.focused_component = None;
|
||||
}
|
||||
|
||||
if let Some(to_focus) = to_focus {
|
||||
to_focus.0.on_focus_change(&mut FocusChangeData {
|
||||
common: &mut c.common(),
|
||||
focused: true,
|
||||
});
|
||||
|
||||
c.layout.focused_component = Some(to_focus.weak());
|
||||
}
|
||||
|
||||
c.finish()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_style_request(&mut self, widget_id: WidgetID, style_request: &event::StyleSetRequest) {
|
||||
let Some(node_id) = self.state.nodes.get(widget_id) else {
|
||||
return;
|
||||
};
|
||||
@@ -728,22 +779,26 @@ impl Layout {
|
||||
match style_request {
|
||||
event::StyleSetRequest::Display(display) => {
|
||||
// refresh the component in case if visibility/display mode has changed
|
||||
if cur_style.display != display
|
||||
if cur_style.display != *display
|
||||
&& let Some(component) = self.registered_components_to_refresh.get(node_id)
|
||||
&& let Some(c) = component.upgrade()
|
||||
{
|
||||
self.components_to_refresh_once.insert(component.clone());
|
||||
self.components_to_refresh_once.insert(Component(c));
|
||||
}
|
||||
|
||||
cur_style.display = display;
|
||||
cur_style.display = *display;
|
||||
}
|
||||
event::StyleSetRequest::Margin(margin) => {
|
||||
cur_style.margin = margin;
|
||||
cur_style.margin = *margin;
|
||||
}
|
||||
event::StyleSetRequest::Width(val) => {
|
||||
cur_style.size.width = val;
|
||||
cur_style.size.width = *val;
|
||||
}
|
||||
event::StyleSetRequest::Height(val) => {
|
||||
cur_style.size.height = val;
|
||||
cur_style.size.height = *val;
|
||||
}
|
||||
event::StyleSetRequest::Size(size) => {
|
||||
cur_style.size = *size;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -786,8 +841,14 @@ impl Layout {
|
||||
}
|
||||
}
|
||||
|
||||
for (widget_id, style_request) in alterables.style_set_requests {
|
||||
self.set_style_request(widget_id, style_request);
|
||||
for c in alterables.components_to_refresh_once {
|
||||
if let Some(c) = c.upgrade() {
|
||||
self.defer_component_refresh(components::Component(c));
|
||||
}
|
||||
}
|
||||
|
||||
for (widget_id, style_request) in &alterables.style_set_requests {
|
||||
self.set_style_request(*widget_id, style_request);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
+3
-1
@@ -18,7 +18,8 @@
|
||||
clippy::needless_pass_by_ref_mut,
|
||||
clippy::use_self,
|
||||
clippy::match_same_arms,
|
||||
clippy::too_many_lines
|
||||
clippy::too_many_lines,
|
||||
clippy::struct_excessive_bools
|
||||
)]
|
||||
|
||||
pub mod animation;
|
||||
@@ -39,6 +40,7 @@ pub mod renderer_vk;
|
||||
pub mod sound;
|
||||
pub mod stack;
|
||||
pub mod task;
|
||||
pub mod theme;
|
||||
pub mod widget;
|
||||
pub mod windowing;
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ pub fn parse_component_button<'a>(
|
||||
key,
|
||||
value,
|
||||
&mut round,
|
||||
ctx.doc_params.globals.defaults().rounding_mult,
|
||||
ctx.layout.state.theme.rounding_mult,
|
||||
);
|
||||
}
|
||||
"color" => {
|
||||
|
||||
@@ -91,6 +91,7 @@ pub fn parse_component_checkbox(
|
||||
radio_group,
|
||||
value: component_value,
|
||||
tooltip: tooltip.get_info(),
|
||||
color_checked: None,
|
||||
},
|
||||
)?;
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
use crate::{
|
||||
components::{Component, editbox},
|
||||
layout::WidgetID,
|
||||
parser::{AttribPair, ParserContext, process_component, style::parse_style},
|
||||
widget::ConstructEssentials,
|
||||
};
|
||||
|
||||
pub fn parse_component_editbox(
|
||||
ctx: &mut ParserContext,
|
||||
parent_id: WidgetID,
|
||||
attribs: &[AttribPair],
|
||||
tag_name: &str,
|
||||
) -> anyhow::Result<WidgetID> {
|
||||
let mut initial_text = String::new();
|
||||
|
||||
let style = parse_style(ctx, attribs, tag_name);
|
||||
|
||||
for pair in attribs {
|
||||
let (key, value) = (pair.attrib.as_ref(), pair.value.as_ref());
|
||||
#[allow(clippy::single_match)]
|
||||
match key {
|
||||
"text" => {
|
||||
initial_text = String::from(value);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
let (widget, component) = editbox::construct(
|
||||
&mut ConstructEssentials {
|
||||
layout: ctx.layout,
|
||||
parent: parent_id,
|
||||
},
|
||||
editbox::Params { style, initial_text },
|
||||
)?;
|
||||
|
||||
process_component(ctx, Component(component), widget.id, attribs);
|
||||
|
||||
Ok(widget.id)
|
||||
}
|
||||
+10
-7
@@ -1,5 +1,6 @@
|
||||
mod component_button;
|
||||
mod component_checkbox;
|
||||
mod component_editbox;
|
||||
mod component_radio_group;
|
||||
mod component_slider;
|
||||
mod component_tabs;
|
||||
@@ -22,6 +23,7 @@ use crate::{
|
||||
parser::{
|
||||
component_button::parse_component_button,
|
||||
component_checkbox::{CheckboxKind, parse_component_checkbox},
|
||||
component_editbox::parse_component_editbox,
|
||||
component_radio_group::parse_component_radio_group,
|
||||
component_slider::parse_component_slider,
|
||||
component_tabs::parse_component_tabs,
|
||||
@@ -460,7 +462,7 @@ impl ParserContext<'_> {
|
||||
}
|
||||
|
||||
fn populate_theme_variables(&mut self) {
|
||||
let def = self.doc_params.globals.defaults();
|
||||
let theme = self.layout.state.theme.clone();
|
||||
|
||||
macro_rules! insert_color_vars {
|
||||
($self:expr, $name:literal, $field:expr, $alpha:expr) => {
|
||||
@@ -477,11 +479,11 @@ impl ParserContext<'_> {
|
||||
};
|
||||
}
|
||||
|
||||
insert_color_vars!(self, "text", def.text_color, def.translucent_alpha);
|
||||
insert_color_vars!(self, "accent", def.accent_color, def.translucent_alpha);
|
||||
insert_color_vars!(self, "danger", def.danger_color, def.translucent_alpha);
|
||||
insert_color_vars!(self, "faded", def.faded_color, def.translucent_alpha);
|
||||
insert_color_vars!(self, "bg", def.bg_color, def.translucent_alpha);
|
||||
insert_color_vars!(self, "text", theme.text_color, theme.translucent_alpha);
|
||||
insert_color_vars!(self, "accent", theme.accent_color, theme.translucent_alpha);
|
||||
insert_color_vars!(self, "danger", theme.danger_color, theme.translucent_alpha);
|
||||
insert_color_vars!(self, "faded", theme.faded_color, theme.translucent_alpha);
|
||||
insert_color_vars!(self, "bg", theme.bg_color, theme.translucent_alpha);
|
||||
}
|
||||
|
||||
fn print_invalid_attrib(&self, tag_name: &str, key: &str, value: &str) {
|
||||
@@ -1053,6 +1055,7 @@ fn parse_child<'a>(
|
||||
file, ctx, child_node, parent_id, &attribs, tag_name,
|
||||
)?);
|
||||
}
|
||||
"EditBox" => new_widget_id = Some(parse_component_editbox(ctx, parent_id, &attribs, tag_name)?),
|
||||
"Tabs" => {
|
||||
new_widget_id = Some(parse_component_tabs(ctx, child_node, parent_id, &attribs, tag_name)?);
|
||||
}
|
||||
@@ -1229,7 +1232,7 @@ pub fn parse_from_assets(
|
||||
|
||||
pub fn new_layout_from_assets(
|
||||
doc_params: &ParseDocumentParams,
|
||||
layout_params: &LayoutParams,
|
||||
layout_params: LayoutParams,
|
||||
) -> anyhow::Result<(Layout, ParserState)> {
|
||||
let mut layout = Layout::new(doc_params.globals.clone(), layout_params)?;
|
||||
let widget = layout.content_root_widget;
|
||||
|
||||
@@ -42,7 +42,7 @@ pub fn parse_widget_image<'a>(
|
||||
key,
|
||||
value,
|
||||
&mut params.round,
|
||||
ctx.doc_params.globals.defaults().rounding_mult,
|
||||
ctx.layout.state.theme.rounding_mult,
|
||||
);
|
||||
}
|
||||
"border" => {
|
||||
|
||||
@@ -45,11 +45,8 @@ pub fn parse_widget_label<'a>(
|
||||
}
|
||||
}
|
||||
|
||||
let globals = ctx.layout.state.globals.clone();
|
||||
|
||||
let (widget, _) = ctx
|
||||
.layout
|
||||
.add_child(parent_id, WidgetLabel::create(&mut globals.get(), params), style)?;
|
||||
let label = WidgetLabel::create(&mut ctx.layout.state, params);
|
||||
let (widget, _) = ctx.layout.add_child(parent_id, label, style)?;
|
||||
|
||||
parse_widget_universal(ctx, &widget, attribs, tag_name);
|
||||
parse_children(file, ctx, node, widget.id)?;
|
||||
|
||||
@@ -47,7 +47,7 @@ pub fn parse_widget_rectangle<'a>(
|
||||
key,
|
||||
value,
|
||||
&mut params.round,
|
||||
ctx.doc_params.globals.defaults().rounding_mult,
|
||||
ctx.layout.state.theme.rounding_mult,
|
||||
);
|
||||
}
|
||||
"border" => {
|
||||
|
||||
+12
-3
@@ -39,6 +39,11 @@ impl<T: StackItem<T>, const STACK_MAX: usize> GenericStack<T, STACK_MAX> {
|
||||
pub const fn get(&self) -> &T {
|
||||
&self.stack[(self.top - 1) as usize]
|
||||
}
|
||||
|
||||
pub const fn parent(&self) -> &T {
|
||||
debug_assert!(self.top >= 2);
|
||||
&self.stack[(self.top - 2) as usize]
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: StackItem<T>, const STACK_MAX: usize> Default for GenericStack<T, STACK_MAX> {
|
||||
@@ -54,9 +59,11 @@ impl<T: StackItem<T>, const STACK_MAX: usize> Default for GenericStack<T, STACK_
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct Transform {
|
||||
pub rel_pos: Vec2,
|
||||
pub visual_dim: Vec2, // for convenience
|
||||
pub raw_dim: Vec2, // for convenience
|
||||
pub abs_pos: Vec2, // for convenience, will be set after pushing
|
||||
pub visual_dim: Vec2,
|
||||
pub raw_dim: Vec2,
|
||||
pub content_rel_pos: Vec2,
|
||||
pub content_dim: Vec2,
|
||||
pub abs_pos: Vec2, // for convenience, will be set after pushing
|
||||
pub transform: glam::Mat4,
|
||||
pub transform_rel: glam::Mat4,
|
||||
}
|
||||
@@ -68,6 +75,8 @@ impl Default for Transform {
|
||||
rel_pos: Default::default(),
|
||||
visual_dim: Default::default(),
|
||||
raw_dim: Default::default(),
|
||||
content_dim: Default::default(),
|
||||
content_rel_pos: Default::default(),
|
||||
transform: Mat4::IDENTITY,
|
||||
transform_rel: Default::default(),
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
use crate::drawing;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct WguiTheme {
|
||||
pub dark_mode: bool,
|
||||
pub text_color: drawing::Color,
|
||||
pub button_color: drawing::Color,
|
||||
pub accent_color: drawing::Color,
|
||||
pub danger_color: drawing::Color,
|
||||
pub faded_color: drawing::Color,
|
||||
pub bg_color: drawing::Color,
|
||||
pub editbox_color: drawing::Color,
|
||||
pub translucent_alpha: f32,
|
||||
pub animation_mult: f32,
|
||||
pub rounding_mult: f32,
|
||||
pub gradient_intensity: f32, // currently used for buttons
|
||||
}
|
||||
|
||||
impl Default for WguiTheme {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
dark_mode: true,
|
||||
text_color: drawing::Color::new(1.0, 1.0, 1.0, 1.0),
|
||||
button_color: drawing::Color::new(1.0, 1.0, 1.0, 0.02),
|
||||
accent_color: drawing::Color::new(0.13, 0.68, 1.0, 1.0),
|
||||
danger_color: drawing::Color::new(0.9, 0.0, 0.0, 1.0),
|
||||
faded_color: drawing::Color::new(0.67, 0.74, 0.80, 1.0),
|
||||
bg_color: drawing::Color::new(0.0, 0.07, 0.1, 0.75),
|
||||
editbox_color: drawing::Color::new(0.15, 0.25, 0.35, 0.95),
|
||||
translucent_alpha: 0.5,
|
||||
animation_mult: 1.0,
|
||||
rounding_mult: 1.0,
|
||||
gradient_intensity: 0.3,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,8 +9,9 @@ use crate::{
|
||||
event::CallbackDataCommon,
|
||||
globals::Globals,
|
||||
i18n::Translation,
|
||||
layout::WidgetID,
|
||||
layout::{LayoutState, WidgetID},
|
||||
renderer_vk::text::TextStyle,
|
||||
theme::WguiTheme,
|
||||
widget::WidgetStateFlags,
|
||||
};
|
||||
|
||||
@@ -31,9 +32,13 @@ pub struct WidgetLabel {
|
||||
}
|
||||
|
||||
impl WidgetLabel {
|
||||
pub fn create(globals: &mut Globals, mut params: WidgetLabelParams) -> WidgetState {
|
||||
pub fn create(state: &mut LayoutState, params: WidgetLabelParams) -> WidgetState {
|
||||
WidgetLabel::create_ex(&mut state.globals.get(), &state.theme, params)
|
||||
}
|
||||
|
||||
pub fn create_ex(globals: &mut Globals, theme: &WguiTheme, mut params: WidgetLabelParams) -> WidgetState {
|
||||
if params.style.color.is_none() {
|
||||
params.style.color = Some(globals.defaults.text_color);
|
||||
params.style.color = Some(theme.text_color);
|
||||
}
|
||||
|
||||
let metrics = Metrics::from(¶ms.style);
|
||||
|
||||
+27
-19
@@ -311,6 +311,8 @@ impl WidgetState {
|
||||
}
|
||||
|
||||
pub fn get_scroll_shift_smooth(&self, info: &ScrollbarInfo, l: &taffy::Layout, timestep_alpha: f32) -> (Vec2, bool) {
|
||||
//return (self.get_scroll_shift_raw(info, l), false);
|
||||
|
||||
let currently_animating = self.data.scrolling_cur != self.data.scrolling_cur_prev;
|
||||
|
||||
let scrolling = self
|
||||
@@ -368,7 +370,8 @@ impl WidgetState {
|
||||
return;
|
||||
}
|
||||
|
||||
let transform = state.transform_stack.get();
|
||||
let transform_widget = state.transform_stack.get();
|
||||
let transform_parent = state.transform_stack.parent();
|
||||
|
||||
let thickness = 6.0;
|
||||
let margin = 6.0;
|
||||
@@ -385,12 +388,15 @@ impl WidgetState {
|
||||
PrimitiveExtent {
|
||||
boundary: drawing::Boundary::from_pos_size(
|
||||
Vec2::new(
|
||||
transform.rel_pos.x + transform.raw_dim.x * (1.0 - info.handle_size.x) * self.data.scrolling_cur.x,
|
||||
transform.rel_pos.y + transform.raw_dim.y - thickness - margin,
|
||||
// X
|
||||
transform_widget.content_rel_pos.x
|
||||
+ transform_parent.raw_dim.x * (1.0 - info.handle_size.x) * self.data.scrolling_cur.x,
|
||||
// Y
|
||||
transform_widget.content_rel_pos.y + transform_widget.raw_dim.y - thickness - margin,
|
||||
),
|
||||
Vec2::new(transform.raw_dim.x * info.handle_size.x, thickness),
|
||||
Vec2::new(transform_widget.raw_dim.x * info.handle_size.x, thickness),
|
||||
),
|
||||
transform: transform.transform,
|
||||
transform: transform_parent.transform,
|
||||
},
|
||||
rect_params,
|
||||
));
|
||||
@@ -402,12 +408,15 @@ impl WidgetState {
|
||||
PrimitiveExtent {
|
||||
boundary: drawing::Boundary::from_pos_size(
|
||||
Vec2::new(
|
||||
transform.rel_pos.x + transform.raw_dim.x - thickness - margin,
|
||||
transform.rel_pos.y + transform.raw_dim.y * (1.0 - info.handle_size.y) * self.data.scrolling_cur.y,
|
||||
// X
|
||||
transform_widget.content_rel_pos.x + transform_widget.raw_dim.x - thickness - margin,
|
||||
// Y
|
||||
transform_widget.content_rel_pos.y
|
||||
+ transform_parent.raw_dim.y * (1.0 - info.handle_size.y) * self.data.scrolling_cur.y,
|
||||
),
|
||||
Vec2::new(thickness, transform.raw_dim.y * info.handle_size.y),
|
||||
Vec2::new(thickness, transform_widget.raw_dim.y * info.handle_size.y),
|
||||
),
|
||||
transform: transform.transform,
|
||||
transform: transform_parent.transform,
|
||||
},
|
||||
rect_params,
|
||||
));
|
||||
@@ -489,16 +498,19 @@ impl WidgetState {
|
||||
let mut res: Option<InvokeListenersResult> = None;
|
||||
|
||||
match &event {
|
||||
Event::TextInput(e) => {
|
||||
res = Some(self.invoke_listeners(
|
||||
&mut invoke_data,
|
||||
EventListenerKind::TextInput,
|
||||
CallbackMetadata::TextInput(e.clone()),
|
||||
)?);
|
||||
}
|
||||
Event::MouseDown(e) => {
|
||||
if hovered && self.data.set_device_pressed(e.device, true) {
|
||||
res = Some(self.invoke_listeners(
|
||||
&mut invoke_data,
|
||||
EventListenerKind::MousePress,
|
||||
CallbackMetadata::MouseButton(event::MouseButton {
|
||||
index: e.index,
|
||||
pos: e.pos,
|
||||
device: e.device,
|
||||
}),
|
||||
CallbackMetadata::MouseButton(*e),
|
||||
)?);
|
||||
}
|
||||
}
|
||||
@@ -507,11 +519,7 @@ impl WidgetState {
|
||||
res = Some(self.invoke_listeners(
|
||||
&mut invoke_data,
|
||||
EventListenerKind::MouseRelease,
|
||||
CallbackMetadata::MouseButton(event::MouseButton {
|
||||
index: e.index,
|
||||
pos: e.pos,
|
||||
device: e.device,
|
||||
}),
|
||||
CallbackMetadata::MouseButton(*e),
|
||||
)?);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ drm-fourcc = "2.2.0"
|
||||
idmap = { workspace = true }
|
||||
libc = "0.2.178"
|
||||
log = { workspace = true }
|
||||
pipewire = { git = "https://gitlab.freedesktop.org/galister/pipewire-rs.git", rev = "ba32202c3c391004c3bb533b58fa75a50e47ff57", features = [
|
||||
pipewire = { git = "https://gitlab.freedesktop.org/galister/pipewire-rs.git", rev = "6ef2cd1af41e5e8844c63c37ea9a01f0758079b0", features = [
|
||||
"v0_3_33",
|
||||
], optional = true }
|
||||
#pipewire = { version = "0.8.0", features = ["v0_3_33"], optional = true }
|
||||
|
||||
@@ -7,9 +7,7 @@ use strum::{AsRefStr, EnumProperty, EnumString, VariantArray};
|
||||
use wayvr_ipc::packet_client::WvrProcessLaunchParams;
|
||||
|
||||
use crate::{
|
||||
astr_containers::{AStrMap, AStrSet},
|
||||
overlays::{BackendAttribValue, ToastDisplayMethod, ToastTopic},
|
||||
windowing::OverlayWindowState,
|
||||
astr_containers::{AStrMap, AStrSet}, locale::{self}, overlays::{BackendAttribValue, ToastDisplayMethod, ToastTopic}, windowing::OverlayWindowState
|
||||
};
|
||||
|
||||
pub type PwTokenMap = AStrMap<String>;
|
||||
@@ -140,6 +138,8 @@ const fn def_max_height() -> u16 {
|
||||
1440
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct GeneralConfig {
|
||||
#[serde(default = "def_theme_path")]
|
||||
@@ -151,6 +151,8 @@ pub struct GeneralConfig {
|
||||
pub color_faded: Option<String>,
|
||||
pub color_background: Option<String>,
|
||||
|
||||
pub language: Option<locale::Language>, // auto-detected at runtime if unset
|
||||
|
||||
#[serde(default = "def_one")]
|
||||
#[serde(alias = "ui_animation_speed", alias = "animation_speed" /* old name */)]
|
||||
pub ui_animation_speed: f32,
|
||||
@@ -322,4 +324,7 @@ pub struct GeneralConfig {
|
||||
|
||||
#[serde(default)]
|
||||
pub handsfree_pointer: HandsfreePointer,
|
||||
|
||||
#[serde(default = "def_one")]
|
||||
pub grid_opacity: f32,
|
||||
}
|
||||
@@ -8,6 +8,7 @@ pub mod dash_interface;
|
||||
pub mod dash_interface_emulated;
|
||||
pub mod desktop_finder;
|
||||
mod handle;
|
||||
pub mod locale;
|
||||
pub mod overlays;
|
||||
pub mod timestep;
|
||||
pub mod windowing;
|
||||
@@ -0,0 +1,91 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum::{AsRefStr, EnumProperty, EnumString, VariantArray};
|
||||
|
||||
use crate::config::GeneralConfig;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, AsRefStr, EnumString, EnumProperty, VariantArray)]
|
||||
pub enum Language {
|
||||
#[strum(props(Text = "English"))]
|
||||
English,
|
||||
#[strum(props(Text = "English(UK)"))]
|
||||
British,
|
||||
#[strum(props(Text = "Polski"))]
|
||||
Polish,
|
||||
#[strum(props(Text = "日本語"))]
|
||||
Japanese,
|
||||
#[strum(props(Text = "German"))]
|
||||
German,
|
||||
#[strum(props(Text = "Italiano"))]
|
||||
Italian,
|
||||
#[strum(props(Text = "简体中文"))]
|
||||
ChineseSimplified,
|
||||
#[strum(props(Text = "Español"))]
|
||||
Spanish,
|
||||
}
|
||||
|
||||
impl Language {
|
||||
pub const fn code(&self) -> &'static str {
|
||||
match self {
|
||||
Language::English => "en_US",
|
||||
Language::British => "en_GB",
|
||||
Language::Polish => "pl_PL",
|
||||
Language::Japanese => "ja_JP",
|
||||
Language::German => "de_DE",
|
||||
Language::Italian => "it_IT",
|
||||
Language::ChineseSimplified => "zh_CN",
|
||||
Language::Spanish => "es_ES",
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn get_default() -> Self {
|
||||
Self::English
|
||||
}
|
||||
|
||||
pub const fn all_codes() -> &'static [&'static str] {
|
||||
&["en", "pl", "ja", "de", "it", "zh_CN", "es"]
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WayVRLangsList {}
|
||||
|
||||
impl wgui::i18n::LangsList for WayVRLangsList {
|
||||
fn all_locale(&self) -> &'static [&'static str] {
|
||||
Language::all_codes()
|
||||
}
|
||||
|
||||
fn default_lang(&self) -> &'static str {
|
||||
Language::get_default().code()
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
const G_LANGS_LIST: WayVRLangsList = WayVRLangsList {};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct WayVRLangProvider {
|
||||
forced_lang: Option<Rc<str>>,
|
||||
}
|
||||
|
||||
impl wgui::assets::LangProvider for WayVRLangProvider {
|
||||
fn langs_list(&self) -> &dyn wgui::i18n::LangsList {
|
||||
&G_LANGS_LIST
|
||||
}
|
||||
|
||||
fn forced_lang(&self) -> Option<&str> {
|
||||
self.forced_lang.as_ref().map(|lang| lang.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
impl WayVRLangProvider {
|
||||
pub fn from_config(config: &GeneralConfig) -> Self {
|
||||
if let Some(lang) = &config.language {
|
||||
return Self {
|
||||
forced_lang: Some(lang.code().into()),
|
||||
};
|
||||
}
|
||||
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user