Compare commits

..
46 Commits
Author SHA1 Message Date
founta 00bc179655 Add space reset to openvr actions (#475)
* Add space reset to openvr actions

* remove additional reset state storage
2026-03-28 14:44:14 +09:00
oo8dev a4115f5e4b Merge pull request #478 from wayvr-org/move-doc
docs: move out docs to a separate repo/website, fix typos
2026-03-25 18:39:59 +00:00
Aleksander 6c896bb956 fix typo 2026-03-25 19:39:20 +01:00
galister d9c8689c18 docs move: continuation
Updated links and references to the new GitHub organization.
2026-03-26 03:33:45 +09:00
Aleksander 5df6ce93f8 docs: move out docs to a separate repo/website, fix typos
[skip ci]
2026-03-24 00:19:27 +01:00
Earthgames ee4bb5ca29 Allow the checkbox to be themed (#462) 2026-03-23 12:24:16 +09:00
hare_ware b89ae4726c Revert "fix bindings for wmr controllers"
This reverts commit 285f6db134.
2026-03-23 12:23:01 +09:00
galister b0bbc25083 dont die if autostart app cant be started 2026-03-20 12:48:52 +09:00
Craig McLure 635df298fe Updated the pipewire reference in Cargo.lock 2026-03-18 22:22:00 +09:00
galister 786847cc2f update pipewire-rs to fix build on arch 2026-03-18 12:31:42 +09:00
Aleksander d9f89faf33 wayvr: "Fix floor" countdown timer and sound
- 5-second timeout sound with countdown timer
- sort locale JSON
- modify toast lerp amount
2026-03-16 13:47:51 +09:00
galister 769978560e oops 2026-03-16 13:46:23 +09:00
galister bebc39e766 dbus notify timeout is ms 2026-03-16 13:38:10 +09:00
oo8dev bb15a4e867 Merge pull request #466 from wlx-team/toast-mp3-update
update toast.mp3 sound
2026-03-14 12:23:30 +00:00
Aleksander 84015c7972 update toast.mp3 sound
[skip ci]
2026-03-14 13:20:48 +01:00
galister f2c34fba8f sticky modifier keys explanation in README 2026-03-11 10:44:01 +09:00
Earthgames 1a74eb1507 Use a more common name 2026-03-08 01:18:35 +09:00
Earthgames 6b3f8a7b42 Only convert locale one time 2026-03-08 01:18:35 +09:00
Earthgames ad78fdab24 Hack in full lang codes
Add British
2026-03-08 01:18:35 +09:00
Earthgames 12ceebec6d Used locale in chrono 2026-03-08 01:18:35 +09:00
Earthgames 6f9ca5b3b7 Added documentation for default colors 2026-03-06 12:23:50 +09:00
oo8dev c54b211872 Merge pull request #460 from wlx-team/defaults-globals-refactor
wgui: separate globals from themes
2026-03-05 21:55:46 +00:00
Aleksander cb899235b4 wgui: separate globals and themes 2026-03-05 19:28:42 +01:00
Aleksander a83af3d833 translations 2026-03-05 12:42:04 +01:00
galister e8c8a67fc0 add wiki links to readme 2026-03-03 02:52:51 +09:00
Earthgames 2b4a0b6a0d Changed old link to the new one 2026-03-01 13:53:49 +09:00
galister 5ffa5e59bf skybox cleanups 2026-02-28 13:33:54 +09:00
galister ed8ca1e068 fix copy-paste fail causing stuck images 2026-02-28 13:33:27 +09:00
galister f21776b123 box CompositionLayerColorScaleBiasKHR 2026-02-28 13:10:02 +09:00
Torge Matthies ab3f873a38 fix grid opacity not updating live 2026-02-27 12:30:27 +09:00
oo8dev de6047107e Merge pull request #450 from openglfreak/feat-grid-opacity
add option for grid opacity
2026-02-25 21:14:11 +01:00
Torge Matthies 39152b8548 add option for grid opacity 2026-02-25 20:59:17 +01:00
Kresny b517afc379 Rebind toggle_dashboard and show_hide (#449) 2026-02-25 14:58:30 +09:00
oo8dev b84d565b1e Merge pull request #443 from wlx-team/feat-editbox
Editbox, wgui fixes, doc
2026-02-21 12:23:49 +01:00
Sapphire 46f3a54203 don't block inputs for headless sessions 2026-02-21 14:13:31 +09:00
Sapphire 4afaa6d048 reduce indentations in block_inputs 2026-02-21 14:13:31 +09:00
Sapphire 26f078d512 unblock inputs on exit 2026-02-21 14:13:31 +09:00
Aleksander 124d817752 fix logic bug in push_scissor_stack 2026-02-16 20:33:43 +01:00
Aleksander 2ddb419676 editbox keyboard input, scrollbars fix (scissoring broken atm), clippy 2026-02-14 22:20:51 +01:00
Aleksander 841241a7e6 wgui: Editbox basics, component focus, minor refactoring 2026-02-14 19:24:36 +01:00
Aleksander c902f0cd44 wgui: widgets.md: document tabs 2026-02-14 19:24:36 +01:00
Aleksander 756c42609d use SlotMap 2026-02-14 16:04:36 +01:00
Aleksander 4fc439abe2 uidev: fix color formats
Current implementation picks up the first available color format, which could be HDR (A2R10G10B10_UNORM_PACK32 in my case), but we don't have any color profiles. Stick back to SDR-only instead.
2026-02-14 10:18:41 +01:00
oo8dev 9685576272 Merge pull request #438 from wlx-team/feat-language-selector
Settings: Language selector, "Requires restart" info
2026-02-13 08:33:32 +01:00
galister d2f01c06ec Add Open Collective link 2026-02-13 13:29:53 +09:00
Aleksander 94c36076af Language selector and "requires restart" info 2026-02-12 20:53:06 +01:00
98 changed files with 1823 additions and 1121 deletions

No files matched your search

+1
View File
@@ -0,0 +1 @@
open_collective: wlx-team
Generated
+16 -45
View File
@@ -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"
+30 -14
View File
@@ -1,4 +1,4 @@
![WayVR splash screen header](https://github.com/wlx-team/wayvr/blob/guide/wayvr-readme-header.webp?raw=true)
![WayVR splash screen header](https://github.com/wayvr-org/wayvr/blob/guide/wayvr-readme-header.webp?raw=true)
# 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.
![Screenshot of WayVR being used as an OpenXR home environment](https://github.com/wlx-team/wayvr/blob/guide/wayvr-readme-screenshot.webp?raw=true)
![Screenshot of WayVR being used as an OpenXR home environment](https://github.com/wayvr-org/wayvr/blob/guide/wayvr-readme-screenshot.webp?raw=true)
## Join the Linux VR Community
We are available on either **Discord** or **Matrix space**:
[![LVRA Discord](https://img.shields.io/discord/1065291958328758352?style=for-the-badge&logo=discord)](https://discord.gg/EHAYe3tTYa) [![LVRA Matrix](https://img.shields.io/matrix/linux-vr-adventures:matrix.org?logo=matrix&style=for-the-badge)](https://matrix.to/#/#linux-vr-adventures:matrix.org)
[![LVRA Discord](https://img.shields.io/discord/1065291958328758352?style=for-the-badge/&logo=discord)](https://discord.gg/EHAYe3tTYa) [![LVRA Matrix](https://img.shields.io/matrix/linux-vr-adventures:matrix.org?logo=matrix&style=for-the-badge)](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
![Index Controller Bindings](https://github.com/wlx-team/wayvr/blob/guide/wlx-index.png)
![Index Controller Bindings](https://github.com/wayvr-org/wayvr/blob/guide/wlx-index.png)
![Touch Controller Bindings](https://github.com/wlx-team/wayvr/blob/guide/wlx-oculus.png)
![Touch Controller Bindings](https://github.com/wayvr-org/wayvr/blob/guide/wlx-oculus.png)
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
+11 -12
View File
@@ -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 -->
-1
View File
@@ -44,7 +44,6 @@
flex_direction="row"
flex_wrap="wrap"
gap="4"
overflow_y="scroll"
/>
</elements>
</layout>
+1 -1
View File
@@ -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>
+5 -2
View File
@@ -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": {
+8 -4
View File
@@ -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",
+5 -2
View File
@@ -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": {
+5 -1
View File
@@ -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",
+5 -2
View File
@@ -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": {
+9 -6
View File
@@ -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",
+5 -1
View File
@@ -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 -22
View File
@@ -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(())
}
+168 -84
View File
@@ -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);
}
+16 -28
View File
@@ -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)?;
}
}
+2 -1
View File
@@ -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 {
-2
View File
@@ -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),
+7 -12
View File
@@ -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);
+3 -9
View File
@@ -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);
+8 -11
View File
@@ -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(())
+12 -4
View File
@@ -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
View File
@@ -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 {
+5 -2
View File
@@ -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 })
}
+11 -9
View File
@@ -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 })
}
}
+7 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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" }
-42
View File
@@ -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.
+4 -3
View File
@@ -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"
}
+43 -42
View File
@@ -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": {
+4 -3
View File
@@ -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"
}
+4 -3
View File
@@ -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"
}
+4 -3
View File
@@ -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": "完了"
}
+4 -3
View File
@@ -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"
}
+4 -3
View File
@@ -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": "完成"
}
+9
View File
@@ -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)
+8
View File
@@ -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) {
+34 -38
View File
@@ -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}"),
}
}
}
+12
View File
@@ -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();
+4
View File
@@ -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",
},
},
+4 -12
View File
@@ -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,
+44 -15
View File
@@ -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![
CompositionLayer::Equirect2(sky),
CompositionLayer::Quad(grid),
@@ -254,5 +283,5 @@ pub(super) fn create_skybox(xr: &XrState, app: &AppState) -> Option<Skybox> {
xr.instance
.exts()
.khr_composition_layer_equirect2
.and_then(|_| Skybox::new(app).ok())
.and_then(|_| Skybox::new(xr, app).ok())
}
+5
View File
@@ -10,6 +10,7 @@ use wlx_common::{
SerializedWindowStates,
},
config_io,
locale::Language,
overlays::BackendAttribValue,
};
@@ -129,6 +130,7 @@ pub struct AutoSettings {
pub block_poses_on_kbd_interaction: bool,
pub space_drag_multiplier: f32,
pub use_skybox: bool,
pub grid_opacity: f32,
pub use_passthrough: bool,
pub screen_render_down: bool,
pub pointer_lerp_factor: f32,
@@ -143,6 +145,7 @@ pub struct AutoSettings {
pub keyboard_middle_click_mode: AltModifier,
pub autostart_apps: Vec<WvrProcessLaunchParams>,
pub handsfree_pointer: HandsfreePointer,
pub language: Option<Language>,
}
fn get_settings_path() -> PathBuf {
@@ -178,6 +181,7 @@ pub fn save_settings(config: &GeneralConfig) -> anyhow::Result<()> {
block_poses_on_kbd_interaction: config.block_poses_on_kbd_interaction,
space_drag_multiplier: config.space_drag_multiplier,
use_skybox: config.use_skybox,
grid_opacity: config.grid_opacity,
use_passthrough: config.use_passthrough,
screen_render_down: config.screen_render_down,
pointer_lerp_factor: config.pointer_lerp_factor,
@@ -192,6 +196,7 @@ pub fn save_settings(config: &GeneralConfig) -> anyhow::Result<()> {
keyboard_middle_click_mode: config.keyboard_middle_click_mode,
autostart_apps: config.autostart_apps.clone(),
handsfree_pointer: config.handsfree_pointer,
language: config.language,
};
let json = serde_json::to_string_pretty(&conf).unwrap(); // want panic
-221
View File
@@ -1,221 +0,0 @@
# Custom UI Elements
Elements on custom panels may be modified at runtime using `wayvrctl`.
For more, refer to: `wayvrctl panel-modify --help`
### Labels
#### Clock label
Clock labels are driven by the current time, adhering to the user's 12/24 hour setting as well as timezone settings.
Available display values are: `name` (timezone name), `time`, `date`, `dow` or a custom [format string](https://docs.rs/chrono/latest/chrono/format/strftime/index.html) 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.
+30 -12
View File
@@ -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, _| {
+23 -2
View File
@@ -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();
+6 -5
View File
@@ -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
View File
@@ -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!");
+12 -12
View File
@@ -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,
+10 -8
View File
@@ -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();
}),
)
+2 -2
View File
@@ -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)
+2 -4
View File
@@ -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
+2 -2
View File
@@ -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 } => {
+2 -1
View File
@@ -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);
}
+9 -1
View File
@@ -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 {
+5
View File
@@ -50,6 +50,11 @@
"type": "boolean",
"requirement": "optional"
},
{
"name": "/actions/default/in/SpaceReset",
"type": "boolean",
"requirement": "optional"
},
{
"name": "/actions/default/in/LeftHand",
"type": "pose",
+3
View File
@@ -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.
-1
View File
@@ -16,4 +16,3 @@ void main()
}
out_color = vec4(1.0, 1.0, 1.0, grid * fade);
}
+28 -13
View File
@@ -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,
+2 -2
View File
@@ -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)
}
+3 -3
View File
@@ -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()],
+1 -1
View File
@@ -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
+2
View File
@@ -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
View File
@@ -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
+7
View File
@@ -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>> {
+47 -64
View File
@@ -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))
}
+28 -25
View File
@@ -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),
]
},
};
+418
View File
@@ -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(&params.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))
}
+7
View File
@@ -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)]
+21 -33
View File
@@ -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))
}
+2 -2
View File
@@ -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 {
+11 -16
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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;
+1 -1
View File
@@ -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" => {
+1
View File
@@ -91,6 +91,7 @@ pub fn parse_component_checkbox(
radio_group,
value: component_value,
tooltip: tooltip.get_info(),
color_checked: None,
},
)?;
+40
View File
@@ -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
View File
@@ -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;
+1 -1
View File
@@ -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" => {
+2 -5
View File
@@ -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)?;
+1 -1
View File
@@ -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
View File
@@ -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(),
}
+36
View File
@@ -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,
}
}
}
+8 -3
View File
@@ -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(&params.style);
+27 -19
View File
@@ -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),
)?);
}
}
+1 -1
View File
@@ -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 }
+8 -3
View File
@@ -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,
}
+1
View File
@@ -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;
+91
View File
@@ -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()
}
}