Compare commits
128 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
5723c3c1df | |
|
|
37896620eb | |
|
|
6dc76c881f | |
|
|
4b78e39af7 | |
|
|
7f97dc884f | |
|
|
9806027e19 | |
|
|
066f033a91 | |
|
|
1591466a8d | |
|
|
901c18520e | |
|
|
e39085aa2b | |
|
|
48d58f61d2 | |
|
|
8691cf99d2 | |
|
|
80bfb177be | |
|
|
20743f91eb | |
|
|
5cf7b986f3 | |
|
|
3d71746ff2 | |
|
|
c7b16c5713 | |
|
|
ed1d708802 | |
|
|
557a0775c3 | |
|
|
2309f89701 | |
|
|
cdd658d4ea | |
|
|
ec2e0501c1 | |
|
|
f30c650bba | |
|
|
45af52ddf9 | |
|
|
10b14794ed | |
|
|
6b81f07cc8 | |
|
|
9734955ebb | |
|
|
dd3108d423 | |
|
|
bf7276b2dd | |
|
|
90c4567505 | |
|
|
6cddb43b0f | |
|
|
f3732c7087 | |
|
|
07bd841bc6 | |
|
|
9986b71b26 | |
|
|
16446f7808 | |
|
|
5615186654 | |
|
|
244822ffb3 | |
|
|
266b0fd507 | |
|
|
0d8937ff80 | |
|
|
c59e195d94 | |
|
|
994dffc4fb | |
|
|
701e41296b | |
|
|
b0dba25f36 | |
|
|
1bee41aea9 | |
|
|
2035d9ba76 | |
|
|
24e3099b7b | |
|
|
aee5bf69da | |
|
|
62881c08b3 | |
|
|
0c87d47ce6 | |
|
|
47bb5ab7a2 | |
|
|
e5e798b599 | |
|
|
68daa5b3df | |
|
|
f2dcdcc6c3 | |
|
|
a3460fbe70 | |
|
|
7f822b6787 | |
|
|
1d8b22d992 | |
|
|
1238808d87 | |
|
|
26f5be36b6 | |
|
|
0c1b0906c1 | |
|
|
5eb258e310 | |
|
|
bec00c6f48 | |
|
|
db1675d399 | |
|
|
18f99b5daf | |
|
|
543f157895 | |
|
|
8ad31cfa28 | |
|
|
80277e0c12 | |
|
|
0a4fc34fac | |
|
|
e035fb9d27 | |
|
|
97fcde1a3b | |
|
|
c16c403966 | |
|
|
d5148e2107 | |
|
|
0f6f344c97 | |
|
|
41c6f43a3c | |
|
|
f44c4b78bc | |
|
|
5420efa4d3 | |
|
|
89f43b95ef | |
|
|
00bc179655 | |
|
|
a4115f5e4b | |
|
|
6c896bb956 | |
|
|
d9c8689c18 | |
|
|
5df6ce93f8 | |
|
|
ee4bb5ca29 | |
|
|
b89ae4726c | |
|
|
b0bbc25083 | |
|
|
635df298fe | |
|
|
786847cc2f | |
|
|
d9f89faf33 | |
|
|
769978560e | |
|
|
bebc39e766 | |
|
|
bb15a4e867 | |
|
|
84015c7972 | |
|
|
f2c34fba8f | |
|
|
1a74eb1507 | |
|
|
6b3f8a7b42 | |
|
|
ad78fdab24 | |
|
|
12ceebec6d | |
|
|
6f9ca5b3b7 | |
|
|
c54b211872 | |
|
|
cb899235b4 | |
|
|
a83af3d833 | |
|
|
e8c8a67fc0 | |
|
|
2b4a0b6a0d | |
|
|
5ffa5e59bf | |
|
|
ed8ca1e068 | |
|
|
f21776b123 | |
|
|
ab3f873a38 | |
|
|
de6047107e | |
|
|
39152b8548 | |
|
|
b517afc379 | |
|
|
b84d565b1e | |
|
|
46f3a54203 | |
|
|
4afaa6d048 | |
|
|
26f078d512 | |
|
|
124d817752 | |
|
|
2ddb419676 | |
|
|
841241a7e6 | |
|
|
c902f0cd44 | |
|
|
756c42609d | |
|
|
4fc439abe2 | |
|
|
9685576272 | |
|
|
d2f01c06ec | |
|
|
94c36076af | |
|
|
a4134030fe | |
|
|
ebacbf175a | |
|
|
6c987b4492 | |
|
|
8acb2236bd | |
|
|
5dae6f8387 | |
|
|
5ab242f8e0 |
|
|
@ -0,0 +1 @@
|
||||||
|
open_collective: wayvr-org
|
||||||
|
|
@ -22,25 +22,28 @@ If this is a regression, please mention which version was working previously.
|
||||||
**Kernel version**:
|
**Kernel version**:
|
||||||
|
|
||||||
**VR Runtime**:
|
**VR Runtime**:
|
||||||
- [ ] Monado/WiVRn
|
- [ ] Monado
|
||||||
- [ ] SteamVR/ALVR
|
- [ ] WiVRn
|
||||||
|
- [ ] ALVR
|
||||||
|
- [ ] SteamLink
|
||||||
|
- [ ] SteamVR (not SteamLink)
|
||||||
|
|
||||||
<!-- Run `vulkaninfo --summary` and paste the devices section from the bottom. -->
|
<!-- Run `vulkaninfo --summary` and paste the devices section from the bottom. -->
|
||||||
**GPU models and driver versions**:
|
**GPU models and driver versions**:
|
||||||
|
|
||||||
## Overlay Logs
|
## Overlay Logs
|
||||||
|
|
||||||
<!-- Start the overlay once more with the following environment variables:
|
<!-- Start the overlay from terminal, with additional environment variables:
|
||||||
RUST_BACKTRACE=full
|
|
||||||
RUST_LOG=debug
|
|
||||||
If your issue is graphical or crash or freeze, also add:
|
|
||||||
VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation
|
|
||||||
|
|
||||||
Next, create the empty log file: echo > /tmp/wlx.log
|
AppImage:
|
||||||
|
RUST_BACKTRACE=full RUST_LOG=debug /path/to/WayVR.AppImage
|
||||||
|
|
||||||
Be sure to go and reproduce the issue once more, after these have been set.
|
System or AUR package:
|
||||||
|
RUST_BACKTRACE=full RUST_LOG=debug wayvr
|
||||||
|
|
||||||
Upload the log file from: /tmp/wlx.log
|
Reproduce the issue once more, while WayVR is running from the terminal.
|
||||||
|
|
||||||
|
Upload the log file from: /tmp/wayvr.log
|
||||||
|
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ env:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-24.04
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
working-directory: ./wayvr
|
working-directory: ./wayvr
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ env:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build_appimage:
|
build_appimage:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-24.04
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
working-directory: ./wayvr
|
working-directory: ./wayvr
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ env:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-24.04
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
working-directory: ./wayvr
|
working-directory: ./wayvr
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ env:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-24.04
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
working-directory: ./wayvr
|
working-directory: ./wayvr
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ env:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-24.04
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
working-directory: ./wayvr
|
working-directory: ./wayvr
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ env:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-24.04
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
working-directory: ./wayvr
|
working-directory: ./wayvr
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ env:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-24.04
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
working-directory: ./wayvr
|
working-directory: ./wayvr
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ env:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
make_release:
|
make_release:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-24.04
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
working-directory: ./wayvr
|
working-directory: ./wayvr
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
sudo add-apt-repository -syn universe
|
|
||||||
sudo add-apt-repository -syn ppa:pipewire-debian/pipewire-upstream || sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 25088A0359807596
|
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y fuse cmake pkg-config fontconfig libasound2-dev libxkbcommon-dev libxkbcommon-x11-0 libxkbcommon-x11-dev libopenxr-dev libfontconfig-dev libdbus-1-dev libpipewire-0.3-0 libpipewire-0.3-dev libspa-0.2-dev libx11-6 libxext6 libxrandr2 libx11-dev libxext-dev libxrandr-dev libopenvr-dev libopenvr-api1 libwayland-dev libegl-dev libxcb-glx0 libxcb-glx0-dev
|
sudo apt-get install -y fuse cmake pkg-config fontconfig libasound2-dev libxkbcommon-dev libxkbcommon-x11-0 libxkbcommon-x11-dev libopenxr-dev libfontconfig-dev libdbus-1-dev libpipewire-0.3-0 libpipewire-0.3-dev libspa-0.2-dev libx11-6 libxext6 libxrandr2 libx11-dev libxext-dev libxrandr-dev libopenvr-dev libopenvr-api1 libwayland-dev libegl-dev libxcb-glx0 libxcb-glx0-dev
|
||||||
rustup update
|
rustup update
|
||||||
|
|
|
||||||
75
Cargo.toml
|
|
@ -1,3 +1,42 @@
|
||||||
|
[workspace]
|
||||||
|
resolver = "3"
|
||||||
|
members = [
|
||||||
|
"dash-frontend",
|
||||||
|
"scripts/prost_build",
|
||||||
|
"uidev",
|
||||||
|
"wayvr",
|
||||||
|
"wayvr-ipc",
|
||||||
|
"wayvrctl",
|
||||||
|
"wgui",
|
||||||
|
"wlx-capture",
|
||||||
|
"wlx-common",
|
||||||
|
]
|
||||||
|
|
||||||
|
[workspace.dependencies]
|
||||||
|
anyhow = "1.0.100"
|
||||||
|
clap = { version = "4.5.53", features = ["derive"] }
|
||||||
|
glam = { version = "0.30.9", features = ["mint", "serde"] }
|
||||||
|
idmap = "0.2.2"
|
||||||
|
idmap-derive = "0.2.22"
|
||||||
|
log = "0.4.29"
|
||||||
|
regex = "1.12.2"
|
||||||
|
rust-embed = "8.9.0"
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
serde_json = "1.0.145"
|
||||||
|
slotmap = "1.1.1"
|
||||||
|
smol = "2.0.2"
|
||||||
|
strum = { version = "0.27.2", features = ["derive"] }
|
||||||
|
uuid = { version = "1.19.0", features = ["fast-rng", "v4", "serde"] }
|
||||||
|
vulkano = { version = "0.35.2", default-features = false, features = [
|
||||||
|
"macros",
|
||||||
|
] }
|
||||||
|
vulkano-shaders = "0.35.0"
|
||||||
|
wayland-client = { version = "0.31.11" }
|
||||||
|
xdg = "3.0.0"
|
||||||
|
|
||||||
|
[patch.crates-io]
|
||||||
|
vulkano = { git = "https://github.com/galister/vulkano.git", rev = "cf7f92867928a56ce16b376037c1120f2b167678" }
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
opt-level = 1
|
opt-level = 1
|
||||||
debug = true
|
debug = true
|
||||||
|
|
@ -19,39 +58,3 @@ incremental = true
|
||||||
[profile.release-with-debug]
|
[profile.release-with-debug]
|
||||||
inherits = "release"
|
inherits = "release"
|
||||||
debug = true
|
debug = true
|
||||||
|
|
||||||
[workspace]
|
|
||||||
members = [
|
|
||||||
"uidev",
|
|
||||||
"wgui",
|
|
||||||
"wlx-common",
|
|
||||||
"wayvr",
|
|
||||||
"wlx-capture",
|
|
||||||
"dash-frontend",
|
|
||||||
"wayvr-ipc",
|
|
||||||
"wayvrctl",
|
|
||||||
]
|
|
||||||
resolver = "3"
|
|
||||||
|
|
||||||
[patch.crates-io]
|
|
||||||
vulkano = { git = "https://github.com/galister/vulkano.git", rev = "cf7f92867928a56ce16b376037c1120f2b167678" }
|
|
||||||
|
|
||||||
[workspace.dependencies]
|
|
||||||
anyhow = "1.0.100"
|
|
||||||
glam = { version = "0.30.9", features = ["mint", "serde"] }
|
|
||||||
clap = { version = "4.5.53", features = ["derive"] }
|
|
||||||
xdg = "3.0.0"
|
|
||||||
idmap = "0.2.2"
|
|
||||||
idmap-derive = "0.2.22"
|
|
||||||
log = "0.4.29"
|
|
||||||
regex = "1.12.2"
|
|
||||||
rust-embed = "8.9.0"
|
|
||||||
serde = { version = "1", features = ["derive"] }
|
|
||||||
serde_json = "1.0.145"
|
|
||||||
slotmap = "1.1.1"
|
|
||||||
strum = { version = "0.27.2", features = ["derive"] }
|
|
||||||
vulkano = { version = "0.35.2", default-features = false, features = [
|
|
||||||
"macros",
|
|
||||||
] }
|
|
||||||
vulkano-shaders = "0.35.0"
|
|
||||||
wayland-client = { version = "0.31.11" }
|
|
||||||
|
|
|
||||||
44
README.md
|
|
@ -1,4 +1,4 @@
|
||||||

|

|
||||||
|
|
||||||
# WayVR (previously WlxOverlay-S)
|
# 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.
|
In comparison to similar overlays, WayVR aims to run alongside VR games and experiences while having as little performance impact as possible. The UI appearance and rendering techniques are kept as simple and efficient as possible, while still allowing a high degree of customizability.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Join the Linux VR Community
|
## Join the Linux VR Community
|
||||||
|
|
||||||
We are available on either **Discord** or **Matrix space**:
|
We are available on either **Discord** or **Matrix space**:
|
||||||
|
|
||||||
[](https://discord.gg/EHAYe3tTYa) [](https://matrix.to/#/#linux-vr-adventures:matrix.org)
|
[](https://discord.gg/EHAYe3tTYa) [](https://matrix.to/#/#linux-vr-adventures:matrix.org)
|
||||||
|
|
||||||
Questions/issues specific to WayVR will be handled in the `wayvr` chat room. Feel free to ask anything.
|
Questions/issues specific to WayVR will be handled in the `wayvr` chat room. Feel free to ask anything.
|
||||||
|
|
||||||
|
|
@ -24,9 +24,11 @@ Questions/issues specific to WayVR will be handled in the `wayvr` chat room. Fee
|
||||||
|
|
||||||
There are multiple ways to install WayVR:
|
There are multiple ways to install WayVR:
|
||||||
|
|
||||||
1. AppImage: Download from [Releases](https://github.com/wlx-team/wayvr/releases)
|
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. AUR package: [wayvr](https://aur.archlinux.org/packages/wayvr) or [wayvr-git](https://aur.archlinux.org/packages/wayvr-git)
|
||||||
1. [Building from source](https://github.com/wlx-team/wayvr/wiki/Building-from-Source).
|
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. [Homebrew-XR](https://tangled.org/matrixfurry.com/homebrew-xr) package (for Bazzite, etc.): [wayvr](https://tangled.org/matrixfurry.com/homebrew-xr/#installing-applications)
|
||||||
|
1. [Docs: Building from source](https://wayvr.org/docs/basics/building-from-source/).
|
||||||
|
|
||||||
### General Setup
|
### General Setup
|
||||||
|
|
||||||
|
|
@ -39,7 +41,7 @@ There are multiple ways to install WayVR:
|
||||||
|
|
||||||
For users specifically running **SteamVR via Steam Flatpak**, follow these steps:
|
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. `WayVR-*.AppImage --appimage-extract`
|
||||||
1. `chmod +x squashfs-root/AppRun`
|
1. `chmod +x squashfs-root/AppRun`
|
||||||
1. Move the newly created `squashfs-root` folder to a location accessible by the Steam Flatpak.
|
1. Move the newly created `squashfs-root` folder to a location accessible by the Steam Flatpak.
|
||||||
|
|
@ -63,7 +65,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.
|
**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.
|
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.
|
**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.
|
||||||
|
|
||||||
|
|
@ -130,26 +132,42 @@ Typing
|
||||||
- While using the ORANGE laser, all keystrokes will have SHIFT applied.
|
- While using the ORANGE laser, all keystrokes will have SHIFT applied.
|
||||||
- Purple laser is customizable via the settings, no modifier by default.
|
- 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
|
### Default Bindings
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
To customize bindings on OpenXR, refer to the [OpenXR Bindings wiki page](https://github.com/wlx-team/wayvr/wiki/OpenXR-Bindings).
|
### Changing Bindings
|
||||||
|
|
||||||
If your bindings are not supported, please reach out. \
|
SteamVR: Simply change the bindings from the SteamVR bindings section. If WayVR doesn't show up on the list, select any other title and then press back on the top left. (SteamVR is weird like that sometimes)
|
||||||
|
|
||||||
|
OpenXR (Monado/WiVRn): See [Docs: OpenXR Bindings](https://wayvr.org/docs/various/openxr-bindings/)
|
||||||
|
|
||||||
|
If your controllers are not supported, please reach out. \
|
||||||
We would like to work with you and include additional bindings.
|
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
|
## Troubleshooting
|
||||||
|
|
||||||
When an error is detected, we often print tips for fixing it into the log file.
|
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.
|
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
|
## Known Issues
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,26 +8,25 @@ authors = ["galister", "oo8dev"]
|
||||||
repository = "https://github.com/wlx-team/wayvr"
|
repository = "https://github.com/wlx-team/wayvr"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
wayvr-ipc = { path = "../wayvr-ipc", default-features = false }
|
|
||||||
wgui = { path = "../wgui/" }
|
|
||||||
wlx-common = { path = "../wlx-common" }
|
|
||||||
|
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
|
async-native-tls = "0.5.0"
|
||||||
|
chrono = "0.4.42"
|
||||||
glam = { workspace = true, features = ["mint", "serde"] }
|
glam = { workspace = true, features = ["mint", "serde"] }
|
||||||
|
http-body-util = "0.1.3"
|
||||||
|
hyper = { version = "1.8.1", features = ["client", "http1", "http2"] }
|
||||||
|
keyvalues-parser = { git = "https://codeberg.org/CosmicHarper/vdf-rs.git", rev = "fc6dcbea9eb13cacb98dea40063f6f56cde6e145" }
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
xdg.workspace = true
|
|
||||||
rust-embed.workspace = true
|
rust-embed.workspace = true
|
||||||
serde = { workspace = true, features = ["rc"] }
|
serde = { workspace = true, features = ["rc"] }
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
strum.workspace = true
|
smol = { workspace = true }
|
||||||
|
|
||||||
chrono = "0.4.42"
|
|
||||||
keyvalues-parser = { git = "https://github.com/CosmicHorrorDev/vdf-rs.git", rev = "fc6dcbea9eb13cacb98dea40063f6f56cde6e145" }
|
|
||||||
smol = "2.0.2"
|
|
||||||
hyper = { version = "1.8.1", features = ["client", "http1", "http2"] }
|
|
||||||
http-body-util = "0.1.3"
|
|
||||||
async-native-tls = "0.5.0"
|
|
||||||
smol-hyper = "0.1.1"
|
smol-hyper = "0.1.1"
|
||||||
|
strum.workspace = true
|
||||||
|
uuid.workspace = true
|
||||||
|
wayvr-ipc = { path = "../wayvr-ipc", default-features = false }
|
||||||
|
wgui = { path = "../wgui/" }
|
||||||
|
wlx-common = { path = "../wlx-common" }
|
||||||
|
xdg.workspace = true
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["monado"]
|
default = ["monado"]
|
||||||
|
|
|
||||||
|
After Width: | Height: | Size: 9.8 KiB |
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE --><path fill="#33FF99" d="m9.55 18l-5.7-5.7l1.425-1.425L9.55 15.15l9.175-9.175L20.15 7.4z"/></svg>
|
||||||
|
After Width: | Height: | Size: 295 B |
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE --><path fill="currentColor" d="m12 16l-5-5l1.4-1.45l2.6 2.6V4h2v8.15l2.6-2.6L17 11zm-6 4q-.825 0-1.412-.587T4 18v-3h2v3h12v-3h2v3q0 .825-.587 1.413T18 20z"/></svg>
|
||||||
|
After Width: | Height: | Size: 360 B |
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE --><path fill="currentColor" d="m12 22l-4.25-4.25l1.425-1.425L11 18.15V13H5.875L7.7 14.8l-1.45 1.45L2 12l4.225-4.225L7.65 9.2L5.85 11H11V5.85L9.175 7.675L7.75 6.25L12 2l4.25 4.25l-1.425 1.425L13 5.85V11h5.125L16.3 9.2l1.45-1.45L22 12l-4.25 4.25l-1.425-1.425L18.15 13H13v5.125l1.8-1.825l1.45 1.45z"/></svg>
|
||||||
|
After Width: | Height: | Size: 500 B |
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE --><path fill="#FF4455" d="M12 17q.425 0 .713-.288T13 16t-.288-.712T12 15t-.712.288T11 16t.288.713T12 17m-1-4h2V7h-2zm1 9q-2.075 0-3.9-.788t-3.175-2.137T2.788 15.9T2 12t.788-3.9t2.137-3.175T8.1 2.788T12 2t3.9.788t3.175 2.137T21.213 8.1T22 12t-.788 3.9t-2.137 3.175t-3.175 2.138T12 22m0-2q3.35 0 5.675-2.325T20 12t-2.325-5.675T12 4T6.325 6.325T4 12t2.325 5.675T12 20m0-8"/></svg>
|
||||||
|
After Width: | Height: | Size: 574 B |
|
|
@ -0,0 +1,46 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
width="32"
|
||||||
|
height="32"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1"
|
||||||
|
sodipodi:docname="gravity.svg"
|
||||||
|
inkscape:version="1.4.4 (dcaf3e7d9e, 2026-05-05)"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview1"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#000000"
|
||||||
|
borderopacity="0.25"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:zoom="23.234375"
|
||||||
|
inkscape:cx="17.861466"
|
||||||
|
inkscape:cy="22.488231"
|
||||||
|
inkscape:window-width="1582"
|
||||||
|
inkscape:window-height="1302"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="0"
|
||||||
|
inkscape:current-layer="svg1" />
|
||||||
|
<defs
|
||||||
|
id="defs1" />
|
||||||
|
<!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE -->
|
||||||
|
<!-- Modified it a little bit, there's no "space gravity" icon available. -->
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="m 15.76786,18.274928 -1.391472,0.295932 -0.674684,-2.9751 -3.051267,-0.735557 -2.4062791,-3.292082 -0.8846145,1.824345 1.561484,2.213127 -1.1706157,0.808349 -1.9932655,-2.862487 1.752944,-3.7745856 Q 7.740355,9.2978686 8.2693798,9.1366908 8.7984057,8.9755132 9.3446883,8.9671549 9.9537305,8.9704882 10.547911,8.8283107 11.142091,8.6861403 11.667399,8.364791 12.192707,8.0434408 12.541821,7.5511898 12.890935,7.0589389 13.170167,6.5208564 l 1.228561,0.6477209 q -0.321651,0.575352 -0.696034,1.1164692 -0.374382,0.5411172 -0.890945,0.9726251 -0.466314,0.3718768 -0.998843,0.6112737 -0.532529,0.2393967 -1.117089,0.3386867 L 12,12 l 2.176682,-1.469824 4.352172,0.934365 -0.326914,1.35612 -3.582453,-0.734464 -2.11263,1.442217 2.281549,0.535655 z M 7.5517982,8.9044443 Q 7.0233673,9.1322023 6.4853273,8.9182692 5.9472884,8.7043353 5.7186139,8.1755407 5.4899403,7.6467461 5.7054294,7.108795 5.9209195,6.5708439 6.4475175,6.3423563 6.9741154,6.1138698 7.5149037,6.3288971 8.0556931,6.5439235 8.2807018,7.07126 8.5057095,7.5985974 8.2948017,8.1383704 8.0838948,8.6781445 7.5517982,8.9044443"
|
||||||
|
id="path1"
|
||||||
|
style="stroke-width:0.697484" />
|
||||||
|
<path
|
||||||
|
id="path2"
|
||||||
|
style="fill:none;stroke:currentColor;stroke-width:2.43525;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:0, 7.30575"
|
||||||
|
d="M 22.480952,12 C 22.480952,17.78847 17.78847,22.480952 12,22.480952 6.2115299,22.480952 1.5190477,17.78847 1.5190477,12 1.5190475,6.2115298 6.2115298,1.5190477 12,1.5190477 c 0.653263,0 1.292568,0.059766 1.912744,0.1741274 C 18.787854,2.5921561 22.480952,6.8647934 22.480952,12 Z"
|
||||||
|
sodipodi:nodetypes="ssssss" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.8 KiB |
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Design Icons by Pictogrammers - https://github.com/Templarian/MaterialDesign/blob/master/LICENSE --><path fill="currentColor" d="M12 4V2A10 10 0 0 0 2 12h2a8 8 0 0 1 8-8"/></svg>
|
||||||
|
After Width: | Height: | Size: 286 B |
|
|
@ -0,0 +1,3 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE -->
|
||||||
|
<path fill="currentColor" d="M12 19q1.65 0 2.825-1.175T16 15v-4q0-1.65-1.175-2.825T12 7T9.175 8.175T8 11v4q0 1.65 1.175 2.825T12 19m-2-3h4v-2h-4zm0-4h4v-2h-4zm2 9q-1.625 0-3.012-.8T6.8 18H4v-2h2.1q-.075-.5-.088-1T6 14H4v-2h2q0-.5.012-1t.088-1H4V8h2.8q.35-.575.788-1.075T8.6 6.05L7 4.4L8.4 3l2.15 2.15q.7-.225 1.425-.225t1.425.225L15.6 3L17 4.4l-1.65 1.65q.575.375 1.038.862T17.2 8H20v2h-2.1q.075.5.088 1T18 12h2v2h-2q0 .5-.013 1t-.087 1H20v2h-2.8q-.8 1.4-2.187 2.2T12 21" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 682 B |
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><!-- Icon from Material Symbols by Google - https://github.com/google/material-design-icons/blob/master/LICENSE --><path fill="currentColor" d="M7 21q-.825 0-1.412-.587T5 19V6H4V4h5V3h6v1h5v2h-1v13q0 .825-.587 1.413T17 21zM17 6H7v13h10zM9 17h2V8H9zm4 0h2V8h-2zM7 6v13z"/></svg>
|
||||||
|
After Width: | Height: | Size: 360 B |
|
|
@ -86,26 +86,26 @@
|
||||||
round="8"
|
round="8"
|
||||||
flex_grow="1"
|
flex_grow="1"
|
||||||
width="100%"
|
width="100%"
|
||||||
overflow_y="scroll"
|
overflow="scroll"
|
||||||
|
position="relative"
|
||||||
>
|
>
|
||||||
<!-- radial gradient -->
|
<!-- radial gradient -->
|
||||||
<rectangle
|
<rectangle
|
||||||
position="absolute" width="100%" height="100%"
|
position="absolute" width="100%" height="100%"
|
||||||
gradient="radial" color="#44BBFF11" color2="#00000000" />
|
gradient="radial" color="#44BBFF11" color2="#00000000" />
|
||||||
|
|
||||||
|
<div overflow_x="scroll" overflow_y="scroll" width="100%" height="100%">
|
||||||
<div
|
<div
|
||||||
id="content"
|
id="content"
|
||||||
flex_direction="column"
|
flex_direction="column"
|
||||||
overflow_x="scroll"
|
|
||||||
overflow_y="scroll"
|
|
||||||
padding="16"
|
padding="16"
|
||||||
gap="8"
|
gap="8"
|
||||||
width="100%"
|
width="100%"
|
||||||
min_height="100%"
|
height="100%"
|
||||||
>
|
>
|
||||||
<!-- filled-in at runtime -->
|
<!-- filled-in at runtime -->
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div position="absolute" id="popup_manager" width="100%" height="100%" />
|
<div position="absolute" id="popup_manager" width="100%" height="100%" />
|
||||||
</rectangle>
|
</rectangle>
|
||||||
<!-- BOTTOM PANEL -->
|
<!-- BOTTOM PANEL -->
|
||||||
|
|
|
||||||
|
|
@ -11,10 +11,10 @@
|
||||||
gap="8"
|
gap="8"
|
||||||
round="8" />
|
round="8" />
|
||||||
|
|
||||||
<!-- src, text, translation -->
|
<!-- src_builtin, text, translation -->
|
||||||
<template name="GroupBoxTitle">
|
<template name="GroupBoxTitle">
|
||||||
<div flex_direction="row" align_items="center" gap="8">
|
<div flex_direction="row" align_items="center" gap="8">
|
||||||
<sprite src="${src}" src_builtin="${src_builtin}" width="24" height="24" />
|
<sprite src_builtin="${src_builtin}" width="24" height="24" />
|
||||||
<label text="${text}" translation="${translation}" weight="bold" size="18" />
|
<label text="${text}" translation="${translation}" weight="bold" size="18" />
|
||||||
</div>
|
</div>
|
||||||
<rectangle color="#FFFFFF44" width="100%" height="2" />
|
<rectangle color="#FFFFFF44" width="100%" height="2" />
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
<layout>
|
||||||
|
<template name="LoadingWithText">
|
||||||
|
<div id="root" width="100%" height="100%" align_items="center" justify_content="center">
|
||||||
|
<div flex_direction="row" gap="8" align_items="center" >
|
||||||
|
<sprite id="sprite_loading" src_builtin="dashboard/loading.svg" width="32" height="32"/>
|
||||||
|
<label translation="LOADING" weight="bold"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template name="LoadingWithoutText">
|
||||||
|
<div id="root" width="100%" height="100%" align_items="center" justify_content="center">
|
||||||
|
<sprite id="sprite_loading" src_builtin="dashboard/loading.svg" width="32" height="32"/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</layout>
|
||||||
|
|
@ -44,7 +44,6 @@
|
||||||
flex_direction="row"
|
flex_direction="row"
|
||||||
flex_wrap="wrap"
|
flex_wrap="wrap"
|
||||||
gap="4"
|
gap="4"
|
||||||
overflow_y="scroll"
|
|
||||||
/>
|
/>
|
||||||
</elements>
|
</elements>
|
||||||
</layout>
|
</layout>
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<layout>
|
<layout>
|
||||||
<elements>
|
<elements>
|
||||||
<div id="running_games_list_parent" align_self="center" />
|
<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>
|
</elements>
|
||||||
</layout>
|
</layout>
|
||||||
|
|
@ -1,37 +1,14 @@
|
||||||
<layout>
|
<layout>
|
||||||
<include src="../t_group_box.xml" />
|
|
||||||
|
|
||||||
<!-- key: str, value: str -->
|
|
||||||
<template name="BoolFlag">
|
|
||||||
<div flex_direction="row" gap="4">
|
|
||||||
<label text="${key}" />
|
|
||||||
<label weight="bold" text="${value}" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- name, checked, flag_* -->
|
|
||||||
<template name="Cell">
|
|
||||||
<rectangle macro="group_box">
|
|
||||||
<CheckBox id="checkbox" text="${name}" checked="${checked}" />
|
|
||||||
<div flex_direction="row" gap="8">
|
|
||||||
<BoolFlag key="Active:" value="${flag_active}" />
|
|
||||||
<BoolFlag key="Focused:" value="${flag_focused}" />
|
|
||||||
<BoolFlag key="IO active:" value="${flag_io_active}" />
|
|
||||||
<BoolFlag key="Overlay:" value="${flag_overlay}" />
|
|
||||||
<BoolFlag key="Primary:" value="${flag_primary}" />
|
|
||||||
<BoolFlag key="Visible:" value="${flag_visible}" />
|
|
||||||
</div>
|
|
||||||
</rectangle>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<elements>
|
<elements>
|
||||||
<label translation="DISPLAY_BRIGHTNESS" />
|
<div gap="4">
|
||||||
<Slider id="slider_brightness" width="300" height="24" min_value="0" max_value="140" />
|
<Tabs id="tabs">
|
||||||
|
<Tab name="general_settings" translation="GENERAL_SETTINGS" sprite_src_builtin="dashboard/settings.svg" />
|
||||||
<label translation="PROCESS_LIST" />
|
<Tab name="process_list" translation="PROCESS_LIST" sprite_src_builtin="dashboard/cpu.svg" />
|
||||||
<div id="list_parent" flex_direction="column" gap="8">
|
<Tab name="debug_timings" translation="DEBUG_INFO" sprite_src_builtin="dashboard/not_a_bug.svg" />
|
||||||
<!-- filled at runtime -->
|
</Tabs>
|
||||||
|
<div gap="4" id="content" width="100%">
|
||||||
|
<!-- filled-in at runtime -->
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</elements>
|
</elements>
|
||||||
</layout>
|
</layout>
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
<layout>
|
||||||
|
<include src="../t_group_box.xml" />
|
||||||
|
|
||||||
|
<!-- name, limit_min, limit_max -->
|
||||||
|
<template name="DebugGraph">
|
||||||
|
<rectangle flex_direction="column" align_items="center" gap="8" border="2" color="#ffffff11" color2="#ffffff22" gradient="vertical" border_color="#ffffff33" round="4" padding="4">
|
||||||
|
<label text="${name}" size="9" weight="bold" />
|
||||||
|
<BarGraph id="graph" width="180" height="70" unit="ms" limit_min="${limit_min}" limit_max="${limit_max}" capacity="50" />
|
||||||
|
</rectangle>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- text -->
|
||||||
|
<template name="SessionButton">
|
||||||
|
<Button id="button" text="${text}" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<elements>
|
||||||
|
<rectangle macro="group_box">
|
||||||
|
<GroupBoxTitle src_builtin="dashboard/apps.svg" text="Sessions" />
|
||||||
|
<div id="session_list_parent" flex_wrap="wrap" flex_direction="row" gap="8" />
|
||||||
|
<GroupBoxTitle src_builtin="dashboard/not_a_bug.svg" text="Debug timings" />
|
||||||
|
<div id="timings_parent" flex_wrap="wrap" flex_direction="row" gap="8">
|
||||||
|
<label text="Please select which session you want to monitor" />
|
||||||
|
</div>
|
||||||
|
</rectangle>
|
||||||
|
</elements>
|
||||||
|
</layout>
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
<layout>
|
||||||
|
<include src="../t_group_box.xml" />
|
||||||
|
|
||||||
|
<macro name="slider" width="100%" height="24"/>
|
||||||
|
|
||||||
|
<theme>
|
||||||
|
<var key="s_width" value="500"/>
|
||||||
|
</theme>
|
||||||
|
|
||||||
|
<elements>
|
||||||
|
<div flex_direction="column" gap="8">
|
||||||
|
<rectangle macro="group_box">
|
||||||
|
<GroupBoxTitle src_builtin="dashboard/settings.svg" translation="GENERAL_SETTINGS" />
|
||||||
|
|
||||||
|
<label translation="DISPLAY_BRIGHTNESS" />
|
||||||
|
<Slider id="slider_brightness" width="300" height="24" min_value="0" max_value="140" />
|
||||||
|
</rectangle>
|
||||||
|
<rectangle macro="group_box">
|
||||||
|
<GroupBoxTitle src_builtin="dashboard/settings.svg" translation="APP_SETTINGS.COLOR_KEYING" />
|
||||||
|
<div gap="8" width="~s_width">
|
||||||
|
<div gap="8" flex_direction="column" width="100%">
|
||||||
|
<label text="Color"/>
|
||||||
|
<ColorSelector id="cs_keying" height="24" width="100%"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div gap="8" flex_direction="column" width="100%">
|
||||||
|
<label text="Despill"/>
|
||||||
|
<Slider id="slider_keying_despill" min_value="0" max_value="100" macro="slider"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div gap="8" flex_direction="column" width="100%">
|
||||||
|
<label text="Curve (0 = disabled)"/>
|
||||||
|
<Slider id="slider_keying_curve" min_value="0" max_value="100" macro="slider"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div gap="8" width="~s_width">
|
||||||
|
<div gap="8" flex_direction="column" width="100%">
|
||||||
|
<label text="Hue range"/>
|
||||||
|
<Slider id="slider_keying_hue_range" min_value="0" max_value="100" macro="slider"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div gap="8" flex_direction="column" width="100%">
|
||||||
|
<label text="Saturation range"/>
|
||||||
|
<Slider id="slider_keying_saturation_range" min_value="0" max_value="100" macro="slider"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div gap="8" flex_direction="column" width="100%">
|
||||||
|
<label text="Value range"/>
|
||||||
|
<Slider id="slider_keying_value_range" min_value="0" max_value="100" macro="slider"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</rectangle>
|
||||||
|
</div>
|
||||||
|
</elements>
|
||||||
|
</layout>
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
<layout>
|
||||||
|
<include src="../t_group_box.xml" />
|
||||||
|
|
||||||
|
<!-- key: str, value: str -->
|
||||||
|
<template name="BoolFlag">
|
||||||
|
<div flex_direction="row" gap="4">
|
||||||
|
<label text="${key}" />
|
||||||
|
<label weight="bold" text="${value}" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- name, checked, flag_* -->
|
||||||
|
<template name="Cell">
|
||||||
|
<rectangle macro="group_box">
|
||||||
|
<CheckBox id="checkbox" text="${name}" checked="${checked}" />
|
||||||
|
<div flex_direction="row" gap="8" flex_wrap="wrap">
|
||||||
|
<BoolFlag key="Active:" value="${flag_active}" />
|
||||||
|
<BoolFlag key="Focused:" value="${flag_focused}" />
|
||||||
|
<BoolFlag key="IO active:" value="${flag_io_active}" />
|
||||||
|
<BoolFlag key="Overlay:" value="${flag_overlay}" />
|
||||||
|
<BoolFlag key="Primary:" value="${flag_primary}" />
|
||||||
|
<BoolFlag key="Visible:" value="${flag_visible}" />
|
||||||
|
</div>
|
||||||
|
</rectangle>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<elements>
|
||||||
|
<rectangle macro="group_box">
|
||||||
|
<GroupBoxTitle src_builtin="dashboard/cpu.svg" translation="PROCESS_LIST" />
|
||||||
|
<div id="list_parent" flex_direction="column" gap="8">
|
||||||
|
<!-- filled at runtime -->
|
||||||
|
</div>
|
||||||
|
</rectangle>
|
||||||
|
</elements>
|
||||||
|
</layout>
|
||||||
|
|
@ -13,8 +13,13 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template name="SliderSetting">
|
<template name="SliderSetting">
|
||||||
<label text="${text}" translation="${translation}" />
|
<Slider id="${id}" width="200" height="24" min_value="${min}" max_value="${max}" step="${step}" value="${value}" tooltip="${tooltip}" />
|
||||||
<Slider id="${id}" width="250" height="24" min_value="${min}" max_value="${max}" step="${step}" value="${value}" tooltip="${tooltip}" />
|
<label text="${text}" weight="bold" translation="${translation}" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template name="RangeSetting">
|
||||||
|
<Slider id="${id}" width="200" height="24" min_value="${min}" max_value="${max}" step="${step}" value="${value}" value2="${value2}" tooltip="${tooltip}" />
|
||||||
|
<label text="${text}" weight="bold" translation="${translation}" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template name="SelectSetting">
|
<template name="SelectSetting">
|
||||||
|
|
@ -48,13 +53,17 @@
|
||||||
<div gap="4">
|
<div gap="4">
|
||||||
<Tabs id="tabs">
|
<Tabs id="tabs">
|
||||||
<Tab name="look_and_feel" translation="APP_SETTINGS.LOOK_AND_FEEL" sprite_src_builtin="dashboard/palette.svg" />
|
<Tab name="look_and_feel" translation="APP_SETTINGS.LOOK_AND_FEEL" sprite_src_builtin="dashboard/palette.svg" />
|
||||||
|
<Tab name="skybox" translation="APP_SETTINGS.SKYBOX" sprite_src_builtin="dashboard/globe.svg" />
|
||||||
<Tab name="features" translation="APP_SETTINGS.FEATURES" sprite_src_builtin="dashboard/options.svg" />
|
<Tab name="features" translation="APP_SETTINGS.FEATURES" sprite_src_builtin="dashboard/options.svg" />
|
||||||
<Tab name="controls" translation="APP_SETTINGS.CONTROLS" sprite_src_builtin="dashboard/controller.svg" />
|
<Tab name="controls" translation="APP_SETTINGS.CONTROLS" sprite_src_builtin="dashboard/controller.svg" />
|
||||||
|
<Tab name="space_drag" translation="APP_SETTINGS.SPACE_DRAG" sprite_src_builtin="dashboard/drag.svg" />
|
||||||
<Tab name="misc" translation="APP_SETTINGS.MISC" sprite_src_builtin="dashboard/blocks.svg" />
|
<Tab name="misc" translation="APP_SETTINGS.MISC" sprite_src_builtin="dashboard/blocks.svg" />
|
||||||
<Tab name="autostart_apps" translation="APP_SETTINGS.AUTOSTART_APPS" sprite_src_builtin="dashboard/apps.svg" />
|
<Tab name="autostart_apps" translation="APP_SETTINGS.AUTOSTART_APPS" sprite_src_builtin="dashboard/apps.svg" />
|
||||||
<Tab name="troubleshooting" translation="APP_SETTINGS.TROUBLESHOOTING" sprite_src_builtin="dashboard/cpu.svg" />
|
<Tab name="troubleshooting" translation="APP_SETTINGS.TROUBLESHOOTING" sprite_src_builtin="dashboard/cpu.svg" />
|
||||||
</Tabs>
|
</Tabs>
|
||||||
<div flex_wrap="wrap" justify_content="stretch" gap="4" id="settings_root" width="100%" />
|
<div flex_wrap="wrap" justify_content="stretch" gap="4" id="settings_root" width="100%">
|
||||||
|
<!-- filled-in at runtime -->
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</elements>
|
</elements>
|
||||||
</layout>
|
</layout>
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
<layout>
|
||||||
|
<include src="../t_group_box.xml" />
|
||||||
|
|
||||||
|
<elements>
|
||||||
|
<div id="common_options_parent" flex_direction="column" gap="8"/>
|
||||||
|
<rectangle macro="group_box">
|
||||||
|
<GroupBoxTitle src_builtin="dashboard/gravity.svg" translation="APP_SETTINGS.SPACE_GRAVITY_GRAVITY" />
|
||||||
|
<div id="gravity_enabled_parent"/>
|
||||||
|
<div id="space_gravity_parent" flex_direction="column" gap="8">
|
||||||
|
<!-- filled-in at runtime -->
|
||||||
|
</div>
|
||||||
|
</rectangle>
|
||||||
|
</elements>
|
||||||
|
</layout>
|
||||||
|
|
@ -1,16 +1,16 @@
|
||||||
<layout>
|
<layout>
|
||||||
<include src="../t_group_box.xml" />
|
<include src_builtin="../t_group_box.xml" />
|
||||||
|
|
||||||
<!-- device_name, device_icon -->
|
<!-- device_name, device_icon -->
|
||||||
<template name="DeviceSlider">
|
<template name="DeviceSlider">
|
||||||
<rectangle macro="group_box">
|
<rectangle macro="group_box">
|
||||||
<div width="100%" align_items="center" justify_content="center" gap="8">
|
<div width="100%" align_items="center" justify_content="center" gap="8">
|
||||||
<sprite src="${device_icon}" width="16" height="16" />
|
<sprite src_builtin="${device_icon}" width="16" height="16" />
|
||||||
<label text="${device_name}" margin_right="8" size="12" weight="bold" />
|
<label text="${device_name}" margin_right="8" size="12" weight="bold" />
|
||||||
</div>
|
</div>
|
||||||
<div width="100%" align_items="center">
|
<div width="100%" align_items="center">
|
||||||
<CheckBox id="checkbox" />
|
<CheckBox id="checkbox" />
|
||||||
<Button sprite_src="${volume_icon}" id="btn_mute" width="32" />
|
<Button sprite_src_builtin="${volume_icon}" id="btn_mute" width="32" />
|
||||||
<Slider id="slider" flex_grow="1" height="16" min_value="0" max_value="150" margin_left="8" />
|
<Slider id="slider" flex_grow="1" height="16" min_value="0" max_value="150" margin_left="8" />
|
||||||
</div>
|
</div>
|
||||||
</rectangle>
|
</rectangle>
|
||||||
|
|
@ -41,7 +41,7 @@
|
||||||
flex_grow="1"
|
flex_grow="1"
|
||||||
id="${id}"
|
id="${id}"
|
||||||
translation="${translation}"
|
translation="${translation}"
|
||||||
sprite_src="${src}">
|
sprite_src_builtin="${src}">
|
||||||
</Button>
|
</Button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
<layout>
|
||||||
|
<template name="DialogBoxButton">
|
||||||
|
<Button id="btn" translation="CLOSE_WINDOW" align_self="start" sprite_src_builtin="${icon}"/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<elements>
|
||||||
|
<div flex_direction="column" align_items="center" justify_content="center" width="100%" gap="32">
|
||||||
|
<label id="label_message" size="18" weight="bold"/>
|
||||||
|
|
||||||
|
<div id="buttons" gap="8">
|
||||||
|
<!-- filled-in at runtime -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</elements>
|
||||||
|
</layout>
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
<layout>
|
||||||
|
<include src="../t_separator.xml"/>
|
||||||
|
|
||||||
|
<template name="btn_close">
|
||||||
|
<Button id="btn" translation="CLOSE_WINDOW" align_self="start" sprite_src_builtin="dashboard/check.svg"/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<elements>
|
||||||
|
<div align_items="center" justify_content="center" width="100%">
|
||||||
|
<div id="content" flex_direction="column" gap="8" width="100%">
|
||||||
|
<label translation="DOWNLOADING_FILE" size="24" weight="bold"/>
|
||||||
|
<Separator/>
|
||||||
|
<label id="label_target_path" color="~color_text_translucent" />
|
||||||
|
|
||||||
|
<div gap="8" align_items="center">
|
||||||
|
<div id="loading_parent"/>
|
||||||
|
<label id="label_status"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</elements>
|
||||||
|
</layout>
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
width="100%"
|
width="100%"
|
||||||
height="100%"
|
height="100%"
|
||||||
flex_direction="column"
|
flex_direction="column"
|
||||||
|
position="absolute"
|
||||||
>
|
>
|
||||||
<!-- Top black bar -->
|
<!-- Top black bar -->
|
||||||
<rectangle
|
<rectangle
|
||||||
|
|
@ -32,13 +33,17 @@
|
||||||
</div>
|
</div>
|
||||||
</rectangle>
|
</rectangle>
|
||||||
|
|
||||||
<!-- Content -->
|
<rectangle
|
||||||
<rectangle height="100%"
|
width="100%"
|
||||||
|
height="100%"
|
||||||
color="#010310fe"
|
color="#010310fe"
|
||||||
color2="#051c55fc"
|
color2="#051c55fc"
|
||||||
gradient="vertical"
|
gradient="vertical"
|
||||||
padding="16"
|
position="relative"
|
||||||
id="content">
|
>
|
||||||
|
<div id="content" padding="16" width="100%" height="100%" position="absolute" overflow_y="scroll">
|
||||||
|
<!-- Content, filled-in at runtime -->
|
||||||
|
</div>
|
||||||
</rectangle>
|
</rectangle>
|
||||||
</div>
|
</div>
|
||||||
</elements>
|
</elements>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
<layout>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
parameters:
|
||||||
|
"text"
|
||||||
|
"sprite"
|
||||||
|
|
||||||
|
ids:
|
||||||
|
"button"
|
||||||
|
-->
|
||||||
|
<template name="ResolutionButton">
|
||||||
|
<Button id="button" sprite_src_builtin="${sprite}" text="${text}"/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<include src="../t_separator.xml"/>
|
||||||
|
|
||||||
|
<elements>
|
||||||
|
<div gap="8" flex_direction="column" min_width="100%" overflow_y="scroll">
|
||||||
|
<div gap="8" flex_direction="column" align_items="center">
|
||||||
|
<div id="resolution_buttons" gap="8" flex_direction="row">
|
||||||
|
<!-- filled-in at runtime -->
|
||||||
|
</div>
|
||||||
|
<image id="image" width="400" height="200" round="8" border="2" border_color="~color_accent"/>
|
||||||
|
<label id="label_author" weight="bold"/>
|
||||||
|
<label id="label_description" wrap="1"/>
|
||||||
|
<Separator/>
|
||||||
|
<!-- nerdy stuff below -->
|
||||||
|
<div gap="24" justify_content="center">
|
||||||
|
<label size="10" id="label_creation_date"/>
|
||||||
|
<label size="10" id="label_modification_date"/>
|
||||||
|
<label size="10" id="label_version"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</elements>
|
||||||
|
</layout>
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
<layout>
|
||||||
|
<elements>
|
||||||
|
<div id="list" gap="8" flex_wrap="wrap" align_self="baseline">
|
||||||
|
<!-- filled-in at runtime -->
|
||||||
|
</div>
|
||||||
|
</elements>
|
||||||
|
</layout>
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
<layout>
|
||||||
|
<elements>
|
||||||
|
<div flex_direction="column" gap="8" width="100%" align_items="center">
|
||||||
|
<div flex_direction="row" gap="4" align_self="end">
|
||||||
|
<Button id="btn_refresh" tooltip="RELOAD_FROM_DISK" width="32" height="32" sprite_src_builtin="dashboard/refresh.svg" />
|
||||||
|
<Button id="btn_download_skymaps" height="32" translation="APP_SETTINGS.BROWSE_ONLINE_CATALOG" sprite_src_builtin="dashboard/download.svg"/>
|
||||||
|
</div>
|
||||||
|
<div id="list_parent" gap="8" flex_direction="row" flex_wrap="wrap" />
|
||||||
|
</div>
|
||||||
|
</elements>
|
||||||
|
</layout>
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
<layout>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
ids:
|
||||||
|
"button"
|
||||||
|
"image_preview"
|
||||||
|
"label_title"
|
||||||
|
"label_desc"
|
||||||
|
-->
|
||||||
|
<template name="Cell">
|
||||||
|
<Button
|
||||||
|
id="button"
|
||||||
|
padding="8"
|
||||||
|
round="8"
|
||||||
|
flex_direction="column"
|
||||||
|
gap="4"
|
||||||
|
width="256"
|
||||||
|
align_items="center"
|
||||||
|
align_self="start">
|
||||||
|
<image id="image_preview" width="100%" height="128" round="6">
|
||||||
|
<!-- new_pass is required, because we need to render rectangles at the top of the image. Sorry. -->
|
||||||
|
<div new_pass="1" id="resolution_pips" gap="4" margin="6"/>
|
||||||
|
</image>
|
||||||
|
<label id="label_title" wrap="1" weight="bold"/>
|
||||||
|
<label id="label_author" wrap="1"/>
|
||||||
|
</Button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
|
||||||
|
params:
|
||||||
|
"color"
|
||||||
|
"text"
|
||||||
|
-->
|
||||||
|
<template name="ResolutionPip">
|
||||||
|
<rectangle color="${color}" padding_left="4" padding_right="4" padding_top="2" padding_bottom="2" round="3" align_self="start">
|
||||||
|
<label text="${text}" weight="bold" size="12" shadow="#000000" shadow_x="2" shadow_y="2"/>
|
||||||
|
</rectangle>
|
||||||
|
</template>
|
||||||
|
</layout>
|
||||||
|
|
@ -10,7 +10,6 @@
|
||||||
"APP_SETTINGS": {
|
"APP_SETTINGS": {
|
||||||
"HIDE_USERNAME": "Benutzernamen ausblenden",
|
"HIDE_USERNAME": "Benutzernamen ausblenden",
|
||||||
"OPAQUE_BACKGROUND": "Undurchsichtiger Hintergrund",
|
"OPAQUE_BACKGROUND": "Undurchsichtiger Hintergrund",
|
||||||
"WLX": {},
|
|
||||||
"LOOK_AND_FEEL": "Aussehen und Verhalten",
|
"LOOK_AND_FEEL": "Aussehen und Verhalten",
|
||||||
"HIDE_GRAB_HELP": "Greif-Hilfe ausblenden",
|
"HIDE_GRAB_HELP": "Greif-Hilfe ausblenden",
|
||||||
"ANIMATION_SPEED": "UI-Animationsgeschwindigkeit",
|
"ANIMATION_SPEED": "UI-Animationsgeschwindigkeit",
|
||||||
|
|
@ -36,8 +35,6 @@
|
||||||
"SCROLL_SPEED": "Scrollgeschwindigkeit",
|
"SCROLL_SPEED": "Scrollgeschwindigkeit",
|
||||||
"LONG_PRESS_DURATION": "Dauer für lange Drückvorgänge",
|
"LONG_PRESS_DURATION": "Dauer für lange Drückvorgänge",
|
||||||
"POINTER_LERP_FACTOR": "Zeigerglättung",
|
"POINTER_LERP_FACTOR": "Zeigerglättung",
|
||||||
"XR_CLICK_SENSITIVITY": "XR-Klicksensitivität",
|
|
||||||
"XR_CLICK_SENSITIVITY_RELEASE": "XR-Loslassempfindlichkeit",
|
|
||||||
"CLICK_FREEZE_TIME_MS": "Klick-Freeze-Zeit (ms)",
|
"CLICK_FREEZE_TIME_MS": "Klick-Freeze-Zeit (ms)",
|
||||||
"MISC": "Verschiedenes",
|
"MISC": "Verschiedenes",
|
||||||
"XWAYLAND_BY_DEFAULT": "Standardmäßig Apps im Kompatibilitätsmodus ausführen",
|
"XWAYLAND_BY_DEFAULT": "Standardmäßig Apps im Kompatibilitätsmodus ausführen",
|
||||||
|
|
@ -46,8 +43,6 @@
|
||||||
"SCREEN_RENDER_DOWN": "Bildschirm bei niedrigerer Auflösung rendern",
|
"SCREEN_RENDER_DOWN": "Bildschirm bei niedrigerer Auflösung rendern",
|
||||||
"UPRIGHT_SCREEN_FIX_HELP": "Behebt hochstehende Bildschirme auf einigen Desktops",
|
"UPRIGHT_SCREEN_FIX_HELP": "Behebt hochstehende Bildschirme auf einigen Desktops",
|
||||||
"DOUBLE_CURSOR_FIX_HELP": "Aktivieren Sie dies, wenn Sie 2 Cursor sehen",
|
"DOUBLE_CURSOR_FIX_HELP": "Aktivieren Sie dies, wenn Sie 2 Cursor sehen",
|
||||||
"XR_CLICK_SENSITIVITY_HELP": "Analoge Trigger-Empfindlichkeit",
|
|
||||||
"XR_CLICK_SENSITIVITY_RELEASE_HELP": "Muss niedriger als Klick sein",
|
|
||||||
"CLICK_FREEZE_TIME_MS_HELP": "Hilft bei der Präzision von Doppelklicks",
|
"CLICK_FREEZE_TIME_MS_HELP": "Hilft bei der Präzision von Doppelklicks",
|
||||||
"LEFT_HANDED_MOUSE_HELP": "Verwenden Sie diese Option, wenn die Maustasten vertauscht sind",
|
"LEFT_HANDED_MOUSE_HELP": "Verwenden Sie diese Option, wenn die Maustasten vertauscht sind",
|
||||||
"BLOCK_GAME_INPUT_HELP": "Blockiert alle Eingaben, wenn ein Overlay angefahren wird",
|
"BLOCK_GAME_INPUT_HELP": "Blockiert alle Eingaben, wenn ein Overlay angefahren wird",
|
||||||
|
|
@ -87,7 +82,13 @@
|
||||||
"HANDSFREE_POINTER_HELP": "Eingabe, die bei Bewegung\nder Controller verwendet wird, wenn diese nicht verfügbar sind.\nLinkes Kneifen greift, rechtes klickt.",
|
"HANDSFREE_POINTER_HELP": "Eingabe, die bei Bewegung\nder Controller verwendet wird, wenn diese nicht verfügbar sind.\nLinkes Kneifen greift, rechtes klickt.",
|
||||||
"UI_GRADIENT_INTENSITY": "UI-Verlaufsintensität",
|
"UI_GRADIENT_INTENSITY": "UI-Verlaufsintensität",
|
||||||
"RESET_PLAYSPACE": "Spielbereich zurücksetzen",
|
"RESET_PLAYSPACE": "Spielbereich zurücksetzen",
|
||||||
"RESET_PLAYSPACE_HELP": "Den Abstand des Spielbereichs 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",
|
||||||
|
"LANGUAGE": "Sprache",
|
||||||
|
"REQUIRES_RESTART": "Erfordert Neustart",
|
||||||
|
"GRID_OPACITY": "Bodenraster-Undurchsichtigkeit",
|
||||||
|
"GRID_OPACITY_HELP": "Undurchsichtigkeit des Bodenrasters, wenn der Skybox aktiviert ist"
|
||||||
},
|
},
|
||||||
"HELLO": "Hallo!",
|
"HELLO": "Hallo!",
|
||||||
"AUDIO": {
|
"AUDIO": {
|
||||||
|
|
@ -156,5 +157,6 @@
|
||||||
"PROCESS": {
|
"PROCESS": {
|
||||||
"STOP": "Stopp",
|
"STOP": "Stopp",
|
||||||
"FORCE_KILL": "Erzwinge Beenden"
|
"FORCE_KILL": "Erzwinge Beenden"
|
||||||
}
|
},
|
||||||
|
"DEBUG_INFO": "Debug-Informationen"
|
||||||
}
|
}
|
||||||
|
|
@ -38,8 +38,11 @@
|
||||||
"BLOCK_GAME_INPUT_IGNORE_WATCH_HELP": "Do not block input when watch is hovered",
|
"BLOCK_GAME_INPUT_IGNORE_WATCH_HELP": "Do not block input when watch is hovered",
|
||||||
"BLOCK_POSES_ON_KBD_INTERACTION": "Block poses when interacting with keyboard",
|
"BLOCK_POSES_ON_KBD_INTERACTION": "Block poses when interacting with keyboard",
|
||||||
"BLOCK_POSES_ON_KBD_INTERACTION_HELP": "Blocks the game from receiving poses when the keyboard is hovered and 'Block game input' is enabled",
|
"BLOCK_POSES_ON_KBD_INTERACTION_HELP": "Blocks the game from receiving poses when the keyboard is hovered and 'Block game input' is enabled",
|
||||||
|
"BROWSE_ONLINE_CATALOG": "Browse online catalog...",
|
||||||
|
"BROWSE_SKYMAPS": "Browse skymaps",
|
||||||
"CAPTURE_METHOD": "Wayland screen capture",
|
"CAPTURE_METHOD": "Wayland screen capture",
|
||||||
"CAPTURE_METHOD_HELP": "Try changing this if you are\nexperiencing black or glitchy screens",
|
"CAPTURE_METHOD_HELP": "Try changing this if you are\nexperiencing black or glitchy screens",
|
||||||
|
"COLOR_KEYING": "Color keying",
|
||||||
"CLEAR_PIPEWIRE_TOKENS": "Clear PipeWire tokens",
|
"CLEAR_PIPEWIRE_TOKENS": "Clear PipeWire tokens",
|
||||||
"CLEAR_PIPEWIRE_TOKENS_HELP": "Prompt for screen selection on next start",
|
"CLEAR_PIPEWIRE_TOKENS_HELP": "Prompt for screen selection on next start",
|
||||||
"CLEAR_SAVED_STATE": "Clear saved state",
|
"CLEAR_SAVED_STATE": "Clear saved state",
|
||||||
|
|
@ -52,6 +55,7 @@
|
||||||
"DELETE_ALL_CONFIGS_HELP": "Remove all configuration files from conf.d",
|
"DELETE_ALL_CONFIGS_HELP": "Remove all configuration files from conf.d",
|
||||||
"DOUBLE_CURSOR_FIX": "Double cursor fix",
|
"DOUBLE_CURSOR_FIX": "Double cursor fix",
|
||||||
"DOUBLE_CURSOR_FIX_HELP": "Enable this if you see 2 cursors",
|
"DOUBLE_CURSOR_FIX_HELP": "Enable this if you see 2 cursors",
|
||||||
|
"ENABLED": "Enabled",
|
||||||
"FEATURES": "Features",
|
"FEATURES": "Features",
|
||||||
"FOCUS_FOLLOWS_MOUSE_MODE": "Mouse move on trigger touch",
|
"FOCUS_FOLLOWS_MOUSE_MODE": "Mouse move on trigger touch",
|
||||||
"HANDSFREE_POINTER": "Handsfree mode",
|
"HANDSFREE_POINTER": "Handsfree mode",
|
||||||
|
|
@ -63,6 +67,7 @@
|
||||||
"KEYBOARD_MIDDLE_CLICK": "Keyboard middle click",
|
"KEYBOARD_MIDDLE_CLICK": "Keyboard middle click",
|
||||||
"KEYBOARD_MIDDLE_CLICK_HELP": "Modifier to use when typing\nwith purple laser",
|
"KEYBOARD_MIDDLE_CLICK_HELP": "Modifier to use when typing\nwith purple laser",
|
||||||
"KEYBOARD_SOUND_ENABLED": "Keyboard sounds",
|
"KEYBOARD_SOUND_ENABLED": "Keyboard sounds",
|
||||||
|
"LANGUAGE": "Language",
|
||||||
"LEFT_HANDED_MOUSE": "Left-handed mouse",
|
"LEFT_HANDED_MOUSE": "Left-handed mouse",
|
||||||
"LEFT_HANDED_MOUSE_HELP": "Use this if mouse buttons are swapped",
|
"LEFT_HANDED_MOUSE_HELP": "Use this if mouse buttons are swapped",
|
||||||
"LONG_PRESS_DURATION": "Long press duration",
|
"LONG_PRESS_DURATION": "Long press duration",
|
||||||
|
|
@ -70,14 +75,16 @@
|
||||||
"MISC": "Miscellaneous",
|
"MISC": "Miscellaneous",
|
||||||
"NOTIFICATIONS_ENABLED": "Enable notifications",
|
"NOTIFICATIONS_ENABLED": "Enable notifications",
|
||||||
"NOTIFICATIONS_SOUND_ENABLED": "Notification sounds",
|
"NOTIFICATIONS_SOUND_ENABLED": "Notification sounds",
|
||||||
|
"NOT_SUPPORTED": "Not supported",
|
||||||
|
"NO_SKYMAPS_FOUND": "No skymaps found",
|
||||||
"OPAQUE_BACKGROUND": "Opaque background",
|
"OPAQUE_BACKGROUND": "Opaque background",
|
||||||
"OPTION": {
|
"OPTION": {
|
||||||
"AUTO": "Automatic",
|
"AUTO": "Automatic",
|
||||||
"AUTO_HELP": "ScreenCopy GPU if supported,\notherwise PipeWire GPU.",
|
"AUTO_HELP": "ScreenCopy GPU if supported,\notherwise PipeWire GPU.",
|
||||||
"EYE_PINCH": "Eye + pinch",
|
|
||||||
"HMD_PINCH": "HMD + pinch",
|
|
||||||
"EYE_ONLY": "Eye only",
|
"EYE_ONLY": "Eye only",
|
||||||
|
"EYE_PINCH": "Eye + pinch",
|
||||||
"HMD_ONLY": "HMD only",
|
"HMD_ONLY": "HMD only",
|
||||||
|
"HMD_PINCH": "HMD + pinch",
|
||||||
"NONE": "None",
|
"NONE": "None",
|
||||||
"PIPEWIRE_HELP": "Fast GPU capture,\nstandard on all desktops.",
|
"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",
|
"PW_FALLBACK_HELP": "Slow method with high CPU usage.\nTry in case PipeWire GPU doesn't work",
|
||||||
|
|
@ -85,6 +92,7 @@
|
||||||
"SCREENCOPY_HELP": "Slow, no screen share popups.\nWorks on: Hyprland, Niri, River, Sway"
|
"SCREENCOPY_HELP": "Slow, no screen share popups.\nWorks on: Hyprland, Niri, River, Sway"
|
||||||
},
|
},
|
||||||
"POINTER_LERP_FACTOR": "Pointer smoothing",
|
"POINTER_LERP_FACTOR": "Pointer smoothing",
|
||||||
|
"REQUIRES_RESTART": "Requires restart",
|
||||||
"RESET_PLAYSPACE": "Reset playspace",
|
"RESET_PLAYSPACE": "Reset playspace",
|
||||||
"RESET_PLAYSPACE_HELP": "Clear the stage space offset.",
|
"RESET_PLAYSPACE_HELP": "Clear the stage space offset.",
|
||||||
"RESTART_SOFTWARE": "Restart software",
|
"RESTART_SOFTWARE": "Restart software",
|
||||||
|
|
@ -93,9 +101,24 @@
|
||||||
"SCREEN_RENDER_DOWN": "Render screen at lower resolution",
|
"SCREEN_RENDER_DOWN": "Render screen at lower resolution",
|
||||||
"SCREEN_RENDER_DOWN_HELP": "Helps with aliasing on high-res screens",
|
"SCREEN_RENDER_DOWN_HELP": "Helps with aliasing on high-res screens",
|
||||||
"SCROLL_SPEED": "Scroll speed",
|
"SCROLL_SPEED": "Scroll speed",
|
||||||
|
"SELECT_VARIANT": "Select variant",
|
||||||
|
"ENABLE_WATCH": "Enable watch",
|
||||||
"SETS_ON_WATCH": "Sets on watch",
|
"SETS_ON_WATCH": "Sets on watch",
|
||||||
|
"SKYBOX": "Skybox",
|
||||||
|
"SKYMAP_ALREADY_DOWNLOADED": "This skymap is already downloaded. Select desired action.",
|
||||||
|
"SPACE_DRAG": "Space drag",
|
||||||
"SPACE_DRAG_MULTIPLIER": "Space drag multiplier",
|
"SPACE_DRAG_MULTIPLIER": "Space drag multiplier",
|
||||||
"SPACE_DRAG_UNLOCKED": "Allow space drag on all axes",
|
"SPACE_DRAG_UNLOCKED": "Allow space drag on all axes",
|
||||||
|
"SPACE_GRAVITY_DAMPING": "Damping",
|
||||||
|
"SPACE_GRAVITY_DAMPING_HELP": "Artificial drag to slow down movement. 0.1 - high drag, 1.0 - no drag",
|
||||||
|
"SPACE_GRAVITY_FLING_STRENGTH": "Fling strength",
|
||||||
|
"SPACE_GRAVITY_FLING_STRENGTH_HELP": "Intensity multiplier of gravitational launch force after space-drag.\n0.0 - no movement at all, 2.0 - double intensity",
|
||||||
|
"SPACE_GRAVITY_GRAVITY": "Gravity",
|
||||||
|
"SPACE_GRAVITY_GRAVITY_HELP": "Amount of downwards force. 0.0 - no gravity",
|
||||||
|
"SPACE_GRAVITY_GROUND_FRICTION": "Ground friction",
|
||||||
|
"SPACE_GRAVITY_GROUND_FRICTION_HELP": "Amount of friction slowing you down if you're touching the ground.\n0.0 - no friction (just like on ice), 1.0 - rough surface",
|
||||||
|
"SPACE_GRAVITY_FLOOR_HEIGHT": "Floor height",
|
||||||
|
"SPACE_GRAVITY_FLOOR_HEIGHT_HELP": "The Y position where the floor is. Gravity stops when you reach this height.",
|
||||||
"SPACE_ROTATE_UNLOCKED": "Allow space rotate on all axes",
|
"SPACE_ROTATE_UNLOCKED": "Allow space rotate on all axes",
|
||||||
"TROUBLESHOOTING": "Troubleshooting",
|
"TROUBLESHOOTING": "Troubleshooting",
|
||||||
"UI_GRADIENT_INTENSITY": "UI Gradient intensity",
|
"UI_GRADIENT_INTENSITY": "UI Gradient intensity",
|
||||||
|
|
@ -105,10 +128,12 @@
|
||||||
"USE_PASSTHROUGH_HELP": "Allow passthrough if the XR runtime supports it",
|
"USE_PASSTHROUGH_HELP": "Allow passthrough if the XR runtime supports it",
|
||||||
"USE_SKYBOX": "Enable skybox",
|
"USE_SKYBOX": "Enable skybox",
|
||||||
"USE_SKYBOX_HELP": "Show a skybox if there's no scene app or passthrough",
|
"USE_SKYBOX_HELP": "Show a skybox if there's no scene app or passthrough",
|
||||||
"XR_CLICK_SENSITIVITY": "XR click sensitivity",
|
"WATCH_VIEW_ANGLE": "Watch view angles",
|
||||||
"XR_CLICK_SENSITIVITY_HELP": "Analog trigger sensitivity",
|
"WATCH_VIEW_ANGLE_HELP": "Control how the watch fades away",
|
||||||
"XR_CLICK_SENSITIVITY_RELEASE": "XR release sensitivity",
|
"GRID_OPACITY": "Floor grid opacity",
|
||||||
"XR_CLICK_SENSITIVITY_RELEASE_HELP": "Must be lower than click",
|
"GRID_OPACITY_HELP": "Opacity of the floor grid when the skybox is enabled",
|
||||||
|
"XR_CLICK_SENSITIVITY": "XR trigger sensitivity",
|
||||||
|
"XR_CLICK_SENSITIVITY_HELP": "Press and release values for analog triggers",
|
||||||
"XWAYLAND_BY_DEFAULT": "Run apps in Compatibility mode by default"
|
"XWAYLAND_BY_DEFAULT": "Run apps in Compatibility mode by default"
|
||||||
},
|
},
|
||||||
"APPLICATION_LAUNCHER": "Application launcher",
|
"APPLICATION_LAUNCHER": "Application launcher",
|
||||||
|
|
@ -129,7 +154,12 @@
|
||||||
"VOLUME": "Volume"
|
"VOLUME": "Volume"
|
||||||
},
|
},
|
||||||
"CLOSE_WINDOW": "Close window",
|
"CLOSE_WINDOW": "Close window",
|
||||||
|
"CREATION_DATE": "Creation date",
|
||||||
|
"DEBUG_INFO": "Debug info",
|
||||||
"DISPLAY_BRIGHTNESS": "Display brightness",
|
"DISPLAY_BRIGHTNESS": "Display brightness",
|
||||||
|
"DOWNLOADER": "Downloader",
|
||||||
|
"DOWNLOAD_AGAIN": "Download again",
|
||||||
|
"DOWNLOADING_FILE": "Downloading file...",
|
||||||
"FAILED_TO_LAUNCH_APPLICATION": "Failed to launch a application:",
|
"FAILED_TO_LAUNCH_APPLICATION": "Failed to launch a application:",
|
||||||
"GAME_LAUNCHED": "Game launched",
|
"GAME_LAUNCHED": "Game launched",
|
||||||
"GAME_LIST": {
|
"GAME_LIST": {
|
||||||
|
|
@ -143,19 +173,25 @@
|
||||||
"HELLO_USER": "Hello, {USER}!",
|
"HELLO_USER": "Hello, {USER}!",
|
||||||
"HIDE": "Hide",
|
"HIDE": "Hide",
|
||||||
"HOME_SCREEN": "Home",
|
"HOME_SCREEN": "Home",
|
||||||
|
"MODIFICATION_DATE": "Modification date",
|
||||||
"MONADO_RUNTIME": "Monado runtime",
|
"MONADO_RUNTIME": "Monado runtime",
|
||||||
|
"LOADING": "Loading...",
|
||||||
"POPUP_ADD_DISPLAY": {
|
"POPUP_ADD_DISPLAY": {
|
||||||
"RESOLUTION": "Resolution"
|
"RESOLUTION": "Resolution"
|
||||||
},
|
},
|
||||||
"PROCESS": {
|
"PROCESS": {
|
||||||
"STOP": "Stop",
|
"FORCE_KILL": "Force-kill",
|
||||||
"FORCE_KILL": "Force-kill"
|
"STOP": "Stop"
|
||||||
},
|
},
|
||||||
"PROCESS_LIST": "Process list",
|
"PROCESS_LIST": "Process list",
|
||||||
"REFRESH": "Refresh",
|
"REFRESH": "Refresh",
|
||||||
"REMOVE": "Remove",
|
"REMOVE": "Remove",
|
||||||
|
"APPLY": "Apply",
|
||||||
|
"RELOAD_FROM_DISK": "Reload from disk",
|
||||||
"SETTINGS": "Settings",
|
"SETTINGS": "Settings",
|
||||||
"SHOW": "Show",
|
"SHOW": "Show",
|
||||||
|
"TARGET_PATH": "Target path",
|
||||||
"TERMINATE_PROCESS": "Terminate process",
|
"TERMINATE_PROCESS": "Terminate process",
|
||||||
|
"VERSION": "Version",
|
||||||
"WIDTH": "Width"
|
"WIDTH": "Width"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@
|
||||||
"APP_SETTINGS": {
|
"APP_SETTINGS": {
|
||||||
"HIDE_USERNAME": "Ocultar nombre de usuario",
|
"HIDE_USERNAME": "Ocultar nombre de usuario",
|
||||||
"OPAQUE_BACKGROUND": "Fondo opaco",
|
"OPAQUE_BACKGROUND": "Fondo opaco",
|
||||||
"WLX": {},
|
|
||||||
"LOOK_AND_FEEL": "Apariencia y estilo",
|
"LOOK_AND_FEEL": "Apariencia y estilo",
|
||||||
"HIDE_GRAB_HELP": "Ocultar ayuda para agarrar",
|
"HIDE_GRAB_HELP": "Ocultar ayuda para agarrar",
|
||||||
"ANIMATION_SPEED": "Velocidad de animación de la IU",
|
"ANIMATION_SPEED": "Velocidad de animación de la IU",
|
||||||
|
|
@ -36,8 +35,6 @@
|
||||||
"SCROLL_SPEED": "Velocidad de desplazamiento",
|
"SCROLL_SPEED": "Velocidad de desplazamiento",
|
||||||
"LONG_PRESS_DURATION": "Duración de la pulsación larga",
|
"LONG_PRESS_DURATION": "Duración de la pulsación larga",
|
||||||
"POINTER_LERP_FACTOR": "Suavizado del puntero",
|
"POINTER_LERP_FACTOR": "Suavizado del puntero",
|
||||||
"XR_CLICK_SENSITIVITY": "Sensibilidad del clic XR",
|
|
||||||
"XR_CLICK_SENSITIVITY_RELEASE": "Sensibilidad de liberación de OpenXR",
|
|
||||||
"CLICK_FREEZE_TIME_MS": "Tiempo de congelación al hacer clic (ms)",
|
"CLICK_FREEZE_TIME_MS": "Tiempo de congelación al hacer clic (ms)",
|
||||||
"MISC": "Miscelánea",
|
"MISC": "Miscelánea",
|
||||||
"XWAYLAND_BY_DEFAULT": "Ejecutar aplicaciones en modo de compatibilidad por defecto",
|
"XWAYLAND_BY_DEFAULT": "Ejecutar aplicaciones en modo de compatibilidad por defecto",
|
||||||
|
|
@ -46,8 +43,6 @@
|
||||||
"SCREEN_RENDER_DOWN": "Renderizar pantalla a menor resolución",
|
"SCREEN_RENDER_DOWN": "Renderizar pantalla a menor resolución",
|
||||||
"UPRIGHT_SCREEN_FIX_HELP": "Corrige pantallas en posición vertical en algunos escritorios",
|
"UPRIGHT_SCREEN_FIX_HELP": "Corrige pantallas en posición vertical en algunos escritorios",
|
||||||
"DOUBLE_CURSOR_FIX_HELP": "Habilita esto si ves 2 cursores",
|
"DOUBLE_CURSOR_FIX_HELP": "Habilita esto si ves 2 cursores",
|
||||||
"XR_CLICK_SENSITIVITY_HELP": "Sensibilidad del gatillo analógico",
|
|
||||||
"XR_CLICK_SENSITIVITY_RELEASE_HELP": "Debe ser inferior a 'click'",
|
|
||||||
"CLICK_FREEZE_TIME_MS_HELP": "Ayuda con la precisión de los dobles clics",
|
"CLICK_FREEZE_TIME_MS_HELP": "Ayuda con la precisión de los dobles clics",
|
||||||
"LEFT_HANDED_MOUSE_HELP": "Utilice esto si los botones del ratón están intercambiados",
|
"LEFT_HANDED_MOUSE_HELP": "Utilice esto si los botones del ratón están intercambiados",
|
||||||
"BLOCK_GAME_INPUT_HELP": "Bloquea toda la entrada cuando se pasa el cursor sobre un overlay",
|
"BLOCK_GAME_INPUT_HELP": "Bloquea toda la entrada cuando se pasa el cursor sobre un overlay",
|
||||||
|
|
@ -87,7 +82,13 @@
|
||||||
"HANDSFREE_POINTER_HELP": "Entrada a utilizar cuando no\nestén disponibles los mandos de movimiento.\nPellizco con la izquierda para agarrar, con la derecha para hacer clic.",
|
"HANDSFREE_POINTER_HELP": "Entrada a utilizar cuando no\nestén disponibles los mandos de movimiento.\nPellizco con la izquierda para agarrar, con la derecha para hacer clic.",
|
||||||
"UI_GRADIENT_INTENSITY": "Intensidad del degradado de la IU",
|
"UI_GRADIENT_INTENSITY": "Intensidad del degradado de la IU",
|
||||||
"RESET_PLAYSPACE": "Restablecer espacio de juego",
|
"RESET_PLAYSPACE": "Restablecer espacio de juego",
|
||||||
"RESET_PLAYSPACE_HELP": "Borrar el desplazamiento del 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",
|
||||||
|
"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!",
|
"HELLO": "¡Hola!",
|
||||||
"AUDIO": {
|
"AUDIO": {
|
||||||
|
|
@ -156,5 +157,6 @@
|
||||||
"PROCESS": {
|
"PROCESS": {
|
||||||
"STOP": "Detener",
|
"STOP": "Detener",
|
||||||
"FORCE_KILL": "Forzar cierre"
|
"FORCE_KILL": "Forzar cierre"
|
||||||
}
|
},
|
||||||
|
"DEBUG_INFO": "Información de depuración"
|
||||||
}
|
}
|
||||||
|
|
@ -97,17 +97,19 @@
|
||||||
"USE_PASSTHROUGH_HELP": "Consenti il passthrough se supportato dal runtime XR",
|
"USE_PASSTHROUGH_HELP": "Consenti il passthrough se supportato dal runtime XR",
|
||||||
"USE_SKYBOX": "Abilita skybox",
|
"USE_SKYBOX": "Abilita skybox",
|
||||||
"USE_SKYBOX_HELP": "Mostra uno skybox se non c'è un'app di scena o passthrough",
|
"USE_SKYBOX_HELP": "Mostra uno skybox se non c'è un'app di scena o passthrough",
|
||||||
"XR_CLICK_SENSITIVITY": "Sensibilità del click XR",
|
|
||||||
"XR_CLICK_SENSITIVITY_HELP": "Sensibilità del trigger analogico",
|
|
||||||
"XR_CLICK_SENSITIVITY_RELEASE": "Sensibilità di rilascio XR",
|
|
||||||
"XR_CLICK_SENSITIVITY_RELEASE_HELP": "Deve essere inferiore al clic",
|
|
||||||
"XWAYLAND_BY_DEFAULT": "Esegui le app in modalità Compatibilità per impostazione predefinita",
|
"XWAYLAND_BY_DEFAULT": "Esegui le app in modalità Compatibilità per impostazione predefinita",
|
||||||
"AUTOSTART_APPS": "App da avviare all'avvio",
|
"AUTOSTART_APPS": "App da avviare all'avvio",
|
||||||
"HANDSFREE_POINTER": "Modalità a mani libere",
|
"HANDSFREE_POINTER": "Modalità a mani libere",
|
||||||
"HANDSFREE_POINTER_HELP": "Input da usare quando i\ncontroller di movimento non sono disponibili.\nPizzico sinistro per afferrare, destro per cliccare.",
|
"HANDSFREE_POINTER_HELP": "Input da usare quando i\ncontroller di movimento non sono disponibili.\nPizzico sinistro per afferrare, destro per cliccare.",
|
||||||
"UI_GRADIENT_INTENSITY": "Intensità gradiente dell'interfaccia utente",
|
"UI_GRADIENT_INTENSITY": "Intensità gradiente dell'interfaccia utente",
|
||||||
"RESET_PLAYSPACE": "Ripristina playspace",
|
"RESET_PLAYSPACE": "Ripristina playspace",
|
||||||
"RESET_PLAYSPACE_HELP": "Cancella l'offset dello spazio di gioco."
|
"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",
|
||||||
|
"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_LAUNCHER": "Lanciatore applicazioni",
|
||||||
"APPLICATION_STARTED": "Applicazione avviata",
|
"APPLICATION_STARTED": "Applicazione avviata",
|
||||||
|
|
@ -155,5 +157,6 @@
|
||||||
"PROCESS": {
|
"PROCESS": {
|
||||||
"STOP": "Interrompi",
|
"STOP": "Interrompi",
|
||||||
"FORCE_KILL": "Uccidi forzatamente"
|
"FORCE_KILL": "Uccidi forzatamente"
|
||||||
}
|
},
|
||||||
|
"DEBUG_INFO": "Informazioni di debug"
|
||||||
}
|
}
|
||||||
|
|
@ -10,7 +10,6 @@
|
||||||
"APP_SETTINGS": {
|
"APP_SETTINGS": {
|
||||||
"HIDE_USERNAME": "ユーザー名を非表示",
|
"HIDE_USERNAME": "ユーザー名を非表示",
|
||||||
"OPAQUE_BACKGROUND": "不透明な背景",
|
"OPAQUE_BACKGROUND": "不透明な背景",
|
||||||
"WLX": {},
|
|
||||||
"LOOK_AND_FEEL": "外観",
|
"LOOK_AND_FEEL": "外観",
|
||||||
"HIDE_GRAB_HELP": "グリップ動作中にのヘルプを非表示",
|
"HIDE_GRAB_HELP": "グリップ動作中にのヘルプを非表示",
|
||||||
"ANIMATION_SPEED": "UIアニメーション速度",
|
"ANIMATION_SPEED": "UIアニメーション速度",
|
||||||
|
|
@ -36,8 +35,6 @@
|
||||||
"SCROLL_SPEED": "スクロール速度",
|
"SCROLL_SPEED": "スクロール速度",
|
||||||
"LONG_PRESS_DURATION": "長押し時間",
|
"LONG_PRESS_DURATION": "長押し時間",
|
||||||
"POINTER_LERP_FACTOR": "ポインターのスムージング",
|
"POINTER_LERP_FACTOR": "ポインターのスムージング",
|
||||||
"XR_CLICK_SENSITIVITY": "XRクリック感度",
|
|
||||||
"XR_CLICK_SENSITIVITY_RELEASE": "XRリリース感度",
|
|
||||||
"CLICK_FREEZE_TIME_MS": "クリックで一時停止時間 (ms)",
|
"CLICK_FREEZE_TIME_MS": "クリックで一時停止時間 (ms)",
|
||||||
"MISC": "その他",
|
"MISC": "その他",
|
||||||
"XWAYLAND_BY_DEFAULT": "アプリ実行のデフォルトは互換モード",
|
"XWAYLAND_BY_DEFAULT": "アプリ実行のデフォルトは互換モード",
|
||||||
|
|
@ -46,8 +43,6 @@
|
||||||
"SCREEN_RENDER_DOWN": "画面の解像度を縮小",
|
"SCREEN_RENDER_DOWN": "画面の解像度を縮小",
|
||||||
"UPRIGHT_SCREEN_FIX_HELP": "一部のデスクトップで縦向きの画面を修正",
|
"UPRIGHT_SCREEN_FIX_HELP": "一部のデスクトップで縦向きの画面を修正",
|
||||||
"DOUBLE_CURSOR_FIX_HELP": "2つのカーソルが表示される場合は、これを有効にします",
|
"DOUBLE_CURSOR_FIX_HELP": "2つのカーソルが表示される場合は、これを有効にします",
|
||||||
"XR_CLICK_SENSITIVITY_HELP": "アナログトリガの感度",
|
|
||||||
"XR_CLICK_SENSITIVITY_RELEASE_HELP": "クリックより低くする必要があります",
|
|
||||||
"CLICK_FREEZE_TIME_MS_HELP": "ダブルクリックの精度向上に役立ちます",
|
"CLICK_FREEZE_TIME_MS_HELP": "ダブルクリックの精度向上に役立ちます",
|
||||||
"LEFT_HANDED_MOUSE_HELP": "マウスボタンが入れ替わっている場合に有効にします",
|
"LEFT_HANDED_MOUSE_HELP": "マウスボタンが入れ替わっている場合に有効にします",
|
||||||
"BLOCK_GAME_INPUT_HELP": "オーバーレイ上にマウスカーソルがあるときに入力をブロックします",
|
"BLOCK_GAME_INPUT_HELP": "オーバーレイ上にマウスカーソルがあるときに入力をブロックします",
|
||||||
|
|
@ -87,7 +82,13 @@
|
||||||
"HANDSFREE_POINTER_HELP": "モーションコントローラーが利用できない場合の入力方法。\n左手のピンチは掴み、右手のピンチはクリックです。",
|
"HANDSFREE_POINTER_HELP": "モーションコントローラーが利用できない場合の入力方法。\n左手のピンチは掴み、右手のピンチはクリックです。",
|
||||||
"UI_GRADIENT_INTENSITY": "UIグラデーションの強さ",
|
"UI_GRADIENT_INTENSITY": "UIグラデーションの強さ",
|
||||||
"RESET_PLAYSPACE": "プレイエリアをリセット",
|
"RESET_PLAYSPACE": "プレイエリアをリセット",
|
||||||
"RESET_PLAYSPACE_HELP": "プレイエリアのオフセットをクリアします。"
|
"RESET_PLAYSPACE_HELP": "プレイエリアのオフセットをクリアします。",
|
||||||
|
"BLOCK_POSES_ON_KBD_INTERACTION": "キーボード操作時のポーズをブロック",
|
||||||
|
"BLOCK_POSES_ON_KBD_INTERACTION_HELP": "キーボードがホバーされ、「ゲーム入力をブロック」が有効になっている場合、ゲームがポーズを受信することをブロックします",
|
||||||
|
"LANGUAGE": "言語",
|
||||||
|
"REQUIRES_RESTART": "再起動が必要です",
|
||||||
|
"GRID_OPACITY": "フロアグリッドの不透明度",
|
||||||
|
"GRID_OPACITY_HELP": "スカイボックスが有効なときの床グリッドの不透明度"
|
||||||
},
|
},
|
||||||
"HELLO": "こんにちは!",
|
"HELLO": "こんにちは!",
|
||||||
"AUDIO": {
|
"AUDIO": {
|
||||||
|
|
@ -156,5 +157,6 @@
|
||||||
"PROCESS": {
|
"PROCESS": {
|
||||||
"STOP": "停止",
|
"STOP": "停止",
|
||||||
"FORCE_KILL": "強制終了"
|
"FORCE_KILL": "強制終了"
|
||||||
}
|
},
|
||||||
|
"DEBUG_INFO": "デバッグ情報"
|
||||||
}
|
}
|
||||||
|
|
@ -5,7 +5,6 @@
|
||||||
"APP_SETTINGS": {
|
"APP_SETTINGS": {
|
||||||
"HIDE_USERNAME": "Ukryj nazwę użytkownika",
|
"HIDE_USERNAME": "Ukryj nazwę użytkownika",
|
||||||
"OPAQUE_BACKGROUND": "Nieprzezroczyste tło",
|
"OPAQUE_BACKGROUND": "Nieprzezroczyste tło",
|
||||||
"WLX": {},
|
|
||||||
"LOOK_AND_FEEL": "Wygląd i działanie",
|
"LOOK_AND_FEEL": "Wygląd i działanie",
|
||||||
"HIDE_GRAB_HELP": "Ukryj pomoc dotyczącą chwytania",
|
"HIDE_GRAB_HELP": "Ukryj pomoc dotyczącą chwytania",
|
||||||
"ANIMATION_SPEED": "Prędkość animacji UI",
|
"ANIMATION_SPEED": "Prędkość animacji UI",
|
||||||
|
|
@ -31,18 +30,13 @@
|
||||||
"SCROLL_SPEED": "Prędkość przewijania",
|
"SCROLL_SPEED": "Prędkość przewijania",
|
||||||
"LONG_PRESS_DURATION": "Czas długiego przytrzymania",
|
"LONG_PRESS_DURATION": "Czas długiego przytrzymania",
|
||||||
"POINTER_LERP_FACTOR": "Wygładzanie wskaźnika",
|
"POINTER_LERP_FACTOR": "Wygładzanie wskaźnika",
|
||||||
"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)",
|
"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",
|
"XWAYLAND_BY_DEFAULT": "Uruchamiaj aplikacje domyślnie w trybie kompatybilności",
|
||||||
"UPRIGHT_SCREEN_FIX": "Naprawa pozycji ekranu",
|
"UPRIGHT_SCREEN_FIX": "Naprawa pozycji ekranu",
|
||||||
"DOUBLE_CURSOR_FIX": "Naprawa podwójnego kursora",
|
"DOUBLE_CURSOR_FIX": "Naprawa podwójnego kursora",
|
||||||
"SCREEN_RENDER_DOWN": "Renderuj ekran w niższej rozdzielczości",
|
"SCREEN_RENDER_DOWN": "Renderuj ekran w niższej rozdzielczości",
|
||||||
"UPRIGHT_SCREEN_FIX_HELP": "Naprawia pionowe ekrany na niektórych komputerach",
|
"UPRIGHT_SCREEN_FIX_HELP": "Naprawia pionowe ekrany na niektórych komputerach",
|
||||||
"DOUBLE_CURSOR_FIX_HELP": "Włącz to, jeśli widzisz 2 kursory",
|
"DOUBLE_CURSOR_FIX_HELP": "Włącz to, jeśli widzisz 2 kursory",
|
||||||
"XR_CLICK_SENSITIVITY_HELP": "Czułość analogowego spustu",
|
|
||||||
"XR_CLICK_SENSITIVITY_RELEASE_HELP": "Musi być niższa niż kliknięcie",
|
|
||||||
"CLICK_FREEZE_TIME_MS_HELP": "Pomaga w precyzji podwójnego kliknięcia",
|
"CLICK_FREEZE_TIME_MS_HELP": "Pomaga w precyzji podwójnego kliknięcia",
|
||||||
"LEFT_HANDED_MOUSE_HELP": "Użyj tego, jeśli przyciski myszy są zamienione",
|
"LEFT_HANDED_MOUSE_HELP": "Użyj tego, jeśli przyciski myszy są zamienione",
|
||||||
"BLOCK_GAME_INPUT_HELP": "Blokuje wszystkie dane wejściowe, gdy kursor najedzie na nakładkę",
|
"BLOCK_GAME_INPUT_HELP": "Blokuje wszystkie dane wejściowe, gdy kursor najedzie na nakładkę",
|
||||||
|
|
@ -50,12 +44,13 @@
|
||||||
"USE_SKYBOX_HELP": "Wyświetlaj niebo, jeśli nie ma aplikacji sceny lub passthrough",
|
"USE_SKYBOX_HELP": "Wyświetlaj niebo, jeśli nie ma aplikacji sceny lub passthrough",
|
||||||
"USE_PASSTHROUGH_HELP": "Pozwól na passthrough, jeśli runtime XR to obsługuje",
|
"USE_PASSTHROUGH_HELP": "Pozwól na passthrough, jeśli runtime XR to obsługuje",
|
||||||
"SCREEN_RENDER_DOWN_HELP": "Pomaga redukować aliasing na ekranach o wysokiej rozdzielczości",
|
"SCREEN_RENDER_DOWN_HELP": "Pomaga redukować aliasing na ekranach o wysokiej rozdzielczości",
|
||||||
|
"ENABLE_WATCH": "Włącz zegarek",
|
||||||
"SETS_ON_WATCH": "Lista zestawów na zegarku",
|
"SETS_ON_WATCH": "Lista zestawów na zegarku",
|
||||||
"TROUBLESHOOTING": "Rozwiązywanie problemów",
|
"TROUBLESHOOTING": "Rozwiązywanie problemów",
|
||||||
"CLEAR_SAVED_STATE": "Wyczyść zapisany stan",
|
"CLEAR_SAVED_STATE": "Wyczyść zapisany stan",
|
||||||
"CLEAR_PIPEWIRE_TOKENS": "Wyczyść tokeny PipeWire",
|
"CLEAR_PIPEWIRE_TOKENS": "Wyczyść tokeny PipeWire",
|
||||||
"DELETE_ALL_CONFIGS": "Wyczyść konfigurację",
|
"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_SAVED_STATE_HELP": "Zresetuj zestawy i pozycje nakładek",
|
||||||
"CLEAR_PIPEWIRE_TOKENS_HELP": "Zapytaj o wybór ekranu przy następnym uruchomieniu",
|
"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",
|
"DELETE_ALL_CONFIGS_HELP": "Usuń wszystkie pliki konfiguracyjne z katalogu conf.d",
|
||||||
|
|
@ -72,17 +67,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_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",
|
"SCREENCOPY_HELP": "Wolne, bez wyskakujących okienek udostępniania ekranu.\nDziała na: Hyprland, Niri, River, Sway",
|
||||||
"NONE": "Brak",
|
"NONE": "Brak",
|
||||||
"HMD_PINCH": "HMD + szczyknięcie",
|
"HMD_PINCH": "HMD + ściśnięcie placami",
|
||||||
"EYE_PINCH": "Ściśnięcie palcami + oko",
|
"EYE_PINCH": "Ściśnięcie palcami + oko",
|
||||||
"EYE_ONLY": "Tylko oko",
|
"EYE_ONLY": "Tylko oko",
|
||||||
"HMD_ONLY": "Tylko HMD"
|
"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": "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.",
|
"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",
|
"UI_GRADIENT_INTENSITY": "Intensywność gradientu UI",
|
||||||
"RESET_PLAYSPACE": "Zresetuj przestrzeń gry",
|
"RESET_PLAYSPACE": "Zresetuj przestrzeń gry",
|
||||||
"RESET_PLAYSPACE_HELP": "Wyczyść przesunięcie przestrzeni 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'",
|
||||||
|
"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",
|
"APPLICATION_LAUNCHER": "Uruchamiacz aplikacji",
|
||||||
"APPLICATIONS": "Aplikacje",
|
"APPLICATIONS": "Aplikacje",
|
||||||
|
|
@ -156,5 +158,6 @@
|
||||||
"PROCESS": {
|
"PROCESS": {
|
||||||
"STOP": "Zatrzymaj",
|
"STOP": "Zatrzymaj",
|
||||||
"FORCE_KILL": "Wymuś zakończenie"
|
"FORCE_KILL": "Wymuś zakończenie"
|
||||||
}
|
},
|
||||||
|
"DEBUG_INFO": "Informacje debugowania"
|
||||||
}
|
}
|
||||||
|
|
@ -97,17 +97,19 @@
|
||||||
"USE_PASSTHROUGH_HELP": "如果 XR 运行时支持,则允许穿透",
|
"USE_PASSTHROUGH_HELP": "如果 XR 运行时支持,则允许穿透",
|
||||||
"USE_SKYBOX": "启用天空盒",
|
"USE_SKYBOX": "启用天空盒",
|
||||||
"USE_SKYBOX_HELP": "如果没有场景应用或穿透,则显示天空盒",
|
"USE_SKYBOX_HELP": "如果没有场景应用或穿透,则显示天空盒",
|
||||||
"XR_CLICK_SENSITIVITY": "XR 点击灵敏度",
|
|
||||||
"XR_CLICK_SENSITIVITY_HELP": "模拟扳机灵敏度",
|
|
||||||
"XR_CLICK_SENSITIVITY_RELEASE": "XR 释放灵敏度",
|
|
||||||
"XR_CLICK_SENSITIVITY_RELEASE_HELP": "必须低于点击灵敏度",
|
|
||||||
"XWAYLAND_BY_DEFAULT": "默认以兼容模式运行应用",
|
"XWAYLAND_BY_DEFAULT": "默认以兼容模式运行应用",
|
||||||
"AUTOSTART_APPS": "开机启动应用",
|
"AUTOSTART_APPS": "开机启动应用",
|
||||||
"HANDSFREE_POINTER": "免提模式",
|
"HANDSFREE_POINTER": "免提模式",
|
||||||
"HANDSFREE_POINTER_HELP": "当运动控制器不可用时使用的输入。\n左手捏合为抓取,右手为点击。",
|
"HANDSFREE_POINTER_HELP": "当运动控制器不可用时使用的输入。\n左手捏合为抓取,右手为点击。",
|
||||||
"UI_GRADIENT_INTENSITY": "UI 渐变强度",
|
"UI_GRADIENT_INTENSITY": "UI 渐变强度",
|
||||||
"RESET_PLAYSPACE": "重置游戏空间",
|
"RESET_PLAYSPACE": "重置游戏空间",
|
||||||
"RESET_PLAYSPACE_HELP": "清除舞台空间偏移。"
|
"RESET_PLAYSPACE_HELP": "清除舞台空间偏移。",
|
||||||
|
"BLOCK_POSES_ON_KBD_INTERACTION": "与键盘交互时阻止姿势",
|
||||||
|
"BLOCK_POSES_ON_KBD_INTERACTION_HELP": "当键盘悬停且启用“阻止游戏输入”时,阻止游戏接收姿势",
|
||||||
|
"LANGUAGE": "语言",
|
||||||
|
"REQUIRES_RESTART": "需要重启",
|
||||||
|
"GRID_OPACITY": "地面网格不透明度",
|
||||||
|
"GRID_OPACITY_HELP": "启用天空盒时地板网格的不透明度"
|
||||||
},
|
},
|
||||||
"APPLICATION_LAUNCHER": "应用启动器",
|
"APPLICATION_LAUNCHER": "应用启动器",
|
||||||
"APPLICATION_STARTED": "应用已启动",
|
"APPLICATION_STARTED": "应用已启动",
|
||||||
|
|
@ -155,5 +157,6 @@
|
||||||
"PROCESS": {
|
"PROCESS": {
|
||||||
"STOP": "停止",
|
"STOP": "停止",
|
||||||
"FORCE_KILL": "强制关闭"
|
"FORCE_KILL": "强制关闭"
|
||||||
}
|
},
|
||||||
|
"DEBUG_INFO": "调试信息"
|
||||||
}
|
}
|
||||||
|
|
@ -12,12 +12,15 @@ use wgui::{
|
||||||
parser::{Fetchable, ParseDocumentParams, ParserState},
|
parser::{Fetchable, ParseDocumentParams, ParserState},
|
||||||
renderer_vk::text::custom_glyph::CustomGlyphData,
|
renderer_vk::text::custom_glyph::CustomGlyphData,
|
||||||
task::Tasks,
|
task::Tasks,
|
||||||
|
theme::WguiTheme,
|
||||||
widget::{label::WidgetLabel, rectangle::WidgetRectangle, sprite::WidgetSprite},
|
widget::{label::WidgetLabel, rectangle::WidgetRectangle, sprite::WidgetSprite},
|
||||||
windowing::window::{WguiWindow, WguiWindowParams, WguiWindowParamsExtra, WguiWindowPlacement},
|
windowing::window::{WguiWindow, WguiWindowParams, WguiWindowParamsExtra, WguiWindowPlacement},
|
||||||
};
|
};
|
||||||
use wlx_common::{
|
use wlx_common::{
|
||||||
|
async_executor::AsyncExecutor,
|
||||||
audio,
|
audio,
|
||||||
dash_interface::{BoxDashInterface, RecenterMode},
|
dash_interface::{BoxDashInterface, RecenterMode},
|
||||||
|
locale::WayVRLangProvider,
|
||||||
timestep::{self, Timestep},
|
timestep::{self, Timestep},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -25,9 +28,8 @@ use crate::{
|
||||||
assets,
|
assets,
|
||||||
tab::{Tab, TabType, apps::TabApps, games::TabGames, home::TabHome, monado::TabMonado, settings::TabSettings},
|
tab::{Tab, TabType, apps::TabApps, games::TabGames, home::TabHome, monado::TabMonado, settings::TabSettings},
|
||||||
util::{
|
util::{
|
||||||
popup_manager::{MountPopupParams, PopupManager, PopupManagerParams},
|
popup_manager::{MountPopupOnceParams, PopupManager, PopupManagerParams},
|
||||||
toast_manager::ToastManager,
|
toast_manager::ToastManager,
|
||||||
various::AsyncExecutor,
|
|
||||||
},
|
},
|
||||||
views,
|
views,
|
||||||
};
|
};
|
||||||
|
|
@ -43,7 +45,7 @@ pub type FrontendTasks = Tasks<FrontendTask>;
|
||||||
|
|
||||||
pub struct Frontend<T> {
|
pub struct Frontend<T> {
|
||||||
pub layout: Layout,
|
pub layout: Layout,
|
||||||
globals: WguiGlobals,
|
pub globals: WguiGlobals,
|
||||||
|
|
||||||
pub interface: BoxDashInterface<T>,
|
pub interface: BoxDashInterface<T>,
|
||||||
|
|
||||||
|
|
@ -81,9 +83,11 @@ pub struct FrontendUpdateResult {
|
||||||
pub sounds_to_play: Vec<SoundType>,
|
pub sounds_to_play: Vec<SoundType>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct InitParams<T> {
|
pub struct InitParams<'a, T> {
|
||||||
pub interface: BoxDashInterface<T>,
|
pub interface: BoxDashInterface<T>,
|
||||||
|
pub lang_provider: &'a WayVRLangProvider,
|
||||||
pub has_monado: bool,
|
pub has_monado: bool,
|
||||||
|
pub theme: Rc<WguiTheme>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
|
@ -97,19 +101,18 @@ pub enum FrontendTask {
|
||||||
SetTab(TabType),
|
SetTab(TabType),
|
||||||
RefreshClock,
|
RefreshClock,
|
||||||
RefreshBackground,
|
RefreshBackground,
|
||||||
MountPopup(MountPopupParams),
|
MountPopupOnce(MountPopupOnceParams),
|
||||||
RefreshPopupManager,
|
RefreshPopupManager,
|
||||||
ShowAudioSettings,
|
ShowAudioSettings,
|
||||||
UpdateAudioSettingsView,
|
UpdateAudioSettingsView,
|
||||||
RecenterPlayspace,
|
RecenterPlayspace,
|
||||||
PushToast(Translation),
|
PushToast(Translation),
|
||||||
PlaySound(SoundType),
|
PlaySound(SoundType),
|
||||||
UpdateWguiDefaultsFromConfig,
|
|
||||||
HideDashboard,
|
HideDashboard,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: 'static> Frontend<T> {
|
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 mut assets = Box::new(assets::Asset {});
|
||||||
|
|
||||||
let font_binary_bold = assets.load_from_path_gzip("Quicksand-Bold.ttf.gz")?;
|
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(
|
let globals = WguiGlobals::new(
|
||||||
assets,
|
assets,
|
||||||
wgui::globals::Defaults::default(),
|
params.lang_provider,
|
||||||
&WguiFontConfig {
|
&WguiFontConfig {
|
||||||
binaries: vec![&font_binary_regular, &font_binary_bold, &font_binary_light],
|
binaries: vec![&font_binary_regular, &font_binary_bold, &font_binary_light],
|
||||||
family_name_sans_serif: "Quicksand",
|
family_name_sans_serif: "Quicksand",
|
||||||
|
|
@ -128,15 +131,16 @@ impl<T: 'static> Frontend<T> {
|
||||||
PathBuf::new(), //FIXME: pass from somewhere else
|
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(
|
let (layout, state) = wgui::parser::new_layout_from_assets(
|
||||||
&ParseDocumentParams {
|
&ParseDocumentParams {
|
||||||
globals: globals.clone(),
|
globals: globals.clone(),
|
||||||
path: AssetPath::BuiltIn("gui/dashboard.xml"),
|
path: AssetPath::BuiltIn("gui/dashboard.xml"),
|
||||||
extra: Default::default(),
|
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")?;
|
let id_popup_manager = state.get_widget_id("popup_manager")?;
|
||||||
|
|
@ -182,7 +186,6 @@ impl<T: 'static> Frontend<T> {
|
||||||
// init some things first
|
// init some things first
|
||||||
frontend.tasks.push(FrontendTask::RefreshBackground);
|
frontend.tasks.push(FrontendTask::RefreshBackground);
|
||||||
frontend.tasks.push(FrontendTask::RefreshClock);
|
frontend.tasks.push(FrontendTask::RefreshClock);
|
||||||
frontend.tasks.push(FrontendTask::UpdateWguiDefaultsFromConfig);
|
|
||||||
|
|
||||||
Frontend::register_widgets(&mut frontend)?;
|
Frontend::register_widgets(&mut frontend)?;
|
||||||
|
|
||||||
|
|
@ -193,16 +196,6 @@ impl<T: 'static> Frontend<T> {
|
||||||
self.sounds_to_play.push(sound_type);
|
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<()> {
|
fn play_sound(&mut self, audio_system: &mut audio::AudioSystem, sound_type: SoundType) -> anyhow::Result<()> {
|
||||||
let mut assets = self.globals.assets_builtin();
|
let mut assets = self.globals.assets_builtin();
|
||||||
|
|
||||||
|
|
@ -270,7 +263,7 @@ impl<T: 'static> Frontend<T> {
|
||||||
{
|
{
|
||||||
// always 30 times per second
|
// always 30 times per second
|
||||||
while self.timestep.on_tick() {
|
while self.timestep.on_tick() {
|
||||||
self.toast_manager.tick(&self.globals, &mut self.layout)?;
|
self.toast_manager.tick(&mut self.layout)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -286,10 +279,8 @@ impl<T: 'static> Frontend<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_time(&mut self, data: &mut T) -> anyhow::Result<()> {
|
fn update_time(&mut self, data: &mut T) -> anyhow::Result<()> {
|
||||||
let mut c = self.layout.start_common();
|
|
||||||
let mut common = c.common();
|
|
||||||
|
|
||||||
{
|
{
|
||||||
|
let mut common = self.layout.common();
|
||||||
let mut label = common
|
let mut label = common
|
||||||
.state
|
.state
|
||||||
.widgets
|
.widgets
|
||||||
|
|
@ -310,27 +301,20 @@ impl<T: 'static> Frontend<T> {
|
||||||
label.set_text(&mut common, Translation::from_raw_text(&text));
|
label.set_text(&mut common, Translation::from_raw_text(&text));
|
||||||
}
|
}
|
||||||
|
|
||||||
c.finish()?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mount_popup(&mut self, params: MountPopupParams, data: &mut T) -> anyhow::Result<()> {
|
fn mount_popup_once(&mut self, params: MountPopupOnceParams, data: &mut T) -> anyhow::Result<()> {
|
||||||
let config = self.interface.general_config(data);
|
let config = self.interface.general_config(data);
|
||||||
|
|
||||||
self.popup_manager.mount_popup(
|
self
|
||||||
self.globals.clone(),
|
.popup_manager
|
||||||
&mut self.layout,
|
.mount_popup_once(&self.globals, &mut self.layout, &self.tasks, params, config)?;
|
||||||
self.tasks.clone(),
|
|
||||||
params,
|
|
||||||
config,
|
|
||||||
)?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn refresh_popup_manager(&mut self) -> anyhow::Result<()> {
|
fn refresh_popup_manager(&mut self) -> anyhow::Result<()> {
|
||||||
let mut c = self.layout.start_common();
|
self.popup_manager.refresh(&mut self.layout.alterables);
|
||||||
self.popup_manager.refresh(c.common().alterables);
|
|
||||||
c.finish()?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -363,7 +347,7 @@ impl<T: 'static> Frontend<T> {
|
||||||
FrontendTask::SetTab(tab_type) => self.set_tab(params.data, tab_type)?,
|
FrontendTask::SetTab(tab_type) => self.set_tab(params.data, tab_type)?,
|
||||||
FrontendTask::RefreshClock => self.update_time(params.data)?,
|
FrontendTask::RefreshClock => self.update_time(params.data)?,
|
||||||
FrontendTask::RefreshBackground => self.update_background(params.data)?,
|
FrontendTask::RefreshBackground => self.update_background(params.data)?,
|
||||||
FrontendTask::MountPopup(popup_params) => self.mount_popup(popup_params, params.data)?,
|
FrontendTask::MountPopupOnce(popup_params) => self.mount_popup_once(popup_params, params.data)?,
|
||||||
FrontendTask::RefreshPopupManager => self.refresh_popup_manager()?,
|
FrontendTask::RefreshPopupManager => self.refresh_popup_manager()?,
|
||||||
FrontendTask::ShowAudioSettings => self.action_show_audio_settings()?,
|
FrontendTask::ShowAudioSettings => self.action_show_audio_settings()?,
|
||||||
FrontendTask::UpdateAudioSettingsView => self.action_update_audio_settings()?,
|
FrontendTask::UpdateAudioSettingsView => self.action_update_audio_settings()?,
|
||||||
|
|
@ -371,16 +355,12 @@ impl<T: 'static> Frontend<T> {
|
||||||
FrontendTask::PushToast(content) => self.toast_manager.push(content),
|
FrontendTask::PushToast(content) => self.toast_manager.push(content),
|
||||||
FrontendTask::PlaySound(sound_type) => self.queue_play_sound(sound_type),
|
FrontendTask::PlaySound(sound_type) => self.queue_play_sound(sound_type),
|
||||||
FrontendTask::HideDashboard => self.action_hide_dashboard(params.data),
|
FrontendTask::HideDashboard => self.action_hide_dashboard(params.data),
|
||||||
FrontendTask::UpdateWguiDefaultsFromConfig => {
|
|
||||||
Frontend::update_defaults_from_config(&self.globals, &mut self.interface, params.data)
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_tab_title(&mut self, translation: &str, icon: &str) -> anyhow::Result<()> {
|
fn set_tab_title(&mut self, translation: &str, icon: &str) -> anyhow::Result<()> {
|
||||||
let mut c = self.layout.start_common();
|
let mut common = self.layout.common();
|
||||||
let mut common = c.common();
|
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut label = common
|
let mut label = common
|
||||||
|
|
@ -396,12 +376,11 @@ impl<T: 'static> Frontend<T> {
|
||||||
.widgets
|
.widgets
|
||||||
.cast_as::<WidgetSprite>(self.widgets.id_sprite_titlebar_icon)?;
|
.cast_as::<WidgetSprite>(self.widgets.id_sprite_titlebar_icon)?;
|
||||||
sprite.set_content(
|
sprite.set_content(
|
||||||
&mut common,
|
common.alterables,
|
||||||
Some(CustomGlyphData::from_assets(&self.globals, AssetPath::BuiltIn(icon))?),
|
Some(CustomGlyphData::from_assets(&self.globals, AssetPath::BuiltIn(icon))?),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
c.finish()?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -495,7 +474,6 @@ impl<T: 'static> Frontend<T> {
|
||||||
|
|
||||||
fn action_show_audio_settings(&mut self) -> anyhow::Result<()> {
|
fn action_show_audio_settings(&mut self) -> anyhow::Result<()> {
|
||||||
self.window_audio_settings.open(&mut WguiWindowParams {
|
self.window_audio_settings.open(&mut WguiWindowParams {
|
||||||
globals: &self.globals,
|
|
||||||
position: Vec2::new(64.0, 64.0),
|
position: Vec2::new(64.0, 64.0),
|
||||||
layout: &mut self.layout,
|
layout: &mut self.layout,
|
||||||
extra: WguiWindowParamsExtra {
|
extra: WguiWindowParamsExtra {
|
||||||
|
|
|
||||||
|
|
@ -9,26 +9,20 @@ use wgui::{
|
||||||
assets::AssetPath,
|
assets::AssetPath,
|
||||||
components::button::{ButtonClickCallback, ComponentButton},
|
components::button::{ButtonClickCallback, ComponentButton},
|
||||||
globals::WguiGlobals,
|
globals::WguiGlobals,
|
||||||
i18n::Translation,
|
|
||||||
layout::{WidgetID, WidgetPair},
|
layout::{WidgetID, WidgetPair},
|
||||||
parser::{Fetchable, ParseDocumentParams, ParserState},
|
parser::{Fetchable, ParseDocumentParams, ParserState},
|
||||||
task::Tasks,
|
|
||||||
};
|
};
|
||||||
use wlx_common::desktop_finder::DesktopEntry;
|
use wlx_common::desktop_finder::DesktopEntry;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
frontend::{Frontend, FrontendTask, FrontendTasks},
|
frontend::{Frontend, FrontendTasks},
|
||||||
tab::{Tab, TabType},
|
tab::{Tab, TabType},
|
||||||
util::popup_manager::{MountPopupParams, PopupHandle},
|
util::popup_manager::PopupHolder,
|
||||||
views::{self, app_launcher},
|
views::{self},
|
||||||
};
|
};
|
||||||
|
|
||||||
enum Task {
|
|
||||||
CloseLauncher,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct State {
|
struct State {
|
||||||
view_launcher: Option<(PopupHandle, views::app_launcher::View)>,
|
view_launcher: PopupHolder<views::app_launcher::View>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct TabApps<T> {
|
pub struct TabApps<T> {
|
||||||
|
|
@ -37,7 +31,6 @@ pub struct TabApps<T> {
|
||||||
|
|
||||||
state: Rc<RefCell<State>>,
|
state: Rc<RefCell<State>>,
|
||||||
app_list: AppList,
|
app_list: AppList,
|
||||||
tasks: Tasks<Task>,
|
|
||||||
marker: PhantomData<T>,
|
marker: PhantomData<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -47,21 +40,13 @@ impl<T> Tab<T> for TabApps<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, frontend: &mut Frontend<T>, _time_ms: u32, data: &mut T) -> anyhow::Result<()> {
|
fn update(&mut self, frontend: &mut Frontend<T>, _time_ms: u32, data: &mut T) -> anyhow::Result<()> {
|
||||||
let mut state = self.state.borrow_mut();
|
let state = self.state.borrow_mut();
|
||||||
|
|
||||||
for task in self.tasks.drain() {
|
self.app_list.tick(frontend, &self.state, &mut self.parser_state)?;
|
||||||
match task {
|
|
||||||
Task::CloseLauncher => state.view_launcher = None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self
|
state
|
||||||
.app_list
|
.view_launcher
|
||||||
.tick(frontend, &self.state, &self.tasks, &mut self.parser_state)?;
|
.with_view_res(|view| view.update(&mut frontend.interface, data))?;
|
||||||
|
|
||||||
if let Some((_, launcher)) = &mut state.view_launcher {
|
|
||||||
launcher.update(&mut frontend.interface, data)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -79,40 +64,14 @@ fn on_app_click(
|
||||||
globals: WguiGlobals,
|
globals: WguiGlobals,
|
||||||
entry: DesktopEntry,
|
entry: DesktopEntry,
|
||||||
state: Rc<RefCell<State>>,
|
state: Rc<RefCell<State>>,
|
||||||
tasks: Tasks<Task>,
|
|
||||||
) -> ButtonClickCallback {
|
) -> ButtonClickCallback {
|
||||||
Rc::new(move |_common, _evt| {
|
Rc::new(move |_common, _evt| {
|
||||||
frontend_tasks.push(FrontendTask::MountPopup(MountPopupParams {
|
views::app_launcher::mount_popup(
|
||||||
title: Translation::from_raw_text(&entry.app_name),
|
frontend_tasks.clone(),
|
||||||
on_content: {
|
globals.clone(),
|
||||||
// this is awful
|
entry.clone(),
|
||||||
let state = state.clone();
|
state.borrow_mut().view_launcher.clone(),
|
||||||
let entry = entry.clone();
|
);
|
||||||
let globals = globals.clone();
|
|
||||||
let frontend_tasks = frontend_tasks.clone();
|
|
||||||
let tasks = tasks.clone();
|
|
||||||
|
|
||||||
Rc::new(move |data| {
|
|
||||||
let on_launched = {
|
|
||||||
let tasks = tasks.clone();
|
|
||||||
Box::new(move || tasks.push(Task::CloseLauncher))
|
|
||||||
};
|
|
||||||
|
|
||||||
let view = app_launcher::View::new(app_launcher::Params {
|
|
||||||
entry: entry.clone(),
|
|
||||||
globals: &globals,
|
|
||||||
layout: data.layout,
|
|
||||||
parent_id: data.id_content,
|
|
||||||
frontend_tasks: &frontend_tasks,
|
|
||||||
config: data.config,
|
|
||||||
on_launched,
|
|
||||||
})?;
|
|
||||||
|
|
||||||
state.borrow_mut().view_launcher = Some((data.handle, view));
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -128,8 +87,9 @@ fn doc_params(globals: WguiGlobals) -> ParseDocumentParams<'static> {
|
||||||
impl<T> TabApps<T> {
|
impl<T> TabApps<T> {
|
||||||
pub fn new(frontend: &mut Frontend<T>, parent_id: WidgetID, data: &mut T) -> anyhow::Result<Self> {
|
pub fn new(frontend: &mut Frontend<T>, parent_id: WidgetID, data: &mut T) -> anyhow::Result<Self> {
|
||||||
let globals = frontend.layout.state.globals.clone();
|
let globals = frontend.layout.state.globals.clone();
|
||||||
let tasks = Tasks::new();
|
let state = Rc::new(RefCell::new(State {
|
||||||
let state = Rc::new(RefCell::new(State { view_launcher: None }));
|
view_launcher: Default::default(),
|
||||||
|
}));
|
||||||
|
|
||||||
let parser_state = wgui::parser::parse_from_assets(&doc_params(globals.clone()), &mut frontend.layout, parent_id)?;
|
let parser_state = wgui::parser::parse_from_assets(&doc_params(globals.clone()), &mut frontend.layout, parent_id)?;
|
||||||
let app_list_parent = parser_state.fetch_widget(&frontend.layout.state, "app_list_parent")?;
|
let app_list_parent = parser_state.fetch_widget(&frontend.layout.state, "app_list_parent")?;
|
||||||
|
|
@ -157,7 +117,6 @@ impl<T> TabApps<T> {
|
||||||
app_list,
|
app_list,
|
||||||
parser_state,
|
parser_state,
|
||||||
state,
|
state,
|
||||||
tasks,
|
|
||||||
marker: PhantomData,
|
marker: PhantomData,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -286,7 +245,7 @@ impl AppList {
|
||||||
let mut params = HashMap::<Rc<str>, Rc<str>>::new();
|
let mut params = HashMap::<Rc<str>, Rc<str>>::new();
|
||||||
params.insert("text".into(), category_name.into());
|
params.insert("text".into(), category_name.into());
|
||||||
|
|
||||||
parser_state.parse_template(
|
parser_state.realize_template(
|
||||||
doc_params,
|
doc_params,
|
||||||
"CategoryText",
|
"CategoryText",
|
||||||
&mut frontend.layout,
|
&mut frontend.layout,
|
||||||
|
|
@ -318,7 +277,7 @@ impl AppList {
|
||||||
);
|
);
|
||||||
params.insert("name".into(), entry.app_name.clone());
|
params.insert("name".into(), entry.app_name.clone());
|
||||||
|
|
||||||
let data = parser_state.parse_template(
|
let data = parser_state.realize_template(
|
||||||
doc_params,
|
doc_params,
|
||||||
"AppEntry",
|
"AppEntry",
|
||||||
&mut frontend.layout,
|
&mut frontend.layout,
|
||||||
|
|
@ -334,11 +293,10 @@ impl AppList {
|
||||||
&mut self,
|
&mut self,
|
||||||
frontend: &mut Frontend<T>,
|
frontend: &mut Frontend<T>,
|
||||||
state: &Rc<RefCell<State>>,
|
state: &Rc<RefCell<State>>,
|
||||||
tasks: &Tasks<Task>,
|
|
||||||
parser_state: &mut ParserState,
|
parser_state: &mut ParserState,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
// load 4 entries for a single frame at most
|
// load 30 entries for a single frame at most
|
||||||
for _ in 0..4 {
|
for _ in 0..30 {
|
||||||
if let Some(entry) = self.entries_to_mount.pop_front() {
|
if let Some(entry) = self.entries_to_mount.pop_front() {
|
||||||
let globals = frontend.layout.state.globals.clone();
|
let globals = frontend.layout.state.globals.clone();
|
||||||
let button = self.mount_entry(frontend, parser_state, &doc_params(globals.clone()), &entry)?;
|
let button = self.mount_entry(frontend, parser_state, &doc_params(globals.clone()), &entry)?;
|
||||||
|
|
@ -348,7 +306,6 @@ impl AppList {
|
||||||
globals.clone(),
|
globals.clone(),
|
||||||
entry.clone(),
|
entry.clone(),
|
||||||
state.clone(),
|
state.clone(),
|
||||||
tasks.clone(),
|
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ use crate::{
|
||||||
frontend::Frontend,
|
frontend::Frontend,
|
||||||
tab::{Tab, TabType},
|
tab::{Tab, TabType},
|
||||||
util::steam_utils::SteamUtils,
|
util::steam_utils::SteamUtils,
|
||||||
views::{game_list, running_games_list},
|
views::{ViewTrait, ViewUpdateParams, game_list, running_games_list},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct TabGames<T> {
|
pub struct TabGames<T> {
|
||||||
|
|
@ -19,7 +19,6 @@ pub struct TabGames<T> {
|
||||||
|
|
||||||
view_game_list: game_list::View,
|
view_game_list: game_list::View,
|
||||||
view_running_games_list: running_games_list::View,
|
view_running_games_list: running_games_list::View,
|
||||||
steam_utils: SteamUtils,
|
|
||||||
marker: PhantomData<T>,
|
marker: PhantomData<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -28,10 +27,20 @@ impl<T> Tab<T> for TabGames<T> {
|
||||||
TabType::Games
|
TabType::Games
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, frontend: &mut Frontend<T>, time_ms: u32, _data: &mut T) -> anyhow::Result<()> {
|
fn update(&mut self, frontend: &mut Frontend<T>, time_ms: u32, data: &mut T) -> anyhow::Result<()> {
|
||||||
self
|
let mut config_change_kind = None;
|
||||||
.view_game_list
|
|
||||||
.update(&mut frontend.layout, &mut self.steam_utils, &frontend.executor)?;
|
self.view_game_list.update(&mut ViewUpdateParams {
|
||||||
|
layout: &mut frontend.layout,
|
||||||
|
executor: &frontend.executor,
|
||||||
|
general_config: frontend.interface.general_config(data),
|
||||||
|
config_change_kind: &mut config_change_kind,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
if let Some(kind) = config_change_kind {
|
||||||
|
frontend.interface.config_changed(data, kind);
|
||||||
|
}
|
||||||
|
|
||||||
self.view_running_games_list.update(&mut frontend.layout, time_ms)?;
|
self.view_running_games_list.update(&mut frontend.layout, time_ms)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -54,16 +63,17 @@ impl<T> TabGames<T> {
|
||||||
let game_list_parent = state.get_widget_id("game_list_parent")?;
|
let game_list_parent = state.get_widget_id("game_list_parent")?;
|
||||||
let id_running_games_list_parent = state.get_widget_id("running_games_list_parent")?;
|
let id_running_games_list_parent = state.get_widget_id("running_games_list_parent")?;
|
||||||
|
|
||||||
|
let mut steam_utils = SteamUtils::new()?;
|
||||||
|
|
||||||
let view_game_list = game_list::View::new(game_list::Params {
|
let view_game_list = game_list::View::new(game_list::Params {
|
||||||
executor: frontend.executor.clone(),
|
executor: frontend.executor.clone(),
|
||||||
frontend_tasks: frontend.tasks.clone(),
|
frontend_tasks: frontend.tasks.clone(),
|
||||||
globals: globals.clone(),
|
globals: globals.clone(),
|
||||||
layout: &mut frontend.layout,
|
layout: &mut frontend.layout,
|
||||||
parent_id: game_list_parent,
|
parent_id: game_list_parent,
|
||||||
|
steam_utils: &steam_utils,
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let mut steam_utils = SteamUtils::new()?;
|
|
||||||
|
|
||||||
let view_running_games_list = running_games_list::View::new(running_games_list::Params {
|
let view_running_games_list = running_games_list::View::new(running_games_list::Params {
|
||||||
globals: globals.clone(),
|
globals: globals.clone(),
|
||||||
layout: &mut frontend.layout,
|
layout: &mut frontend.layout,
|
||||||
|
|
@ -77,7 +87,6 @@ impl<T> TabGames<T> {
|
||||||
view_game_list,
|
view_game_list,
|
||||||
view_running_games_list,
|
view_running_games_list,
|
||||||
marker: PhantomData,
|
marker: PhantomData,
|
||||||
steam_utils,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -59,9 +59,12 @@ impl<T> TabHome<T> {
|
||||||
parent_id,
|
parent_id,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let mut c = frontend.layout.start_common();
|
let widget_label = state.fetch_widget(&frontend.layout.state, "label_hello")?.widget;
|
||||||
let widget_label = state.fetch_widget(&c.layout.state, "label_hello")?.widget;
|
configure_label_hello(
|
||||||
configure_label_hello(&mut c.common(), widget_label, frontend.interface.general_config(data));
|
&mut frontend.layout.common(),
|
||||||
|
widget_label,
|
||||||
|
frontend.interface.general_config(data),
|
||||||
|
);
|
||||||
|
|
||||||
let btn_apps = state.fetch_component_as::<ComponentButton>("btn_apps")?;
|
let btn_apps = state.fetch_component_as::<ComponentButton>("btn_apps")?;
|
||||||
let btn_games = state.fetch_component_as::<ComponentButton>("btn_games")?;
|
let btn_games = state.fetch_component_as::<ComponentButton>("btn_games")?;
|
||||||
|
|
|
||||||
|
|
@ -2,24 +2,133 @@ use std::{collections::HashMap, marker::PhantomData, rc::Rc};
|
||||||
|
|
||||||
use wgui::{
|
use wgui::{
|
||||||
assets::AssetPath,
|
assets::AssetPath,
|
||||||
components::{checkbox::ComponentCheckbox, slider::ComponentSlider},
|
components::{
|
||||||
|
bar_graph::{ComponentBarGraph, ValueCell},
|
||||||
|
button::ComponentButton,
|
||||||
|
checkbox::ComponentCheckbox,
|
||||||
|
color_selector::{ColorSelectorChangedCallback, ComponentColorSelector},
|
||||||
|
slider::{ComponentSlider, SliderValueChangedCallback},
|
||||||
|
tabs::ComponentTabs,
|
||||||
|
},
|
||||||
|
drawing::Color,
|
||||||
globals::WguiGlobals,
|
globals::WguiGlobals,
|
||||||
layout::WidgetID,
|
layout::{Layout, WidgetID},
|
||||||
parser::{self, Fetchable, ParseDocumentParams, ParserState},
|
parser::{self, Fetchable, ParseDocumentParams, ParserState},
|
||||||
task::Tasks,
|
task::Tasks,
|
||||||
};
|
};
|
||||||
use wlx_common::dash_interface;
|
use wlx_common::{
|
||||||
|
config::GeneralConfig,
|
||||||
|
dash_interface::{self, ConfigChangeKind, MonadoDumpSessionFrame},
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
frontend::Frontend,
|
frontend::Frontend,
|
||||||
tab::{Tab, TabType},
|
tab::{Tab, TabType},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Clone)]
|
||||||
|
enum TabNameEnum {
|
||||||
|
GeneralSettings,
|
||||||
|
ProcessList,
|
||||||
|
DebugTimings,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TabNameEnum {
|
||||||
|
fn from_string(s: &str) -> Option<Self> {
|
||||||
|
match s {
|
||||||
|
"general_settings" => Some(TabNameEnum::GeneralSettings),
|
||||||
|
"process_list" => Some(TabNameEnum::ProcessList),
|
||||||
|
"debug_timings" => Some(TabNameEnum::DebugTimings),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
enum Task {
|
enum Task {
|
||||||
Refresh,
|
|
||||||
FocusClient(String),
|
|
||||||
SetBrightness(f32),
|
SetBrightness(f32),
|
||||||
|
SetTab(TabNameEnum),
|
||||||
|
|
||||||
|
// `ProcessList` tab
|
||||||
|
ProcessListRefresh,
|
||||||
|
ProcessListFocusClient(String),
|
||||||
|
|
||||||
|
// `DebugTimings` tab
|
||||||
|
DebugTimingsRefreshSessionList,
|
||||||
|
DebugTimingsSetSessionId(i64),
|
||||||
|
|
||||||
|
GeneralSettingsChromaUpdate,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SubtabProcessList {
|
||||||
|
id_list_parent: WidgetID,
|
||||||
|
state: ParserState,
|
||||||
|
cells: Vec<parser::ParserData>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SubtabGeneralSettings {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
state: ParserState,
|
||||||
|
|
||||||
|
slider_keying_curve: Rc<ComponentSlider>,
|
||||||
|
slider_keying_despill: Rc<ComponentSlider>,
|
||||||
|
slider_keying_hue_range: Rc<ComponentSlider>,
|
||||||
|
slider_keying_saturation_range: Rc<ComponentSlider>,
|
||||||
|
slider_keying_value_range: Rc<ComponentSlider>,
|
||||||
|
cs_keying: Rc<ComponentColorSelector>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DebugGraph {
|
||||||
|
graph: Rc<ComponentBarGraph>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DebugSessionList {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
buttons: Vec<Rc<ComponentButton>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TimingsSession {
|
||||||
|
resolved_name: Option<String>,
|
||||||
|
last_frame: MonadoDumpSessionFrame,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Graphs {
|
||||||
|
predicted_display_time: DebugGraph,
|
||||||
|
predicted_frame_time: DebugGraph,
|
||||||
|
predicted_wake_up_time: DebugGraph,
|
||||||
|
predicted_gpu_done_time: DebugGraph,
|
||||||
|
predicted_display_period: DebugGraph,
|
||||||
|
display_time: DebugGraph,
|
||||||
|
when_predicted: DebugGraph,
|
||||||
|
when_wait_woke: DebugGraph,
|
||||||
|
when_begin: DebugGraph,
|
||||||
|
when_delivered: DebugGraph,
|
||||||
|
when_gpu_done: DebugGraph,
|
||||||
|
}
|
||||||
|
|
||||||
|
type SessionsMap = HashMap<i64 /* session id */, TimingsSession>;
|
||||||
|
|
||||||
|
struct SubtabDebugTimings {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
state: ParserState,
|
||||||
|
|
||||||
|
graphs: Option<Graphs>,
|
||||||
|
session_list: DebugSessionList,
|
||||||
|
selected_session_id: Option<i64>,
|
||||||
|
|
||||||
|
id_sessions_list_parent: WidgetID,
|
||||||
|
id_timings_parent: WidgetID,
|
||||||
|
|
||||||
|
sessions: SessionsMap,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[allow(clippy::large_enum_variant)]
|
||||||
|
enum Subtab {
|
||||||
|
Empty,
|
||||||
|
GeneralSettings(SubtabGeneralSettings),
|
||||||
|
ProcessList(SubtabProcessList),
|
||||||
|
DebugTimings(SubtabDebugTimings),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct TabMonado<T> {
|
pub struct TabMonado<T> {
|
||||||
|
|
@ -29,10 +138,8 @@ pub struct TabMonado<T> {
|
||||||
|
|
||||||
marker: PhantomData<T>,
|
marker: PhantomData<T>,
|
||||||
|
|
||||||
globals: WguiGlobals,
|
id_content: WidgetID,
|
||||||
id_list_parent: WidgetID,
|
subtab: Subtab,
|
||||||
|
|
||||||
cells: Vec<parser::ParserData>,
|
|
||||||
|
|
||||||
ticks: u32,
|
ticks: u32,
|
||||||
}
|
}
|
||||||
|
|
@ -45,15 +152,72 @@ impl<T> Tab<T> for TabMonado<T> {
|
||||||
fn update(&mut self, frontend: &mut Frontend<T>, _time_ms: u32, data: &mut T) -> anyhow::Result<()> {
|
fn update(&mut self, frontend: &mut Frontend<T>, _time_ms: u32, data: &mut T) -> anyhow::Result<()> {
|
||||||
for task in self.tasks.drain() {
|
for task in self.tasks.drain() {
|
||||||
match task {
|
match task {
|
||||||
Task::Refresh => self.refresh(frontend, data)?,
|
Task::ProcessListRefresh => {
|
||||||
Task::FocusClient(name) => self.focus_client(frontend, data, name)?,
|
if let Subtab::ProcessList(process_list) = &mut self.subtab {
|
||||||
|
process_list.refresh(frontend, data, &self.tasks)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Task::ProcessListFocusClient(client_name) => {
|
||||||
|
if let Subtab::ProcessList(process_list) = &mut self.subtab {
|
||||||
|
process_list.focus_client(frontend, data, client_name, &self.tasks)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Task::DebugTimingsRefreshSessionList => {
|
||||||
|
if let Subtab::DebugTimings(tab) = &mut self.subtab {
|
||||||
|
tab.refresh_session_list(&mut frontend.layout, &self.tasks)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Task::DebugTimingsSetSessionId(session_id) => {
|
||||||
|
if let Subtab::DebugTimings(tab) = &mut self.subtab {
|
||||||
|
tab.set_session_id(&mut frontend.layout, session_id)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Task::GeneralSettingsChromaUpdate => {
|
||||||
|
if let Subtab::GeneralSettings(tab) = &mut self.subtab {
|
||||||
|
tab.chroma_update(frontend.interface.general_config(data));
|
||||||
|
frontend
|
||||||
|
.interface
|
||||||
|
.config_changed(data, ConfigChangeKind::EnvironmentBlend);
|
||||||
|
}
|
||||||
|
}
|
||||||
Task::SetBrightness(brightness) => self.set_brightness(frontend, data, brightness),
|
Task::SetBrightness(brightness) => self.set_brightness(frontend, data, brightness),
|
||||||
|
Task::SetTab(tab) => {
|
||||||
|
frontend.layout.remove_children(self.id_content);
|
||||||
|
match tab {
|
||||||
|
TabNameEnum::GeneralSettings => {
|
||||||
|
self.subtab = Subtab::GeneralSettings(SubtabGeneralSettings::new(
|
||||||
|
self.id_content,
|
||||||
|
frontend,
|
||||||
|
data,
|
||||||
|
&self.tasks,
|
||||||
|
)?)
|
||||||
|
}
|
||||||
|
TabNameEnum::ProcessList => {
|
||||||
|
self.tasks.push(Task::ProcessListRefresh);
|
||||||
|
self.subtab = Subtab::ProcessList(SubtabProcessList::new(self.id_content, frontend)?)
|
||||||
|
}
|
||||||
|
TabNameEnum::DebugTimings => {
|
||||||
|
self.subtab = Subtab::DebugTimings(SubtabDebugTimings::new(self.id_content, frontend, &self.tasks)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
match &mut self.subtab {
|
||||||
|
Subtab::Empty => {}
|
||||||
|
Subtab::GeneralSettings(_) => {}
|
||||||
|
Subtab::ProcessList(_) => {
|
||||||
// every few seconds
|
// every few seconds
|
||||||
if self.ticks.is_multiple_of(500) {
|
if let Subtab::ProcessList(_) = &self.subtab
|
||||||
self.tasks.push(Task::Refresh);
|
&& self.ticks.is_multiple_of(500)
|
||||||
|
{
|
||||||
|
self.tasks.push(Task::ProcessListRefresh);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Subtab::DebugTimings(timings) => {
|
||||||
|
timings.update(&self.tasks, data, frontend)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.ticks += 1;
|
self.ticks += 1;
|
||||||
|
|
@ -62,7 +226,7 @@ impl<T> Tab<T> for TabMonado<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn doc_params(globals: &'_ WguiGlobals) -> ParseDocumentParams<'_> {
|
fn doc_params_monado(globals: &'_ WguiGlobals) -> ParseDocumentParams<'_> {
|
||||||
ParseDocumentParams {
|
ParseDocumentParams {
|
||||||
globals: globals.clone(),
|
globals: globals.clone(),
|
||||||
path: AssetPath::BuiltIn("gui/tab/monado.xml"),
|
path: AssetPath::BuiltIn("gui/tab/monado.xml"),
|
||||||
|
|
@ -70,6 +234,30 @@ fn doc_params(globals: &'_ WguiGlobals) -> ParseDocumentParams<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn doc_params_tab_process_list(globals: &'_ WguiGlobals) -> ParseDocumentParams<'_> {
|
||||||
|
ParseDocumentParams {
|
||||||
|
globals: globals.clone(),
|
||||||
|
path: AssetPath::BuiltIn("gui/tab/monado_tab_process_list.xml"),
|
||||||
|
extra: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn doc_params_tab_general_settings(globals: &'_ WguiGlobals) -> ParseDocumentParams<'_> {
|
||||||
|
ParseDocumentParams {
|
||||||
|
globals: globals.clone(),
|
||||||
|
path: AssetPath::BuiltIn("gui/tab/monado_tab_general_settings.xml"),
|
||||||
|
extra: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn doc_params_tab_debug_timings(globals: &'_ WguiGlobals) -> ParseDocumentParams<'_> {
|
||||||
|
ParseDocumentParams {
|
||||||
|
globals: globals.clone(),
|
||||||
|
path: AssetPath::BuiltIn("gui/tab/monado_tab_debug_timings.xml"),
|
||||||
|
extra: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn yesno(n: bool) -> &'static str {
|
fn yesno(n: bool) -> &'static str {
|
||||||
match n {
|
match n {
|
||||||
true => "yes",
|
true => "yes",
|
||||||
|
|
@ -77,29 +265,393 @@ fn yesno(n: bool) -> &'static str {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> TabMonado<T> {
|
const SLIDER_MULTIPLIER: f32 = 100.0;
|
||||||
pub fn new(frontend: &mut Frontend<T>, parent_id: WidgetID) -> anyhow::Result<Self> {
|
|
||||||
let globals = frontend.layout.state.globals.clone();
|
|
||||||
let state = wgui::parser::parse_from_assets(&doc_params(&globals), &mut frontend.layout, parent_id)?;
|
|
||||||
|
|
||||||
let id_list_parent = state.get_widget_id("list_parent")?;
|
impl SubtabGeneralSettings {
|
||||||
|
fn new<T>(
|
||||||
|
parent_id: WidgetID,
|
||||||
|
frontend: &mut Frontend<T>,
|
||||||
|
data: &mut T,
|
||||||
|
tasks: &Tasks<Task>,
|
||||||
|
) -> anyhow::Result<Self> {
|
||||||
|
let state = wgui::parser::parse_from_assets(
|
||||||
|
&doc_params_tab_general_settings(&frontend.globals),
|
||||||
|
&mut frontend.layout,
|
||||||
|
parent_id,
|
||||||
|
)?;
|
||||||
|
|
||||||
let tasks = Tasks::<Task>::new();
|
// get brightness
|
||||||
|
let slider_brightness = state.fetch_component_as::<ComponentSlider>("slider_brightness")?;
|
||||||
|
if let Some(brightness) = frontend.interface.monado_brightness_get(data) {
|
||||||
|
slider_brightness.set_value_primary(&mut frontend.layout.common(), brightness * 100.0);
|
||||||
|
|
||||||
tasks.push(Task::Refresh);
|
slider_brightness.on_value_changed({
|
||||||
|
let tasks = tasks.clone();
|
||||||
|
Box::new(move |_common, e| {
|
||||||
|
tasks.push(Task::SetBrightness(e.value / 100.0));
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let config = frontend.interface.general_config(data);
|
||||||
|
|
||||||
|
let cs_keying = state.fetch_component_as::<ComponentColorSelector>("cs_keying")?;
|
||||||
|
let slider_keying_despill = state.fetch_component_as::<ComponentSlider>("slider_keying_despill")?;
|
||||||
|
let slider_keying_curve = state.fetch_component_as::<ComponentSlider>("slider_keying_curve")?;
|
||||||
|
let slider_keying_hue_range = state.fetch_component_as::<ComponentSlider>("slider_keying_hue_range")?;
|
||||||
|
let slider_keying_saturation_range =
|
||||||
|
state.fetch_component_as::<ComponentSlider>("slider_keying_saturation_range")?;
|
||||||
|
let slider_keying_value_range = state.fetch_component_as::<ComponentSlider>("slider_keying_value_range")?;
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut common = frontend.layout.common();
|
||||||
|
|
||||||
|
// set initial values
|
||||||
|
let (rgb, range_h, range_s, range_v) = config.chroma_key_params.get_rgb_and_hsv_ranges();
|
||||||
|
slider_keying_curve.set_value_primary(&mut common, config.chroma_key_params.curve * SLIDER_MULTIPLIER);
|
||||||
|
slider_keying_despill.set_value_primary(&mut common, config.chroma_key_params.despill * SLIDER_MULTIPLIER);
|
||||||
|
slider_keying_hue_range.set_value_primary(&mut common, range_h * SLIDER_MULTIPLIER);
|
||||||
|
slider_keying_saturation_range.set_value_primary(&mut common, range_s * SLIDER_MULTIPLIER);
|
||||||
|
slider_keying_value_range.set_value_primary(&mut common, range_v * SLIDER_MULTIPLIER);
|
||||||
|
cs_keying.set_color(&mut common, rgb);
|
||||||
|
|
||||||
|
// prepare callbacks
|
||||||
|
fn get_slider_callback(tasks: &Tasks<Task>) -> SliderValueChangedCallback {
|
||||||
|
Box::new({
|
||||||
|
let tasks = tasks.clone();
|
||||||
|
move |_, _| {
|
||||||
|
tasks.push(Task::GeneralSettingsChromaUpdate);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_color_selector_callback(tasks: &Tasks<Task>) -> ColorSelectorChangedCallback {
|
||||||
|
Box::new({
|
||||||
|
let tasks = tasks.clone();
|
||||||
|
move |_, _| {
|
||||||
|
tasks.push(Task::GeneralSettingsChromaUpdate);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
slider_keying_curve.on_value_changed(get_slider_callback(tasks));
|
||||||
|
slider_keying_despill.on_value_changed(get_slider_callback(tasks));
|
||||||
|
slider_keying_hue_range.on_value_changed(get_slider_callback(tasks));
|
||||||
|
slider_keying_saturation_range.on_value_changed(get_slider_callback(tasks));
|
||||||
|
slider_keying_value_range.on_value_changed(get_slider_callback(tasks));
|
||||||
|
cs_keying.on_changed(get_color_selector_callback(tasks));
|
||||||
|
}
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
state,
|
state,
|
||||||
marker: PhantomData,
|
slider_keying_curve,
|
||||||
|
slider_keying_despill,
|
||||||
|
slider_keying_hue_range,
|
||||||
|
slider_keying_saturation_range,
|
||||||
|
slider_keying_value_range,
|
||||||
|
cs_keying,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn chroma_update(&mut self, config: &mut GeneralConfig) {
|
||||||
|
let val_curve = self.slider_keying_curve.get_value_primary();
|
||||||
|
let val_despill = self.slider_keying_despill.get_value_primary();
|
||||||
|
let val_range_h = self.slider_keying_hue_range.get_value_primary();
|
||||||
|
let val_range_s = self.slider_keying_saturation_range.get_value_primary();
|
||||||
|
let val_range_v = self.slider_keying_value_range.get_value_primary();
|
||||||
|
let val_rgb = self.cs_keying.get_color();
|
||||||
|
|
||||||
|
config.chroma_key_params.despill = val_despill / SLIDER_MULTIPLIER;
|
||||||
|
config.chroma_key_params.curve = val_curve / SLIDER_MULTIPLIER;
|
||||||
|
config.chroma_key_params.update_hsv_range_from_rgb(
|
||||||
|
val_rgb,
|
||||||
|
val_range_h / SLIDER_MULTIPLIER,
|
||||||
|
val_range_s / SLIDER_MULTIPLIER,
|
||||||
|
val_range_v / SLIDER_MULTIPLIER,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mount_sessions_list(
|
||||||
|
state: &mut ParserState,
|
||||||
|
layout: &mut Layout,
|
||||||
|
tasks: &Tasks<Task>,
|
||||||
|
id_parent: WidgetID,
|
||||||
|
sessions: &SessionsMap,
|
||||||
|
) -> anyhow::Result<DebugSessionList> {
|
||||||
|
let mut buttons = Vec::new();
|
||||||
|
let globals = layout.state.globals.clone();
|
||||||
|
layout.remove_children(id_parent);
|
||||||
|
|
||||||
|
for (session_id, session) in sessions {
|
||||||
|
let mut params = HashMap::new();
|
||||||
|
|
||||||
|
params.insert(
|
||||||
|
Rc::from("text"),
|
||||||
|
Rc::from(format!(
|
||||||
|
"{} (ID {})",
|
||||||
|
session.resolved_name.as_ref().map_or("Unknown", |s| s.as_str()),
|
||||||
|
session_id,
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
|
||||||
|
let data = state.realize_template(
|
||||||
|
&doc_params_tab_debug_timings(&globals),
|
||||||
|
"SessionButton",
|
||||||
|
layout,
|
||||||
|
id_parent,
|
||||||
|
params,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let button = data.fetch_component_as::<ComponentButton>("button")?;
|
||||||
|
|
||||||
|
button.on_click({
|
||||||
|
let tasks = tasks.clone();
|
||||||
|
let session_id = *session_id;
|
||||||
|
Rc::new(move |_, _| {
|
||||||
|
tasks.push(Task::DebugTimingsSetSessionId(session_id));
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
buttons.push(button);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(DebugSessionList { buttons })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mount_graph(
|
||||||
|
state: &mut ParserState,
|
||||||
|
layout: &mut Layout,
|
||||||
|
id_parent: WidgetID,
|
||||||
|
name: &'static str,
|
||||||
|
limits: (f32, f32),
|
||||||
|
) -> anyhow::Result<DebugGraph> {
|
||||||
|
let globals = layout.state.globals.clone();
|
||||||
|
let mut params = HashMap::new();
|
||||||
|
params.insert(Rc::from("name"), Rc::from(name));
|
||||||
|
params.insert(Rc::from("limit_min"), Rc::from(limits.0.to_string()));
|
||||||
|
params.insert(Rc::from("limit_max"), Rc::from(limits.1.to_string()));
|
||||||
|
|
||||||
|
let data = state.realize_template(
|
||||||
|
&doc_params_tab_debug_timings(&globals),
|
||||||
|
"DebugGraph",
|
||||||
|
layout,
|
||||||
|
id_parent,
|
||||||
|
params,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let graph = data.fetch_component_as::<ComponentBarGraph>("graph")?;
|
||||||
|
Ok(DebugGraph { graph })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ns_to_ms(ns: i64) -> f32 {
|
||||||
|
(ns / 1000) as f32 / 1000.0
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SubtabDebugTimings {
|
||||||
|
fn new<T>(parent_id: WidgetID, frontend: &mut Frontend<T>, tasks: &Tasks<Task>) -> anyhow::Result<Self> {
|
||||||
|
let mut state = wgui::parser::parse_from_assets(
|
||||||
|
&doc_params_tab_debug_timings(&frontend.globals),
|
||||||
|
&mut frontend.layout,
|
||||||
|
parent_id,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let id_timings_parent = state.get_widget_id("timings_parent")?;
|
||||||
|
let id_sessions_list_parent = state.get_widget_id("session_list_parent")?;
|
||||||
|
|
||||||
|
let sessions = Default::default();
|
||||||
|
|
||||||
|
let session_list = mount_sessions_list(
|
||||||
|
&mut state,
|
||||||
|
&mut frontend.layout,
|
||||||
tasks,
|
tasks,
|
||||||
globals,
|
id_sessions_list_parent,
|
||||||
|
&sessions,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
state,
|
||||||
|
graphs: None,
|
||||||
|
session_list,
|
||||||
|
id_sessions_list_parent,
|
||||||
|
id_timings_parent,
|
||||||
|
sessions,
|
||||||
|
selected_session_id: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_session_id(&mut self, layout: &mut Layout, session_id: i64) -> anyhow::Result<()> {
|
||||||
|
layout.remove_children(self.id_timings_parent);
|
||||||
|
|
||||||
|
let mut graph = |name: &'static str, limits: (f32, f32)| -> anyhow::Result<DebugGraph> {
|
||||||
|
mount_graph(&mut self.state, layout, self.id_timings_parent, name, limits)
|
||||||
|
};
|
||||||
|
|
||||||
|
// populate graphs
|
||||||
|
self.graphs = Some(Graphs {
|
||||||
|
predicted_display_time: graph("Predicted display time", (0.0, 30.0))?,
|
||||||
|
predicted_frame_time: graph("Predicted frame time", (0.0, 30.0))?,
|
||||||
|
predicted_wake_up_time: graph("Predicted wake-up time", (0.0, 30.0))?,
|
||||||
|
predicted_gpu_done_time: graph("Predicted GPU done time", (0.0, 30.0))?,
|
||||||
|
predicted_display_period: graph("Predicted display period", (0.0, 30.0))?,
|
||||||
|
display_time: graph("Display time", (0.0, 30.0))?,
|
||||||
|
when_predicted: graph("When predicted", (0.0, 30.0))?,
|
||||||
|
when_wait_woke: graph("When wait woke", (0.0, 30.0))?,
|
||||||
|
when_begin: graph("When begin", (0.0, 30.0))?,
|
||||||
|
when_delivered: graph("When delivered", (0.0, 30.0))?,
|
||||||
|
when_gpu_done: graph("When GPU done", (0.0, 30.0))?,
|
||||||
|
});
|
||||||
|
|
||||||
|
self.selected_session_id = Some(session_id);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn refresh_session_list(&mut self, layout: &mut Layout, tasks: &Tasks<Task>) -> anyhow::Result<()> {
|
||||||
|
self.session_list = mount_sessions_list(
|
||||||
|
&mut self.state,
|
||||||
|
layout,
|
||||||
|
tasks,
|
||||||
|
self.id_sessions_list_parent,
|
||||||
|
&self.sessions,
|
||||||
|
)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update<T>(&mut self, tasks: &Tasks<Task>, data: &mut T, frontend: &mut Frontend<T>) -> anyhow::Result<()> {
|
||||||
|
if !frontend.interface.monado_metrics_set_enabled(data, true) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let frames = frontend.interface.monado_metrics_dump_session_frames(data);
|
||||||
|
if frames.is_empty() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let col_green = Color::new(0.0, 1.0, 0.0, 1.0);
|
||||||
|
|
||||||
|
for frame in frames {
|
||||||
|
//log::info!("{:?}", frame);
|
||||||
|
|
||||||
|
match self.sessions.get_mut(&frame.session_id) {
|
||||||
|
Some(session) => {
|
||||||
|
if let Some(graphs) = &mut self.graphs
|
||||||
|
&& let Some(selected_session_id) = self.selected_session_id
|
||||||
|
&& selected_session_id == frame.session_id
|
||||||
|
{
|
||||||
|
let predicted_display_time = ns_to_ms(session.last_frame.predicted_display_time_ns as i64);
|
||||||
|
let predicted_frame_time = ns_to_ms(frame.predicted_frame_time_ns as i64);
|
||||||
|
let predicted_wake_up_time =
|
||||||
|
ns_to_ms(frame.predicted_wake_up_time_ns as i64 - session.last_frame.predicted_wake_up_time_ns as i64);
|
||||||
|
let predicted_gpu_done_time =
|
||||||
|
ns_to_ms(frame.predicted_gpu_done_time_ns as i64 - session.last_frame.predicted_gpu_done_time_ns as i64);
|
||||||
|
let predicted_display_period = ns_to_ms(session.last_frame.predicted_display_period_ns as i64); // 6.944 ms for 144Hz
|
||||||
|
let display_time = ns_to_ms(frame.display_time_ns as i64 - session.last_frame.display_time_ns as i64);
|
||||||
|
let when_predicted = ns_to_ms(frame.when_predicted_ns as i64 - session.last_frame.when_predicted_ns as i64);
|
||||||
|
let when_wait_woke = ns_to_ms(frame.when_wait_woke_ns as i64 - session.last_frame.when_wait_woke_ns as i64);
|
||||||
|
let when_begin = ns_to_ms(frame.when_begin_ns as i64 - session.last_frame.when_begin_ns as i64);
|
||||||
|
let when_delivered = ns_to_ms(frame.when_delivered_ns as i64 - session.last_frame.when_delivered_ns as i64);
|
||||||
|
let when_gpu_done = ns_to_ms(frame.when_gpu_done_ns as i64 - session.last_frame.when_gpu_done_ns as i64);
|
||||||
|
|
||||||
|
graphs.predicted_display_time.graph.push_value(ValueCell {
|
||||||
|
value: predicted_display_time,
|
||||||
|
color: col_green,
|
||||||
|
});
|
||||||
|
|
||||||
|
graphs.predicted_frame_time.graph.push_value(ValueCell {
|
||||||
|
value: predicted_frame_time,
|
||||||
|
color: col_green,
|
||||||
|
});
|
||||||
|
|
||||||
|
graphs.predicted_wake_up_time.graph.push_value(ValueCell {
|
||||||
|
value: predicted_wake_up_time,
|
||||||
|
color: col_green,
|
||||||
|
});
|
||||||
|
|
||||||
|
graphs.predicted_gpu_done_time.graph.push_value(ValueCell {
|
||||||
|
value: predicted_gpu_done_time,
|
||||||
|
color: col_green,
|
||||||
|
});
|
||||||
|
|
||||||
|
graphs.predicted_display_period.graph.push_value(ValueCell {
|
||||||
|
value: predicted_display_period,
|
||||||
|
color: col_green,
|
||||||
|
});
|
||||||
|
|
||||||
|
graphs.display_time.graph.push_value(ValueCell {
|
||||||
|
value: display_time,
|
||||||
|
color: col_green,
|
||||||
|
});
|
||||||
|
|
||||||
|
graphs.when_predicted.graph.push_value(ValueCell {
|
||||||
|
value: when_predicted,
|
||||||
|
color: col_green,
|
||||||
|
});
|
||||||
|
|
||||||
|
graphs.when_wait_woke.graph.push_value(ValueCell {
|
||||||
|
value: when_wait_woke,
|
||||||
|
color: col_green,
|
||||||
|
});
|
||||||
|
|
||||||
|
graphs.when_begin.graph.push_value(ValueCell {
|
||||||
|
value: when_begin,
|
||||||
|
color: col_green,
|
||||||
|
});
|
||||||
|
|
||||||
|
graphs.when_delivered.graph.push_value(ValueCell {
|
||||||
|
value: when_delivered,
|
||||||
|
color: col_green,
|
||||||
|
});
|
||||||
|
|
||||||
|
graphs.when_gpu_done.graph.push_value(ValueCell {
|
||||||
|
value: when_gpu_done,
|
||||||
|
color: col_green,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
session.last_frame = frame;
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
self.sessions.insert(
|
||||||
|
frame.session_id,
|
||||||
|
TimingsSession {
|
||||||
|
last_frame: frame,
|
||||||
|
resolved_name: None, /* TODO! find client ID from session ID */
|
||||||
|
},
|
||||||
|
);
|
||||||
|
tasks.push(Task::DebugTimingsRefreshSessionList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
frontend.layout.mark_redraw();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SubtabProcessList {
|
||||||
|
fn new<T>(parent_id: WidgetID, frontend: &mut Frontend<T>) -> anyhow::Result<Self> {
|
||||||
|
let state = wgui::parser::parse_from_assets(
|
||||||
|
&doc_params_tab_process_list(&frontend.globals),
|
||||||
|
&mut frontend.layout,
|
||||||
|
parent_id,
|
||||||
|
)?;
|
||||||
|
let id_list_parent = state.get_widget_id("list_parent")?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
state,
|
||||||
id_list_parent,
|
id_list_parent,
|
||||||
ticks: 0,
|
|
||||||
cells: Vec::new(),
|
cells: Vec::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mount_client(&mut self, frontend: &mut Frontend<T>, client: &dash_interface::MonadoClient) -> anyhow::Result<()> {
|
fn mount_client(
|
||||||
|
&mut self,
|
||||||
|
layout: &mut Layout,
|
||||||
|
client: &dash_interface::MonadoClient,
|
||||||
|
tasks: &Tasks<Task>,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
let mut par = HashMap::<Rc<str>, Rc<str>>::new();
|
let mut par = HashMap::<Rc<str>, Rc<str>>::new();
|
||||||
par.insert(
|
par.insert(
|
||||||
"checked".into(),
|
"checked".into(),
|
||||||
|
|
@ -109,7 +661,10 @@ impl<T> TabMonado<T> {
|
||||||
Rc::from("0")
|
Rc::from("0")
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
par.insert("name".into(), client.name.clone().into());
|
par.insert(
|
||||||
|
"name".into(),
|
||||||
|
format!("{} (Client ID: {})", client.name, client.id).into(),
|
||||||
|
);
|
||||||
par.insert("flag_active".into(), yesno(client.is_active).into());
|
par.insert("flag_active".into(), yesno(client.is_active).into());
|
||||||
par.insert("flag_focused".into(), yesno(client.is_focused).into());
|
par.insert("flag_focused".into(), yesno(client.is_focused).into());
|
||||||
par.insert("flag_io_active".into(), yesno(client.is_io_active).into());
|
par.insert("flag_io_active".into(), yesno(client.is_io_active).into());
|
||||||
|
|
@ -117,21 +672,23 @@ impl<T> TabMonado<T> {
|
||||||
par.insert("flag_primary".into(), yesno(client.is_primary).into());
|
par.insert("flag_primary".into(), yesno(client.is_primary).into());
|
||||||
par.insert("flag_visible".into(), yesno(client.is_visible).into());
|
par.insert("flag_visible".into(), yesno(client.is_visible).into());
|
||||||
|
|
||||||
let state_cell = self.state.parse_template(
|
let globals = layout.state.globals.clone();
|
||||||
&doc_params(&self.globals),
|
|
||||||
|
let state_cell = self.state.realize_template(
|
||||||
|
&doc_params_tab_process_list(&globals),
|
||||||
"Cell",
|
"Cell",
|
||||||
&mut frontend.layout,
|
layout,
|
||||||
self.id_list_parent,
|
self.id_list_parent,
|
||||||
par,
|
par,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let checkbox = state_cell.fetch_component_as::<ComponentCheckbox>("checkbox")?;
|
let checkbox = state_cell.fetch_component_as::<ComponentCheckbox>("checkbox")?;
|
||||||
checkbox.on_toggle({
|
checkbox.on_toggle({
|
||||||
let tasks = self.tasks.clone();
|
let tasks = tasks.clone();
|
||||||
let client_name = client.name.clone();
|
let client_name = client.name.clone();
|
||||||
Box::new(move |_common, e| {
|
Box::new(move |_common, e| {
|
||||||
if e.checked {
|
if e.checked {
|
||||||
tasks.push(Task::FocusClient(client_name.clone()));
|
tasks.push(Task::ProcessListFocusClient(client_name.clone()));
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
|
|
@ -142,41 +699,63 @@ impl<T> TabMonado<T> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn refresh(&mut self, frontend: &mut Frontend<T>, data: &mut T) -> anyhow::Result<()> {
|
fn focus_client<T>(
|
||||||
|
&mut self,
|
||||||
|
frontend: &mut Frontend<T>,
|
||||||
|
data: &mut T,
|
||||||
|
name: String,
|
||||||
|
tasks: &Tasks<Task>,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
frontend.interface.monado_client_focus(data, &name)?;
|
||||||
|
tasks.push(Task::ProcessListRefresh);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn refresh<T>(&mut self, frontend: &mut Frontend<T>, data: &mut T, tasks: &Tasks<Task>) -> anyhow::Result<()> {
|
||||||
log::debug!("refreshing monado client list");
|
log::debug!("refreshing monado client list");
|
||||||
|
|
||||||
let clients = frontend.interface.monado_client_list(data)?;
|
let clients = frontend.interface.monado_client_list(data, true)?;
|
||||||
|
|
||||||
frontend.layout.remove_children(self.id_list_parent);
|
frontend.layout.remove_children(self.id_list_parent);
|
||||||
self.cells.clear();
|
self.cells.clear();
|
||||||
|
|
||||||
for client in clients {
|
for client in clients {
|
||||||
self.mount_client(frontend, &client)?;
|
self.mount_client(&mut frontend.layout, &client, tasks)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// get brightness
|
Ok(())
|
||||||
let slider_brightness = self.state.fetch_component_as::<ComponentSlider>("slider_brightness")?;
|
}
|
||||||
if let Some(brightness) = frontend.interface.monado_brightness_get(data) {
|
}
|
||||||
let mut c = frontend.layout.start_common();
|
|
||||||
slider_brightness.set_value(&mut c.common(), brightness * 100.0);
|
|
||||||
c.finish()?;
|
|
||||||
|
|
||||||
slider_brightness.on_value_changed({
|
impl<T> TabMonado<T> {
|
||||||
let tasks = self.tasks.clone();
|
pub fn new(frontend: &mut Frontend<T>, parent_id: WidgetID) -> anyhow::Result<Self> {
|
||||||
Box::new(move |_common, e| {
|
let globals = frontend.layout.state.globals.clone();
|
||||||
tasks.push(Task::SetBrightness(e.value / 100.0));
|
let state = wgui::parser::parse_from_assets(&doc_params_monado(&globals), &mut frontend.layout, parent_id)?;
|
||||||
|
let id_content = state.get_widget_id("content")?;
|
||||||
|
let tabs = state.fetch_component_as::<ComponentTabs>("tabs")?;
|
||||||
|
|
||||||
|
let tasks = Tasks::<Task>::new();
|
||||||
|
|
||||||
|
tabs.on_select({
|
||||||
|
let tasks = tasks.clone();
|
||||||
|
Rc::new(move |_common, evt| {
|
||||||
|
if let Some(tab) = TabNameEnum::from_string(&evt.name) {
|
||||||
|
tasks.push(Task::SetTab(tab));
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
tasks.push(Task::SetTab(TabNameEnum::ProcessList));
|
||||||
}
|
|
||||||
|
|
||||||
fn focus_client(&mut self, frontend: &mut Frontend<T>, data: &mut T, name: String) -> anyhow::Result<()> {
|
Ok(Self {
|
||||||
frontend.interface.monado_client_focus(data, &name)?;
|
state,
|
||||||
self.tasks.push(Task::Refresh);
|
marker: PhantomData,
|
||||||
Ok(())
|
tasks,
|
||||||
|
id_content,
|
||||||
|
ticks: 0,
|
||||||
|
subtab: Subtab::Empty,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_brightness(&mut self, frontend: &mut Frontend<T>, data: &mut T, brightness: f32) {
|
fn set_brightness(&mut self, frontend: &mut Frontend<T>, data: &mut T, brightness: f32) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,377 @@
|
||||||
|
use std::{collections::HashMap, rc::Rc};
|
||||||
|
|
||||||
|
use crate::tab::settings::{self, SettingType, Task, horiz_cell, mount_requires_restart};
|
||||||
|
use wgui::{
|
||||||
|
components::{
|
||||||
|
button::{ButtonClickEvent, ComponentButton},
|
||||||
|
checkbox::ComponentCheckbox,
|
||||||
|
slider::ComponentSlider,
|
||||||
|
},
|
||||||
|
layout::{Layout, WidgetID},
|
||||||
|
parser::{Fetchable, ParseDocumentParams, ParserState},
|
||||||
|
task::Tasks,
|
||||||
|
widget::label::WidgetLabel,
|
||||||
|
windowing::context_menu,
|
||||||
|
};
|
||||||
|
use wlx_common::config::GeneralConfig;
|
||||||
|
|
||||||
|
pub fn options_category(
|
||||||
|
mp: &mut MacroParams,
|
||||||
|
parent: WidgetID,
|
||||||
|
translation: &str,
|
||||||
|
icon: &str,
|
||||||
|
) -> anyhow::Result<WidgetID> {
|
||||||
|
let id = mp.idx.to_string();
|
||||||
|
mp.idx += 1;
|
||||||
|
|
||||||
|
let mut params: HashMap<Rc<str>, Rc<str>> = HashMap::new();
|
||||||
|
params.insert(Rc::from("translation"), Rc::from(translation));
|
||||||
|
params.insert(Rc::from("icon"), Rc::from(icon));
|
||||||
|
params.insert(Rc::from("id"), Rc::from(id.as_ref()));
|
||||||
|
|
||||||
|
mp.parser_state
|
||||||
|
.instantiate_template(mp.doc_params, "SettingsGroupBox", mp.layout, parent, params)?;
|
||||||
|
|
||||||
|
mp.parser_state.get_widget_id(&id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn options_checkbox(mp: &mut MacroParams, parent: WidgetID, setting: SettingType) -> anyhow::Result<()> {
|
||||||
|
let id = mp.idx.to_string();
|
||||||
|
mp.idx += 1;
|
||||||
|
|
||||||
|
let mut params: HashMap<Rc<str>, Rc<str>> = HashMap::new();
|
||||||
|
params.insert(Rc::from("id"), Rc::from(id.as_ref()));
|
||||||
|
|
||||||
|
match setting.get_translation() {
|
||||||
|
Ok(translation) => params.insert(Rc::from("translation"), translation.into()),
|
||||||
|
Err(raw_text) => params.insert(Rc::from("text"), raw_text.into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(tooltip) = setting.get_tooltip() {
|
||||||
|
params.insert(Rc::from("tooltip"), Rc::from(tooltip));
|
||||||
|
}
|
||||||
|
|
||||||
|
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, parent)?;
|
||||||
|
|
||||||
|
mp.parser_state
|
||||||
|
.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({
|
||||||
|
let tasks = mp.tasks.clone();
|
||||||
|
move |_common, e| {
|
||||||
|
tasks.push(Task::UpdateBool(setting, e.checked));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn options_slider_f32(
|
||||||
|
mp: &mut MacroParams,
|
||||||
|
parent: WidgetID,
|
||||||
|
setting: SettingType,
|
||||||
|
min: f32,
|
||||||
|
max: f32,
|
||||||
|
step: f32,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let id = mp.idx.to_string();
|
||||||
|
mp.idx += 1;
|
||||||
|
|
||||||
|
let mut params: HashMap<Rc<str>, Rc<str>> = HashMap::new();
|
||||||
|
params.insert(Rc::from("id"), Rc::from(id.as_ref()));
|
||||||
|
|
||||||
|
match setting.get_translation() {
|
||||||
|
Ok(translation) => params.insert(Rc::from("translation"), translation.into()),
|
||||||
|
Err(raw_text) => params.insert(Rc::from("text"), raw_text.into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(tooltip) = setting.get_tooltip() {
|
||||||
|
params.insert(Rc::from("tooltip"), Rc::from(tooltip));
|
||||||
|
}
|
||||||
|
|
||||||
|
let value = setting.mut_f32(mp.config).to_string();
|
||||||
|
params.insert(Rc::from("value"), Rc::from(value));
|
||||||
|
params.insert(Rc::from("min"), Rc::from(min.to_string()));
|
||||||
|
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, parent)?;
|
||||||
|
|
||||||
|
mp.parser_state
|
||||||
|
.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({
|
||||||
|
let tasks = mp.tasks.clone();
|
||||||
|
move |_common, e| {
|
||||||
|
tasks.push(Task::UpdateFloat(setting, e.value));
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn options_range_f32(
|
||||||
|
mp: &mut MacroParams,
|
||||||
|
parent: WidgetID,
|
||||||
|
setting: SettingType,
|
||||||
|
setting2: SettingType,
|
||||||
|
min: f32,
|
||||||
|
max: f32,
|
||||||
|
step: f32,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let id = mp.idx.to_string();
|
||||||
|
mp.idx += 1;
|
||||||
|
|
||||||
|
let mut params: HashMap<Rc<str>, Rc<str>> = HashMap::new();
|
||||||
|
params.insert(Rc::from("id"), Rc::from(id.as_ref()));
|
||||||
|
|
||||||
|
match setting.get_translation() {
|
||||||
|
Ok(translation) => params.insert(Rc::from("translation"), translation.into()),
|
||||||
|
Err(raw_text) => params.insert(Rc::from("text"), raw_text.into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(tooltip) = setting.get_tooltip() {
|
||||||
|
params.insert(Rc::from("tooltip"), Rc::from(tooltip));
|
||||||
|
}
|
||||||
|
|
||||||
|
let value = setting.mut_f32(mp.config).to_string();
|
||||||
|
let value2 = setting2.mut_f32(mp.config).to_string();
|
||||||
|
params.insert(Rc::from("value"), Rc::from(value));
|
||||||
|
params.insert(Rc::from("value2"), Rc::from(value2));
|
||||||
|
params.insert(Rc::from("min"), Rc::from(min.to_string()));
|
||||||
|
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, parent)?;
|
||||||
|
|
||||||
|
mp.parser_state
|
||||||
|
.instantiate_template(mp.doc_params, "RangeSetting", 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({
|
||||||
|
let tasks = mp.tasks.clone();
|
||||||
|
move |_common, e| {
|
||||||
|
if matches!(e.index, wgui::components::slider::ValueIndex::Primary) {
|
||||||
|
tasks.push(Task::UpdateFloat(setting, e.value));
|
||||||
|
} else {
|
||||||
|
tasks.push(Task::UpdateFloat(setting2, e.value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn options_slider_i32(
|
||||||
|
mp: &mut MacroParams,
|
||||||
|
parent: WidgetID,
|
||||||
|
setting: SettingType,
|
||||||
|
min: i32,
|
||||||
|
max: i32,
|
||||||
|
step: i32,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let id = mp.idx.to_string();
|
||||||
|
mp.idx += 1;
|
||||||
|
|
||||||
|
let mut params: HashMap<Rc<str>, Rc<str>> = HashMap::new();
|
||||||
|
params.insert(Rc::from("id"), Rc::from(id.as_ref()));
|
||||||
|
|
||||||
|
match setting.get_translation() {
|
||||||
|
Ok(translation) => params.insert(Rc::from("translation"), translation.into()),
|
||||||
|
Err(raw_text) => params.insert(Rc::from("text"), raw_text.into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(tooltip) = setting.get_tooltip() {
|
||||||
|
params.insert(Rc::from("tooltip"), Rc::from(tooltip));
|
||||||
|
}
|
||||||
|
|
||||||
|
let id_cell = horiz_cell(mp.layout, parent)?;
|
||||||
|
|
||||||
|
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()));
|
||||||
|
params.insert(Rc::from("max"), Rc::from(max.to_string()));
|
||||||
|
params.insert(Rc::from("step"), Rc::from(step.to_string()));
|
||||||
|
|
||||||
|
mp.parser_state
|
||||||
|
.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({
|
||||||
|
let tasks = mp.tasks.clone();
|
||||||
|
move |_common, e| {
|
||||||
|
tasks.push(Task::UpdateInt(setting, e.value as i32));
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn options_dropdown<EnumType>(
|
||||||
|
mp: &mut MacroParams,
|
||||||
|
parent: WidgetID,
|
||||||
|
setting: &'static SettingType,
|
||||||
|
) -> anyhow::Result<()>
|
||||||
|
where
|
||||||
|
EnumType: strum::VariantArray + strum::EnumProperty + std::convert::AsRef<str> + Copy + 'static,
|
||||||
|
{
|
||||||
|
let id = mp.idx.to_string();
|
||||||
|
mp.idx += 1;
|
||||||
|
|
||||||
|
let mut params: HashMap<Rc<str>, Rc<str>> = HashMap::new();
|
||||||
|
params.insert(Rc::from("id"), Rc::from(id.as_ref()));
|
||||||
|
|
||||||
|
match setting.get_translation() {
|
||||||
|
Ok(translation) => params.insert(Rc::from("translation"), translation.into()),
|
||||||
|
Err(raw_text) => params.insert(Rc::from("text"), raw_text.into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(tooltip) = setting.get_tooltip() {
|
||||||
|
params.insert(Rc::from("tooltip"), Rc::from(tooltip));
|
||||||
|
}
|
||||||
|
|
||||||
|
let id_cell = horiz_cell(mp.layout, parent)?;
|
||||||
|
|
||||||
|
mp.parser_state
|
||||||
|
.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);
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut label = mp
|
||||||
|
.parser_state
|
||||||
|
.fetch_widget_as::<WidgetLabel>(&mp.layout.state, &format!("{id}_value"))?;
|
||||||
|
label.set_text_simple(&mut mp.layout.state.globals.get(), title);
|
||||||
|
}
|
||||||
|
|
||||||
|
let btn = mp.parser_state.fetch_component_as::<ComponentButton>(&id)?;
|
||||||
|
btn.on_click(Rc::new({
|
||||||
|
let tasks = mp.tasks.clone();
|
||||||
|
move |_common, e: ButtonClickEvent| {
|
||||||
|
tasks.push(Task::OpenContextMenu(
|
||||||
|
e.mouse_pos_absolute.unwrap_or_default(),
|
||||||
|
EnumType::VARIANTS
|
||||||
|
.iter()
|
||||||
|
.filter_map(|item| {
|
||||||
|
if item.get_bool("Hidden").unwrap_or(false) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let value = item.as_ref();
|
||||||
|
let title = SettingType::get_enum_title_inner(*item);
|
||||||
|
let tooltip = SettingType::get_enum_tooltip_inner(*item);
|
||||||
|
|
||||||
|
let text = &title.text;
|
||||||
|
let translated = if title.translated { "1" } else { "0" };
|
||||||
|
|
||||||
|
Some(context_menu::Cell {
|
||||||
|
action_name: Some(format!("{setting_str};{id};{value};{text};{translated}").into()),
|
||||||
|
title,
|
||||||
|
tooltip,
|
||||||
|
attribs: vec![],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn options_danger_button(
|
||||||
|
mp: &mut MacroParams,
|
||||||
|
parent: WidgetID,
|
||||||
|
translation: &str,
|
||||||
|
icon: &str,
|
||||||
|
task: Task,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let id = mp.idx.to_string();
|
||||||
|
mp.idx += 1;
|
||||||
|
|
||||||
|
let mut params: HashMap<Rc<str>, Rc<str>> = HashMap::new();
|
||||||
|
params.insert(Rc::from("id"), Rc::from(id.as_ref()));
|
||||||
|
params.insert(Rc::from("translation"), Rc::from(translation));
|
||||||
|
params.insert(Rc::from("icon"), Rc::from(icon));
|
||||||
|
|
||||||
|
mp.parser_state
|
||||||
|
.instantiate_template(mp.doc_params, "DangerButton", mp.layout, parent, params)?;
|
||||||
|
|
||||||
|
let btn = mp.parser_state.fetch_component_as::<ComponentButton>(&id)?;
|
||||||
|
btn.on_click(Rc::new({
|
||||||
|
let tasks = mp.tasks.clone();
|
||||||
|
move |_common, _e| {
|
||||||
|
tasks.push(task.clone());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn options_autostart_app(
|
||||||
|
mp: &mut MacroParams,
|
||||||
|
parent: WidgetID,
|
||||||
|
text: &str,
|
||||||
|
ids: &mut Vec<Rc<str>>,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let id = mp.idx.to_string();
|
||||||
|
mp.idx += 1;
|
||||||
|
|
||||||
|
let mut params: HashMap<Rc<str>, Rc<str>> = HashMap::new();
|
||||||
|
params.insert(Rc::from("id"), Rc::from(id.as_ref()));
|
||||||
|
params.insert(Rc::from("text"), Rc::from(text));
|
||||||
|
|
||||||
|
mp.parser_state
|
||||||
|
.instantiate_template(mp.doc_params, "AutostartApp", mp.layout, parent, params)?;
|
||||||
|
|
||||||
|
let btn = mp.parser_state.fetch_component_as::<ComponentButton>(&id)?;
|
||||||
|
let id: Rc<str> = Rc::from(id);
|
||||||
|
|
||||||
|
ids.push(id.clone());
|
||||||
|
|
||||||
|
btn.on_click(Rc::new({
|
||||||
|
let tasks = mp.tasks.clone();
|
||||||
|
move |_common, _e| {
|
||||||
|
tasks.push(Task::RemoveAutostartApp(id.clone()));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct MacroParams<'a> {
|
||||||
|
pub layout: &'a mut Layout,
|
||||||
|
pub parser_state: &'a mut ParserState,
|
||||||
|
pub doc_params: &'a ParseDocumentParams<'a>,
|
||||||
|
pub config: &'a mut GeneralConfig,
|
||||||
|
pub tasks: Tasks<settings::Task>,
|
||||||
|
pub idx: usize,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use crate::tab::settings::{
|
||||||
|
SettingsMountParams, SettingsTab,
|
||||||
|
macros::{options_autostart_app, options_category},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct State {}
|
||||||
|
|
||||||
|
impl SettingsTab for State {}
|
||||||
|
|
||||||
|
impl State {
|
||||||
|
pub fn mount(par: SettingsMountParams, app_button_ids: &mut Vec<Rc<str>>) -> anyhow::Result<State> {
|
||||||
|
*app_button_ids = Vec::new();
|
||||||
|
|
||||||
|
if !par.mp.config.autostart_apps.is_empty() {
|
||||||
|
let c = options_category(
|
||||||
|
par.mp,
|
||||||
|
par.id_parent,
|
||||||
|
"APP_SETTINGS.AUTOSTART_APPS",
|
||||||
|
"dashboard/apps.svg",
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// todo: prevent clone
|
||||||
|
let autostart_apps = par.mp.config.autostart_apps.clone();
|
||||||
|
for app in autostart_apps {
|
||||||
|
options_autostart_app(par.mp, c, &app.name, app_button_ids)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(State {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
use crate::tab::settings::{
|
||||||
|
SettingType, SettingsMountParams, SettingsTab,
|
||||||
|
macros::{
|
||||||
|
options_category, options_checkbox, options_dropdown, options_range_f32, options_slider_f32, options_slider_i32,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct State {}
|
||||||
|
|
||||||
|
impl SettingsTab for State {}
|
||||||
|
|
||||||
|
impl State {
|
||||||
|
pub fn mount(par: SettingsMountParams) -> anyhow::Result<State> {
|
||||||
|
let c = options_category(
|
||||||
|
par.mp,
|
||||||
|
par.id_parent,
|
||||||
|
"APP_SETTINGS.CONTROLS",
|
||||||
|
"dashboard/controller.svg",
|
||||||
|
)?;
|
||||||
|
options_dropdown::<wlx_common::config::AltModifier>(par.mp, c, &SettingType::KeyboardMiddleClick)?;
|
||||||
|
options_dropdown::<wlx_common::config::HandsfreePointer>(par.mp, c, &SettingType::HandsfreePointer)?;
|
||||||
|
options_checkbox(par.mp, c, SettingType::FocusFollowsMouseMode)?;
|
||||||
|
options_checkbox(par.mp, c, SettingType::LeftHandedMouse)?;
|
||||||
|
options_checkbox(par.mp, c, SettingType::AllowSliding)?;
|
||||||
|
options_checkbox(par.mp, c, SettingType::InvertScrollDirectionX)?;
|
||||||
|
options_checkbox(par.mp, c, SettingType::InvertScrollDirectionY)?;
|
||||||
|
options_slider_f32(par.mp, c, SettingType::ScrollSpeed, 0.1, 5.0, 0.1)?;
|
||||||
|
options_slider_f32(par.mp, c, SettingType::LongPressDuration, 0.1, 2.0, 0.1)?;
|
||||||
|
|
||||||
|
if par.feats.openxr {
|
||||||
|
options_slider_f32(par.mp, c, SettingType::PointerLerpFactor, 0.1, 1.0, 0.1)?;
|
||||||
|
options_range_f32(
|
||||||
|
par.mp,
|
||||||
|
c,
|
||||||
|
SettingType::XrClickSensitivityRelease,
|
||||||
|
SettingType::XrClickSensitivity,
|
||||||
|
0.1,
|
||||||
|
0.9,
|
||||||
|
0.1,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
options_slider_i32(par.mp, c, SettingType::ClickFreezeTimeMs, 0, 500, 50)?;
|
||||||
|
Ok(State {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
use crate::tab::settings::{
|
||||||
|
SettingType, SettingsMountParams, SettingsTab,
|
||||||
|
macros::{options_category, options_checkbox, options_range_f32, options_slider_f32},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct State {}
|
||||||
|
|
||||||
|
impl SettingsTab for State {}
|
||||||
|
|
||||||
|
impl State {
|
||||||
|
pub fn mount(par: SettingsMountParams) -> anyhow::Result<State> {
|
||||||
|
let c = options_category(par.mp, par.id_parent, "APP_SETTINGS.FEATURES", "dashboard/options.svg")?;
|
||||||
|
options_checkbox(par.mp, c, SettingType::NotificationsEnabled)?;
|
||||||
|
options_checkbox(par.mp, c, SettingType::NotificationsSoundEnabled)?;
|
||||||
|
options_checkbox(par.mp, c, SettingType::KeyboardSoundEnabled)?;
|
||||||
|
if !par.feats.openxr || par.feats.monado {
|
||||||
|
// monado or openvr
|
||||||
|
options_checkbox(par.mp, c, SettingType::BlockGameInput)?;
|
||||||
|
options_checkbox(par.mp, c, SettingType::BlockGameInputIgnoreWatch)?;
|
||||||
|
}
|
||||||
|
if par.feats.monado {
|
||||||
|
// monado-only
|
||||||
|
options_checkbox(par.mp, c, SettingType::BlockPosesOnKbdInteraction)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
options_range_f32(
|
||||||
|
par.mp,
|
||||||
|
c,
|
||||||
|
SettingType::WatchViewAngleMin,
|
||||||
|
SettingType::WatchViewAngleMax,
|
||||||
|
0.1,
|
||||||
|
1.0,
|
||||||
|
0.1,
|
||||||
|
)?;
|
||||||
|
Ok(State {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
use crate::tab::settings::{
|
||||||
|
SettingType, SettingsMountParams, SettingsTab,
|
||||||
|
macros::{options_category, options_checkbox, options_dropdown, options_slider_f32},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct State {}
|
||||||
|
|
||||||
|
impl SettingsTab for State {}
|
||||||
|
|
||||||
|
impl State {
|
||||||
|
pub fn mount(par: SettingsMountParams) -> anyhow::Result<State> {
|
||||||
|
let c = options_category(
|
||||||
|
par.mp,
|
||||||
|
par.id_parent,
|
||||||
|
"APP_SETTINGS.LOOK_AND_FEEL",
|
||||||
|
"dashboard/palette.svg",
|
||||||
|
)?;
|
||||||
|
options_dropdown::<wlx_common::locale::Language>(par.mp, c, &SettingType::Language)?;
|
||||||
|
options_checkbox(par.mp, c, SettingType::OpaqueBackground)?;
|
||||||
|
options_checkbox(par.mp, c, SettingType::HideUsername)?;
|
||||||
|
options_checkbox(par.mp, c, SettingType::HideGrabHelp)?;
|
||||||
|
options_slider_f32(par.mp, c, SettingType::UiAnimationSpeed, 0.5, 5.0, 0.1)?; // min, max, step
|
||||||
|
options_slider_f32(par.mp, c, SettingType::UiGradientIntensity, 0.0, 1.0, 0.05)?; // min, max, step
|
||||||
|
options_slider_f32(par.mp, c, SettingType::UiRoundMultiplier, 0.1, 5.0, 0.1)?;
|
||||||
|
options_checkbox(par.mp, c, SettingType::EnableWatch)?;
|
||||||
|
options_checkbox(par.mp, c, SettingType::SetsOnWatch)?;
|
||||||
|
options_checkbox(par.mp, c, SettingType::Clock12h)?;
|
||||||
|
Ok(State {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
use crate::tab::settings::{
|
||||||
|
SettingType, SettingsMountParams, SettingsTab,
|
||||||
|
macros::{options_category, options_checkbox, options_dropdown},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct State {}
|
||||||
|
|
||||||
|
impl SettingsTab for State {}
|
||||||
|
|
||||||
|
impl State {
|
||||||
|
pub fn mount(par: SettingsMountParams) -> anyhow::Result<State> {
|
||||||
|
let c = options_category(par.mp, par.id_parent, "APP_SETTINGS.MISC", "dashboard/blocks.svg")?;
|
||||||
|
options_dropdown::<wlx_common::config::CaptureMethod>(par.mp, c, &SettingType::CaptureMethod)?;
|
||||||
|
options_checkbox(par.mp, c, SettingType::XwaylandByDefault)?;
|
||||||
|
options_checkbox(par.mp, c, SettingType::UprightScreenFix)?;
|
||||||
|
options_checkbox(par.mp, c, SettingType::DoubleCursorFix)?;
|
||||||
|
options_checkbox(par.mp, c, SettingType::ScreenRenderDown)?;
|
||||||
|
Ok(State {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
use wgui::{assets::AssetPath, i18n::Translation, layout::Layout, task::Tasks};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
frontend::FrontendTasks,
|
||||||
|
tab::settings::{
|
||||||
|
SettingType, SettingsMountParams, SettingsTab,
|
||||||
|
macros::{options_category, options_checkbox, options_slider_f32},
|
||||||
|
},
|
||||||
|
util::{popup_manager::PopupHolder, wgui_simple},
|
||||||
|
views::{self, ViewUpdateParams, skymap_list},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
enum Task {
|
||||||
|
ShowSkymapList,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct State {
|
||||||
|
popup_skymap_list: PopupHolder<skymap_list::View>,
|
||||||
|
tasks: Tasks<Task>,
|
||||||
|
frontend_tasks: FrontendTasks,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SettingsTab for State {
|
||||||
|
fn update(&mut self, par: &mut ViewUpdateParams) -> anyhow::Result<()> {
|
||||||
|
self.popup_skymap_list.update(par)?;
|
||||||
|
|
||||||
|
for task in self.tasks.drain() {
|
||||||
|
match task {
|
||||||
|
Task::ShowSkymapList => self.show_skymap_list(par.layout),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl State {
|
||||||
|
pub fn mount(par: SettingsMountParams) -> anyhow::Result<Self> {
|
||||||
|
let id_category = options_category(par.mp, par.id_parent, "APP_SETTINGS.SKYBOX", "dashboard/globe.svg")?;
|
||||||
|
options_checkbox(par.mp, id_category, SettingType::UseSkybox)?;
|
||||||
|
options_checkbox(par.mp, id_category, SettingType::UsePassthrough)?;
|
||||||
|
options_slider_f32(par.mp, id_category, SettingType::GridOpacity, 0.0, 1.0, 0.05)?;
|
||||||
|
|
||||||
|
let tasks = Tasks::<Task>::new();
|
||||||
|
|
||||||
|
// "Browse skymaps" button
|
||||||
|
wgui_simple::create_button(wgui_simple::CreateButtonParams {
|
||||||
|
id_parent: id_category,
|
||||||
|
layout: par.mp.layout,
|
||||||
|
content: Translation::from_translation_key("APP_SETTINGS.BROWSE_SKYMAPS"),
|
||||||
|
icon_builtin: AssetPath::BuiltIn("dashboard/globe.svg"),
|
||||||
|
on_click: tasks.get_button_click_callback(Task::ShowSkymapList),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
popup_skymap_list: Default::default(),
|
||||||
|
frontend_tasks: par.frontend_tasks.clone(),
|
||||||
|
tasks,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show_skymap_list(&mut self, layout: &mut Layout) {
|
||||||
|
views::skymap_list::mount_popup(
|
||||||
|
self.frontend_tasks.clone(),
|
||||||
|
layout.state.globals.clone(),
|
||||||
|
self.popup_skymap_list.clone(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,138 @@
|
||||||
|
use wgui::{
|
||||||
|
assets::AssetPath,
|
||||||
|
i18n::Translation,
|
||||||
|
layout::{Layout, LayoutTask, WidgetID},
|
||||||
|
parser::{Fetchable, ParseDocumentParams},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
tab::settings::{
|
||||||
|
SettingType, SettingsMountParams, SettingsTab,
|
||||||
|
macros::{options_category, options_checkbox, options_slider_f32},
|
||||||
|
},
|
||||||
|
util::wgui_simple,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct State {
|
||||||
|
id_space_gravity_parent: WidgetID,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_visible(parent: WidgetID, layout: &mut Layout, n: bool) {
|
||||||
|
layout.tasks.push(LayoutTask::SetWidgetVisible(parent, n));
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SettingsTab for State {
|
||||||
|
fn setting_updated(&mut self, sup: &mut super::SettingUpdatedParams) -> anyhow::Result<()> {
|
||||||
|
if sup.setting_type == SettingType::SpaceGravityEnabled {
|
||||||
|
set_visible(
|
||||||
|
self.id_space_gravity_parent,
|
||||||
|
sup.layout,
|
||||||
|
sup.config.space_gravity_enabled,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl State {
|
||||||
|
pub fn mount(par: SettingsMountParams) -> anyhow::Result<State> {
|
||||||
|
let c = options_category(par.mp, par.id_parent, "APP_SETTINGS.SPACE_DRAG", "dashboard/drag.svg")?;
|
||||||
|
|
||||||
|
let globals = par.mp.layout.state.globals.clone();
|
||||||
|
|
||||||
|
let tab_state = wgui::parser::parse_from_assets(
|
||||||
|
&ParseDocumentParams {
|
||||||
|
globals,
|
||||||
|
path: AssetPath::BuiltIn("gui/tab/settings_tab_space_drag.xml"),
|
||||||
|
extra: Default::default(),
|
||||||
|
},
|
||||||
|
par.mp.layout,
|
||||||
|
c,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let id_common_options_parent = tab_state.get_widget_id("common_options_parent")?;
|
||||||
|
let id_gravity_enabled_parent = tab_state.get_widget_id("gravity_enabled_parent")?;
|
||||||
|
let id_space_gravity_parent = tab_state.get_widget_id("space_gravity_parent")?;
|
||||||
|
|
||||||
|
if !par.feats.openxr || par.feats.monado {
|
||||||
|
// monado or openvr
|
||||||
|
options_checkbox(par.mp, id_common_options_parent, SettingType::SpaceDragUnlocked)?;
|
||||||
|
|
||||||
|
options_slider_f32(
|
||||||
|
par.mp,
|
||||||
|
id_common_options_parent,
|
||||||
|
SettingType::SpaceDragMultiplier,
|
||||||
|
-10.0,
|
||||||
|
10.0,
|
||||||
|
0.5,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if par.feats.monado {
|
||||||
|
// openvr can only ever rotate yaw
|
||||||
|
options_checkbox(par.mp, id_common_options_parent, SettingType::SpaceRotateUnlocked)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if par.feats.monado {
|
||||||
|
/* space gravity section */
|
||||||
|
options_checkbox(par.mp, id_gravity_enabled_parent, SettingType::SpaceGravityEnabled)?;
|
||||||
|
|
||||||
|
options_slider_f32(
|
||||||
|
par.mp,
|
||||||
|
id_space_gravity_parent,
|
||||||
|
SettingType::SpaceGravityGravity,
|
||||||
|
0.0,
|
||||||
|
10.0,
|
||||||
|
0.5,
|
||||||
|
)?;
|
||||||
|
options_slider_f32(
|
||||||
|
par.mp,
|
||||||
|
id_space_gravity_parent,
|
||||||
|
SettingType::SpaceGravityDamping,
|
||||||
|
0.1,
|
||||||
|
1.0,
|
||||||
|
0.01,
|
||||||
|
)?;
|
||||||
|
options_slider_f32(
|
||||||
|
par.mp,
|
||||||
|
id_space_gravity_parent,
|
||||||
|
SettingType::SpaceGravityFlingStrength,
|
||||||
|
0.0,
|
||||||
|
3.0,
|
||||||
|
0.1,
|
||||||
|
)?;
|
||||||
|
options_slider_f32(
|
||||||
|
par.mp,
|
||||||
|
id_space_gravity_parent,
|
||||||
|
SettingType::SpaceGravityGroundFriction,
|
||||||
|
0.0,
|
||||||
|
1.0,
|
||||||
|
0.01,
|
||||||
|
)?;
|
||||||
|
options_slider_f32(
|
||||||
|
par.mp,
|
||||||
|
id_space_gravity_parent,
|
||||||
|
SettingType::SpaceGravityFloorHeight,
|
||||||
|
-5.0,
|
||||||
|
5.0,
|
||||||
|
0.1,
|
||||||
|
)?;
|
||||||
|
} else {
|
||||||
|
wgui_simple::create_label(
|
||||||
|
par.mp.layout,
|
||||||
|
id_gravity_enabled_parent,
|
||||||
|
Translation::from_translation_key("APP_SETTINGS.NOT_SUPPORTED"),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
set_visible(
|
||||||
|
id_space_gravity_parent,
|
||||||
|
par.mp.layout,
|
||||||
|
par.mp.config.space_gravity_enabled,
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(State {
|
||||||
|
id_space_gravity_parent,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
use crate::tab::settings::{
|
||||||
|
SettingsMountParams, SettingsTab, Task,
|
||||||
|
macros::{options_category, options_danger_button},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct State {}
|
||||||
|
|
||||||
|
impl SettingsTab for State {}
|
||||||
|
|
||||||
|
impl State {
|
||||||
|
pub fn mount(par: SettingsMountParams) -> anyhow::Result<Self> {
|
||||||
|
let c = options_category(
|
||||||
|
par.mp,
|
||||||
|
par.id_parent,
|
||||||
|
"APP_SETTINGS.TROUBLESHOOTING",
|
||||||
|
"dashboard/cpu.svg",
|
||||||
|
)?;
|
||||||
|
options_danger_button(
|
||||||
|
par.mp,
|
||||||
|
c,
|
||||||
|
"APP_SETTINGS.RESET_PLAYSPACE",
|
||||||
|
"dashboard/recenter.svg",
|
||||||
|
Task::ResetPlayspace,
|
||||||
|
)?;
|
||||||
|
options_danger_button(
|
||||||
|
par.mp,
|
||||||
|
c,
|
||||||
|
"APP_SETTINGS.CLEAR_PIPEWIRE_TOKENS",
|
||||||
|
"dashboard/display.svg",
|
||||||
|
Task::ClearPipewireTokens,
|
||||||
|
)?;
|
||||||
|
options_danger_button(
|
||||||
|
par.mp,
|
||||||
|
c,
|
||||||
|
"APP_SETTINGS.CLEAR_SAVED_STATE",
|
||||||
|
"dashboard/binary.svg",
|
||||||
|
Task::ClearSavedState,
|
||||||
|
)?;
|
||||||
|
options_danger_button(
|
||||||
|
par.mp,
|
||||||
|
c,
|
||||||
|
"APP_SETTINGS.DELETE_ALL_CONFIGS",
|
||||||
|
"dashboard/circle.svg",
|
||||||
|
Task::DeleteAllConfigs,
|
||||||
|
)?;
|
||||||
|
options_danger_button(
|
||||||
|
par.mp,
|
||||||
|
c,
|
||||||
|
"APP_SETTINGS.RESTART_SOFTWARE",
|
||||||
|
"dashboard/refresh.svg",
|
||||||
|
Task::RestartSoftware,
|
||||||
|
)?;
|
||||||
|
Ok(State {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
|
use crate::util::{networking::http_client, steam_utils::AppID};
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use wlx_common::cache_dir;
|
use wlx_common::{async_executor::AsyncExecutor, cache_dir};
|
||||||
|
|
||||||
use crate::util::{http_client, steam_utils::AppID, various::AsyncExecutor};
|
|
||||||
|
|
||||||
pub struct CoverArt {
|
pub struct CoverArt {
|
||||||
// can be empty in case if data couldn't be fetched (use a fallback image then)
|
// can be empty in case if data couldn't be fetched (use a fallback image then)
|
||||||
|
|
@ -24,7 +23,7 @@ pub async fn request_image(executor: AsyncExecutor, app_id: AppID) -> anyhow::Re
|
||||||
app_id
|
app_id
|
||||||
);
|
);
|
||||||
|
|
||||||
match http_client::get(&executor, &url).await {
|
match http_client::get_simple(&executor, &url).await {
|
||||||
Ok(response) => {
|
Ok(response) => {
|
||||||
log::info!("Success");
|
log::info!("Success");
|
||||||
cache_dir::set_data(&cache_file_path, &response.data).await?;
|
cache_dir::set_data(&cache_file_path, &response.data).await?;
|
||||||
|
|
@ -69,7 +68,7 @@ async fn get_app_details_json_internal(
|
||||||
// Fetch from Steam API
|
// Fetch from Steam API
|
||||||
log::info!("Fetching app detail ID {}", app_id);
|
log::info!("Fetching app detail ID {}", app_id);
|
||||||
let url = format!("https://store.steampowered.com/api/appdetails?appids={}", app_id);
|
let url = format!("https://store.steampowered.com/api/appdetails?appids={}", app_id);
|
||||||
let response = http_client::get(&executor, &url).await?;
|
let response = http_client::get_simple(&executor, &url).await?;
|
||||||
let res_utf8 = String::from_utf8(response.data)?;
|
let res_utf8 = String::from_utf8(response.data)?;
|
||||||
let root = serde_json::from_str::<serde_json::Value>(&res_utf8)?;
|
let root = serde_json::from_str::<serde_json::Value>(&res_utf8)?;
|
||||||
let body = root.get(&app_id).context("invalid body")?;
|
let body = root.get(&app_id).context("invalid body")?;
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
pub mod cached_fetcher;
|
pub mod cached_fetcher;
|
||||||
pub mod http_client;
|
pub mod networking;
|
||||||
pub mod pactl_wrapper;
|
pub mod pactl_wrapper;
|
||||||
pub mod popup_manager;
|
pub mod popup_manager;
|
||||||
pub mod steam_utils;
|
pub mod steam_utils;
|
||||||
pub mod toast_manager;
|
pub mod toast_manager;
|
||||||
pub mod various;
|
|
||||||
pub mod wgui_simple;
|
pub mod wgui_simple;
|
||||||
|
|
|
||||||
|
|
@ -13,16 +13,38 @@ use smol::{net::TcpStream, prelude::*};
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::task::{Context, Poll};
|
use std::task::{Context, Poll};
|
||||||
|
use wlx_common::async_executor::AsyncExecutor;
|
||||||
use crate::util::various::AsyncExecutor;
|
|
||||||
pub struct HttpClientResponse {
|
pub struct HttpClientResponse {
|
||||||
pub data: Vec<u8>,
|
pub data: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get(executor: &AsyncExecutor, url: &str) -> anyhow::Result<HttpClientResponse> {
|
impl HttpClientResponse {
|
||||||
log::info!("fetching URL \"{}\"", url);
|
pub fn into_json<T>(self) -> anyhow::Result<T>
|
||||||
|
where
|
||||||
|
T: for<'a> serde::Deserialize<'a>,
|
||||||
|
{
|
||||||
|
let utf8 = str::from_utf8(&self.data)?;
|
||||||
|
Ok(serde_json::from_str::<T>(utf8)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let url: hyper::Uri = url.try_into()?;
|
pub struct ProgressFuncData {
|
||||||
|
pub bytes_downloaded: u64,
|
||||||
|
pub file_size: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type ProgressFunc = Box<dyn Fn(ProgressFuncData)>;
|
||||||
|
|
||||||
|
pub struct GetParams<'a> {
|
||||||
|
pub executor: &'a AsyncExecutor,
|
||||||
|
pub url: &'a str,
|
||||||
|
pub on_progress: Option<ProgressFunc>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get(params: GetParams<'_>) -> anyhow::Result<HttpClientResponse> {
|
||||||
|
log::info!("fetching URL \"{}\"", params.url);
|
||||||
|
|
||||||
|
let url: hyper::Uri = params.url.try_into()?;
|
||||||
let req = Request::builder()
|
let req = Request::builder()
|
||||||
.header(
|
.header(
|
||||||
hyper::header::HOST,
|
hyper::header::HOST,
|
||||||
|
|
@ -31,23 +53,55 @@ pub async fn get(executor: &AsyncExecutor, url: &str) -> anyhow::Result<HttpClie
|
||||||
.uri(url)
|
.uri(url)
|
||||||
.body(Empty::new())?;
|
.body(Empty::new())?;
|
||||||
|
|
||||||
let resp = fetch(executor, req).await?;
|
let resp = fetch(params.executor, req).await?;
|
||||||
|
|
||||||
if !resp.status().is_success() {
|
if !resp.status().is_success() {
|
||||||
// non-200 HTTP response
|
// non-200 HTTP response
|
||||||
anyhow::bail!("non-200 HTTP response: {}", resp.status().as_str());
|
anyhow::bail!("non-200 HTTP response: {}", resp.status().as_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
let body = BodyStream::new(resp.into_body())
|
let mut bytes_downloaded: u64 = 0;
|
||||||
|
let mut file_size: u64 = 1;
|
||||||
|
|
||||||
|
let (parts, body) = resp.into_parts();
|
||||||
|
|
||||||
|
// that's a pretty interesting way to get file size :]
|
||||||
|
if let Some(val) = parts.headers.get("Content-Length")
|
||||||
|
&& let Ok(str) = val.to_str()
|
||||||
|
&& let Ok(s) = str.parse()
|
||||||
|
{
|
||||||
|
file_size = s;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut on_progress = params.on_progress;
|
||||||
|
|
||||||
|
let data = BodyStream::new(body)
|
||||||
.try_fold(Vec::new(), |mut body, chunk| {
|
.try_fold(Vec::new(), |mut body, chunk| {
|
||||||
if let Some(chunk) = chunk.data_ref() {
|
if let Some(chunk) = chunk.data_ref() {
|
||||||
|
bytes_downloaded += chunk.len() as u64;
|
||||||
body.extend_from_slice(chunk);
|
body.extend_from_slice(chunk);
|
||||||
|
|
||||||
|
if let Some(on_progress) = &mut on_progress {
|
||||||
|
on_progress(ProgressFuncData {
|
||||||
|
bytes_downloaded,
|
||||||
|
file_size,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(body)
|
Ok(body)
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(HttpClientResponse { data: body })
|
Ok(HttpClientResponse { data })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_simple(executor: &AsyncExecutor, url: &str) -> anyhow::Result<HttpClientResponse> {
|
||||||
|
get(GetParams {
|
||||||
|
executor,
|
||||||
|
url,
|
||||||
|
on_progress: None,
|
||||||
|
})
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn fetch(
|
async fn fetch(
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use wgui::{globals::WguiGlobals, renderer_vk::text::custom_glyph::CustomGlyphData};
|
||||||
|
use wlx_common::async_executor::AsyncExecutor;
|
||||||
|
|
||||||
|
use crate::util::networking::http_client;
|
||||||
|
|
||||||
|
pub async fn fetch_to_glyph_data(
|
||||||
|
globals: &WguiGlobals,
|
||||||
|
executor: &AsyncExecutor,
|
||||||
|
url: &str,
|
||||||
|
) -> anyhow::Result<(CustomGlyphData, Rc<Vec<u8>>)> {
|
||||||
|
let res = http_client::get_simple(executor, url).await?;
|
||||||
|
let glyph_data = CustomGlyphData::from_bytes_raster(globals, url, &res.data)?;
|
||||||
|
Ok((glyph_data, Rc::new(res.data)))
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
pub mod http_client;
|
||||||
|
pub mod image_fetch;
|
||||||
|
pub mod skymap_catalog;
|
||||||
|
|
||||||
|
// pub const WAYVR_ROOT_URL: &str = "https://wayvr.org";
|
||||||
|
pub const WAYVR_SKYMAPS_ROOT: &str = "https://wayvr.org/skymaps";
|
||||||
|
|
@ -0,0 +1,189 @@
|
||||||
|
use crate::util::networking::{self, WAYVR_SKYMAPS_ROOT, http_client};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use wlx_common::{async_executor::AsyncExecutor, config_io};
|
||||||
|
pub type SkymapUuid = uuid::Uuid;
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Serialize, Deserialize, Debug)]
|
||||||
|
pub enum SkymapResolution {
|
||||||
|
Res2k,
|
||||||
|
Res4k,
|
||||||
|
Res8k,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SkymapResolution {
|
||||||
|
pub const fn get_display_str(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
SkymapResolution::Res2k => "2K (2 MiB VRAM)",
|
||||||
|
SkymapResolution::Res4k => "4K (8 MiB VRAM)",
|
||||||
|
SkymapResolution::Res8k => "8K (33 MiB VRAM)",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn get_display_str_simple(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
SkymapResolution::Res2k => "2K",
|
||||||
|
SkymapResolution::Res4k => "4K",
|
||||||
|
SkymapResolution::Res8k => "8K",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_display_str_simple(text: &str) -> Option<SkymapResolution> {
|
||||||
|
match text {
|
||||||
|
"2K" => Some(SkymapResolution::Res2k),
|
||||||
|
"4K" => Some(SkymapResolution::Res4k),
|
||||||
|
"8K" => Some(SkymapResolution::Res8k),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
|
pub struct SkymapCatalogEntryFiles {
|
||||||
|
pub size_8k: Option<String>, // "my_skymap_8k.dds"
|
||||||
|
pub size_4k: Option<String>, // "my_skymap_4k.dds"
|
||||||
|
pub size_2k: String, // we should have *at least* this
|
||||||
|
pub preview: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SkymapCatalogEntryFiles {
|
||||||
|
pub fn get_url_preview(&self) -> String {
|
||||||
|
format!("{}/files/{}", WAYVR_SKYMAPS_ROOT, self.preview)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_filename_from_res(&self, res: SkymapResolution) -> Option<String> {
|
||||||
|
match res {
|
||||||
|
SkymapResolution::Res2k => Some(&self.size_2k),
|
||||||
|
SkymapResolution::Res4k => self.size_4k.as_ref(),
|
||||||
|
SkymapResolution::Res8k => self.size_8k.as_ref(),
|
||||||
|
}
|
||||||
|
.map(|raw_filename| {
|
||||||
|
// sanitize filename, do not allow "../" just in case
|
||||||
|
PathBuf::from(raw_filename)
|
||||||
|
.file_name()
|
||||||
|
.map(|s| String::from(s.to_string_lossy()))
|
||||||
|
})?
|
||||||
|
}
|
||||||
|
|
||||||
|
// example result: "https://wayvr.org/skymaps/files/my_skymap_8k.dds"
|
||||||
|
pub fn get_url_from_res(&self, res: SkymapResolution) -> Option<String> {
|
||||||
|
let filename = self.get_filename_from_res(res)?;
|
||||||
|
Some(format!("{}/files/{}", WAYVR_SKYMAPS_ROOT, filename))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_preview_path(&self) -> PathBuf {
|
||||||
|
config_io::get_skymaps_root().join(&self.preview)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn save_preview_to_file(&self, data: &[u8]) -> anyhow::Result<()> {
|
||||||
|
std::fs::write(self.get_preview_path(), data)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_preview_file(&self) {
|
||||||
|
let _dont_care = std::fs::remove_file(self.get_preview_path());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
|
pub struct SkymapCatalogEntry {
|
||||||
|
pub uuid: SkymapUuid,
|
||||||
|
pub created_at: String,
|
||||||
|
pub modified_at: String,
|
||||||
|
pub version: u32,
|
||||||
|
pub name: String,
|
||||||
|
pub description: String,
|
||||||
|
pub author: String,
|
||||||
|
pub files: SkymapCatalogEntryFiles,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SkymapCatalogEntry {
|
||||||
|
pub fn get_destination_path(&self, resolution: SkymapResolution) -> Option<PathBuf> {
|
||||||
|
let filename = self.files.get_filename_from_res(resolution)?;
|
||||||
|
Some(config_io::get_skymaps_root().join(filename))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_destination_metadata_path(&self) -> PathBuf {
|
||||||
|
config_io::get_skymaps_root().join(format!("{}.json", self.uuid))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_downloaded(&self, resolution: SkymapResolution) -> anyhow::Result<bool> {
|
||||||
|
let Some(full_path) = self.get_destination_path(resolution) else {
|
||||||
|
return Ok(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(std::fs::exists(full_path)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has_any_downloaded(&self) -> bool {
|
||||||
|
self.is_downloaded(SkymapResolution::Res2k).unwrap_or(false)
|
||||||
|
|| self.is_downloaded(SkymapResolution::Res4k).unwrap_or(false)
|
||||||
|
|| self.is_downloaded(SkymapResolution::Res8k).unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_file(&self, resolution: SkymapResolution) {
|
||||||
|
let Some(full_path) = self.get_destination_path(resolution) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let _dont_care = std::fs::remove_file(full_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn save_metadata(&self) -> anyhow::Result<()> {
|
||||||
|
let json = serde_json::to_string_pretty(self)?;
|
||||||
|
std::fs::write(self.get_destination_metadata_path(), json)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_metadata(&self) {
|
||||||
|
let _dont_care = std::fs::remove_file(self.get_destination_metadata_path());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
pub struct SkymapCatalog {
|
||||||
|
pub version: u32,
|
||||||
|
pub r#type: String,
|
||||||
|
pub entries: Vec<SkymapCatalogEntry>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SkymapCatalog {
|
||||||
|
fn validate(&self) -> anyhow::Result<()> {
|
||||||
|
if self.version != 1 {
|
||||||
|
anyhow::bail!("Unsupported version");
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.r#type != "wayvr_skymaps" {
|
||||||
|
anyhow::bail!("Unsupported type");
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn request_catalog(executor: &AsyncExecutor) -> anyhow::Result<SkymapCatalog> {
|
||||||
|
log::info!("Fetching skymap list");
|
||||||
|
|
||||||
|
let res = http_client::get_simple(executor, &format!("{}/catalog.json", networking::WAYVR_SKYMAPS_ROOT)).await?;
|
||||||
|
let catalog = res.into_json::<SkymapCatalog>()?;
|
||||||
|
catalog.validate()?;
|
||||||
|
|
||||||
|
Ok(catalog)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_entries_from_disk() -> anyhow::Result<Vec<SkymapCatalogEntry>> {
|
||||||
|
let mut entries = Vec::<SkymapCatalogEntry>::new();
|
||||||
|
|
||||||
|
let skymaps_root = config_io::get_skymaps_root();
|
||||||
|
|
||||||
|
for uuid in config_io::get_skymaps_uuids().unwrap_or_default() {
|
||||||
|
let metadata_path = skymaps_root.join(format!("{}.json", uuid));
|
||||||
|
let Ok(data) = std::fs::read_to_string(metadata_path) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let entry = serde_json::from_str::<SkymapCatalogEntry>(&data)?;
|
||||||
|
entries.push(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(entries)
|
||||||
|
}
|
||||||
|
|
@ -6,17 +6,19 @@ use std::{
|
||||||
use wgui::{
|
use wgui::{
|
||||||
assets::AssetPath,
|
assets::AssetPath,
|
||||||
components::button::ComponentButton,
|
components::button::ComponentButton,
|
||||||
event::{EventAlterables, StyleSetRequest},
|
event::EventAlterables,
|
||||||
globals::WguiGlobals,
|
globals::WguiGlobals,
|
||||||
i18n::Translation,
|
i18n::Translation,
|
||||||
layout::{Layout, LayoutTask, LayoutTasks, WidgetID},
|
layout::{Layout, LayoutTask, LayoutTasks, WidgetID},
|
||||||
parser::{Fetchable, ParseDocumentParams, ParserState},
|
parser::{Fetchable, ParseDocumentParams, ParserState},
|
||||||
taffy::Display,
|
|
||||||
widget::label::WidgetLabel,
|
widget::label::WidgetLabel,
|
||||||
};
|
};
|
||||||
use wlx_common::config::GeneralConfig;
|
use wlx_common::config::GeneralConfig;
|
||||||
|
|
||||||
use crate::frontend::{FrontendTask, FrontendTasks};
|
use crate::{
|
||||||
|
frontend::{FrontendTask, FrontendTasks},
|
||||||
|
views::{ViewTrait, ViewUpdateParams},
|
||||||
|
};
|
||||||
|
|
||||||
pub struct PopupManagerParams {
|
pub struct PopupManagerParams {
|
||||||
pub parent_id: WidgetID,
|
pub parent_id: WidgetID,
|
||||||
|
|
@ -34,15 +36,136 @@ pub struct MountedPopup {
|
||||||
frontend_tasks: FrontendTasks,
|
frontend_tasks: FrontendTasks,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
struct MountedPopupState {
|
struct MountedPopupState {
|
||||||
mounted_popup: Option<MountedPopup>,
|
mounted_popup: Option<MountedPopup>,
|
||||||
|
closed_callback: Option<PopupClosedCallback>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Default, Clone)]
|
||||||
pub struct PopupHandle {
|
pub struct PopupHandle {
|
||||||
state: Rc<RefCell<MountedPopupState>>,
|
state: Rc<RefCell<MountedPopupState>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct PopupHolderState<ViewType: ViewTrait> {
|
||||||
|
popup_handle: PopupHandle,
|
||||||
|
view: Option<ViewType>,
|
||||||
|
on_view_close: Option<Box<dyn FnOnce()>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// we can't use #[derive(Default)] due to the fact that ViewType can't be Default.
|
||||||
|
impl<ViewType: ViewTrait> Default for PopupHolderState<ViewType> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
popup_handle: Default::default(),
|
||||||
|
view: None,
|
||||||
|
on_view_close: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PopupHolder<ViewType: ViewTrait> {
|
||||||
|
state: Rc<RefCell<PopupHolderState<ViewType>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<ViewType: ViewTrait> Default for PopupHolder<ViewType> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
state: Rc::new(RefCell::new(PopupHolderState::default())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<ViewType: ViewTrait> PopupHolderState<ViewType> {
|
||||||
|
fn close(&mut self) {
|
||||||
|
if self.view.is_some() {
|
||||||
|
self.view = None;
|
||||||
|
if let Some(on_close) = self.on_view_close.take() {
|
||||||
|
on_close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.popup_handle.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<ViewType: ViewTrait> Drop for PopupHolderState<ViewType> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// we can't derive(Clone) due to the fact that ViewType is non-cloneable
|
||||||
|
impl<ViewType: ViewTrait> Clone for PopupHolder<ViewType> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
state: self.state.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<ViewType: ViewTrait> PopupHolder<ViewType> {
|
||||||
|
pub fn update(&self, par: &mut ViewUpdateParams) -> anyhow::Result<()> {
|
||||||
|
let mut state = self.state.borrow_mut();
|
||||||
|
let Some(view) = &mut state.view else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
view.update(par)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_view(&self, handle: PopupHandle, view: ViewType, on_view_close: Option<Box<dyn FnOnce()>>) {
|
||||||
|
let mut state = self.state.borrow_mut();
|
||||||
|
state.view = Some(view);
|
||||||
|
state.popup_handle = handle;
|
||||||
|
state.on_view_close = on_view_close;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get underlying ViewType object in a closure and return its value
|
||||||
|
// example usage:
|
||||||
|
//
|
||||||
|
// ```rs
|
||||||
|
// holder.with_view(|view| {
|
||||||
|
// view.foo();
|
||||||
|
// })
|
||||||
|
// ```
|
||||||
|
//
|
||||||
|
pub fn with_view<F, R>(&self, f: F) -> Option<R>
|
||||||
|
where
|
||||||
|
F: FnOnce(&mut ViewType) -> R,
|
||||||
|
{
|
||||||
|
let mut state = self.state.borrow_mut();
|
||||||
|
state.view.as_mut().map(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Same as with_view, but the closure expects a simple anyhow::Result<()> type
|
||||||
|
pub fn with_view_res<F>(&self, f: F) -> anyhow::Result<()>
|
||||||
|
where
|
||||||
|
F: FnOnce(&mut ViewType) -> anyhow::Result<()>,
|
||||||
|
{
|
||||||
|
if let Some(res) = self.with_view(f) {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_close_callback(&self, layout: &Layout) -> Box<dyn FnOnce()>
|
||||||
|
where
|
||||||
|
ViewType: 'static,
|
||||||
|
{
|
||||||
|
let layout_tasks = layout.tasks.clone();
|
||||||
|
let weak_state = Rc::downgrade(&self.state);
|
||||||
|
Box::new(move || {
|
||||||
|
// we can't borrow State here yet, dispatch it.
|
||||||
|
layout_tasks.push(LayoutTask::Dispatch(Box::new(move |_common| {
|
||||||
|
if let Some(state) = weak_state.upgrade() {
|
||||||
|
state.borrow_mut().close();
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
})));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl PopupHandle {
|
impl PopupHandle {
|
||||||
pub fn close(&self) {
|
pub fn close(&self) {
|
||||||
self.state.borrow_mut().mounted_popup = None; // Drop will be called
|
self.state.borrow_mut().mounted_popup = None; // Drop will be called
|
||||||
|
|
@ -61,10 +184,24 @@ pub struct PopupContentFuncData<'a> {
|
||||||
pub id_content: WidgetID,
|
pub id_content: WidgetID,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PopupClosedCallback = Box<dyn FnOnce()>;
|
||||||
|
type OnContentCallback = Box<dyn FnOnce(PopupContentFuncData) -> anyhow::Result<PopupClosedCallback>>;
|
||||||
|
|
||||||
|
// we need to implement Clone here, but the underlying function can be called only once.
|
||||||
|
// on_content will be cleared after the first call
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct MountPopupParams {
|
pub struct MountPopupOnceParams {
|
||||||
pub title: Translation,
|
title: Translation,
|
||||||
pub on_content: Rc<dyn Fn(PopupContentFuncData) -> anyhow::Result<()>>,
|
on_content: Rc<RefCell<Option<OnContentCallback>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MountPopupOnceParams {
|
||||||
|
pub fn new(title: Translation, on_content: OnContentCallback) -> Self {
|
||||||
|
Self {
|
||||||
|
title,
|
||||||
|
on_content: Rc::new(RefCell::new(Some(on_content))),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for MountedPopup {
|
impl Drop for MountedPopup {
|
||||||
|
|
@ -78,25 +215,23 @@ impl State {
|
||||||
fn refresh_stack(&mut self, alterables: &mut EventAlterables) {
|
fn refresh_stack(&mut self, alterables: &mut EventAlterables) {
|
||||||
// show only the topmost popup
|
// show only the topmost popup
|
||||||
self.popup_stack.retain(|weak| {
|
self.popup_stack.retain(|weak| {
|
||||||
|
let retain = {
|
||||||
let Some(popup) = weak.upgrade() else {
|
let Some(popup) = weak.upgrade() else {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
popup.borrow_mut().mounted_popup.is_some()
|
popup.borrow_mut().mounted_popup.is_some()
|
||||||
|
};
|
||||||
|
if !retain {
|
||||||
|
log::debug!("removing popup from popup_stack");
|
||||||
|
}
|
||||||
|
retain
|
||||||
});
|
});
|
||||||
|
|
||||||
for (idx, popup) in self.popup_stack.iter().enumerate() {
|
for (idx, popup) in self.popup_stack.iter().enumerate() {
|
||||||
let popup = popup.upgrade().unwrap(); // safe
|
let popup = popup.upgrade().unwrap(); // safe
|
||||||
let popup = popup.borrow_mut();
|
let popup = popup.borrow_mut();
|
||||||
let mounted_popup = popup.mounted_popup.as_ref().unwrap(); // safe;
|
let mounted_popup = popup.mounted_popup.as_ref().unwrap(); // safe;
|
||||||
|
alterables.set_widget_visible(mounted_popup.id_root, idx == self.popup_stack.len() - 1);
|
||||||
alterables.set_style(
|
|
||||||
mounted_popup.id_root,
|
|
||||||
StyleSetRequest::Display(if idx == self.popup_stack.len() - 1 {
|
|
||||||
Display::Flex
|
|
||||||
} else {
|
|
||||||
Display::None
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -116,16 +251,13 @@ impl PopupManager {
|
||||||
state.refresh_stack(alterables);
|
state.refresh_stack(alterables);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mount a new popup on top of the existing popup stack.
|
fn mount_popup_prepare(
|
||||||
/// Only the topmost popup is visible.
|
&self,
|
||||||
pub fn mount_popup(
|
globals: &WguiGlobals,
|
||||||
&mut self,
|
|
||||||
globals: WguiGlobals,
|
|
||||||
layout: &mut Layout,
|
layout: &mut Layout,
|
||||||
frontend_tasks: FrontendTasks,
|
frontend_tasks: &FrontendTasks,
|
||||||
params: MountPopupParams,
|
popup_title: &Translation,
|
||||||
config: &GeneralConfig,
|
) -> anyhow::Result<(PopupHandle, WidgetID /* content widget ID */)> {
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
let doc_params = &ParseDocumentParams {
|
let doc_params = &ParseDocumentParams {
|
||||||
globals: globals.clone(),
|
globals: globals.clone(),
|
||||||
path: AssetPath::BuiltIn("gui/view/popup_window.xml"),
|
path: AssetPath::BuiltIn("gui/view/popup_window.xml"),
|
||||||
|
|
@ -138,7 +270,7 @@ impl PopupManager {
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut label_title = state.fetch_widget_as::<WidgetLabel>(&layout.state, "popup_title")?;
|
let mut label_title = state.fetch_widget_as::<WidgetLabel>(&layout.state, "popup_title")?;
|
||||||
label_title.set_text_simple(&mut globals.get(), params.title);
|
label_title.set_text_simple(&mut globals.get(), popup_title.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
let but_back = state.fetch_component_as::<ComponentButton>("but_back")?;
|
let but_back = state.fetch_component_as::<ComponentButton>("but_back")?;
|
||||||
|
|
@ -152,6 +284,7 @@ impl PopupManager {
|
||||||
|
|
||||||
let mounted_popup_state = MountedPopupState {
|
let mounted_popup_state = MountedPopupState {
|
||||||
mounted_popup: Some(mounted_popup),
|
mounted_popup: Some(mounted_popup),
|
||||||
|
closed_callback: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let popup_handle = PopupHandle {
|
let popup_handle = PopupHandle {
|
||||||
|
|
@ -159,28 +292,56 @@ impl PopupManager {
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut state = self.state.borrow_mut();
|
let mut state = self.state.borrow_mut();
|
||||||
|
log::debug!("pushing popup to popup_stack");
|
||||||
state.popup_stack.push(Rc::downgrade(&popup_handle.state));
|
state.popup_stack.push(Rc::downgrade(&popup_handle.state));
|
||||||
|
|
||||||
but_back.on_click({
|
but_back.on_click({
|
||||||
let popup_handle = Rc::downgrade(&popup_handle.state);
|
let popup_handle = Rc::downgrade(&popup_handle.state);
|
||||||
Rc::new(move |_common, _evt| {
|
Rc::new(move |_common, _evt| {
|
||||||
if let Some(popup_handle) = popup_handle.upgrade() {
|
if let Some(popup_handle) = popup_handle.upgrade()
|
||||||
popup_handle.borrow_mut().mounted_popup = None; // will call Drop
|
&& let Some(closed_callback) = {
|
||||||
|
let mut state = popup_handle.borrow_mut();
|
||||||
|
state.mounted_popup = None; // will call Drop
|
||||||
|
state.closed_callback.take()
|
||||||
|
} {
|
||||||
|
log::debug!("closed_callback called");
|
||||||
|
closed_callback();
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
frontend_tasks.push(FrontendTask::RefreshPopupManager);
|
frontend_tasks.push(FrontendTask::RefreshPopupManager);
|
||||||
|
Ok((popup_handle, id_content))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mount a new popup on top of the existing popup stack.
|
||||||
|
/// Only the topmost popup is visible.
|
||||||
|
pub fn mount_popup_once(
|
||||||
|
&mut self,
|
||||||
|
globals: &WguiGlobals,
|
||||||
|
layout: &mut Layout,
|
||||||
|
frontend_tasks: &FrontendTasks,
|
||||||
|
params: MountPopupOnceParams,
|
||||||
|
config: &GeneralConfig,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let mut func = params.on_content.borrow_mut();
|
||||||
|
let Some(on_content_func) = func.take() else {
|
||||||
|
anyhow::bail!("mount_popup_once called more than once");
|
||||||
|
};
|
||||||
|
|
||||||
|
let (popup_handle, id_content) = self.mount_popup_prepare(globals, layout, frontend_tasks, ¶ms.title)?;
|
||||||
|
|
||||||
// mount user-set popup content
|
// mount user-set popup content
|
||||||
(*params.on_content)(PopupContentFuncData {
|
let closed_callback = on_content_func(PopupContentFuncData {
|
||||||
layout,
|
layout,
|
||||||
handle: popup_handle.clone(),
|
handle: popup_handle.clone(),
|
||||||
id_content,
|
id_content,
|
||||||
config,
|
config,
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
popup_handle.state.borrow_mut().closed_callback = Some(closed_callback);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ use keyvalues_parser::{Obj, Vdf};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct SteamUtils {
|
pub struct SteamUtils {
|
||||||
steam_root: PathBuf,
|
steam_root: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ use wgui::{
|
||||||
animation::{Animation, AnimationEasing},
|
animation::{Animation, AnimationEasing},
|
||||||
components::tooltip::{TOOLTIP_BORDER_COLOR, TOOLTIP_COLOR},
|
components::tooltip::{TOOLTIP_BORDER_COLOR, TOOLTIP_COLOR},
|
||||||
drawing::Color,
|
drawing::Color,
|
||||||
globals::WguiGlobals,
|
|
||||||
i18n::Translation,
|
i18n::Translation,
|
||||||
layout::{Layout, LayoutTask, LayoutTasks, WidgetID},
|
layout::{Layout, LayoutTask, LayoutTasks, WidgetID},
|
||||||
renderer_vk::{
|
renderer_vk::{
|
||||||
|
|
@ -61,15 +60,7 @@ impl ToastManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mount_toast(
|
fn mount_toast(&self, layout: &mut Layout, state: &mut State, content: Translation) -> anyhow::Result<()> {
|
||||||
&self,
|
|
||||||
globals: &WguiGlobals,
|
|
||||||
layout: &mut Layout,
|
|
||||||
state: &mut State,
|
|
||||||
content: Translation,
|
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
let mut globals = globals.get();
|
|
||||||
|
|
||||||
let (root, _) = layout.add_topmost_child(
|
let (root, _) = layout.add_topmost_child(
|
||||||
WidgetDiv::create(),
|
WidgetDiv::create(),
|
||||||
taffy::Style {
|
taffy::Style {
|
||||||
|
|
@ -110,10 +101,8 @@ impl ToastManager {
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let (label, _) = layout.add_child(
|
let label = WidgetLabel::create(
|
||||||
rect.id,
|
&mut layout.state,
|
||||||
WidgetLabel::create(
|
|
||||||
&mut globals,
|
|
||||||
WidgetLabelParams {
|
WidgetLabelParams {
|
||||||
content,
|
content,
|
||||||
style: TextStyle {
|
style: TextStyle {
|
||||||
|
|
@ -123,14 +112,13 @@ impl ToastManager {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
),
|
);
|
||||||
taffy::Style { ..Default::default() },
|
let (label, _) = layout.add_child(rect.id, label, taffy::Style { ..Default::default() })?;
|
||||||
)?;
|
|
||||||
|
|
||||||
// show-up animation
|
// show-up animation
|
||||||
layout.animations.add(Animation::new(
|
layout.animations.add(Animation::new(
|
||||||
rect.id,
|
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,
|
AnimationEasing::Linear,
|
||||||
Box::new(move |common, data| {
|
Box::new(move |common, data| {
|
||||||
let pos_showup = AnimationEasing::OutQuint.interpolate((data.pos * 4.0).min(1.0));
|
let pos_showup = AnimationEasing::OutQuint.interpolate((data.pos * 4.0).min(1.0));
|
||||||
|
|
@ -161,7 +149,7 @@ impl ToastManager {
|
||||||
Ok(())
|
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();
|
let mut state = self.state.borrow_mut();
|
||||||
if state.timeout > 0 {
|
if state.timeout > 0 {
|
||||||
state.timeout -= 1;
|
state.timeout -= 1;
|
||||||
|
|
@ -176,7 +164,7 @@ impl ToastManager {
|
||||||
state.timeout = TOAST_DURATION_TICKS;
|
state.timeout = TOAST_DURATION_TICKS;
|
||||||
// mount next
|
// mount next
|
||||||
if let Some(content) = state.queue.pop_front() {
|
if let Some(content) = state.queue.pop_front() {
|
||||||
self.mount_toast(globals, layout, &mut state, content)?;
|
self.mount_toast(layout, &mut state, content)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,54 @@
|
||||||
|
use glam::{Mat4, Vec2};
|
||||||
use wgui::{
|
use wgui::{
|
||||||
|
animation::{Animation, AnimationEasing},
|
||||||
|
assets::AssetPath,
|
||||||
|
components::{self, button::ButtonClickCallback},
|
||||||
|
drawing,
|
||||||
i18n::Translation,
|
i18n::Translation,
|
||||||
layout::{Layout, WidgetID},
|
layout::{Layout, LayoutTask, WidgetID},
|
||||||
renderer_vk::text::TextStyle,
|
parser::{Fetchable, ParseDocumentParams},
|
||||||
widget::label::{WidgetLabel, WidgetLabelParams},
|
renderer_vk::{
|
||||||
|
text::{FontWeight, TextStyle, custom_glyph::CustomGlyphData},
|
||||||
|
util::centered_matrix,
|
||||||
|
},
|
||||||
|
taffy::{self, prelude::length},
|
||||||
|
widget::{
|
||||||
|
ConstructEssentials,
|
||||||
|
label::{WidgetLabel, WidgetLabelParams},
|
||||||
|
sprite::{WidgetSprite, WidgetSpriteParams},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn create_label(layout: &mut Layout, parent: WidgetID, content: Translation) -> anyhow::Result<()> {
|
pub struct CreateButtonParams<'a> {
|
||||||
|
pub id_parent: WidgetID,
|
||||||
|
pub layout: &'a mut Layout,
|
||||||
|
pub content: Translation,
|
||||||
|
pub icon_builtin: AssetPath<'a>,
|
||||||
|
pub on_click: ButtonClickCallback,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_button(par: CreateButtonParams) -> anyhow::Result<()> {
|
||||||
|
let (_, button) = components::button::construct(
|
||||||
|
&mut ConstructEssentials {
|
||||||
|
layout: par.layout,
|
||||||
|
parent: par.id_parent,
|
||||||
|
},
|
||||||
|
components::button::Params {
|
||||||
|
text: Some(par.content),
|
||||||
|
sprite_src: Some(par.icon_builtin),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
button.on_click(par.on_click);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn create_label(layout: &mut Layout, id_parent: WidgetID, content: Translation) -> anyhow::Result<()> {
|
||||||
let label = WidgetLabel::create(
|
let label = WidgetLabel::create(
|
||||||
&mut layout.state.globals.get(),
|
&mut layout.state,
|
||||||
WidgetLabelParams {
|
WidgetLabelParams {
|
||||||
content,
|
content,
|
||||||
style: TextStyle {
|
style: TextStyle {
|
||||||
|
|
@ -17,7 +58,102 @@ pub fn create_label(layout: &mut Layout, parent: WidgetID, content: Translation)
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
layout.add_child(id_parent, label, Default::default())?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_label_error(layout: &mut Layout, parent: WidgetID, content: String) -> anyhow::Result<()> {
|
||||||
|
let label = WidgetLabel::create(
|
||||||
|
&mut layout.state,
|
||||||
|
WidgetLabelParams {
|
||||||
|
content: Translation::from_raw_text_string(content),
|
||||||
|
style: TextStyle {
|
||||||
|
wrap: true,
|
||||||
|
color: Some(drawing::Color::new(1.0, 0.5, 0.0, 1.0)),
|
||||||
|
weight: Some(FontWeight::Bold),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
layout.add_child(parent, label, Default::default())?;
|
layout.add_child(parent, label, Default::default())?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn create_icon(layout: &mut Layout, id_parent: WidgetID, size: Vec2, path: AssetPath) -> anyhow::Result<WidgetID> {
|
||||||
|
let widget_sprite = WidgetSprite::create(WidgetSpriteParams {
|
||||||
|
color: None,
|
||||||
|
glyph_data: Some(CustomGlyphData::from_assets(&layout.state.globals, path)?),
|
||||||
|
});
|
||||||
|
|
||||||
|
let size = taffy::Size {
|
||||||
|
width: length(size.x),
|
||||||
|
height: length(size.y),
|
||||||
|
};
|
||||||
|
|
||||||
|
let (widget, _) = layout.add_child(
|
||||||
|
id_parent,
|
||||||
|
widget_sprite,
|
||||||
|
taffy::Style {
|
||||||
|
min_size: size,
|
||||||
|
max_size: size,
|
||||||
|
size,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(widget.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CreateLoadingParams<'a> {
|
||||||
|
pub layout: &'a mut Layout,
|
||||||
|
pub parent_id: WidgetID,
|
||||||
|
pub with_text: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_loading(par: CreateLoadingParams) -> anyhow::Result<WidgetID> {
|
||||||
|
let doc_params = ParseDocumentParams {
|
||||||
|
globals: par.layout.state.globals.clone(),
|
||||||
|
path: AssetPath::BuiltIn("gui/t_loading.xml"),
|
||||||
|
extra: Default::default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut parser_state = wgui::parser::parse_from_assets(&doc_params, par.layout, par.parent_id)?;
|
||||||
|
|
||||||
|
let data = parser_state.realize_template(
|
||||||
|
&doc_params,
|
||||||
|
if par.with_text {
|
||||||
|
"LoadingWithText"
|
||||||
|
} else {
|
||||||
|
"LoadingWithoutText"
|
||||||
|
},
|
||||||
|
par.layout,
|
||||||
|
par.parent_id,
|
||||||
|
Default::default(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let id_root = data.get_widget_id("root")?;
|
||||||
|
let id_sprite_loading = data.get_widget_id("sprite_loading")?;
|
||||||
|
|
||||||
|
par.layout.animations.add(Animation::new(
|
||||||
|
id_sprite_loading,
|
||||||
|
60 * 30, /* spin it for 30 seconds at most */
|
||||||
|
AnimationEasing::Linear,
|
||||||
|
Box::new(move |common, data| {
|
||||||
|
// spin it
|
||||||
|
data.data.transform = centered_matrix(data.widget_boundary.size, &Mat4::from_rotation_z(data.pos * 400.0));
|
||||||
|
if data.pos == 1.0 {
|
||||||
|
// remove the spinner, do not waste energy
|
||||||
|
common
|
||||||
|
.alterables
|
||||||
|
.tasks
|
||||||
|
.push(LayoutTask::RemoveWidget(id_sprite_loading));
|
||||||
|
}
|
||||||
|
common.alterables.mark_redraw();
|
||||||
|
}),
|
||||||
|
));
|
||||||
|
|
||||||
|
Ok(id_root)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,11 @@ use wgui::{
|
||||||
};
|
};
|
||||||
use wlx_common::{config::GeneralConfig, dash_interface::BoxDashInterface, desktop_finder::DesktopEntry};
|
use wlx_common::{config::GeneralConfig, dash_interface::BoxDashInterface, desktop_finder::DesktopEntry};
|
||||||
|
|
||||||
use crate::frontend::{FrontendTask, FrontendTasks, SoundType};
|
use crate::{
|
||||||
|
frontend::{FrontendTask, FrontendTasks, SoundType},
|
||||||
|
util::popup_manager::{MountPopupOnceParams, PopupHolder},
|
||||||
|
views::{ViewTrait, ViewUpdateParams},
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Clone, Copy, Eq, PartialEq, EnumString, VariantNames, AsRefStr)]
|
#[derive(Clone, Copy, Eq, PartialEq, EnumString, VariantNames, AsRefStr)]
|
||||||
enum PosMode {
|
enum PosMode {
|
||||||
|
|
@ -50,7 +54,6 @@ enum CompositorMode {
|
||||||
enum Task {
|
enum Task {
|
||||||
SetCompositor(CompositorMode),
|
SetCompositor(CompositorMode),
|
||||||
SetRes(ResMode),
|
SetRes(ResMode),
|
||||||
SetPos(PosMode), // TODO?
|
|
||||||
SetOrientation(OrientationMode),
|
SetOrientation(OrientationMode),
|
||||||
SetAutoStart(bool),
|
SetAutoStart(bool),
|
||||||
Launch,
|
Launch,
|
||||||
|
|
@ -67,7 +70,7 @@ struct LaunchParams<'a, T> {
|
||||||
interface: &'a mut BoxDashInterface<T>,
|
interface: &'a mut BoxDashInterface<T>,
|
||||||
auto_start: bool,
|
auto_start: bool,
|
||||||
data: &'a mut T,
|
data: &'a mut T,
|
||||||
on_launched: &'a dyn Fn(),
|
on_launched: Option<Box<dyn FnOnce()>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct View {
|
pub struct View {
|
||||||
|
|
@ -92,7 +95,7 @@ pub struct View {
|
||||||
|
|
||||||
auto_start: bool,
|
auto_start: bool,
|
||||||
|
|
||||||
on_launched: Box<dyn Fn()>,
|
on_launched: Option<Box<dyn FnOnce()>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Params<'a> {
|
pub struct Params<'a> {
|
||||||
|
|
@ -102,7 +105,13 @@ pub struct Params<'a> {
|
||||||
pub parent_id: WidgetID,
|
pub parent_id: WidgetID,
|
||||||
pub config: &'a GeneralConfig,
|
pub config: &'a GeneralConfig,
|
||||||
pub frontend_tasks: &'a FrontendTasks,
|
pub frontend_tasks: &'a FrontendTasks,
|
||||||
pub on_launched: Box<dyn Fn()>,
|
pub on_launched: Box<dyn FnOnce()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ViewTrait for View {
|
||||||
|
fn update(&mut self, _par: &mut ViewUpdateParams) -> anyhow::Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl View {
|
impl View {
|
||||||
|
|
@ -281,7 +290,7 @@ impl View {
|
||||||
entry: params.entry,
|
entry: params.entry,
|
||||||
frontend_tasks: params.frontend_tasks.clone(),
|
frontend_tasks: params.frontend_tasks.clone(),
|
||||||
globals: params.globals.clone(),
|
globals: params.globals.clone(),
|
||||||
on_launched: params.on_launched,
|
on_launched: Some(params.on_launched),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -295,7 +304,6 @@ impl View {
|
||||||
match task {
|
match task {
|
||||||
Task::SetCompositor(mode) => self.compositor_mode = mode,
|
Task::SetCompositor(mode) => self.compositor_mode = mode,
|
||||||
Task::SetRes(mode) => self.res_mode = mode,
|
Task::SetRes(mode) => self.res_mode = mode,
|
||||||
Task::SetPos(mode) => self.pos_mode = mode,
|
|
||||||
Task::SetOrientation(mode) => self.orientation_mode = mode,
|
Task::SetOrientation(mode) => self.orientation_mode = mode,
|
||||||
Task::SetAutoStart(auto_start) => self.auto_start = auto_start,
|
Task::SetAutoStart(auto_start) => self.auto_start = auto_start,
|
||||||
Task::Launch => self.action_launch(interface, data),
|
Task::Launch => self.action_launch(interface, data),
|
||||||
|
|
@ -318,7 +326,7 @@ impl View {
|
||||||
auto_start: self.auto_start,
|
auto_start: self.auto_start,
|
||||||
interface,
|
interface,
|
||||||
data,
|
data,
|
||||||
on_launched: &self.on_launched,
|
on_launched: self.on_launched.take(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -336,7 +344,7 @@ impl View {
|
||||||
))));
|
))));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn launch<T>(params: LaunchParams<T>) -> anyhow::Result<()> {
|
fn launch<T>(mut params: LaunchParams<T>) -> anyhow::Result<()> {
|
||||||
let mut env = Vec::<String>::new();
|
let mut env = Vec::<String>::new();
|
||||||
|
|
||||||
if params.compositor_mode == CompositorMode::Native {
|
if params.compositor_mode == CompositorMode::Native {
|
||||||
|
|
@ -392,7 +400,9 @@ impl View {
|
||||||
|
|
||||||
params.frontend_tasks.push(FrontendTask::PlaySound(SoundType::Launch));
|
params.frontend_tasks.push(FrontendTask::PlaySound(SoundType::Launch));
|
||||||
|
|
||||||
(*params.on_launched)();
|
if let Some(on_launched) = params.on_launched.take() {
|
||||||
|
on_launched();
|
||||||
|
}
|
||||||
|
|
||||||
// we're done!
|
// we're done!
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -422,3 +432,26 @@ impl View {
|
||||||
[width as u32, height as u32]
|
[width as u32, height as u32]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn mount_popup(frontend_tasks: FrontendTasks, globals: WguiGlobals, entry: DesktopEntry, popup: PopupHolder<View>) {
|
||||||
|
frontend_tasks
|
||||||
|
.clone()
|
||||||
|
.push(FrontendTask::MountPopupOnce(MountPopupOnceParams::new(
|
||||||
|
Translation::from_raw_text(&entry.app_name),
|
||||||
|
Box::new(move |data| {
|
||||||
|
let on_launched = popup.get_close_callback(data.layout);
|
||||||
|
let view = View::new(Params {
|
||||||
|
entry: entry.clone(),
|
||||||
|
globals: &globals,
|
||||||
|
layout: data.layout,
|
||||||
|
parent_id: data.id_content,
|
||||||
|
frontend_tasks: &frontend_tasks,
|
||||||
|
config: data.config,
|
||||||
|
on_launched,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
popup.set_view(data.handle, view, None);
|
||||||
|
Ok(popup.get_close_callback(data.layout))
|
||||||
|
}),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -110,8 +110,7 @@ struct MultiSelectorParams<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mount_multi_selector(params: MultiSelectorParams) -> anyhow::Result<()> {
|
fn mount_multi_selector(params: MultiSelectorParams) -> anyhow::Result<()> {
|
||||||
let globals = params.ess.layout.state.globals.clone();
|
let accent_color = params.ess.layout.state.theme.accent_color;
|
||||||
let accent_color = globals.defaults().accent_color;
|
|
||||||
|
|
||||||
for cell in params.cells {
|
for cell in params.cells {
|
||||||
let highlighted = cell.key == params.def_cell;
|
let highlighted = cell.key == params.def_cell;
|
||||||
|
|
@ -654,11 +653,6 @@ impl View {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_button_highlights(&self, layout: &mut Layout) -> anyhow::Result<()> {
|
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();
|
|
||||||
|
|
||||||
let num: u8 = match &self.mode {
|
let num: u8 = match &self.mode {
|
||||||
CurrentMode::Sinks => 0,
|
CurrentMode::Sinks => 0,
|
||||||
CurrentMode::Sources => 1,
|
CurrentMode::Sources => 1,
|
||||||
|
|
@ -666,22 +660,21 @@ impl View {
|
||||||
CurrentMode::CardProfileSelector(_) => 255,
|
CurrentMode::CardProfileSelector(_) => 255,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let mut com = layout.common();
|
||||||
|
|
||||||
let mut perform = |btn_num: u8, btn: &Rc<ComponentButton>| {
|
let mut perform = |btn_num: u8, btn: &Rc<ComponentButton>| {
|
||||||
btn.set_color(
|
let color = if num == btn_num {
|
||||||
&mut common,
|
com.state.theme.accent_color
|
||||||
if num == btn_num {
|
|
||||||
defaults.accent_color
|
|
||||||
} else {
|
} else {
|
||||||
defaults.button_color
|
com.state.theme.button_color
|
||||||
},
|
};
|
||||||
);
|
btn.set_color(&mut com, color);
|
||||||
};
|
};
|
||||||
|
|
||||||
perform(0, &self.btn_sinks);
|
perform(0, &self.btn_sinks);
|
||||||
perform(1, &self.btn_sources);
|
perform(1, &self.btn_sources);
|
||||||
perform(2, &self.btn_cards);
|
perform(2, &self.btn_cards);
|
||||||
|
|
||||||
c.finish()?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -755,7 +748,7 @@ impl View {
|
||||||
|
|
||||||
let data = self
|
let data = self
|
||||||
.state
|
.state
|
||||||
.parse_template(&doc_params(&self.globals), "Card", params.layout, self.id_devices, par)?;
|
.realize_template(&doc_params(&self.globals), "Card", params.layout, self.id_devices, par)?;
|
||||||
|
|
||||||
let btn_card = data.fetch_component_as::<ComponentButton>("btn_card")?;
|
let btn_card = data.fetch_component_as::<ComponentButton>("btn_card")?;
|
||||||
btn_card.on_click({
|
btn_card.on_click({
|
||||||
|
|
@ -769,7 +762,6 @@ impl View {
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
log::info!("mount card TODO: {}", params.card.name);
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -799,7 +791,7 @@ impl View {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
let data = self.state.parse_template(
|
let data = self.state.realize_template(
|
||||||
&doc_params(&self.globals),
|
&doc_params(&self.globals),
|
||||||
"DeviceSlider",
|
"DeviceSlider",
|
||||||
params.layout,
|
params.layout,
|
||||||
|
|
@ -807,14 +799,12 @@ impl View {
|
||||||
par,
|
par,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let mut c = params.layout.start_common();
|
let mut common = params.layout.common();
|
||||||
let mut common = c.common();
|
|
||||||
|
|
||||||
let checkbox = data.fetch_component_as::<ComponentCheckbox>("checkbox")?;
|
let checkbox = data.fetch_component_as::<ComponentCheckbox>("checkbox")?;
|
||||||
let btn_mute = data.fetch_component_as::<ComponentButton>("btn_mute")?;
|
let btn_mute = data.fetch_component_as::<ComponentButton>("btn_mute")?;
|
||||||
let slider = data.fetch_component_as::<ComponentSlider>("slider")?;
|
let slider = data.fetch_component_as::<ComponentSlider>("slider")?;
|
||||||
|
|
||||||
slider.set_value(&mut common, params.control.on_volume_request()? / VOLUME_MULT);
|
slider.set_value_primary(&mut common, params.control.on_volume_request()? / VOLUME_MULT);
|
||||||
|
|
||||||
checkbox.set_checked(&mut common, params.checked);
|
checkbox.set_checked(&mut common, params.checked);
|
||||||
|
|
||||||
|
|
@ -829,8 +819,10 @@ impl View {
|
||||||
slider.on_value_changed({
|
slider.on_value_changed({
|
||||||
let control = params.control.clone();
|
let control = params.control.clone();
|
||||||
Box::new(move |_common, event| {
|
Box::new(move |_common, event| {
|
||||||
control.on_volume_change(event.value * VOLUME_MULT)?;
|
if let Err(e) = control.on_volume_change(event.value * VOLUME_MULT) {
|
||||||
Ok(())
|
log::error!("{:?}", e);
|
||||||
|
debug_assert!(false);
|
||||||
|
};
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -842,8 +834,6 @@ impl View {
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
c.finish()?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -946,7 +936,7 @@ impl View {
|
||||||
layout.remove_children(self.id_devices);
|
layout.remove_children(self.id_devices);
|
||||||
|
|
||||||
{
|
{
|
||||||
let data = self.state.parse_template(
|
let data = self.state.realize_template(
|
||||||
&doc_params(&self.globals),
|
&doc_params(&self.globals),
|
||||||
"SelectAudioProfileText",
|
"SelectAudioProfileText",
|
||||||
layout,
|
layout,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,127 @@
|
||||||
|
use std::{collections::HashMap, rc::Rc};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
frontend::{FrontendTask, FrontendTasks},
|
||||||
|
util::popup_manager::{MountPopupOnceParams, PopupHolder},
|
||||||
|
views::{ViewTrait, ViewUpdateParams},
|
||||||
|
};
|
||||||
|
use wgui::{
|
||||||
|
assets::AssetPath,
|
||||||
|
components::button::ComponentButton,
|
||||||
|
globals::WguiGlobals,
|
||||||
|
i18n::Translation,
|
||||||
|
layout::{Layout, WidgetID},
|
||||||
|
parser::{Fetchable, ParseDocumentParams, ParserState},
|
||||||
|
task::Tasks,
|
||||||
|
widget::label::WidgetLabel,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct ButtonEntry {
|
||||||
|
pub content: Translation, // button text
|
||||||
|
pub icon: &'static str, // sprite_src_builtin
|
||||||
|
pub action: &'static str, // action name (will be passed into on_action_click)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Params {
|
||||||
|
pub globals: WguiGlobals,
|
||||||
|
pub entries: Vec<ButtonEntry>,
|
||||||
|
pub message: Translation,
|
||||||
|
pub on_action_click: Box<dyn FnOnce(&'static str)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
enum Task {
|
||||||
|
ActionClicked(&'static str),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct View {
|
||||||
|
tasks: Tasks<Task>,
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
parser_state: ParserState,
|
||||||
|
|
||||||
|
on_action_click: Option<Box<dyn FnOnce(&'static str)>>,
|
||||||
|
on_close_request: Option<Box<dyn FnOnce()>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn doc_params(globals: &WguiGlobals) -> ParseDocumentParams<'_> {
|
||||||
|
ParseDocumentParams {
|
||||||
|
globals: globals.clone(),
|
||||||
|
path: AssetPath::BuiltIn("gui/view/dialog_box.xml"),
|
||||||
|
extra: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ViewTrait for View {
|
||||||
|
fn update(&mut self, _par: &mut ViewUpdateParams) -> anyhow::Result<()> {
|
||||||
|
for task in self.tasks.drain() {
|
||||||
|
match task {
|
||||||
|
Task::ActionClicked(action) => {
|
||||||
|
if let Some(func) = self.on_action_click.take() {
|
||||||
|
func(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(on_close) = self.on_close_request.take() {
|
||||||
|
on_close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl View {
|
||||||
|
pub fn new(
|
||||||
|
layout: &mut Layout,
|
||||||
|
id_parent: WidgetID,
|
||||||
|
on_close_request: Box<dyn FnOnce()>,
|
||||||
|
par: Params,
|
||||||
|
) -> anyhow::Result<Self> {
|
||||||
|
let tasks = Tasks::<Task>::new();
|
||||||
|
|
||||||
|
let mut parser_state = wgui::parser::parse_from_assets(&doc_params(&par.globals), layout, id_parent)?;
|
||||||
|
let id_buttons = parser_state.get_widget_id("buttons")?;
|
||||||
|
|
||||||
|
{
|
||||||
|
let label_message = parser_state.fetch_widget(&layout.state, "label_message")?.widget;
|
||||||
|
label_message
|
||||||
|
.cast::<WidgetLabel>()?
|
||||||
|
.set_text(&mut layout.common(), par.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
for entry in par.entries {
|
||||||
|
let mut t_par = HashMap::<Rc<str>, Rc<str>>::new();
|
||||||
|
t_par.insert(Rc::from("icon"), Rc::from(entry.icon));
|
||||||
|
|
||||||
|
let data =
|
||||||
|
parser_state.realize_template(&doc_params(&par.globals), "DialogBoxButton", layout, id_buttons, t_par)?;
|
||||||
|
|
||||||
|
let button = data.fetch_component_as::<ComponentButton>("btn")?;
|
||||||
|
button.set_text(&mut layout.common(), entry.content.clone());
|
||||||
|
button.on_click(tasks.get_button_click_callback(Task::ActionClicked(entry.action)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
tasks,
|
||||||
|
parser_state,
|
||||||
|
on_action_click: Some(par.on_action_click),
|
||||||
|
on_close_request: Some(on_close_request),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mount_popup(popup: PopupHolder<View>, frontend_tasks: FrontendTasks, params: Params) {
|
||||||
|
frontend_tasks
|
||||||
|
.clone()
|
||||||
|
.push(FrontendTask::MountPopupOnce(MountPopupOnceParams::new(
|
||||||
|
Translation::from_raw_text("Info"),
|
||||||
|
Box::new(move |data| {
|
||||||
|
let on_close_request = popup.get_close_callback(data.layout);
|
||||||
|
let view = View::new(data.layout, data.id_content, on_close_request, params)?;
|
||||||
|
|
||||||
|
popup.set_view(data.handle, view, None);
|
||||||
|
Ok(popup.get_close_callback(data.layout))
|
||||||
|
}),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,265 @@
|
||||||
|
use crate::{
|
||||||
|
frontend::{FrontendTask, FrontendTasks},
|
||||||
|
util::{
|
||||||
|
networking::http_client::{self, ProgressFuncData},
|
||||||
|
popup_manager::{MountPopupOnceParams, PopupHolder},
|
||||||
|
wgui_simple,
|
||||||
|
},
|
||||||
|
views::{ViewTrait, ViewUpdateParams},
|
||||||
|
};
|
||||||
|
use glam::Vec2;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use wgui::{
|
||||||
|
assets::AssetPath,
|
||||||
|
components::button::ComponentButton,
|
||||||
|
globals::WguiGlobals,
|
||||||
|
i18n::Translation,
|
||||||
|
layout::{Layout, WidgetID},
|
||||||
|
parser::{Fetchable, ParseDocumentParams, ParserState},
|
||||||
|
task::Tasks,
|
||||||
|
widget::label::WidgetLabel,
|
||||||
|
};
|
||||||
|
use wlx_common::async_executor::AsyncExecutor;
|
||||||
|
|
||||||
|
pub struct Params {
|
||||||
|
pub globals: WguiGlobals,
|
||||||
|
pub executor: AsyncExecutor,
|
||||||
|
pub target_path: PathBuf,
|
||||||
|
pub url: String,
|
||||||
|
pub on_downloaded: Box<dyn FnOnce()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
enum Task {
|
||||||
|
StartDownload(/*url*/ String, /*target path*/ PathBuf),
|
||||||
|
SetStatusText(String),
|
||||||
|
ShowIconSuccess,
|
||||||
|
ShowIconError,
|
||||||
|
Close,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct View {
|
||||||
|
globals: WguiGlobals,
|
||||||
|
tasks: Tasks<Task>,
|
||||||
|
executor: AsyncExecutor,
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
parser_state: ParserState,
|
||||||
|
|
||||||
|
id_label_status: WidgetID,
|
||||||
|
id_loading_parent: WidgetID,
|
||||||
|
id_content: WidgetID,
|
||||||
|
on_close_request: Option<Box<dyn FnOnce()>>,
|
||||||
|
on_downloaded: Option<Box<dyn FnOnce()>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn doc_params(globals: &WguiGlobals) -> ParseDocumentParams<'_> {
|
||||||
|
ParseDocumentParams {
|
||||||
|
globals: globals.clone(),
|
||||||
|
path: AssetPath::BuiltIn("gui/view/download_file.xml"),
|
||||||
|
extra: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ViewTrait for View {
|
||||||
|
fn update(&mut self, par: &mut ViewUpdateParams) -> anyhow::Result<()> {
|
||||||
|
for task in self.tasks.drain() {
|
||||||
|
match task {
|
||||||
|
Task::StartDownload(url, path) => {
|
||||||
|
if let Some(on_downloaded) = self.on_downloaded.take() {
|
||||||
|
self
|
||||||
|
.executor
|
||||||
|
.spawn(View::download(
|
||||||
|
self.tasks.clone(),
|
||||||
|
self.executor.clone(),
|
||||||
|
url,
|
||||||
|
path,
|
||||||
|
on_downloaded,
|
||||||
|
))
|
||||||
|
.detach();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Task::SetStatusText(text) => {
|
||||||
|
let widgets = &mut par.layout.state.widgets;
|
||||||
|
widgets
|
||||||
|
.fetch(self.id_label_status)?
|
||||||
|
.cast::<WidgetLabel>()?
|
||||||
|
.set_text(&mut par.layout.common(), Translation::from_raw_text_string(text));
|
||||||
|
}
|
||||||
|
Task::ShowIconSuccess => {
|
||||||
|
par.layout.remove_children(self.id_loading_parent);
|
||||||
|
wgui_simple::create_icon(
|
||||||
|
par.layout,
|
||||||
|
self.id_loading_parent,
|
||||||
|
Vec2::splat(32.0),
|
||||||
|
AssetPath::BuiltIn("dashboard/check.svg"),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// "Close window" button
|
||||||
|
self
|
||||||
|
.parser_state
|
||||||
|
.realize_template(
|
||||||
|
&doc_params(&self.globals),
|
||||||
|
"btn_close",
|
||||||
|
par.layout,
|
||||||
|
self.id_content,
|
||||||
|
Default::default(),
|
||||||
|
)?
|
||||||
|
.fetch_component_as::<ComponentButton>("btn")?
|
||||||
|
.on_click(self.tasks.get_button_click_callback(Task::Close));
|
||||||
|
}
|
||||||
|
Task::ShowIconError => {
|
||||||
|
par.layout.remove_children(self.id_loading_parent);
|
||||||
|
wgui_simple::create_icon(
|
||||||
|
par.layout,
|
||||||
|
self.id_loading_parent,
|
||||||
|
Vec2::splat(32.0),
|
||||||
|
AssetPath::BuiltIn("dashboard/error.svg"),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
Task::Close => {
|
||||||
|
if let Some(on_close) = self.on_close_request.take() {
|
||||||
|
on_close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_async_result<T, E>(error_reason: &'static str, tasks: &Tasks<Task>, result: anyhow::Result<T, E>) -> Option<T>
|
||||||
|
where
|
||||||
|
E: std::fmt::Debug,
|
||||||
|
{
|
||||||
|
match result {
|
||||||
|
Ok(res) => Some(res),
|
||||||
|
Err(e) => {
|
||||||
|
tasks.push(Task::ShowIconError);
|
||||||
|
tasks.push(Task::SetStatusText(format!("{}: {:?}", error_reason, e)));
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl View {
|
||||||
|
pub fn new(
|
||||||
|
layout: &mut Layout,
|
||||||
|
id_parent: WidgetID,
|
||||||
|
on_close_request: Box<dyn FnOnce()>,
|
||||||
|
par: Params,
|
||||||
|
) -> anyhow::Result<Self> {
|
||||||
|
let tasks = Tasks::<Task>::new();
|
||||||
|
|
||||||
|
let parser_state = wgui::parser::parse_from_assets(&doc_params(&par.globals), layout, id_parent)?;
|
||||||
|
let id_label_status = parser_state.get_widget_id("label_status")?;
|
||||||
|
let id_content = parser_state.get_widget_id("content")?;
|
||||||
|
let id_loading_parent = parser_state.get_widget_id("loading_parent")?;
|
||||||
|
|
||||||
|
wgui_simple::create_loading(wgui_simple::CreateLoadingParams {
|
||||||
|
parent_id: id_loading_parent,
|
||||||
|
layout,
|
||||||
|
with_text: false,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let str_target_path = par.globals.i18n().translate("TARGET_PATH");
|
||||||
|
|
||||||
|
{
|
||||||
|
let label_target_path = parser_state.fetch_widget(&layout.state, "label_target_path")?.widget;
|
||||||
|
label_target_path.cast::<WidgetLabel>()?.set_text(
|
||||||
|
&mut layout.common(),
|
||||||
|
Translation::from_raw_text_string(format!("{}: {}", str_target_path, par.target_path.display())),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.push(Task::StartDownload(par.url, par.target_path));
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
tasks,
|
||||||
|
globals: par.globals.clone(),
|
||||||
|
executor: par.executor.clone(),
|
||||||
|
parser_state,
|
||||||
|
id_label_status,
|
||||||
|
id_loading_parent,
|
||||||
|
id_content,
|
||||||
|
on_close_request: Some(on_close_request),
|
||||||
|
on_downloaded: Some(par.on_downloaded),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn download(
|
||||||
|
tasks: Tasks<Task>,
|
||||||
|
executor: AsyncExecutor,
|
||||||
|
url: String,
|
||||||
|
target_path: PathBuf,
|
||||||
|
on_downloaded: Box<dyn FnOnce()>,
|
||||||
|
) -> Option<()> {
|
||||||
|
tasks.push(Task::SetStatusText(String::from("Connecting to the server...")));
|
||||||
|
|
||||||
|
// start downloading from the server with progress reporting
|
||||||
|
let res = handle_async_result(
|
||||||
|
"Download failed",
|
||||||
|
&tasks,
|
||||||
|
http_client::get(http_client::GetParams {
|
||||||
|
executor: &executor,
|
||||||
|
url: &url,
|
||||||
|
on_progress: Some(Box::new({
|
||||||
|
let tasks = tasks.clone();
|
||||||
|
move |data: ProgressFuncData| {
|
||||||
|
tasks.push(Task::SetStatusText(format!(
|
||||||
|
"{}/{} KiB ({}%)",
|
||||||
|
data.bytes_downloaded / 1024,
|
||||||
|
data.file_size / 1024,
|
||||||
|
(data.bytes_downloaded as f32 / data.file_size as f32 * 100.0).round()
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
})
|
||||||
|
.await,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
tasks.push(Task::SetStatusText(String::from("Writing to file...")));
|
||||||
|
|
||||||
|
// create skymaps directory if it doesn't exist yet
|
||||||
|
if let Some(parent) = target_path.parent() {
|
||||||
|
handle_async_result(
|
||||||
|
"Directory creation failed",
|
||||||
|
&tasks,
|
||||||
|
smol::fs::create_dir_all(parent).await,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
handle_async_result(
|
||||||
|
"File write failed",
|
||||||
|
&tasks,
|
||||||
|
smol::fs::write(target_path, res.data).await,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
tasks.push(Task::SetStatusText(String::from("Download finished")));
|
||||||
|
tasks.push(Task::ShowIconSuccess);
|
||||||
|
|
||||||
|
on_downloaded();
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mount_popup(
|
||||||
|
popup: PopupHolder<View>,
|
||||||
|
frontend_tasks: FrontendTasks,
|
||||||
|
on_view_close: Box<dyn FnOnce()>,
|
||||||
|
params: Params,
|
||||||
|
) {
|
||||||
|
frontend_tasks
|
||||||
|
.clone()
|
||||||
|
.push(FrontendTask::MountPopupOnce(MountPopupOnceParams::new(
|
||||||
|
Translation::from_translation_key("DOWNLOADER"),
|
||||||
|
Box::new(move |data| {
|
||||||
|
let on_close_request = popup.get_close_callback(data.layout);
|
||||||
|
let view = View::new(data.layout, data.id_content, on_close_request, params)?;
|
||||||
|
|
||||||
|
popup.set_view(data.handle, view, Some(on_view_close));
|
||||||
|
Ok(popup.get_close_callback(data.layout))
|
||||||
|
}),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
@ -25,11 +25,12 @@ use wgui::{
|
||||||
util::WLength,
|
util::WLength,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
use wlx_common::async_executor::AsyncExecutor;
|
||||||
|
|
||||||
use crate::util::{
|
use crate::util::{
|
||||||
cached_fetcher::{self, CoverArt},
|
cached_fetcher::{self, CoverArt},
|
||||||
steam_utils::{self, AppID},
|
steam_utils::{self, AppID},
|
||||||
various::AsyncExecutor,
|
wgui_simple,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct ViewCommon {
|
pub struct ViewCommon {
|
||||||
|
|
@ -48,6 +49,7 @@ pub struct Params<'a, 'b> {
|
||||||
pub struct View {
|
pub struct View {
|
||||||
pub button: Rc<ComponentButton>,
|
pub button: Rc<ComponentButton>,
|
||||||
id_image_parent: WidgetID,
|
id_image_parent: WidgetID,
|
||||||
|
id_loading: WidgetID,
|
||||||
app_name: String,
|
app_name: String,
|
||||||
app_id: AppID,
|
app_id: AppID,
|
||||||
}
|
}
|
||||||
|
|
@ -98,15 +100,9 @@ impl View {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mount_placeholder_text(
|
fn mount_placeholder_text(&self, layout: &mut Layout, parent: WidgetID, text: &str) -> anyhow::Result<()> {
|
||||||
&self,
|
|
||||||
globals: &WguiGlobals,
|
|
||||||
layout: &mut Layout,
|
|
||||||
parent: WidgetID,
|
|
||||||
text: &str,
|
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
let label = WidgetLabel::create(
|
let label = WidgetLabel::create(
|
||||||
&mut globals.get(),
|
&mut layout.state,
|
||||||
WidgetLabelParams {
|
WidgetLabelParams {
|
||||||
content: Translation::from_raw_text(text),
|
content: Translation::from_raw_text(text),
|
||||||
style: TextStyle {
|
style: TextStyle {
|
||||||
|
|
@ -149,11 +145,13 @@ impl View {
|
||||||
layout: &mut Layout,
|
layout: &mut Layout,
|
||||||
cover_art: &CoverArt,
|
cover_art: &CoverArt,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
|
layout.remove_widget(self.id_loading);
|
||||||
|
|
||||||
if cover_art.compressed_image_data.is_empty() {
|
if cover_art.compressed_image_data.is_empty() {
|
||||||
// mount placeholder
|
// mount placeholder
|
||||||
let img = view_common.get_placeholder_image()?.clone();
|
let img = view_common.get_placeholder_image()?.clone();
|
||||||
self.mount_image(layout, &img)?;
|
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 {
|
} else {
|
||||||
// mount image
|
// mount image
|
||||||
let path = format!("app:{:?}", self.app_id);
|
let path = format!("app:{:?}", self.app_id);
|
||||||
|
|
@ -277,6 +275,12 @@ impl View {
|
||||||
rect_gradient_style(taffy::AlignSelf::End, 0.05),
|
rect_gradient_style(taffy::AlignSelf::End, 0.05),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
let id_loading = wgui_simple::create_loading(wgui_simple::CreateLoadingParams {
|
||||||
|
layout: params.ess.layout,
|
||||||
|
parent_id: image_parent.id,
|
||||||
|
with_text: false,
|
||||||
|
})?;
|
||||||
|
|
||||||
// request cover image data from the internet or disk cache
|
// request cover image data from the internet or disk cache
|
||||||
params
|
params
|
||||||
.executor
|
.executor
|
||||||
|
|
@ -292,6 +296,7 @@ impl View {
|
||||||
id_image_parent: image_parent.id,
|
id_image_parent: image_parent.id,
|
||||||
app_name: params.manifest.name.clone(),
|
app_name: params.manifest.name.clone(),
|
||||||
app_id: params.manifest.app_id.clone(),
|
app_id: params.manifest.app_id.clone(),
|
||||||
|
id_loading,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,10 @@ use crate::{
|
||||||
frontend::{FrontendTask, FrontendTasks, SoundType},
|
frontend::{FrontendTask, FrontendTasks, SoundType},
|
||||||
util::{
|
util::{
|
||||||
cached_fetcher::{self, CoverArt},
|
cached_fetcher::{self, CoverArt},
|
||||||
|
popup_manager::{MountPopupOnceParams, PopupHolder},
|
||||||
steam_utils::{self, AppID, AppManifest},
|
steam_utils::{self, AppID, AppManifest},
|
||||||
various::AsyncExecutor,
|
|
||||||
},
|
},
|
||||||
views::game_cover,
|
views::{ViewTrait, ViewUpdateParams, game_cover},
|
||||||
};
|
};
|
||||||
use wgui::{
|
use wgui::{
|
||||||
assets::AssetPath,
|
assets::AssetPath,
|
||||||
|
|
@ -19,6 +19,7 @@ use wgui::{
|
||||||
task::Tasks,
|
task::Tasks,
|
||||||
widget::{ConstructEssentials, label::WidgetLabel},
|
widget::{ConstructEssentials, label::WidgetLabel},
|
||||||
};
|
};
|
||||||
|
use wlx_common::async_executor::AsyncExecutor;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
enum Task {
|
enum Task {
|
||||||
|
|
@ -34,13 +35,14 @@ pub struct Params<'a> {
|
||||||
pub layout: &'a mut Layout,
|
pub layout: &'a mut Layout,
|
||||||
pub parent_id: WidgetID,
|
pub parent_id: WidgetID,
|
||||||
pub frontend_tasks: &'a FrontendTasks,
|
pub frontend_tasks: &'a FrontendTasks,
|
||||||
pub on_launched: Box<dyn Fn()>,
|
pub on_launched: Box<dyn FnOnce()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct View {
|
pub struct View {
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
state: ParserState,
|
state: ParserState,
|
||||||
tasks: Tasks<Task>,
|
tasks: Tasks<Task>,
|
||||||
on_launched: Box<dyn Fn()>,
|
on_launched: Option<Box<dyn FnOnce()>>,
|
||||||
frontend_tasks: FrontendTasks,
|
frontend_tasks: FrontendTasks,
|
||||||
|
|
||||||
game_cover_view_common: game_cover::ViewCommon,
|
game_cover_view_common: game_cover::ViewCommon,
|
||||||
|
|
@ -48,6 +50,30 @@ pub struct View {
|
||||||
app_id: AppID,
|
app_id: AppID,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ViewTrait for View {
|
||||||
|
fn update(&mut self, par: &mut ViewUpdateParams) -> anyhow::Result<()> {
|
||||||
|
loop {
|
||||||
|
let tasks = self.tasks.drain();
|
||||||
|
if tasks.is_empty() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
for task in tasks {
|
||||||
|
match task {
|
||||||
|
Task::FillAppDetails(details) => self.action_fill_app_details(par.layout, details)?,
|
||||||
|
Task::Launch => self.action_launch(),
|
||||||
|
Task::SetCoverArt(cover_art) => {
|
||||||
|
let _ = self
|
||||||
|
.view_cover
|
||||||
|
.set_cover_art(&mut self.game_cover_view_common, par.layout, &cover_art);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl View {
|
impl View {
|
||||||
async fn fetch_details(executor: AsyncExecutor, tasks: Tasks<Task>, app_id: AppID) {
|
async fn fetch_details(executor: AsyncExecutor, tasks: Tasks<Task>, app_id: AppID) {
|
||||||
let Some(details) = cached_fetcher::get_app_details_json(executor, app_id).await else {
|
let Some(details) = cached_fetcher::get_app_details_json(executor, app_id).await else {
|
||||||
|
|
@ -104,7 +130,7 @@ impl View {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
state,
|
state,
|
||||||
tasks,
|
tasks,
|
||||||
on_launched: params.on_launched,
|
on_launched: Some(params.on_launched),
|
||||||
frontend_tasks: params.frontend_tasks.clone(),
|
frontend_tasks: params.frontend_tasks.clone(),
|
||||||
game_cover_view_common: game_cover::ViewCommon::new(params.globals.clone()),
|
game_cover_view_common: game_cover::ViewCommon::new(params.globals.clone()),
|
||||||
view_cover,
|
view_cover,
|
||||||
|
|
@ -112,43 +138,20 @@ impl View {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(&mut self, layout: &mut Layout) -> anyhow::Result<()> {
|
|
||||||
loop {
|
|
||||||
let tasks = self.tasks.drain();
|
|
||||||
if tasks.is_empty() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
for task in tasks {
|
|
||||||
match task {
|
|
||||||
Task::FillAppDetails(details) => self.action_fill_app_details(layout, details)?,
|
|
||||||
Task::Launch => self.action_launch(),
|
|
||||||
Task::SetCoverArt(cover_art) => {
|
|
||||||
let _ = self
|
|
||||||
.view_cover
|
|
||||||
.set_cover_art(&mut self.game_cover_view_common, layout, &cover_art);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn action_fill_app_details(
|
fn action_fill_app_details(
|
||||||
&mut self,
|
&mut self,
|
||||||
layout: &mut Layout,
|
layout: &mut Layout,
|
||||||
mut details: cached_fetcher::AppDetailsJSONData,
|
mut details: cached_fetcher::AppDetailsJSONData,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let mut c = layout.start_common();
|
|
||||||
|
|
||||||
{
|
{
|
||||||
let label_author = self.state.fetch_widget(&c.layout.state, "label_author")?.widget;
|
let mut c = layout.common();
|
||||||
let label_description = self.state.fetch_widget(&c.layout.state, "label_description")?.widget;
|
let label_author = self.state.fetch_widget(c.state, "label_author")?.widget;
|
||||||
|
let label_description = self.state.fetch_widget(c.state, "label_description")?.widget;
|
||||||
|
|
||||||
if let Some(developer) = details.developers.pop() {
|
if let Some(developer) = details.developers.pop() {
|
||||||
label_author
|
label_author
|
||||||
.cast::<WidgetLabel>()?
|
.cast::<WidgetLabel>()?
|
||||||
.set_text(&mut c.common(), Translation::from_raw_text_string(developer));
|
.set_text(&mut c, Translation::from_raw_text_string(developer));
|
||||||
}
|
}
|
||||||
|
|
||||||
let desc = if let Some(desc) = &details.short_description {
|
let desc = if let Some(desc) = &details.short_description {
|
||||||
|
|
@ -162,11 +165,10 @@ impl View {
|
||||||
if let Some(desc) = desc {
|
if let Some(desc) = desc {
|
||||||
label_description
|
label_description
|
||||||
.cast::<WidgetLabel>()?
|
.cast::<WidgetLabel>()?
|
||||||
.set_text(&mut c.common(), Translation::from_raw_text(desc));
|
.set_text(&mut c, Translation::from_raw_text(desc));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
c.finish()?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -190,6 +192,37 @@ impl View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(*self.on_launched)();
|
if let Some(on_launched) = self.on_launched.take() {
|
||||||
|
on_launched();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mount_popup(
|
||||||
|
frontend_tasks: FrontendTasks,
|
||||||
|
executor: AsyncExecutor,
|
||||||
|
globals: WguiGlobals,
|
||||||
|
manifest: AppManifest,
|
||||||
|
popup: PopupHolder<View>,
|
||||||
|
) {
|
||||||
|
frontend_tasks
|
||||||
|
.clone()
|
||||||
|
.push(FrontendTask::MountPopupOnce(MountPopupOnceParams::new(
|
||||||
|
Translation::from_raw_text(&manifest.name),
|
||||||
|
Box::new(move |data| {
|
||||||
|
let on_launched = popup.get_close_callback(data.layout);
|
||||||
|
let view = View::new(Params {
|
||||||
|
manifest: manifest.clone(),
|
||||||
|
executor: executor.clone(),
|
||||||
|
globals: &globals,
|
||||||
|
layout: data.layout,
|
||||||
|
parent_id: data.id_content,
|
||||||
|
frontend_tasks: &frontend_tasks,
|
||||||
|
on_launched,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
popup.set_view(data.handle, view, None);
|
||||||
|
Ok(popup.get_close_callback(data.layout))
|
||||||
|
}),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use std::{cell::RefCell, collections::HashMap, rc::Rc};
|
use std::{collections::HashMap, rc::Rc};
|
||||||
|
|
||||||
use wgui::{
|
use wgui::{
|
||||||
assets::AssetPath,
|
assets::AssetPath,
|
||||||
|
|
@ -13,23 +13,22 @@ use wgui::{
|
||||||
label::{WidgetLabel, WidgetLabelParams},
|
label::{WidgetLabel, WidgetLabelParams},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
use wlx_common::async_executor::AsyncExecutor;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
frontend::{FrontendTask, FrontendTasks},
|
frontend::FrontendTasks,
|
||||||
util::{
|
util::{
|
||||||
cached_fetcher::CoverArt,
|
cached_fetcher::CoverArt,
|
||||||
popup_manager::{MountPopupParams, PopupHandle},
|
popup_manager::PopupHolder,
|
||||||
steam_utils::{self, AppID, SteamUtils},
|
steam_utils::{self, AppID, SteamUtils},
|
||||||
various::AsyncExecutor,
|
|
||||||
},
|
},
|
||||||
views::{self, game_cover, game_launcher},
|
views::{self, ViewTrait, ViewUpdateParams, game_cover},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
enum Task {
|
enum Task {
|
||||||
AppManifestClicked(steam_utils::AppManifest),
|
AppManifestClicked(steam_utils::AppManifest),
|
||||||
SetCoverArt(AppID, Rc<CoverArt>),
|
SetCoverArt(AppID, Rc<CoverArt>),
|
||||||
CloseLauncher,
|
|
||||||
LoadManifests,
|
LoadManifests,
|
||||||
FillPage(u32),
|
FillPage(u32),
|
||||||
PrevPage,
|
PrevPage,
|
||||||
|
|
@ -42,6 +41,7 @@ pub struct Params<'a> {
|
||||||
pub frontend_tasks: FrontendTasks,
|
pub frontend_tasks: FrontendTasks,
|
||||||
pub layout: &'a mut Layout,
|
pub layout: &'a mut Layout,
|
||||||
pub parent_id: WidgetID,
|
pub parent_id: WidgetID,
|
||||||
|
pub steam_utils: &'a SteamUtils,
|
||||||
}
|
}
|
||||||
|
|
||||||
const MAX_GAMES_PER_PAGE: u32 = 30;
|
const MAX_GAMES_PER_PAGE: u32 = 30;
|
||||||
|
|
@ -50,10 +50,6 @@ pub struct GameCoverCell {
|
||||||
view_cover: game_cover::View,
|
view_cover: game_cover::View,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct State {
|
|
||||||
view_launcher: Option<(PopupHandle, views::game_launcher::View)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct View {
|
pub struct View {
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
parser_state: ParserState,
|
parser_state: ParserState,
|
||||||
|
|
@ -63,12 +59,37 @@ pub struct View {
|
||||||
id_list_parent: WidgetID,
|
id_list_parent: WidgetID,
|
||||||
game_cover_view_common: game_cover::ViewCommon,
|
game_cover_view_common: game_cover::ViewCommon,
|
||||||
executor: AsyncExecutor,
|
executor: AsyncExecutor,
|
||||||
state: Rc<RefCell<State>>,
|
|
||||||
mounted_game_covers: HashMap<AppID, GameCoverCell>,
|
mounted_game_covers: HashMap<AppID, GameCoverCell>,
|
||||||
all_manifests: Vec<steam_utils::AppManifest>,
|
all_manifests: Vec<steam_utils::AppManifest>,
|
||||||
cur_page: u32,
|
cur_page: u32,
|
||||||
page_count: u32,
|
page_count: u32,
|
||||||
id_label_page: WidgetID,
|
id_label_page: WidgetID,
|
||||||
|
view_launcher: PopupHolder<views::game_launcher::View>,
|
||||||
|
steam_utils: SteamUtils,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ViewTrait for View {
|
||||||
|
fn update(&mut self, par: &mut ViewUpdateParams) -> anyhow::Result<()> {
|
||||||
|
loop {
|
||||||
|
let tasks = self.tasks.drain();
|
||||||
|
if tasks.is_empty() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
for task in tasks {
|
||||||
|
match task {
|
||||||
|
Task::LoadManifests => self.load_manifests(),
|
||||||
|
Task::FillPage(page_idx) => self.fill_page(par.layout, par.executor, page_idx)?,
|
||||||
|
Task::AppManifestClicked(manifest) => self.action_app_manifest_clicked(manifest)?,
|
||||||
|
Task::SetCoverArt(app_id, cover_art) => self.set_cover_art(par.layout, app_id, cover_art),
|
||||||
|
Task::PrevPage => self.page_prev(),
|
||||||
|
Task::NextPage => self.page_next(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.view_launcher.update(par)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl View {
|
impl View {
|
||||||
|
|
@ -105,46 +126,15 @@ impl View {
|
||||||
id_list_parent: list_parent.id,
|
id_list_parent: list_parent.id,
|
||||||
mounted_game_covers: HashMap::new(),
|
mounted_game_covers: HashMap::new(),
|
||||||
game_cover_view_common: game_cover::ViewCommon::new(params.globals.clone()),
|
game_cover_view_common: game_cover::ViewCommon::new(params.globals.clone()),
|
||||||
state: Rc::new(RefCell::new(State { view_launcher: None })),
|
|
||||||
executor: params.executor,
|
executor: params.executor,
|
||||||
all_manifests: Vec::new(),
|
all_manifests: Vec::new(),
|
||||||
cur_page: 0,
|
cur_page: 0,
|
||||||
page_count: 0,
|
page_count: 0,
|
||||||
id_label_page,
|
id_label_page,
|
||||||
|
view_launcher: Default::default(),
|
||||||
|
steam_utils: params.steam_utils.clone(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(
|
|
||||||
&mut self,
|
|
||||||
layout: &mut Layout,
|
|
||||||
steam_utils: &mut SteamUtils,
|
|
||||||
executor: &AsyncExecutor,
|
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
loop {
|
|
||||||
let tasks = self.tasks.drain();
|
|
||||||
if tasks.is_empty() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
for task in tasks {
|
|
||||||
match task {
|
|
||||||
Task::LoadManifests => self.load_manifests(steam_utils),
|
|
||||||
Task::FillPage(page_idx) => self.fill_page(layout, executor, page_idx)?,
|
|
||||||
Task::AppManifestClicked(manifest) => self.action_app_manifest_clicked(manifest)?,
|
|
||||||
Task::SetCoverArt(app_id, cover_art) => self.set_cover_art(layout, app_id, cover_art),
|
|
||||||
Task::CloseLauncher => self.state.borrow_mut().view_launcher = None,
|
|
||||||
Task::PrevPage => self.page_prev(),
|
|
||||||
Task::NextPage => self.page_next(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut state = self.state.borrow_mut();
|
|
||||||
if let Some((_, view)) = &mut state.view_launcher {
|
|
||||||
view.update(layout)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fill_game_list(
|
fn fill_game_list(
|
||||||
|
|
@ -187,8 +177,11 @@ fn fill_game_list(
|
||||||
}
|
}
|
||||||
|
|
||||||
impl View {
|
impl View {
|
||||||
fn load_manifests(&mut self, steam_utils: &mut SteamUtils) {
|
fn load_manifests(&mut self) {
|
||||||
match steam_utils.list_installed_games(steam_utils::GameSortMethod::PlayDateDesc) {
|
match self
|
||||||
|
.steam_utils
|
||||||
|
.list_installed_games(steam_utils::GameSortMethod::PlayDateDesc)
|
||||||
|
{
|
||||||
Ok(manifests) => {
|
Ok(manifests) => {
|
||||||
self.page_count = (manifests.len() as u32 + MAX_GAMES_PER_PAGE) / MAX_GAMES_PER_PAGE;
|
self.page_count = (manifests.len() as u32 + MAX_GAMES_PER_PAGE) / MAX_GAMES_PER_PAGE;
|
||||||
self.all_manifests = manifests;
|
self.all_manifests = manifests;
|
||||||
|
|
@ -233,16 +226,14 @@ impl View {
|
||||||
}
|
}
|
||||||
|
|
||||||
// set page text
|
// set page text
|
||||||
let mut c = layout.start_common();
|
|
||||||
{
|
{
|
||||||
let mut common = c.common();
|
let mut c = layout.common();
|
||||||
let mut widget = common.state.widgets.cast_as::<WidgetLabel>(self.id_label_page)?;
|
let mut widget = c.state.widgets.cast_as::<WidgetLabel>(self.id_label_page)?;
|
||||||
widget.set_text(
|
widget.set_text(
|
||||||
&mut common,
|
&mut c,
|
||||||
Translation::from_raw_text_string(format!("{}/{}", self.cur_page + 1, self.page_count)),
|
Translation::from_raw_text_string(format!("{}/{}", self.cur_page + 1, self.page_count)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
c.finish()?;
|
|
||||||
|
|
||||||
fill_game_list(
|
fill_game_list(
|
||||||
&mut ConstructEssentials {
|
&mut ConstructEssentials {
|
||||||
|
|
@ -256,17 +247,14 @@ impl View {
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
if let Some(text) = text.take() {
|
if let Some(text) = text.take() {
|
||||||
layout.add_child(
|
let label = WidgetLabel::create(
|
||||||
self.id_list_parent,
|
&mut layout.state,
|
||||||
WidgetLabel::create(
|
|
||||||
&mut self.globals.get(),
|
|
||||||
WidgetLabelParams {
|
WidgetLabelParams {
|
||||||
content: text,
|
content: text,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
),
|
);
|
||||||
Default::default(),
|
layout.add_child(self.id_list_parent, label, Default::default())?;
|
||||||
)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -286,36 +274,13 @@ impl View {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn action_app_manifest_clicked(&mut self, manifest: steam_utils::AppManifest) -> anyhow::Result<()> {
|
fn action_app_manifest_clicked(&mut self, manifest: steam_utils::AppManifest) -> anyhow::Result<()> {
|
||||||
self.frontend_tasks.push(FrontendTask::MountPopup(MountPopupParams {
|
views::game_launcher::mount_popup(
|
||||||
title: Translation::from_raw_text(&manifest.name),
|
self.frontend_tasks.clone(),
|
||||||
on_content: {
|
self.executor.clone(),
|
||||||
let state = self.state.clone();
|
self.globals.clone(),
|
||||||
let tasks = self.tasks.clone();
|
manifest,
|
||||||
let executor = self.executor.clone();
|
self.view_launcher.clone(),
|
||||||
let globals = self.globals.clone();
|
);
|
||||||
let frontend_tasks = self.frontend_tasks.clone();
|
|
||||||
|
|
||||||
Rc::new(move |data| {
|
|
||||||
let on_launched = {
|
|
||||||
let tasks = tasks.clone();
|
|
||||||
Box::new(move || tasks.push(Task::CloseLauncher))
|
|
||||||
};
|
|
||||||
|
|
||||||
let view = game_launcher::View::new(game_launcher::Params {
|
|
||||||
manifest: manifest.clone(),
|
|
||||||
executor: executor.clone(),
|
|
||||||
globals: &globals,
|
|
||||||
layout: data.layout,
|
|
||||||
parent_id: data.id_content,
|
|
||||||
frontend_tasks: &frontend_tasks,
|
|
||||||
on_launched,
|
|
||||||
})?;
|
|
||||||
|
|
||||||
state.borrow_mut().view_launcher = Some((data.handle, view));
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,25 @@
|
||||||
|
use wlx_common::{async_executor::AsyncExecutor, config::GeneralConfig, dash_interface::ConfigChangeKind};
|
||||||
|
|
||||||
pub mod app_launcher;
|
pub mod app_launcher;
|
||||||
pub mod audio_settings;
|
pub mod audio_settings;
|
||||||
|
pub mod dialog_box;
|
||||||
|
pub mod download_file;
|
||||||
pub mod game_cover;
|
pub mod game_cover;
|
||||||
pub mod game_launcher;
|
pub mod game_launcher;
|
||||||
pub mod game_list;
|
pub mod game_list;
|
||||||
|
pub mod remote_skymap_downloader;
|
||||||
|
pub mod remote_skymap_list;
|
||||||
pub mod running_games_list;
|
pub mod running_games_list;
|
||||||
|
pub mod skymap_list;
|
||||||
|
pub mod skymap_list_cell;
|
||||||
|
|
||||||
|
pub struct ViewUpdateParams<'a> {
|
||||||
|
pub layout: &'a mut wgui::layout::Layout,
|
||||||
|
pub executor: &'a AsyncExecutor,
|
||||||
|
pub general_config: &'a mut GeneralConfig,
|
||||||
|
pub config_change_kind: &'a mut Option<ConfigChangeKind>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ViewTrait {
|
||||||
|
fn update(&mut self, par: &mut ViewUpdateParams) -> anyhow::Result<()>;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,397 @@
|
||||||
|
use std::{collections::HashMap, rc::Rc};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
frontend::{FrontendTask, FrontendTasks},
|
||||||
|
util::{
|
||||||
|
networking::{self, skymap_catalog::SkymapResolution},
|
||||||
|
popup_manager::{MountPopupOnceParams, PopupHolder},
|
||||||
|
},
|
||||||
|
views::{self, ViewTrait, ViewUpdateParams},
|
||||||
|
};
|
||||||
|
use anyhow::Context;
|
||||||
|
use wgui::{
|
||||||
|
assets::AssetPath,
|
||||||
|
components::button::ComponentButton,
|
||||||
|
drawing::Color,
|
||||||
|
globals::WguiGlobals,
|
||||||
|
i18n::Translation,
|
||||||
|
layout::{Layout, WidgetID},
|
||||||
|
parser::{Fetchable, ParseDocumentParams, ParserState},
|
||||||
|
renderer_vk::text::custom_glyph::CustomGlyphData,
|
||||||
|
task::Tasks,
|
||||||
|
widget::{image::WidgetImage, label::WidgetLabel},
|
||||||
|
};
|
||||||
|
use wlx_common::{async_executor::AsyncExecutor, config_io, dash_interface::ConfigChangeKind};
|
||||||
|
|
||||||
|
pub struct Params<'a> {
|
||||||
|
pub globals: &'a WguiGlobals,
|
||||||
|
pub layout: &'a mut Layout,
|
||||||
|
pub executor: &'a AsyncExecutor,
|
||||||
|
pub frontend_tasks: FrontendTasks,
|
||||||
|
pub parent_id: WidgetID,
|
||||||
|
pub entry: networking::skymap_catalog::SkymapCatalogEntry,
|
||||||
|
pub preview_image: CustomGlyphData,
|
||||||
|
pub preview_image_compressed: Rc<Vec<u8>>,
|
||||||
|
pub on_updated_library: Rc<dyn Fn()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
enum Task {
|
||||||
|
Refresh,
|
||||||
|
ResolutionClicked(SkymapResolution),
|
||||||
|
DownloadFinished,
|
||||||
|
RunDownload(SkymapResolution),
|
||||||
|
RemoveFile(SkymapResolution),
|
||||||
|
SetSkymap(SkymapResolution),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct View {
|
||||||
|
entry: networking::skymap_catalog::SkymapCatalogEntry,
|
||||||
|
frontend_tasks: FrontendTasks,
|
||||||
|
globals: WguiGlobals,
|
||||||
|
tasks: Tasks<Task>,
|
||||||
|
executor: AsyncExecutor,
|
||||||
|
|
||||||
|
id_resolution_buttons: WidgetID,
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
parser_state: ParserState,
|
||||||
|
|
||||||
|
popup_download: PopupHolder<views::download_file::View>,
|
||||||
|
popup_dialog_box: PopupHolder<views::dialog_box::View>,
|
||||||
|
|
||||||
|
preview_image_compressed: Rc<Vec<u8>>,
|
||||||
|
on_updated_library: Rc<dyn Fn()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mount_resolution_button(
|
||||||
|
layout: &mut Layout,
|
||||||
|
parser_state: &mut ParserState,
|
||||||
|
doc_params: &ParseDocumentParams,
|
||||||
|
parent_id: WidgetID,
|
||||||
|
res: SkymapResolution,
|
||||||
|
tasks: &Tasks<Task>,
|
||||||
|
already_downloaded: bool,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let mut t = HashMap::<Rc<str>, Rc<str>>::new();
|
||||||
|
t.insert(Rc::from("text"), Rc::from(res.get_display_str()));
|
||||||
|
t.insert(
|
||||||
|
Rc::from("sprite"),
|
||||||
|
Rc::from(match already_downloaded {
|
||||||
|
true => "dashboard/check.svg",
|
||||||
|
false => "dashboard/download.svg",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
let data = parser_state.realize_template(doc_params, "ResolutionButton", layout, parent_id, t)?;
|
||||||
|
let button = data.fetch_component_as::<ComponentButton>("button")?;
|
||||||
|
|
||||||
|
if already_downloaded {
|
||||||
|
button.set_color(&mut layout.common(), Color::new(0.0, 0.4, 0.0, 1.0)); // green
|
||||||
|
}
|
||||||
|
tasks.handle_button(&button, Task::ResolutionClicked(res));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ViewTrait for View {
|
||||||
|
fn update(&mut self, par: &mut ViewUpdateParams) -> anyhow::Result<()> {
|
||||||
|
for task in self.tasks.drain() {
|
||||||
|
match task {
|
||||||
|
Task::ResolutionClicked(resolution) => {
|
||||||
|
self.resolution_clicked(resolution)?;
|
||||||
|
}
|
||||||
|
Task::Refresh => {
|
||||||
|
self.refresh(par.layout)?;
|
||||||
|
}
|
||||||
|
Task::DownloadFinished => {
|
||||||
|
self.download_finished()?;
|
||||||
|
}
|
||||||
|
Task::RunDownload(resolution) => {
|
||||||
|
self.run_download(resolution)?;
|
||||||
|
}
|
||||||
|
Task::RemoveFile(resolution) => {
|
||||||
|
self.remove_file(resolution)?;
|
||||||
|
}
|
||||||
|
Task::SetSkymap(resolution) => {
|
||||||
|
let skymap_file_path = self
|
||||||
|
.entry
|
||||||
|
.get_destination_path(resolution)
|
||||||
|
.context("Skymap not found" /* you shouldn't really see this, like ever. */)?;
|
||||||
|
|
||||||
|
par.general_config.skybox_texture = skymap_file_path
|
||||||
|
.to_str()
|
||||||
|
.context("Skymap filename not valid UTF-8")?
|
||||||
|
.into();
|
||||||
|
*par.config_change_kind = Some(ConfigChangeKind::EnvironmentBlend);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.popup_download.update(par)?;
|
||||||
|
self.popup_dialog_box.update(par)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn doc_params(globals: &WguiGlobals) -> ParseDocumentParams<'_> {
|
||||||
|
ParseDocumentParams {
|
||||||
|
globals: globals.clone(),
|
||||||
|
path: AssetPath::BuiltIn("gui/view/remote_skymap_downloader.xml"),
|
||||||
|
extra: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl View {
|
||||||
|
pub fn new(par: Params) -> anyhow::Result<Self> {
|
||||||
|
let tasks = Tasks::<Task>::new();
|
||||||
|
|
||||||
|
let parser_state = wgui::parser::parse_from_assets(&doc_params(par.globals), par.layout, par.parent_id)?;
|
||||||
|
let id_resolution_buttons = parser_state.get_widget_id("resolution_buttons")?;
|
||||||
|
|
||||||
|
let str_version = par.globals.i18n().translate("VERSION");
|
||||||
|
let str_creation_date = par.globals.i18n().translate("CREATION_DATE");
|
||||||
|
let str_modification_date = par.globals.i18n().translate("MODIFICATION_DATE");
|
||||||
|
|
||||||
|
let image = parser_state.fetch_widget(&par.layout.state, "image")?.widget;
|
||||||
|
let mut image = image.cast::<WidgetImage>()?;
|
||||||
|
image.set_content(&mut par.layout.alterables, Some(par.preview_image));
|
||||||
|
|
||||||
|
// Set author label
|
||||||
|
parser_state
|
||||||
|
.fetch_widget_as::<WidgetLabel>(&par.layout.state, "label_author")?
|
||||||
|
.set_text_simple(
|
||||||
|
&mut par.globals.get(),
|
||||||
|
Translation::from_raw_text_string(format!("by {}", par.entry.author)),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Set description label
|
||||||
|
parser_state
|
||||||
|
.fetch_widget_as::<WidgetLabel>(&par.layout.state, "label_description")?
|
||||||
|
.set_text_simple(
|
||||||
|
&mut par.globals.get(),
|
||||||
|
Translation::from_raw_text(&par.entry.description),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Set version label
|
||||||
|
parser_state
|
||||||
|
.fetch_widget_as::<WidgetLabel>(&par.layout.state, "label_version")?
|
||||||
|
.set_text_simple(
|
||||||
|
&mut par.globals.get(),
|
||||||
|
Translation::from_raw_text_string(format!("{}: {}", str_version, par.entry.version)),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Set creation date label
|
||||||
|
parser_state
|
||||||
|
.fetch_widget_as::<WidgetLabel>(&par.layout.state, "label_creation_date")?
|
||||||
|
.set_text_simple(
|
||||||
|
&mut par.globals.get(),
|
||||||
|
Translation::from_raw_text_string(format!("{}: {}", str_creation_date, par.entry.created_at)),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Set modification date label
|
||||||
|
parser_state
|
||||||
|
.fetch_widget_as::<WidgetLabel>(&par.layout.state, "label_modification_date")?
|
||||||
|
.set_text_simple(
|
||||||
|
&mut par.globals.get(),
|
||||||
|
Translation::from_raw_text_string(format!("{}: {}", str_modification_date, par.entry.created_at)),
|
||||||
|
);
|
||||||
|
|
||||||
|
tasks.push(Task::Refresh);
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
tasks,
|
||||||
|
globals: par.globals.clone(),
|
||||||
|
executor: par.executor.clone(),
|
||||||
|
entry: par.entry,
|
||||||
|
parser_state,
|
||||||
|
frontend_tasks: par.frontend_tasks,
|
||||||
|
popup_download: Default::default(),
|
||||||
|
popup_dialog_box: Default::default(),
|
||||||
|
id_resolution_buttons,
|
||||||
|
preview_image_compressed: par.preview_image_compressed,
|
||||||
|
on_updated_library: par.on_updated_library,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn refresh(&mut self, layout: &mut Layout) -> anyhow::Result<()> {
|
||||||
|
layout.remove_children(self.id_resolution_buttons);
|
||||||
|
|
||||||
|
let files = &self.entry.files;
|
||||||
|
let mut mount_res = |res: SkymapResolution| -> anyhow::Result<()> {
|
||||||
|
mount_resolution_button(
|
||||||
|
layout,
|
||||||
|
&mut self.parser_state,
|
||||||
|
&doc_params(&self.globals),
|
||||||
|
self.id_resolution_buttons,
|
||||||
|
res,
|
||||||
|
&self.tasks,
|
||||||
|
self.entry.is_downloaded(res)?,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
mount_res(SkymapResolution::Res2k)?;
|
||||||
|
if files.size_4k.is_some() {
|
||||||
|
mount_res(SkymapResolution::Res4k)?;
|
||||||
|
}
|
||||||
|
if files.size_8k.is_some() {
|
||||||
|
mount_res(SkymapResolution::Res8k)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolution_clicked(&mut self, resolution: SkymapResolution) -> anyhow::Result<()> {
|
||||||
|
let is_downloaded = self.entry.is_downloaded(resolution).unwrap_or(false);
|
||||||
|
if !is_downloaded {
|
||||||
|
self.tasks.push(Task::RunDownload(resolution));
|
||||||
|
} else {
|
||||||
|
self.show_dialog_box_action(resolution)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show_dialog_box_action(&mut self, resolution: SkymapResolution) -> anyhow::Result<()> {
|
||||||
|
const ACTION_REMOVE: &str = "remove";
|
||||||
|
const ACTION_DOWNLOAD_AGAIN: &str = "download_again";
|
||||||
|
const ACTION_APPLY: &str = "apply";
|
||||||
|
|
||||||
|
let tasks = self.tasks.clone();
|
||||||
|
|
||||||
|
views::dialog_box::mount_popup(
|
||||||
|
self.popup_dialog_box.clone(),
|
||||||
|
self.frontend_tasks.clone(),
|
||||||
|
views::dialog_box::Params {
|
||||||
|
globals: self.globals.clone(),
|
||||||
|
message: Translation::from_translation_key("APP_SETTINGS.SKYMAP_ALREADY_DOWNLOADED"),
|
||||||
|
entries: vec![
|
||||||
|
views::dialog_box::ButtonEntry {
|
||||||
|
content: Translation::from_translation_key("REMOVE"),
|
||||||
|
icon: "dashboard/trash.svg",
|
||||||
|
action: ACTION_REMOVE,
|
||||||
|
},
|
||||||
|
views::dialog_box::ButtonEntry {
|
||||||
|
content: Translation::from_translation_key("DOWNLOAD_AGAIN"),
|
||||||
|
icon: "dashboard/download.svg",
|
||||||
|
action: ACTION_DOWNLOAD_AGAIN,
|
||||||
|
},
|
||||||
|
views::dialog_box::ButtonEntry {
|
||||||
|
content: Translation::from_translation_key("APPLY"),
|
||||||
|
icon: "dashboard/check.svg",
|
||||||
|
action: ACTION_APPLY,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
on_action_click: Box::new(move |action| match action {
|
||||||
|
ACTION_REMOVE => {
|
||||||
|
tasks.push(Task::RemoveFile(resolution));
|
||||||
|
tasks.push(Task::Refresh);
|
||||||
|
}
|
||||||
|
ACTION_DOWNLOAD_AGAIN => {
|
||||||
|
tasks.push(Task::RunDownload(resolution));
|
||||||
|
tasks.push(Task::Refresh);
|
||||||
|
}
|
||||||
|
ACTION_APPLY => {
|
||||||
|
tasks.push(Task::SetSkymap(resolution));
|
||||||
|
tasks.push(Task::Refresh);
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn download_finished(&mut self) -> anyhow::Result<()> {
|
||||||
|
self.entry.save_metadata()?;
|
||||||
|
let mut uuids = config_io::get_skymaps_uuids().unwrap_or_default();
|
||||||
|
let uuid_str = self.entry.uuid.to_string();
|
||||||
|
if !uuids.contains(&uuid_str) {
|
||||||
|
uuids.push(uuid_str);
|
||||||
|
}
|
||||||
|
config_io::set_skymaps_uuids(&uuids)?;
|
||||||
|
|
||||||
|
// Save preview image
|
||||||
|
self.entry.files.save_preview_to_file(&self.preview_image_compressed)?;
|
||||||
|
|
||||||
|
(*self.on_updated_library)();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_download(&mut self, resolution: SkymapResolution) -> anyhow::Result<()> {
|
||||||
|
let Some(url) = self.entry.files.get_url_from_res(resolution) else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(target_path) = self.entry.get_destination_path(resolution) else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
views::download_file::mount_popup(
|
||||||
|
self.popup_download.clone(),
|
||||||
|
self.frontend_tasks.clone(),
|
||||||
|
self.tasks.make_callback_box(Task::Refresh),
|
||||||
|
views::download_file::Params {
|
||||||
|
globals: self.globals.clone(),
|
||||||
|
executor: self.executor.clone(),
|
||||||
|
target_path,
|
||||||
|
url,
|
||||||
|
on_downloaded: self.tasks.make_callback_box(Task::DownloadFinished),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_file(&mut self, resolution: SkymapResolution) -> anyhow::Result<()> {
|
||||||
|
self.entry.remove_file(resolution);
|
||||||
|
|
||||||
|
if !self.entry.has_any_downloaded() {
|
||||||
|
// all skymaps of this uuid are removed, clean-up files
|
||||||
|
self.entry.remove_metadata();
|
||||||
|
|
||||||
|
// remove uuid of this entry from downloaded skymaps uuid and save the file again
|
||||||
|
let mut uuids = config_io::get_skymaps_uuids().unwrap_or_default();
|
||||||
|
uuids.retain(|uuid| *uuid != self.entry.uuid.to_string());
|
||||||
|
config_io::set_skymaps_uuids(&uuids)?;
|
||||||
|
|
||||||
|
// remove "_preview.dds" files from the disk too
|
||||||
|
self.entry.files.remove_preview_file();
|
||||||
|
}
|
||||||
|
|
||||||
|
(*self.on_updated_library)();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub fn mount_popup(
|
||||||
|
frontend_tasks: FrontendTasks,
|
||||||
|
executor: AsyncExecutor,
|
||||||
|
globals: WguiGlobals,
|
||||||
|
entry: networking::skymap_catalog::SkymapCatalogEntry,
|
||||||
|
preview_image: CustomGlyphData,
|
||||||
|
preview_image_compressed: Rc<Vec<u8>>,
|
||||||
|
on_updated_library: Rc<dyn Fn()>,
|
||||||
|
popup: PopupHolder<View>,
|
||||||
|
) {
|
||||||
|
frontend_tasks
|
||||||
|
.clone()
|
||||||
|
.push(FrontendTask::MountPopupOnce(MountPopupOnceParams::new(
|
||||||
|
Translation::from_raw_text(&entry.name),
|
||||||
|
Box::new(move |data| {
|
||||||
|
let view = View::new(Params {
|
||||||
|
globals: &globals,
|
||||||
|
layout: data.layout,
|
||||||
|
executor: &executor,
|
||||||
|
parent_id: data.id_content,
|
||||||
|
entry,
|
||||||
|
preview_image,
|
||||||
|
frontend_tasks: frontend_tasks.clone(),
|
||||||
|
preview_image_compressed,
|
||||||
|
on_updated_library,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
popup.set_view(data.handle, view, None);
|
||||||
|
Ok(popup.get_close_callback(data.layout))
|
||||||
|
}),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,301 @@
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use uuid::Uuid;
|
||||||
|
use wgui::{
|
||||||
|
assets::AssetPath,
|
||||||
|
globals::WguiGlobals,
|
||||||
|
i18n::Translation,
|
||||||
|
layout::{Layout, WidgetID},
|
||||||
|
parser::{Fetchable, ParseDocumentParams},
|
||||||
|
renderer_vk::text::custom_glyph::CustomGlyphData,
|
||||||
|
task::Tasks,
|
||||||
|
};
|
||||||
|
use wlx_common::async_executor::AsyncExecutor;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
frontend::{FrontendTask, FrontendTasks},
|
||||||
|
util::{
|
||||||
|
networking::{
|
||||||
|
self,
|
||||||
|
skymap_catalog::{SkymapCatalog, SkymapCatalogEntry, SkymapUuid},
|
||||||
|
},
|
||||||
|
popup_manager::{MountPopupOnceParams, PopupHolder},
|
||||||
|
wgui_simple,
|
||||||
|
},
|
||||||
|
views::{self, ViewTrait, ViewUpdateParams},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct Params<'a> {
|
||||||
|
pub globals: &'a WguiGlobals,
|
||||||
|
pub layout: &'a mut Layout,
|
||||||
|
pub executor: &'a AsyncExecutor,
|
||||||
|
pub parent_id: WidgetID,
|
||||||
|
pub frontend_tasks: FrontendTasks,
|
||||||
|
pub on_updated_library: Rc<dyn Fn()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
type SetSkymapPreview = (Uuid, Option<(CustomGlyphData, Rc<Vec<u8>>)>);
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
enum Task {
|
||||||
|
SetSkymapCatalog(Rc<anyhow::Result<networking::skymap_catalog::SkymapCatalog>>),
|
||||||
|
SetSkymapPreview(SetSkymapPreview),
|
||||||
|
ShowRemoteSkymapDownloader(SkymapUuid),
|
||||||
|
RefreshCells,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct MountedCell {
|
||||||
|
skymap_uuid: SkymapUuid,
|
||||||
|
view: views::skymap_list_cell::View,
|
||||||
|
preview_image_compressed: Option<Rc<Vec<u8>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct View {
|
||||||
|
id_parent: WidgetID,
|
||||||
|
id_loading: WidgetID,
|
||||||
|
globals: WguiGlobals,
|
||||||
|
tasks: Tasks<Task>,
|
||||||
|
mounted_cells: Vec<MountedCell>,
|
||||||
|
executor: AsyncExecutor,
|
||||||
|
frontend_tasks: FrontendTasks,
|
||||||
|
catalog: Option<SkymapCatalog>,
|
||||||
|
popup_remote_skymap_downloader: PopupHolder<views::remote_skymap_downloader::View>,
|
||||||
|
on_updated_library: Rc<dyn Fn()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_entry_by_uuid(catalog: &SkymapCatalog, skymap_uuid: Uuid) -> Option<&SkymapCatalogEntry> {
|
||||||
|
let entry = catalog.entries.iter().find(|entry| entry.uuid == skymap_uuid)?;
|
||||||
|
|
||||||
|
Some(entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ViewTrait for View {
|
||||||
|
fn update(&mut self, par: &mut ViewUpdateParams) -> anyhow::Result<()> {
|
||||||
|
self.popup_remote_skymap_downloader.update(par)?;
|
||||||
|
|
||||||
|
for task in self.tasks.drain() {
|
||||||
|
match task {
|
||||||
|
Task::SetSkymapCatalog(skymap_catalog) => {
|
||||||
|
par.layout.remove_widget(self.id_loading);
|
||||||
|
match &*skymap_catalog {
|
||||||
|
Ok(skymap_catalog) => {
|
||||||
|
self.mount_catalog(par.layout, skymap_catalog)?;
|
||||||
|
}
|
||||||
|
Err(e) => wgui_simple::create_label_error(
|
||||||
|
par.layout,
|
||||||
|
self.id_parent,
|
||||||
|
format!("Failed to fetch skymap catalog: {:?}", e),
|
||||||
|
)?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Task::SetSkymapPreview((skymap_uuid, opt_preview_image)) => {
|
||||||
|
if let Some(cell) = &mut self
|
||||||
|
.mounted_cells
|
||||||
|
.iter_mut()
|
||||||
|
.find(|cell| cell.skymap_uuid == skymap_uuid)
|
||||||
|
{
|
||||||
|
if let Some((preview_image, preview_image_compressed)) = opt_preview_image {
|
||||||
|
cell.view.set_image(par.layout, Some(preview_image))?;
|
||||||
|
cell.preview_image_compressed = Some(preview_image_compressed);
|
||||||
|
} else {
|
||||||
|
cell.view.set_image(par.layout, None)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Task::ShowRemoteSkymapDownloader(skymap_uuid) => {
|
||||||
|
if let Some((preview_image, preview_image_compressed)) = self.get_image_preview(skymap_uuid) {
|
||||||
|
self.show_remote_skymap_downloader(skymap_uuid, preview_image, preview_image_compressed)?;
|
||||||
|
} else {
|
||||||
|
log::error!("preview image not present, ignoring request");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Task::RefreshCells => {
|
||||||
|
self.refresh_cells(par.layout)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl View {
|
||||||
|
async fn skymap_catalog_request_wrapper(tasks: Tasks<Task>, executor: AsyncExecutor) {
|
||||||
|
let res = networking::skymap_catalog::request_catalog(&executor).await;
|
||||||
|
tasks.push(Task::SetSkymapCatalog(Rc::new(res)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(par: Params) -> anyhow::Result<Self> {
|
||||||
|
let id_loading = wgui_simple::create_loading(wgui_simple::CreateLoadingParams {
|
||||||
|
layout: par.layout,
|
||||||
|
parent_id: par.parent_id,
|
||||||
|
with_text: true,
|
||||||
|
})?;
|
||||||
|
let tasks = Tasks::<Task>::new();
|
||||||
|
let fut = View::skymap_catalog_request_wrapper(tasks.clone(), par.executor.clone());
|
||||||
|
par.executor.spawn(fut).detach();
|
||||||
|
Ok(Self {
|
||||||
|
id_parent: par.parent_id,
|
||||||
|
id_loading,
|
||||||
|
tasks,
|
||||||
|
globals: par.globals.clone(),
|
||||||
|
mounted_cells: Vec::new(),
|
||||||
|
executor: par.executor.clone(),
|
||||||
|
frontend_tasks: par.frontend_tasks,
|
||||||
|
catalog: None,
|
||||||
|
popup_remote_skymap_downloader: Default::default(),
|
||||||
|
on_updated_library: par.on_updated_library,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn request_skymap_preview(
|
||||||
|
globals: WguiGlobals,
|
||||||
|
executor: AsyncExecutor,
|
||||||
|
entry: SkymapCatalogEntry,
|
||||||
|
tasks: Tasks<Task>,
|
||||||
|
) {
|
||||||
|
tasks.push(Task::SetSkymapPreview((
|
||||||
|
entry.uuid,
|
||||||
|
networking::image_fetch::fetch_to_glyph_data(&globals, &executor, &entry.files.get_url_preview())
|
||||||
|
.await
|
||||||
|
.ok(),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn refresh_cells(&mut self, layout: &mut Layout) -> anyhow::Result<()> {
|
||||||
|
let Some(catalog) = &self.catalog else {
|
||||||
|
debug_assert!(false);
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
for cell in &mut self.mounted_cells {
|
||||||
|
if let Some(entry) = get_entry_by_uuid(catalog, cell.skymap_uuid) {
|
||||||
|
cell.view.refresh_resolution_pips(layout, entry)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mount_catalog(
|
||||||
|
&mut self,
|
||||||
|
layout: &mut Layout,
|
||||||
|
catalog: &networking::skymap_catalog::SkymapCatalog,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let doc_params = &ParseDocumentParams {
|
||||||
|
globals: self.globals.clone(),
|
||||||
|
path: AssetPath::BuiltIn("gui/view/remote_skymap_list.xml"),
|
||||||
|
extra: Default::default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let parser_state = wgui::parser::parse_from_assets(doc_params, layout, self.id_parent)?;
|
||||||
|
|
||||||
|
let id_list = parser_state.fetch_widget(&layout.state, "list")?.id;
|
||||||
|
|
||||||
|
for entry in &catalog.entries {
|
||||||
|
let task = View::request_skymap_preview(
|
||||||
|
self.globals.clone(),
|
||||||
|
self.executor.clone(),
|
||||||
|
entry.clone(),
|
||||||
|
self.tasks.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let skymap_uuid = entry.uuid;
|
||||||
|
|
||||||
|
self.mounted_cells.push(MountedCell {
|
||||||
|
preview_image_compressed: None,
|
||||||
|
skymap_uuid: entry.uuid,
|
||||||
|
view: views::skymap_list_cell::View::new(views::skymap_list_cell::Params {
|
||||||
|
id_parent: id_list,
|
||||||
|
layout,
|
||||||
|
entry: Some(entry.clone()),
|
||||||
|
on_click: self
|
||||||
|
.tasks
|
||||||
|
.get_button_click_callback(Task::ShowRemoteSkymapDownloader(skymap_uuid)),
|
||||||
|
})?,
|
||||||
|
});
|
||||||
|
|
||||||
|
self.executor.spawn(task).detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.catalog = Some(catalog.clone());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show_remote_skymap_downloader(
|
||||||
|
&mut self,
|
||||||
|
uuid: SkymapUuid,
|
||||||
|
preview_image: CustomGlyphData,
|
||||||
|
preview_image_compressed: Rc<Vec<u8>>,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let Some(catalog) = &self.catalog else {
|
||||||
|
debug_assert!(false); // impossible
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(entry) = get_entry_by_uuid(catalog, uuid) else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
|
// call our task before calling underlying on_updated_library callback
|
||||||
|
let on_updated_library = Rc::new({
|
||||||
|
let func = self.on_updated_library.clone();
|
||||||
|
let tasks = self.tasks.clone();
|
||||||
|
move || {
|
||||||
|
tasks.push(Task::RefreshCells);
|
||||||
|
(*func)();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
views::remote_skymap_downloader::mount_popup(
|
||||||
|
self.frontend_tasks.clone(),
|
||||||
|
self.executor.clone(),
|
||||||
|
self.globals.clone(),
|
||||||
|
entry.clone(),
|
||||||
|
preview_image,
|
||||||
|
preview_image_compressed,
|
||||||
|
on_updated_library,
|
||||||
|
self.popup_remote_skymap_downloader.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_image_preview(
|
||||||
|
&self,
|
||||||
|
skymap_uuid: SkymapUuid,
|
||||||
|
) -> Option<(CustomGlyphData, Rc<Vec<u8>> /* preview_image_compressed */)> {
|
||||||
|
if let Some(cell) = &self.mounted_cells.iter().find(|mc| mc.skymap_uuid == skymap_uuid) {
|
||||||
|
let image = cell.view.get_image()?;
|
||||||
|
let preview_image_compressed = cell.preview_image_compressed.clone()?;
|
||||||
|
return Some((image, preview_image_compressed));
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mount_popup(
|
||||||
|
frontend_tasks: FrontendTasks,
|
||||||
|
executor: AsyncExecutor,
|
||||||
|
globals: WguiGlobals,
|
||||||
|
on_updated_library: Rc<dyn Fn()>,
|
||||||
|
popup: PopupHolder<View>,
|
||||||
|
) {
|
||||||
|
frontend_tasks
|
||||||
|
.clone()
|
||||||
|
.push(FrontendTask::MountPopupOnce(MountPopupOnceParams::new(
|
||||||
|
Translation::from_translation_key("APP_SETTINGS.BROWSE_ONLINE_CATALOG"),
|
||||||
|
Box::new(move |data| {
|
||||||
|
let view = View::new(Params {
|
||||||
|
globals: &globals,
|
||||||
|
layout: data.layout,
|
||||||
|
executor: &executor,
|
||||||
|
parent_id: data.id_content,
|
||||||
|
frontend_tasks,
|
||||||
|
on_updated_library,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
popup.set_view(data.handle, view, None);
|
||||||
|
Ok(popup.get_close_callback(data.layout))
|
||||||
|
}),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
@ -112,10 +112,7 @@ impl View {
|
||||||
fn fill_list(&mut self, layout: &mut Layout, games: Vec<steam_utils::RunningGame>) -> anyhow::Result<()> {
|
fn fill_list(&mut self, layout: &mut Layout, games: Vec<steam_utils::RunningGame>) -> anyhow::Result<()> {
|
||||||
if games.is_empty() {
|
if games.is_empty() {
|
||||||
// hide self
|
// hide self
|
||||||
layout.tasks.push(LayoutTask::SetWidgetStyle(
|
layout.tasks.push(LayoutTask::SetWidgetVisible(self.parent_id, false));
|
||||||
self.parent_id,
|
|
||||||
StyleSetRequest::Display(Display::None),
|
|
||||||
));
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -127,7 +124,7 @@ impl View {
|
||||||
for game in games {
|
for game in games {
|
||||||
let game_name = View::extract_name_from_appid(&game.app_id, &self.installed_games);
|
let game_name = View::extract_name_from_appid(&game.app_id, &self.installed_games);
|
||||||
|
|
||||||
let t = self.state.parse_template(
|
let t = self.state.realize_template(
|
||||||
&doc_params(layout.state.globals.clone()),
|
&doc_params(layout.state.globals.clone()),
|
||||||
"RunningGameCell",
|
"RunningGameCell",
|
||||||
layout,
|
layout,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,269 @@
|
||||||
|
use anyhow::Context;
|
||||||
|
use wgui::{
|
||||||
|
assets::AssetPath,
|
||||||
|
components::button::ComponentButton,
|
||||||
|
globals::WguiGlobals,
|
||||||
|
i18n::Translation,
|
||||||
|
layout::{Layout, WidgetID},
|
||||||
|
parser::{Fetchable, ParseDocumentParams, ParserState},
|
||||||
|
renderer_vk::text::custom_glyph::CustomGlyphData,
|
||||||
|
task::Tasks,
|
||||||
|
};
|
||||||
|
use wlx_common::{async_executor::AsyncExecutor, config::GeneralConfig, config_io, dash_interface::ConfigChangeKind};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
frontend::{FrontendTask, FrontendTasks},
|
||||||
|
util::{
|
||||||
|
networking::skymap_catalog::{self, SkymapCatalogEntry, SkymapResolution},
|
||||||
|
popup_manager::{MountPopupOnceParams, PopupHolder},
|
||||||
|
},
|
||||||
|
views::{self, ViewTrait, ViewUpdateParams},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
enum Task {
|
||||||
|
DownloadSkymaps,
|
||||||
|
Refresh,
|
||||||
|
ShowSkymapResolutionSelector(SkymapCatalogEntry),
|
||||||
|
SetSkymap(SkymapCatalogEntry, SkymapResolution),
|
||||||
|
SetSkymapBuiltIn,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Params<'a> {
|
||||||
|
pub globals: WguiGlobals,
|
||||||
|
pub layout: &'a mut Layout,
|
||||||
|
pub parent_id: WidgetID,
|
||||||
|
pub frontend_tasks: &'a FrontendTasks,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Cell {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
view: views::skymap_list_cell::View,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct View {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
parser_state: ParserState,
|
||||||
|
tasks: Tasks<Task>,
|
||||||
|
list_parent: WidgetID,
|
||||||
|
frontend_tasks: FrontendTasks,
|
||||||
|
globals: WguiGlobals,
|
||||||
|
popup_remote_skymap_list: PopupHolder<views::remote_skymap_list::View>,
|
||||||
|
popup_dialog_box: PopupHolder<views::dialog_box::View>,
|
||||||
|
cells: Vec<Cell>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ViewTrait for View {
|
||||||
|
fn update(&mut self, par: &mut ViewUpdateParams) -> anyhow::Result<()> {
|
||||||
|
self.popup_remote_skymap_list.update(par)?;
|
||||||
|
self.popup_dialog_box.update(par)?;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let tasks = self.tasks.drain();
|
||||||
|
if tasks.is_empty() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
for task in tasks {
|
||||||
|
match task {
|
||||||
|
Task::DownloadSkymaps => {
|
||||||
|
self.download_skymaps(par.executor)?;
|
||||||
|
}
|
||||||
|
Task::Refresh => {
|
||||||
|
self.refresh(par.layout)?;
|
||||||
|
}
|
||||||
|
Task::ShowSkymapResolutionSelector(entry) => {
|
||||||
|
self.show_skymap_resolution_selector(entry);
|
||||||
|
}
|
||||||
|
Task::SetSkymap(entry, resolution) => {
|
||||||
|
self.set_skymap(entry, resolution, par.general_config, par.config_change_kind)?;
|
||||||
|
}
|
||||||
|
Task::SetSkymapBuiltIn => {
|
||||||
|
par.general_config.skybox_texture = "".into();
|
||||||
|
*par.config_change_kind = Some(ConfigChangeKind::EnvironmentBlend);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl View {
|
||||||
|
pub fn new(params: Params) -> anyhow::Result<Self> {
|
||||||
|
let doc_params = &ParseDocumentParams {
|
||||||
|
globals: params.globals.clone(),
|
||||||
|
path: AssetPath::BuiltIn("gui/view/skymap_list.xml"),
|
||||||
|
extra: Default::default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let parser_state = wgui::parser::parse_from_assets(doc_params, params.layout, params.parent_id)?;
|
||||||
|
let list_parent = parser_state.fetch_widget(¶ms.layout.state, "list_parent")?.id;
|
||||||
|
let tasks = Tasks::new();
|
||||||
|
|
||||||
|
tasks.push(Task::Refresh);
|
||||||
|
|
||||||
|
tasks.handle_button(
|
||||||
|
&parser_state.fetch_component_as::<ComponentButton>("btn_download_skymaps")?,
|
||||||
|
Task::DownloadSkymaps,
|
||||||
|
);
|
||||||
|
|
||||||
|
tasks.handle_button(
|
||||||
|
&parser_state.fetch_component_as::<ComponentButton>("btn_refresh")?,
|
||||||
|
Task::Refresh,
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
parser_state,
|
||||||
|
tasks,
|
||||||
|
list_parent,
|
||||||
|
frontend_tasks: params.frontend_tasks.clone(),
|
||||||
|
globals: params.globals.clone(),
|
||||||
|
popup_remote_skymap_list: Default::default(),
|
||||||
|
popup_dialog_box: Default::default(),
|
||||||
|
cells: Vec::new(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn download_skymaps(&mut self, executor: &AsyncExecutor) -> anyhow::Result<()> {
|
||||||
|
views::remote_skymap_list::mount_popup(
|
||||||
|
self.frontend_tasks.clone(),
|
||||||
|
executor.clone(),
|
||||||
|
self.globals.clone(),
|
||||||
|
self.tasks.make_callback_rc(Task::Refresh), /* on_updated_library */
|
||||||
|
self.popup_remote_skymap_list.clone(),
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_skymap(
|
||||||
|
&mut self,
|
||||||
|
entry: SkymapCatalogEntry,
|
||||||
|
resolution: SkymapResolution,
|
||||||
|
config: &mut GeneralConfig,
|
||||||
|
change_kind: &mut Option<ConfigChangeKind>,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let skymap_file_path = entry
|
||||||
|
.get_destination_path(resolution)
|
||||||
|
.context("Skymap not found" /* you shouldn't really see this, like ever. */)?;
|
||||||
|
|
||||||
|
config.skybox_texture = config_io::get_skymaps_root()
|
||||||
|
.join(skymap_file_path)
|
||||||
|
.to_str()
|
||||||
|
.context("Skymap filename not valid UTF-8")?
|
||||||
|
.into();
|
||||||
|
*change_kind = Some(ConfigChangeKind::EnvironmentBlend);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show_skymap_resolution_selector(&mut self, entry: SkymapCatalogEntry) {
|
||||||
|
let tasks = self.tasks.clone();
|
||||||
|
|
||||||
|
let mut entries = Vec::<views::dialog_box::ButtonEntry>::new();
|
||||||
|
|
||||||
|
let mut add_entry_resolution = |resolution: SkymapResolution| {
|
||||||
|
if entry.is_downloaded(resolution).unwrap_or(false) {
|
||||||
|
entries.push(views::dialog_box::ButtonEntry {
|
||||||
|
content: Translation::from_raw_text(resolution.get_display_str()),
|
||||||
|
icon: "dashboard/globe.svg",
|
||||||
|
action: resolution.get_display_str_simple(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
add_entry_resolution(SkymapResolution::Res2k);
|
||||||
|
add_entry_resolution(SkymapResolution::Res4k);
|
||||||
|
add_entry_resolution(SkymapResolution::Res8k);
|
||||||
|
|
||||||
|
if entries.is_empty() {
|
||||||
|
// if the skymap is not downloaded, how did we get there?!
|
||||||
|
debug_assert!(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
views::dialog_box::mount_popup(
|
||||||
|
self.popup_dialog_box.clone(),
|
||||||
|
self.frontend_tasks.clone(),
|
||||||
|
views::dialog_box::Params {
|
||||||
|
globals: self.globals.clone(),
|
||||||
|
entries,
|
||||||
|
message: Translation::from_translation_key("APP_SETTINGS.SELECT_VARIANT"),
|
||||||
|
on_action_click: Box::new(move |action| {
|
||||||
|
let resolution = SkymapResolution::from_display_str_simple(action).unwrap(); // safety: never fails.
|
||||||
|
tasks.push(Task::SetSkymap(entry, resolution))
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn refresh(&mut self, layout: &mut Layout) -> anyhow::Result<()> {
|
||||||
|
let entries = match skymap_catalog::get_entries_from_disk() {
|
||||||
|
Ok(entries) => entries,
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("failed to get skymap entries: {}", e);
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
layout.remove_children(self.list_parent);
|
||||||
|
self.cells.clear();
|
||||||
|
|
||||||
|
let skymaps_root = config_io::get_skymaps_root();
|
||||||
|
|
||||||
|
let mut view = views::skymap_list_cell::View::new(views::skymap_list_cell::Params {
|
||||||
|
id_parent: self.list_parent,
|
||||||
|
layout,
|
||||||
|
entry: None,
|
||||||
|
on_click: self.tasks.get_button_click_callback(Task::SetSkymapBuiltIn),
|
||||||
|
})?;
|
||||||
|
// load preview image
|
||||||
|
let data = include_bytes!("../../assets/dashboard/builtin-skybox-preview.jpg");
|
||||||
|
if let Ok(glyph_data) = CustomGlyphData::from_bytes_raster(&self.globals, "builtin-skybox-preview.jpg", data) {
|
||||||
|
view.set_image(layout, Some(glyph_data))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.cells.push(Cell { view });
|
||||||
|
|
||||||
|
for entry in &entries {
|
||||||
|
let mut view = views::skymap_list_cell::View::new(views::skymap_list_cell::Params {
|
||||||
|
id_parent: self.list_parent,
|
||||||
|
layout,
|
||||||
|
entry: Some(entry.clone()),
|
||||||
|
on_click: self
|
||||||
|
.tasks
|
||||||
|
.get_button_click_callback(Task::ShowSkymapResolutionSelector(entry.clone())),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// load preview image
|
||||||
|
if let Ok(data) = std::fs::read(skymaps_root.join(&entry.files.preview))
|
||||||
|
&& let Ok(glyph_data) = CustomGlyphData::from_bytes_raster(&self.globals, &entry.files.preview, &data)
|
||||||
|
{
|
||||||
|
view.set_image(layout, Some(glyph_data))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.cells.push(Cell { view });
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mount_popup(frontend_tasks: FrontendTasks, globals: WguiGlobals, popup: PopupHolder<View>) {
|
||||||
|
frontend_tasks
|
||||||
|
.clone()
|
||||||
|
.push(FrontendTask::MountPopupOnce(MountPopupOnceParams::new(
|
||||||
|
Translation::from_translation_key("APP_SETTINGS.BROWSE_SKYMAPS"),
|
||||||
|
Box::new(move |data| {
|
||||||
|
let view = View::new(Params {
|
||||||
|
globals: globals.clone(),
|
||||||
|
layout: data.layout,
|
||||||
|
parent_id: data.id_content,
|
||||||
|
frontend_tasks: &frontend_tasks,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
popup.set_view(data.handle, view, None);
|
||||||
|
Ok(popup.get_close_callback(data.layout))
|
||||||
|
}),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,168 @@
|
||||||
|
use std::{collections::HashMap, rc::Rc};
|
||||||
|
|
||||||
|
use wgui::{
|
||||||
|
assets::AssetPath,
|
||||||
|
components::button::{ButtonClickCallback, ComponentButton},
|
||||||
|
event::EventAlterables,
|
||||||
|
globals::WguiGlobals,
|
||||||
|
i18n::Translation,
|
||||||
|
layout::{Layout, WidgetID},
|
||||||
|
parser::{Fetchable, ParseDocumentParams, ParserState},
|
||||||
|
renderer_vk::text::custom_glyph::CustomGlyphData,
|
||||||
|
widget::{image::WidgetImage, label::WidgetLabel},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::util::{
|
||||||
|
networking::{
|
||||||
|
self,
|
||||||
|
skymap_catalog::{SkymapCatalogEntry, SkymapResolution},
|
||||||
|
},
|
||||||
|
wgui_simple,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct Params<'a> {
|
||||||
|
pub id_parent: WidgetID,
|
||||||
|
pub layout: &'a mut Layout,
|
||||||
|
pub entry: Option<networking::skymap_catalog::SkymapCatalogEntry>,
|
||||||
|
pub on_click: ButtonClickCallback,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct View {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
parser_state: ParserState,
|
||||||
|
id_loading: WidgetID,
|
||||||
|
id_image_preview: WidgetID,
|
||||||
|
id_resolution_pips: WidgetID,
|
||||||
|
image: Option<CustomGlyphData>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn doc_params(globals: &'_ WguiGlobals) -> ParseDocumentParams<'_> {
|
||||||
|
ParseDocumentParams {
|
||||||
|
globals: globals.clone(),
|
||||||
|
path: AssetPath::BuiltIn("gui/view/skymap_list_cell.xml"),
|
||||||
|
extra: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn populate_res_pips(
|
||||||
|
layout: &mut Layout,
|
||||||
|
id_parent: WidgetID,
|
||||||
|
parser_state: &mut ParserState,
|
||||||
|
entry: &SkymapCatalogEntry,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
let globals = layout.state.globals.clone();
|
||||||
|
layout.remove_children(id_parent);
|
||||||
|
|
||||||
|
let mut populate_res_pip = |res: SkymapResolution| -> anyhow::Result<()> {
|
||||||
|
let mut tpar = HashMap::<Rc<str>, Rc<str>>::new();
|
||||||
|
let downloaded = entry.is_downloaded(res).unwrap_or(false);
|
||||||
|
tpar.insert(
|
||||||
|
Rc::from("color"),
|
||||||
|
if downloaded {
|
||||||
|
Rc::from("#11aa40")
|
||||||
|
} else {
|
||||||
|
Rc::from("#444444")
|
||||||
|
},
|
||||||
|
);
|
||||||
|
tpar.insert(Rc::from("text"), res.get_display_str_simple().into());
|
||||||
|
parser_state.realize_template(&doc_params(&globals), "ResolutionPip", layout, id_parent, tpar)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
};
|
||||||
|
|
||||||
|
populate_res_pip(SkymapResolution::Res2k)?;
|
||||||
|
if entry.files.size_4k.is_some() {
|
||||||
|
populate_res_pip(SkymapResolution::Res4k)?;
|
||||||
|
}
|
||||||
|
if entry.files.size_8k.is_some() {
|
||||||
|
populate_res_pip(SkymapResolution::Res8k)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
impl View {
|
||||||
|
pub fn new(par: Params) -> anyhow::Result<Self> {
|
||||||
|
let globals = par.layout.state.globals.clone();
|
||||||
|
|
||||||
|
let mut parser_state = wgui::parser::parse_from_assets(&doc_params(&globals), par.layout, par.id_parent)?;
|
||||||
|
|
||||||
|
let data = parser_state.realize_template(
|
||||||
|
&doc_params(&globals),
|
||||||
|
"Cell",
|
||||||
|
par.layout,
|
||||||
|
par.id_parent,
|
||||||
|
Default::default(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let id_image_preview = data.get_widget_id("image_preview")?;
|
||||||
|
let id_resolution_pips = data.get_widget_id("resolution_pips")?;
|
||||||
|
|
||||||
|
data
|
||||||
|
.fetch_component_as::<ComponentButton>("button")?
|
||||||
|
.on_click(par.on_click);
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut label_title = data.fetch_widget_as::<WidgetLabel>(&par.layout.state, "label_title")?;
|
||||||
|
let mut label_author = data.fetch_widget_as::<WidgetLabel>(&par.layout.state, "label_author")?;
|
||||||
|
|
||||||
|
label_title.set_text_simple(
|
||||||
|
&mut globals.get(),
|
||||||
|
Translation::from_raw_text_string(
|
||||||
|
par
|
||||||
|
.entry
|
||||||
|
.as_ref()
|
||||||
|
.map(|e| e.name.clone())
|
||||||
|
.unwrap_or_else(|| "Built-in Sky Shader".into()),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
label_author.set_text_simple(
|
||||||
|
&mut globals.get(),
|
||||||
|
Translation::from_raw_text_string(format!(
|
||||||
|
"by {}",
|
||||||
|
par.entry.as_ref().map(|e| e.author.as_str()).unwrap_or("WayVR Team")
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let id_loading = wgui_simple::create_loading(wgui_simple::CreateLoadingParams {
|
||||||
|
layout: par.layout,
|
||||||
|
parent_id: id_image_preview,
|
||||||
|
with_text: false,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// Populate resolution pips
|
||||||
|
if let Some(entry) = par.entry.as_ref() {
|
||||||
|
populate_res_pips(par.layout, id_resolution_pips, &mut parser_state, entry)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
parser_state,
|
||||||
|
id_loading,
|
||||||
|
id_image_preview,
|
||||||
|
image: None,
|
||||||
|
id_resolution_pips,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn refresh_resolution_pips(&mut self, layout: &mut Layout, entry: &SkymapCatalogEntry) -> anyhow::Result<()> {
|
||||||
|
populate_res_pips(layout, self.id_resolution_pips, &mut self.parser_state, entry)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_image(&mut self, layout: &mut Layout, content: Option<CustomGlyphData>) -> anyhow::Result<()> {
|
||||||
|
layout.remove_widget(self.id_loading);
|
||||||
|
let mut alt = EventAlterables::default();
|
||||||
|
{
|
||||||
|
let mut image_preview = layout.state.widgets.cast_as::<WidgetImage>(self.id_image_preview)?;
|
||||||
|
image_preview.set_content(&mut alt, content.clone());
|
||||||
|
}
|
||||||
|
layout.process_alterables(alt)?;
|
||||||
|
self.image = content;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_image(&self) -> Option<CustomGlyphData> {
|
||||||
|
self.image.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
[package]
|
||||||
|
name = "prost_build"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
prost-build = "0.14.3"
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
use std::io::Result;
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
prost_build::compile_protos(&["src/monado_metrics.proto"], &["src/"])?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,174 @@
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package monado_metrics;
|
||||||
|
|
||||||
|
|
||||||
|
message Version
|
||||||
|
{
|
||||||
|
uint32 major = 1;
|
||||||
|
uint32 minor = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SessionFrame
|
||||||
|
{
|
||||||
|
//! Which session this frame belongs to.
|
||||||
|
int64 session_id = 1;
|
||||||
|
|
||||||
|
//! ID of frame.
|
||||||
|
int64 frame_id = 2;
|
||||||
|
|
||||||
|
//! How long we thought the frame would take.
|
||||||
|
uint64 predicted_frame_time_ns = 3;
|
||||||
|
|
||||||
|
//! When we predicted the app should wake up.
|
||||||
|
uint64 predicted_wake_up_time_ns = 4;
|
||||||
|
|
||||||
|
//! When the client's GPU work should have completed.
|
||||||
|
uint64 predicted_gpu_done_time_ns = 5;
|
||||||
|
|
||||||
|
//! When we predicted this frame to be shown.
|
||||||
|
uint64 predicted_display_time_ns = 6;
|
||||||
|
|
||||||
|
//! The selected display period.
|
||||||
|
uint64 predicted_display_period_ns = 7;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* When the app told us to display this frame, can be different
|
||||||
|
* then the predicted display time so we track that separately.
|
||||||
|
*/
|
||||||
|
uint64 display_time_ns = 8;
|
||||||
|
|
||||||
|
//! When the predict call was made (inside of xrWaitFrame).
|
||||||
|
uint64 when_predicted_ns = 9;
|
||||||
|
|
||||||
|
//! When the waiting thread was woken up.
|
||||||
|
uint64 when_wait_woke_ns = 10;
|
||||||
|
|
||||||
|
//! When xrBeginFrame was called.
|
||||||
|
uint64 when_begin_ns = 11;
|
||||||
|
|
||||||
|
//! When the layer information was delivered, (inside of xrEndFrame).
|
||||||
|
uint64 when_delivered_ns = 12;
|
||||||
|
|
||||||
|
//! When the scheduled GPU work was completed.
|
||||||
|
uint64 when_gpu_done_ns = 13;
|
||||||
|
|
||||||
|
//! Was this frame discarded.
|
||||||
|
bool discarded = 14;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Used
|
||||||
|
{
|
||||||
|
//! Owning session of the frame.
|
||||||
|
int64 session_id = 1;
|
||||||
|
|
||||||
|
//! Which session frame was used by the compositor.
|
||||||
|
int64 session_frame_id = 2;
|
||||||
|
|
||||||
|
//! The system frame that the session was used by.
|
||||||
|
int64 system_frame_id = 3;
|
||||||
|
|
||||||
|
//! When the frame was latched.
|
||||||
|
uint64 when_ns = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SystemFrame
|
||||||
|
{
|
||||||
|
//! ID of frame.
|
||||||
|
int64 frame_id = 1;
|
||||||
|
|
||||||
|
//! Projected pixels to photon time.
|
||||||
|
uint64 predicted_display_time_ns = 2;
|
||||||
|
|
||||||
|
//! Current period of displaying of frames.
|
||||||
|
uint64 predicted_display_period_ns = 3;
|
||||||
|
|
||||||
|
//! When the compositor should hand pixels to display hardware.
|
||||||
|
uint64 desired_present_time_ns = 4;
|
||||||
|
|
||||||
|
//! The time that the compositor should wake up.
|
||||||
|
uint64 wake_up_time_ns = 5;
|
||||||
|
uint64 present_slop_ns = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Out of band info about a GPU timing from a system compositor.
|
||||||
|
*/
|
||||||
|
message SystemGpuInfo
|
||||||
|
{
|
||||||
|
//! ID of frame.
|
||||||
|
int64 frame_id = 1;
|
||||||
|
|
||||||
|
//! Start of GPU work.
|
||||||
|
uint64 gpu_start_ns = 2;
|
||||||
|
|
||||||
|
//! End of GPU work.
|
||||||
|
uint64 gpu_end_ns = 3;
|
||||||
|
|
||||||
|
//! When the information was gathered.
|
||||||
|
uint64 when_ns = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Information that comes from the vulkan present timing.
|
||||||
|
*/
|
||||||
|
message SystemPresentInfo
|
||||||
|
{
|
||||||
|
//! ID of frame.
|
||||||
|
int64 frame_id = 1;
|
||||||
|
|
||||||
|
//! The total expected time for the compositor to complete a frame.
|
||||||
|
uint64 expected_comp_time_ns = 2;
|
||||||
|
|
||||||
|
//! The time we predicted the compositor to wake up time.
|
||||||
|
uint64 predicted_wake_up_time_ns = 3;
|
||||||
|
|
||||||
|
//! Predicted time for completion of the GPU work.
|
||||||
|
uint64 predicted_done_time_ns = 4;
|
||||||
|
|
||||||
|
//! Predicted display time.
|
||||||
|
uint64 predicted_display_time_ns = 5;
|
||||||
|
|
||||||
|
//! When was this frame predicted.
|
||||||
|
uint64 when_predict_ns = 6;
|
||||||
|
|
||||||
|
//! When we last woke up the compositor after its equivalent of wait_frame.
|
||||||
|
uint64 when_woke_ns = 7;
|
||||||
|
|
||||||
|
//! When the compositor started rendering a frame
|
||||||
|
uint64 when_began_ns = 8;
|
||||||
|
|
||||||
|
//! When the compositor finished rendering a frame
|
||||||
|
uint64 when_submitted_ns = 9;
|
||||||
|
|
||||||
|
//! When new frame timing info was last added.
|
||||||
|
uint64 when_infoed_ns = 10;
|
||||||
|
|
||||||
|
//! When we wanted to start presenting.
|
||||||
|
uint64 desired_present_time_ns = 11;
|
||||||
|
|
||||||
|
//! The slop used for this frame.
|
||||||
|
uint64 present_slop_ns = 12;
|
||||||
|
|
||||||
|
//! Margin of GPU to present time.
|
||||||
|
uint64 present_margin_ns = 13;
|
||||||
|
|
||||||
|
//! When the present time actually happened.
|
||||||
|
uint64 actual_present_time_ns = 14;
|
||||||
|
|
||||||
|
//! The earliest we could have presented.
|
||||||
|
uint64 earliest_present_time_ns = 15;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
message Record
|
||||||
|
{
|
||||||
|
oneof record {
|
||||||
|
Version version = 1;
|
||||||
|
SessionFrame session_frame = 2;
|
||||||
|
Used used = 3;
|
||||||
|
SystemFrame system_frame = 4;
|
||||||
|
SystemGpuInfo system_gpu_info = 5;
|
||||||
|
SystemPresentInfo system_present_info = 6;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -59,10 +59,22 @@
|
||||||
</div>
|
</div>
|
||||||
</rectangle>
|
</rectangle>
|
||||||
|
|
||||||
|
<div gap="4" flex_direction="column">
|
||||||
|
<rectangle macro="rect">
|
||||||
|
<label text="Color selector!"/>
|
||||||
|
<ColorSelector />
|
||||||
|
</rectangle>
|
||||||
<rectangle macro="rect">
|
<rectangle macro="rect">
|
||||||
<label text="Context menu test" />
|
<label text="Context menu test" />
|
||||||
<Button id="button_context_menu" text="Show context menu" />
|
<Button id="button_context_menu" text="Show context menu" />
|
||||||
</rectangle>
|
</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">
|
<rectangle macro="rect">
|
||||||
<label text="visibility test" weight="bold" />
|
<label text="visibility test" weight="bold" />
|
||||||
|
|
@ -82,6 +94,11 @@
|
||||||
<CheckBox text="i'm checked by default" checked="1" />
|
<CheckBox text="i'm checked by default" checked="1" />
|
||||||
</rectangle>
|
</rectangle>
|
||||||
|
|
||||||
|
<rectangle macro="rect" flex_direction="row" gap="16">
|
||||||
|
<label text="range slider test" weight="bold" />
|
||||||
|
<Slider value="0.5" value2="1.0" min_value="-5.0" max_value="20.0" width="200" height="24"/>
|
||||||
|
</rectangle>
|
||||||
|
|
||||||
<rectangle macro="rect" flex_direction="row" align_items="start">
|
<rectangle macro="rect" flex_direction="row" align_items="start">
|
||||||
<div flex_direction="column" gap="8">
|
<div flex_direction="column" gap="8">
|
||||||
<label text="height 16" weight="bold" />
|
<label text="height 16" weight="bold" />
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"BAR": {
|
||||||
|
"OPACITY": "Opacità",
|
||||||
|
"ADDITIVE": "Additivo"
|
||||||
|
},
|
||||||
|
"TESTBED": {
|
||||||
|
"HELLO_WORLD": "Ciao, mondo! Questo è un testo I18n tradotto."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"BAR": {
|
||||||
|
"OPACITY": "不透明度",
|
||||||
|
"ADDITIVE": "加法"
|
||||||
|
},
|
||||||
|
"TESTBED": {
|
||||||
|
"HELLO_WORLD": "你好,世界!这是一段翻译后的国际化文本。"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,25 +1,25 @@
|
||||||
use glam::{vec2, Vec2};
|
use glam::{Vec2, vec2};
|
||||||
use std::sync::Arc;
|
use std::{rc::Rc, sync::Arc};
|
||||||
use testbed::{testbed_any::TestbedAny, Testbed};
|
use testbed::{Testbed, testbed_any::TestbedAny};
|
||||||
|
use tracing_subscriber::EnvFilter;
|
||||||
use tracing_subscriber::filter::LevelFilter;
|
use tracing_subscriber::filter::LevelFilter;
|
||||||
use tracing_subscriber::layer::SubscriberExt;
|
use tracing_subscriber::layer::SubscriberExt;
|
||||||
use tracing_subscriber::util::SubscriberInitExt;
|
use tracing_subscriber::util::SubscriberInitExt;
|
||||||
use tracing_subscriber::EnvFilter;
|
|
||||||
use vulkan::init_window;
|
use vulkan::init_window;
|
||||||
use vulkano::{
|
use vulkano::{
|
||||||
|
Validated, VulkanError,
|
||||||
command_buffer::CommandBufferUsage,
|
command_buffer::CommandBufferUsage,
|
||||||
format::Format,
|
format::Format,
|
||||||
image::{view::ImageView, ImageUsage},
|
image::{ImageUsage, view::ImageView},
|
||||||
swapchain::{
|
swapchain::{
|
||||||
acquire_next_image, ColorSpace, CompositeAlpha, PresentMode, Surface, SurfaceInfo, Swapchain,
|
ColorSpace, CompositeAlpha, PresentMode, Surface, SurfaceInfo, Swapchain, SwapchainCreateInfo,
|
||||||
SwapchainCreateInfo, SwapchainPresentInfo,
|
SwapchainPresentInfo, acquire_next_image,
|
||||||
},
|
},
|
||||||
sync::GpuFuture,
|
sync::GpuFuture,
|
||||||
Validated, VulkanError,
|
|
||||||
};
|
};
|
||||||
use wgui::{
|
use wgui::{
|
||||||
event::{MouseButtonIndex, MouseDownEvent, MouseMotionEvent, MouseUpEvent, MouseWheelEvent},
|
event::{MouseButtonEvent, MouseButtonIndex, MouseMotionEvent, MouseWheelEvent},
|
||||||
gfx::{cmd::WGfxClearMode, WGfx},
|
gfx::{WGfx, cmd::WGfxClearMode},
|
||||||
renderer_vk::{self},
|
renderer_vk::{self},
|
||||||
};
|
};
|
||||||
use winit::{
|
use winit::{
|
||||||
|
|
@ -32,7 +32,7 @@ use wlx_common::{audio, timestep::Timestep};
|
||||||
use crate::{
|
use crate::{
|
||||||
rate_limiter::RateLimiter,
|
rate_limiter::RateLimiter,
|
||||||
testbed::{
|
testbed::{
|
||||||
testbed_dashboard::TestbedDashboard, testbed_generic::TestbedGeneric, TestbedUpdateParams,
|
TestbedUpdateParams, testbed_dashboard::TestbedDashboard, testbed_generic::TestbedGeneric,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -143,7 +143,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
&wgui::event::Event::MouseWheel(MouseWheelEvent {
|
&wgui::event::Event::MouseWheel(MouseWheelEvent {
|
||||||
delta: Vec2::new(x, y),
|
delta: Vec2::new(x, y),
|
||||||
pos: mouse / scale,
|
pos: mouse / scale,
|
||||||
device: 0,
|
device: wgui::event::DeviceBitmask(0),
|
||||||
}),
|
}),
|
||||||
&mut (),
|
&mut (),
|
||||||
&mut (),
|
&mut (),
|
||||||
|
|
@ -157,7 +157,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
&wgui::event::Event::MouseWheel(MouseWheelEvent {
|
&wgui::event::Event::MouseWheel(MouseWheelEvent {
|
||||||
delta: Vec2::new(pos.x as f32 / 5.0, pos.y as f32 / 5.0),
|
delta: Vec2::new(pos.x as f32 / 5.0, pos.y as f32 / 5.0),
|
||||||
pos: mouse / scale,
|
pos: mouse / scale,
|
||||||
device: 0,
|
device: wgui::event::DeviceBitmask(0),
|
||||||
}),
|
}),
|
||||||
&mut (),
|
&mut (),
|
||||||
&mut (),
|
&mut (),
|
||||||
|
|
@ -174,10 +174,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
testbed
|
testbed
|
||||||
.layout()
|
.layout()
|
||||||
.push_event(
|
.push_event(
|
||||||
&wgui::event::Event::MouseDown(MouseDownEvent {
|
&wgui::event::Event::MouseDown(MouseButtonEvent {
|
||||||
pos: mouse / scale,
|
pos: mouse / scale,
|
||||||
index: MouseButtonIndex::Left,
|
index: MouseButtonIndex::Left,
|
||||||
device: 0,
|
device: wgui::event::DeviceBitmask(0),
|
||||||
}),
|
}),
|
||||||
&mut (),
|
&mut (),
|
||||||
&mut (),
|
&mut (),
|
||||||
|
|
@ -187,10 +187,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
testbed
|
testbed
|
||||||
.layout()
|
.layout()
|
||||||
.push_event(
|
.push_event(
|
||||||
&wgui::event::Event::MouseUp(MouseUpEvent {
|
&wgui::event::Event::MouseUp(MouseButtonEvent {
|
||||||
pos: mouse / scale,
|
pos: mouse / scale,
|
||||||
index: MouseButtonIndex::Left,
|
index: MouseButtonIndex::Left,
|
||||||
device: 0,
|
device: wgui::event::DeviceBitmask(0),
|
||||||
}),
|
}),
|
||||||
&mut (),
|
&mut (),
|
||||||
&mut (),
|
&mut (),
|
||||||
|
|
@ -209,7 +209,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
.push_event(
|
.push_event(
|
||||||
&wgui::event::Event::MouseMotion(MouseMotionEvent {
|
&wgui::event::Event::MouseMotion(MouseMotionEvent {
|
||||||
pos: mouse / scale,
|
pos: mouse / scale,
|
||||||
device: 0,
|
device: wgui::event::DeviceBitmask(0),
|
||||||
}),
|
}),
|
||||||
&mut (),
|
&mut (),
|
||||||
&mut (),
|
&mut (),
|
||||||
|
|
@ -221,6 +221,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
if event.state == ElementState::Pressed {
|
if event.state == ElementState::Pressed {
|
||||||
|
if !event.repeat {
|
||||||
if event.physical_key == PhysicalKey::Code(KeyCode::F10) {
|
if event.physical_key == PhysicalKey::Code(KeyCode::F10) {
|
||||||
debug_draw_enabled = !debug_draw_enabled;
|
debug_draw_enabled = !debug_draw_enabled;
|
||||||
if debug_draw_enabled {
|
if debug_draw_enabled {
|
||||||
|
|
@ -249,6 +250,18 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
.unwrap();
|
.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 {
|
Event::WindowEvent {
|
||||||
event: WindowEvent::CloseRequested,
|
event: WindowEvent::CloseRequested,
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ use wgui::{
|
||||||
layout::{Layout, LayoutParams, LayoutUpdateParams},
|
layout::{Layout, LayoutParams, LayoutUpdateParams},
|
||||||
parser::{ParseDocumentParams, ParserState},
|
parser::{ParseDocumentParams, ParserState},
|
||||||
};
|
};
|
||||||
|
use wlx_common::locale::WayVRLangProvider;
|
||||||
|
|
||||||
pub struct TestbedAny {
|
pub struct TestbedAny {
|
||||||
pub layout: Layout,
|
pub layout: Layout,
|
||||||
|
|
@ -28,9 +29,11 @@ impl TestbedAny {
|
||||||
AssetPath::BuiltIn(&format!("gui/{name}.xml"))
|
AssetPath::BuiltIn(&format!("gui/{name}.xml"))
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let lang_provider = WayVRLangProvider::default();
|
||||||
|
|
||||||
let globals = WguiGlobals::new(
|
let globals = WguiGlobals::new(
|
||||||
assets,
|
assets,
|
||||||
wgui::globals::Defaults::default(),
|
&lang_provider,
|
||||||
&WguiFontConfig::default(),
|
&WguiFontConfig::default(),
|
||||||
PathBuf::new(), // cwd
|
PathBuf::new(), // cwd
|
||||||
)?;
|
)?;
|
||||||
|
|
@ -41,7 +44,7 @@ impl TestbedAny {
|
||||||
path,
|
path,
|
||||||
extra: Default::default(),
|
extra: Default::default(),
|
||||||
},
|
},
|
||||||
&LayoutParams::default(),
|
LayoutParams::default(),
|
||||||
)?;
|
)?;
|
||||||
Ok(Self { layout, state })
|
Ok(Self { layout, state })
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
use crate::testbed::{Testbed, TestbedUpdateParams};
|
use crate::testbed::{Testbed, TestbedUpdateParams};
|
||||||
use dash_frontend::frontend::{self, FrontendUpdateParams};
|
use dash_frontend::frontend::{self, FrontendUpdateParams};
|
||||||
use wgui::layout::Layout;
|
use wgui::{layout::Layout, theme::WguiTheme};
|
||||||
use wlx_common::dash_interface_emulated::DashInterfaceEmulated;
|
use wlx_common::{dash_interface_emulated::DashInterfaceEmulated, locale::WayVRLangProvider};
|
||||||
|
|
||||||
pub struct TestbedDashboard {
|
pub struct TestbedDashboard {
|
||||||
frontend: frontend::Frontend<()>,
|
frontend: frontend::Frontend<()>,
|
||||||
|
|
@ -10,14 +12,14 @@ pub struct TestbedDashboard {
|
||||||
impl TestbedDashboard {
|
impl TestbedDashboard {
|
||||||
pub fn new() -> anyhow::Result<Self> {
|
pub fn new() -> anyhow::Result<Self> {
|
||||||
let interface = DashInterfaceEmulated::new();
|
let interface = DashInterfaceEmulated::new();
|
||||||
|
let lang_provider = WayVRLangProvider::default();
|
||||||
|
|
||||||
let frontend = frontend::Frontend::new(
|
let frontend = frontend::Frontend::new(frontend::InitParams {
|
||||||
frontend::InitParams {
|
|
||||||
interface: Box::new(interface),
|
interface: Box::new(interface),
|
||||||
has_monado: true,
|
has_monado: true,
|
||||||
},
|
lang_provider: &lang_provider,
|
||||||
&mut (),
|
theme: Rc::new(WguiTheme::default()),
|
||||||
)?;
|
})?;
|
||||||
Ok(Self { frontend })
|
Ok(Self { frontend })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ use wgui::{
|
||||||
checkbox::ComponentCheckbox,
|
checkbox::ComponentCheckbox,
|
||||||
},
|
},
|
||||||
drawing::Color,
|
drawing::Color,
|
||||||
event::StyleSetRequest,
|
|
||||||
font_config::WguiFontConfig,
|
font_config::WguiFontConfig,
|
||||||
globals::WguiGlobals,
|
globals::WguiGlobals,
|
||||||
i18n::Translation,
|
i18n::Translation,
|
||||||
|
|
@ -21,12 +20,14 @@ use wgui::{
|
||||||
parser::{Fetchable, ParseDocumentExtra, ParseDocumentParams, ParserState},
|
parser::{Fetchable, ParseDocumentExtra, ParseDocumentParams, ParserState},
|
||||||
taffy::{self, prelude::length},
|
taffy::{self, prelude::length},
|
||||||
task::Tasks,
|
task::Tasks,
|
||||||
|
theme::WguiTheme,
|
||||||
widget::{div::WidgetDiv, label::WidgetLabel, rectangle::WidgetRectangle},
|
widget::{div::WidgetDiv, label::WidgetLabel, rectangle::WidgetRectangle},
|
||||||
windowing::{
|
windowing::{
|
||||||
context_menu::{self, TickResult},
|
context_menu::{self, TickResult},
|
||||||
window::{WguiWindow, WguiWindowParams, WguiWindowParamsExtra},
|
window::{WguiWindow, WguiWindowParams, WguiWindowParamsExtra},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
use wlx_common::locale::WayVRLangProvider;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum TestbedTask {
|
pub enum TestbedTask {
|
||||||
|
|
@ -44,7 +45,6 @@ pub struct TestbedGeneric {
|
||||||
pub parser_state: ParserState,
|
pub parser_state: ParserState,
|
||||||
tasks: Tasks<TestbedTask>,
|
tasks: Tasks<TestbedTask>,
|
||||||
|
|
||||||
globals: WguiGlobals,
|
|
||||||
data: Rc<RefCell<Data>>,
|
data: Rc<RefCell<Data>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -85,9 +85,11 @@ impl TestbedGeneric {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(assets: Box<assets::Asset>) -> anyhow::Result<Self> {
|
pub fn new(assets: Box<assets::Asset>) -> anyhow::Result<Self> {
|
||||||
|
let lang_provider = WayVRLangProvider::default();
|
||||||
|
|
||||||
let globals = WguiGlobals::new(
|
let globals = WguiGlobals::new(
|
||||||
assets,
|
assets,
|
||||||
wgui::globals::Defaults::default(),
|
&lang_provider,
|
||||||
&WguiFontConfig::default(),
|
&WguiFontConfig::default(),
|
||||||
PathBuf::new(), // cwd
|
PathBuf::new(), // cwd
|
||||||
)?;
|
)?;
|
||||||
|
|
@ -121,8 +123,9 @@ impl TestbedGeneric {
|
||||||
|
|
||||||
let (layout, parser_state) = wgui::parser::new_layout_from_assets(
|
let (layout, parser_state) = wgui::parser::new_layout_from_assets(
|
||||||
&TestbedGeneric::doc_params(&globals, extra),
|
&TestbedGeneric::doc_params(&globals, extra),
|
||||||
&LayoutParams {
|
LayoutParams {
|
||||||
resize_to_parent: true,
|
resize_to_parent: true,
|
||||||
|
theme: Rc::new(WguiTheme::default()),
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
|
@ -130,14 +133,9 @@ impl TestbedGeneric {
|
||||||
let div_visibility = parser_state.fetch_widget(&layout.state, "div_visibility")?;
|
let div_visibility = parser_state.fetch_widget(&layout.state, "div_visibility")?;
|
||||||
|
|
||||||
cb_visible.on_toggle(Box::new(move |common, evt| {
|
cb_visible.on_toggle(Box::new(move |common, evt| {
|
||||||
common.alterables.set_style(
|
common
|
||||||
div_visibility.id,
|
.alterables
|
||||||
StyleSetRequest::Display(if evt.checked {
|
.set_widget_visible(div_visibility.id, evt.checked);
|
||||||
taffy::Display::Flex
|
|
||||||
} else {
|
|
||||||
taffy::Display::None
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
@ -178,7 +176,6 @@ impl TestbedGeneric {
|
||||||
layout,
|
layout,
|
||||||
parser_state,
|
parser_state,
|
||||||
tasks: Default::default(),
|
tasks: Default::default(),
|
||||||
globals: globals.clone(),
|
|
||||||
data: Rc::new(RefCell::new(Data {
|
data: Rc::new(RefCell::new(Data {
|
||||||
popup_window: WguiWindow::default(),
|
popup_window: WguiWindow::default(),
|
||||||
context_menu: context_menu::ContextMenu::default(),
|
context_menu: context_menu::ContextMenu::default(),
|
||||||
|
|
@ -224,7 +221,6 @@ impl TestbedGeneric {
|
||||||
data: &mut Data,
|
data: &mut Data,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
data.popup_window.open(&mut WguiWindowParams {
|
data.popup_window.open(&mut WguiWindowParams {
|
||||||
globals: &self.globals,
|
|
||||||
position: Vec2::new(128.0, 128.0),
|
position: Vec2::new(128.0, 128.0),
|
||||||
layout: &mut self.layout,
|
layout: &mut self.layout,
|
||||||
extra: WguiWindowParamsExtra {
|
extra: WguiWindowParamsExtra {
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ use anyhow::Context;
|
||||||
use std::sync::{Arc, OnceLock};
|
use std::sync::{Arc, OnceLock};
|
||||||
use vulkano::{
|
use vulkano::{
|
||||||
device::{
|
device::{
|
||||||
physical::{PhysicalDevice, PhysicalDeviceType},
|
|
||||||
Device, DeviceCreateInfo, DeviceExtensions, DeviceFeatures, Queue, QueueCreateInfo, QueueFlags,
|
Device, DeviceCreateInfo, DeviceExtensions, DeviceFeatures, Queue, QueueCreateInfo, QueueFlags,
|
||||||
|
physical::{PhysicalDevice, PhysicalDeviceType},
|
||||||
},
|
},
|
||||||
instance::{Instance, InstanceCreateInfo},
|
instance::{Instance, InstanceCreateInfo},
|
||||||
swapchain::ColorSpace,
|
swapchain::ColorSpace,
|
||||||
|
|
@ -96,8 +96,8 @@ pub fn init_window(
|
||||||
let (format, color_space) = physical_device
|
let (format, color_space) = physical_device
|
||||||
.surface_formats(&surface, vulkano::swapchain::SurfaceInfo::default())?
|
.surface_formats(&surface, vulkano::swapchain::SurfaceInfo::default())?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.next()
|
.find(|(format, _)| !format.components().iter().any(|b| *b > 8))
|
||||||
.context("Could not read surface formats for PhysicalDevice")?;
|
.context("Could not read or no suitable surface formats for PhysicalDevice")?;
|
||||||
|
|
||||||
let (device, queues) = Device::new(
|
let (device, queues) = Device::new(
|
||||||
physical_device,
|
physical_device,
|
||||||
|
|
|
||||||
|
|
@ -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.
|
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)
|
[Wayvrctl Documentation](https://wayvr.org/docs/various/integrations/wayvr-ipc/)
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
[Example WayVR IPC Client usage in WayVR Dashboard (Legacy)](https://github.com/olekolek1000/wayvr-dashboard/blob/master/src-tauri/src/frontend_ipc.rs)
|
|
||||||
|
|
|
||||||