Compare commits

...

77 Commits

Author SHA1 Message Date
galister 4652f708b0 meta → altgr 2026-07-20 16:34:12 +09:00
galister e1879ca891 playspace center? 2026-07-20 00:26:53 +09:00
galister 786660a4d5 fix mirrors only allowing screens 2026-07-19 09:29:17 +09:00
galister 9e43b9b3ad openvr: copy reset_offset logic from openxr 2026-07-19 09:22:35 +09:00
galister 7c16748f88 openvr: copy recenter logic from openxr 2026-07-19 09:22:35 +09:00
galister a29451ceaa build xwayland-satellite for release 2026-07-14 22:19:33 +09:00
galister d93b74cc8a Release 26.7.1 2026-07-14 22:08:11 +09:00
galister 2def8c3a6d fmt 2026-07-14 18:05:04 +09:00
galister 30d786eaa1 feat: window snap angles 2026-07-14 18:04:36 +09:00
galister 779baaf86c fix stereo mouse on apps 2026-07-14 17:36:01 +09:00
galister 7c7e7edbfb fix some xml inconsistencies 2026-07-14 14:33:58 +09:00
galister b1e09910a2 monado tab order switch 2026-07-14 14:18:44 +09:00
galister 5407e53700 fix checkbox tooltips + fmt 2026-07-14 14:14:10 +09:00
galister e895a61ea0 fix openxr recenter 2026-07-14 14:08:42 +09:00
galister 4ed52f0214 clarify some tutorial instructions 2026-07-14 14:08:35 +09:00
galister fcc84e56b3 custom palettes 2026-07-14 00:30:59 +09:00
galister c74c51f7cd fix tooltips staying after parent is gone 2026-07-13 22:03:40 +09:00
galister acaabead52 refactor components_by_widget_id 2026-07-13 22:03:40 +09:00
galister fd8a327395 palette selector improvements 2026-07-13 22:03:40 +09:00
galister 952b6e10b6 nicer palette selector 2026-07-13 15:08:14 +09:00
galister 245666ab06 translations 2026-07-13 13:56:43 +09:00
galister 227e3851bc tweak onBackgroundVariant on Default 2026-07-13 13:54:00 +09:00
galister 4e9b893304 keycaps theming 2026-07-13 13:48:01 +09:00
galister d311813899 standardize button colors 2026-07-13 13:07:54 +09:00
galister 075a6dd63b dont tint app icons 2026-07-13 11:17:08 +09:00
galister 83b9a1920f openxr: recenter also recenters yaw 2026-07-13 10:41:50 +09:00
galister 635cfef63e do not install SteamVR manifest by default 2026-07-13 10:29:38 +09:00
galister a8262c1de4 fix wrapped text flying around 2026-07-13 10:23:02 +09:00
galister a3e3101ab0 log if appimage 2026-07-13 09:46:46 +09:00
galister 28442ac6a8 bundle xwayland-satellite with appimage 2026-07-13 09:41:10 +09:00
galister d6268c0c20
Merge branch 'main' into wgui_named_colors 2026-07-13 09:02:14 +09:00
galister 5c0ae395a2 app launcher button themes 2026-07-13 04:54:13 +09:00
galister 962ed32452 parent_color attrib to fix app icons 2026-07-13 04:50:22 +09:00
galister f3438b1867 screencast.xml colors 2026-07-13 04:04:14 +09:00
galister e0ab4be8e3 fix use_bg_color label anims 2026-07-13 03:48:14 +09:00
galister c4655b7b9a default secondary 2026-07-13 03:30:17 +09:00
Aleksander c32e3ef86e dash-frontend: fix more colors, wgui: fix possible flicker 2026-07-12 20:23:32 +02:00
galister 2ea3b222f3 use_bg_color for label 2026-07-13 03:21:34 +09:00
Aleksander 6f5be3a157 dash-frontend: tweak apps.xml 2026-07-12 20:07:43 +02:00
Aleksander db436d4630 wgui: tweak default theme a little bit 2026-07-12 20:04:47 +02:00
galister 895e95dcf4 dash sidebar + bottom buttons 2026-07-13 02:49:29 +09:00
galister 0516270dcb check box hover 2026-07-13 02:23:51 +09:00
galister 28a9ed2c27 slider & checkbox & keycaps 2026-07-13 01:52:20 +09:00
galister b1fb0d9347 monado tab + group box title colors 2026-07-13 01:39:41 +09:00
galister 031157ea8c settings tab styles 2026-07-13 01:33:51 +09:00
galister 6a064248dc palette requires restart + suppress fake errors 2026-07-13 00:30:22 +09:00
galister 7ab47ada4b fmt 2026-07-13 00:23:33 +09:00
galister 1febd4ed0d hide some palettes to make them fit 2026-07-13 00:22:38 +09:00
galister f2e2c37108 commented out light themes for now 2026-07-13 00:15:26 +09:00
galister b3e4e37992 child labels+sprites to inherit on-color from button 2026-07-13 00:08:51 +09:00
galister fb71975019 button sticky_color 2026-07-12 22:43:19 +09:00
galister 02f1de59b2 tweaks 2026-07-12 22:20:45 +09:00
galister 40430ea94e switch downloader from hyper to urec 2026-07-12 21:31:48 +09:00
galister 867c343c9b swich tokio to smol 2026-07-12 21:31:48 +09:00
galister 2e7a528857 swich tokio to smol 2026-07-12 21:31:48 +09:00
galister 0a7f8340d7 color tweaks & fmt 2026-07-12 21:27:44 +09:00
Aleksander e79dfcb719 dash-frontend: fix light theme everywhere 2026-07-12 13:23:32 +02:00
galister cb99676230 palette config loading 2026-07-12 19:51:43 +09:00
galister bc0fad60d5 color palette translation 2026-07-12 19:30:51 +09:00
Aleksander 3d2689c931 fix dashboard.xml and home.xml 2026-07-12 12:30:25 +02:00
galister dc58c1a555 wip palette selection & persistence 2026-07-12 19:27:47 +09:00
galister a4ce23881a rework palettes; add a range of wip themes 2026-07-12 19:01:17 +09:00
galister 62c210fc1d no num_enum 2026-07-12 16:58:29 +09:00
Aleksander c3cbc43f6f remove stale wgui docs, fix xmls for wayvr 2026-07-11 18:28:44 +02:00
Aleksander 155340b78d dash-frontend: finish theming, wayvr: refactoring, compilable version 2026-07-11 17:33:01 +02:00
galister d7bd243c41 update deps 2026-07-11 23:21:54 +09:00
galister 3c7e91c28c workspace deps sorting 2026-07-11 23:14:19 +09:00
Aleksander a00f2ab8db wgui: Color palette (wip)
(uidev & dash-frontend compilable)
2026-07-11 16:02:03 +02:00
galister b83c54334f format 2026-07-11 22:34:21 +09:00
galister f3dc87fa64 rework to having non-blocking screen cast prompts 2026-07-11 22:33:22 +09:00
Aleksander 9f854d0649 wgui: fix cosmic text not rendering anything 2026-07-10 17:47:55 +02:00
galister 000bc3c166 chore: update wgui deps 2026-07-10 22:51:01 +09:00
galister 1820e2b152 chore: update cosmic-text 2026-07-10 22:51:01 +09:00
galister a10b20aea9 chore: update taffy 2026-07-10 22:51:01 +09:00
oo8dev 80237d36cd
dash-frontend: Application pinning (#565)
* dash-frontend: App pinning (wip)
* App pinning and unpinning, preserve settings (done)
2026-07-07 16:53:55 +02:00
Aleksander cfeef62215 wlx-common: Audio runner on a separate thread 2026-07-07 15:34:57 +02:00
galister 730b546e84
[skip ci] update readme 2026-07-07 03:35:13 +09:00
179 changed files with 6782 additions and 3494 deletions

View File

@ -29,6 +29,9 @@ jobs:
- name: Cargo Build - name: Cargo Build
run: | run: |
../.github/workflows/scripts/appimage_build_wlx.sh ../.github/workflows/scripts/appimage_build_wlx.sh
- name: Build xwayland-satellite
run: |
../.github/workflows/scripts/appimage_build_satellite.sh
- name: Package AppImage - name: Package AppImage
run: | run: |
../.github/workflows/scripts/appimage_package.sh ../.github/workflows/scripts/appimage_package.sh

View File

@ -27,6 +27,9 @@ jobs:
- name: Cargo Build - name: Cargo Build
run: | run: |
../.github/workflows/scripts/appimage_build_wlx.sh ../.github/workflows/scripts/appimage_build_wlx.sh
- name: Build xwayland-satellite
run: |
../.github/workflows/scripts/appimage_build_satellite.sh
- name: Package AppImage - name: Package AppImage
run: | run: |
../.github/workflows/scripts/appimage_package.sh ../.github/workflows/scripts/appimage_package.sh

View File

@ -0,0 +1,17 @@
#!/bin/sh
sudo apt install \
xwayland \
libxcb1-dev \
libxcb-cursor-dev \
clang \
pkg-config
git clone https://github.com/Supreeeme/xwayland-satellite.git
cd xwayland-satellite
cargo build --release
chmod +x ../target/release/xwayland-satellite
cd ..
cp xwayland-satellite/target/release/xwayland-satellite ${APPDIR}/usr/bin

768
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -14,11 +14,17 @@ members = [
[workspace.dependencies] [workspace.dependencies]
anyhow = "1.0.100" anyhow = "1.0.100"
async-channel = "2.3"
async-io = "2.1"
bytes = { version = "1.11.1" } bytes = { version = "1.11.1" }
chrono = "0.4.42"
clap = { version = "4.5.53", features = ["derive"] } clap = { version = "4.5.53", features = ["derive"] }
dbus = { version = "0.9.12" }
glam = { version = "0.30.9", features = ["mint", "serde"] } glam = { version = "0.30.9", features = ["mint", "serde"] }
idmap = "0.2.2" idmap = "0.2.2"
idmap-derive = "0.2.22" idmap-derive = "0.2.22"
interprocess = { version = "2.2.3" }
libc = "0.2.178"
log = "0.4.29" log = "0.4.29"
pipewire = { version = "0.10", features = ["v0_3_33", "v0_3_65"] } pipewire = { version = "0.10", features = ["v0_3_33", "v0_3_65"] }
regex = "1.12.2" regex = "1.12.2"
@ -27,20 +33,30 @@ serde = { version = "1", features = ["derive"] }
serde_json = "1.0.145" serde_json = "1.0.145"
serde_json5 = "0.2.1" serde_json5 = "0.2.1"
slotmap = "1.1.1" slotmap = "1.1.1"
smallvec = "1.15.2"
smol = "2.0.2" smol = "2.0.2"
strum = { version = "0.27.2", features = ["derive"] } strum = { version = "0.27.2", features = ["derive"] }
tracing-subscriber = { version = "0.3.22", features = ["env-filter"] }
uuid = { version = "1.19.0", features = ["fast-rng", "serde", "v4"] } uuid = { version = "1.19.0", features = ["fast-rng", "serde", "v4"] }
vulkano = { version = "0.35.2", default-features = false, features = [ vulkano = {
"macros", version = "0.35.2",
] } default-features = false,
features = [
"macros",
]
}
vulkano-shaders = "0.35.0" vulkano-shaders = "0.35.0"
wayland-client = { version = "0.31" } wayland-client = { version = "0.31" }
wayland-protocols = { version = "0.32" } wayland-protocols = { version = "0.32" }
wayland-protocols-misc = { version = "0.3.12" } wayland-protocols-misc = { version = "0.3.12" }
winit = "0.30.12"
xdg = "3.0.0" xdg = "3.0.0"
[patch.crates-io] [patch.crates-io]
vulkano = { git = "https://github.com/galister/vulkano.git", rev = "cf7f92867928a56ce16b376037c1120f2b167678" } vulkano = {
git = "https://github.com/galister/vulkano.git",
rev = "cf7f92867928a56ce16b376037c1120f2b167678"
}
[profile.dev] [profile.dev]
opt-level = 1 opt-level = 1

View File

@ -84,7 +84,9 @@ Show and hide the working set using:
- Non-vive controller: double-tap B or Y on the left controller. - Non-vive controller: double-tap B or Y on the left controller.
- Vive controller: double-tap the menu button on the left controller (for SteamVR, the `showhide` binding must be bound) - Vive controller: double-tap the menu button on the left controller (for SteamVR, the `showhide` binding must be bound)
See the [bindings](#default-bindings) section on how to grab, move and resize overlay windows. Moving overlays: Grab + Joystick
Resizing overlays: Grab + Click + Joystick
### Pointer Modes AKA Laser Colors ### Pointer Modes AKA Laser Colors
@ -99,20 +101,29 @@ Please see the bindings section below on how to activate these modes.
The guide here uses the colors for ease of getting started. The guide here uses the colors for ease of getting started.
### Edit mode
Ways to enter Edit mode:
- Edit mode button on Watch face
- Taskbar (top of keyboard) hamburger menu
- Edit mode button on the title bar of WayVR apps (next to X button)
Edit mode lets you grab overlays and reposition them in yourset.
It's also possible to change overlay behavior and tweak your overlays in different way.
While in edit mode, try hovering over various overlays to see their options!
### The watch ### The watch
Check your left wrist for the watch. The watch is the primary tool for controlling the app. Check your left wrist for the watch. The watch is the primary tool for controlling the app.
The top of the watch shows device batteries, and the bottom shows your overlay controls. The top of the watch shows device batteries, and the bottom shows your overlay controls.
Enter edit mode (the leftmost button at the bottom) to edit your overlay sets.
While in edit mode, the watch can also be grabbed and passed between your hands. While in edit mode, the watch can also be grabbed and passed between your hands.
After grabbing, the watch will automatically attach to the hand that's opposite from the one that held it. After grabbing, the watch will automatically attach to the hand that's opposite from the one that held it.
In edit mode, try hovering over other overlays to see their advanced options!
### The screens ### The screens
Hovering a pointer over a screen will move the mouse. If more than one pointer is hovering over a screen, the pointer that was last used to click will take precedence. Hovering a pointer over a screen will move the mouse. If more than one pointer is hovering over a screen, the pointer that was last used to click will take precedence.
@ -126,6 +137,10 @@ The click type depends on the laser color:
### The keyboard ### The keyboard
The keyboard sends keys to the screen or WayVR application with the most recent mouse interaction.
On top of the keyboard is the taskbar, which lists WayVR apps as well as screens, mirrors and panels overlays.
Typing Typing
- Use the BLUE laser when typing regularly. - Use the BLUE laser when typing regularly.
@ -146,12 +161,9 @@ Typing
### Changing Bindings ### Changing Bindings
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) - 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 bug)
OpenXR (Monado/WiVRn): See [Docs: OpenXR Bindings](https://wayvr.org/docs/various/openxr-bindings/) - Monado/WiVRn: See WayVR Dashboard → Settings → Controls.
If your controllers are not supported, please reach out. \
We would like to work with you and include additional bindings.
## Customization ## Customization
@ -171,14 +183,16 @@ Check [here](https://wayvr.org/docs/various/troubleshooting/) for tips.
## Known Issues ## Known Issues
### Mouse is not where it should be ### Mouse movement is wrong
If the mouse is moving on a completely different screen, the screens were likely selected in the wrong order: **If the mouse is moving on a completely different screen**, the screens were likely selected in the wrong order:
- Go to Settings and press `Clear PipeWire tokens` and then `Restart software` - Go to Settings and press `Clear PipeWire tokens` and then `Restart software`
- Pay attention to your notifications, which tell you in which order to pick the screens. - Pay attention to your notifications, which tell you in which order to pick the screens.
- If notifications don't show, try starting WayVR from the terminal and look for instructions in there. - If notifications don't show, try starting WayVR from the terminal and look for instructions in there.
**If the mouse is on the correct screen but moves in weird ways**, enter Edit Mode, hover the screen and under Mouse Fixes, select the mode that works.
COSMIC desktop: COSMIC desktop:
- Due to limitations with COSMIC, the mouse can only move on a single display. - Due to limitations with COSMIC, the mouse can only move on a single display.
@ -189,19 +203,12 @@ X11 users:
- DPI scaling is not supported and will mess with the mouse. - DPI scaling is not supported and will mess with the mouse.
- Upright screens are not supported and will mess with the mouse. - Upright screens are not supported and will mess with the mouse.
### Screens are blank or black or frozen on SteamVR 2.14+ ### Screens or launched apps don't work when auto-started by SteamVR or WiVRn
As of SteamVR version 2.14.x, PipeWire capture no longer works when using Steam Link. SteamVR starts WayVR in the Steam runtime, and systemd/flatpak WiVRn can also start WayVR in a sub-optimal environment.
We're unable to completely troubleshoot how and why SteamVR interferes with PipeWire, so consider the following workarounds for the time being: The easiest fix is to start WayVR separately. It's possible to start WayVR before starting SteamVR/WiVRn by passing `--wait`:
- `wayvr --wait` or `/path/to/WayVR.AppImage --wait`
- Use another streamer, such as WiVRn or ALVR. Note that SteamVR on Linux is considered experimental by Valve themselves [disclaimer](https://github.com/ValveSoftware/SteamVR-for-Linux?tab=readme-ov-file#steamvr-release-notes-and-known-issues).
- If your desktop [supports ScreenCopy](https://wayland.app/protocols/wlr-screencopy-unstable-v1#compositor-support), go to Settings and set `Wayland capture method` to `ScreenCopy`
- If your desktop has an X11 mode, try using that
### Modifiers get stuck
Hiding the keyboard will unpress all of its buttons. Alternatively, go to Settings and use the `Restart software` button.
### X11 limitations ### X11 limitations

View File

@ -8,27 +8,32 @@ 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.workspace = true
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
lz4_flex = { version = "0.13.1", features = ["frame"] }
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
smol = { workspace = true } smol = { workspace = true }
smol-hyper = "0.1.1"
strum.workspace = true strum.workspace = true
uuid.workspace = true uuid.workspace = true
wayvr-ipc = { path = "../wayvr-ipc", default-features = false }
wgui = { path = "../wgui/" }
wlx-common = { path = "../wlx-common" }
xdg.workspace = true xdg.workspace = true
async-native-tls = "0.6.0"
http-body-util = "0.1.3"
keyvalues-parser = {
git = "https://codeberg.org/CosmicHarper/vdf-rs.git",
rev = "fc6dcbea9eb13cacb98dea40063f6f56cde6e145"
}
lz4_flex = { version = "0.13.1", features = ["frame"] }
smol-hyper = "0.1.1"
ureq = "3.3.0"
[features] [features]
default = ["monado"] default = ["monado"]
monado = [] monado = []

View File

@ -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="M15 14v-2h8v2zm-4 7l-3.175-2.85q-1.8-1.625-3.088-2.9t-2.125-2.4t-1.225-2.175T1 8.475q0-2.35 1.575-3.912T6.5 3q1.3 0 2.475.538T11 5.075q.85-1 2.025-1.537T15.5 3q2.125 0 3.563 1.288T20.85 7.3q-.45-.175-.9-.262t-.875-.088q-2.525 0-4.3 1.763T13 13q0 1.3.525 2.463T15 17.45q-.475.425-1.237 1.088T12.45 19.7z"/></svg>

After

Width:  |  Height:  |  Size: 538 B

View File

@ -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="M18.288 16.713Q18 16.425 18 16v-2h-2q-.425 0-.712-.288T15 13t.288-.712T16 12h2v-2q0-.425.288-.712T19 9t.713.288T20 10v2h2q.425 0 .713.288T23 13t-.288.713T22 14h-2v2q0 .425-.288.713T19 17t-.712-.288M1.75 23.626v-3.8zM10.3 20.2q-.35-.125-.625-.4q-1.1-1-2.6-2.275t-2.85-2.737t-2.287-3.063T1 8.475q0-2.35 1.575-3.912T6.5 3q1.3 0 2.475.538T11 5.075q.85-1 2.025-1.537T15.5 3q1.575 0 2.788.738T20.2 5.6q.275.475-.087.963T19.075 7h-.4q-2.125 0-3.9 1.713T13 13q0 1.05.338 1.988T14.4 16.7q.275.3.238.7t-.338.675l-1.975 1.75q-.275.275-.625.388t-.7.112t-.7-.125"/></svg>

After

Width:  |  Height:  |  Size: 785 B

View File

@ -1,6 +1,4 @@
<layout> <layout>
<include src="theme.xml" />
<theme> <theme>
<var key="side_size" value="48" /> <var key="side_size" value="48" />
<var key="top_size" value="40" /> <var key="top_size" value="40" />
@ -14,10 +12,8 @@
round="100%" round="100%"
width="~side_button_size" width="~side_button_size"
height="~side_button_size" height="~side_button_size"
color="#44444400" color="background_contrast(transparent)"
hover_color="#333333ff" border_color="background_contrast"
border_color="#00000000"
hover_border_color="#555555ff"
tooltip="${tooltip}" tooltip="${tooltip}"
tooltip_side="${tooltip_side}" tooltip_side="${tooltip_side}"
> >
@ -39,7 +35,7 @@
<rectangle <rectangle
width="100%" width="100%"
round="100%" round="100%"
color="~color_menu_dark" color="background_contrast"
flex_direction="column" flex_direction="column"
justify_content="center" justify_content="center"
align_items="center" align_items="center"
@ -49,7 +45,7 @@
<SideButton id="btn_side_apps" src_builtin="dashboard/apps.svg" tooltip="APPLICATIONS" tooltip_side="right" /> <SideButton id="btn_side_apps" src_builtin="dashboard/apps.svg" tooltip="APPLICATIONS" tooltip_side="right" />
<SideButton id="btn_side_games" src_builtin="dashboard/games.svg" tooltip="GAMES" tooltip_side="right" /> <SideButton id="btn_side_games" src_builtin="dashboard/games.svg" tooltip="GAMES" tooltip_side="right" />
<SideButton id="btn_side_monado" src_builtin="dashboard/monado.svg" tooltip="MONADO_RUNTIME" tooltip_side="right" /> <SideButton id="btn_side_monado" src_builtin="dashboard/monado.svg" tooltip="MONADO_RUNTIME" tooltip_side="right" />
<rectangle height="2" color="#FFFFFF33" width="~side_sprite_size" /> <rectangle height="2" color="outline" width="~side_sprite_size" />
<SideButton id="btn_side_settings" src_builtin="dashboard/settings.svg" tooltip="SETTINGS" tooltip_side="right" /> <SideButton id="btn_side_settings" src_builtin="dashboard/settings.svg" tooltip="SETTINGS" tooltip_side="right" />
</rectangle> </rectangle>
</div> </div>
@ -65,13 +61,14 @@
<div position="relative" width="100%" height="~top_size" min_height="~top_size" max_height="~top_size" justify_content="end"> <div position="relative" width="100%" height="~top_size" min_height="~top_size" max_height="~top_size" justify_content="end">
<!-- Title bar --> <!-- Title bar -->
<div width="100%" align_items="center" justify_content="center"> <div width="100%" align_items="center" justify_content="center">
<rectangle min_width="300" height="100%" consume_mouse_events="1" round="100%" color="~color_top_panel" align_items="center" justify_content="center" gap="8"> <rectangle min_width="300" height="100%" consume_mouse_events="1" round="100%" color="background_contrast" align_items="center" justify_content="center" gap="8">
<sprite id="sprite_titlebar_icon" width="24" height="24" /> <sprite id="sprite_titlebar_icon" width="24" height="24" color="on_background_contrast" />
<label id="label_titlebar_title" weight="bold" size="16" text="Title" /> <label id="label_titlebar_title" weight="bold" size="16" text="Title" color="on_background_contrast" />
</rectangle> </rectangle>
</div> </div>
<!-- Close button --> <!-- Close button -->
<Button position="absolute" id="btn_close" color="~color_top_panel" round="100%" width="~top_size" height="~top_size" tooltip="CLOSE_WINDOW" tooltip_side="left"> <Button position="absolute" id="btn_close" color="background_contrast" border_color="background_contrast" round="100%" width="~top_size" height="~top_size" tooltip="CLOSE_WINDOW" tooltip_side="left"
hover_color="danger" hover_border_color="danger(rgb-mult-0.5)" >
<sprite src_builtin="dashboard/close.svg" width="32" height="32" /> <sprite src_builtin="dashboard/close.svg" width="32" height="32" />
</Button> </Button>
</div> </div>
@ -80,8 +77,8 @@
<!-- color and color2 alpha will be modified at runtime --> <!-- color and color2 alpha will be modified at runtime -->
<rectangle <rectangle
id="rect_content" id="rect_content"
color2="#0d131a00" color2="background_contrast"
color="#252f5300" color="background"
gradient="vertical" gradient="vertical"
round="8" round="8"
flex_grow="1" flex_grow="1"
@ -92,7 +89,7 @@
<!-- 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="background_variant(opacity-0.5)" color2="background_variant(transparent)" />
<div overflow_x="scroll" overflow_y="scroll" width="100%" height="100%"> <div overflow_x="scroll" overflow_y="scroll" width="100%" height="100%">
<div <div
@ -120,36 +117,36 @@
flex_direction="row" flex_direction="row"
align_items="center" align_items="center"
justify_content="space_between" justify_content="space_between"
color="~color_bottom_panel" color="background"
> >
<!-- left gradient --> <!-- left gradient -->
<div position="absolute" width="100%" height="100%" justify_content="start"> <div position="absolute" width="100%" height="100%" justify_content="start">
<rectangle position="absolute" width="40%" height="100%" color="#0a0a0a" color2="#0a0a0a00" round="8" gradient="horizontal" /> <rectangle position="absolute" width="40%" height="100%" color="shadow" color2="shadow(transparent)" round="8" gradient="horizontal" />
</div> </div>
<!-- right gradient --> <!-- right gradient -->
<div position="absolute" width="100%" height="100%" justify_content="end"> <div position="absolute" width="100%" height="100%" justify_content="end">
<rectangle position="absolute" width="40%" height="100%" color2="#0a0a0a" color="#0a0a0a00" round="8" gradient="horizontal" /> <rectangle position="absolute" width="40%" height="100%" color2="shadow" color="shadow(transparent)" round="8" gradient="horizontal" />
</div> </div>
<!-- top shine --> <!-- top shine -->
<div position="absolute" width="100%" height="100%" justify_content="center"> <div position="absolute" width="100%" height="100%" justify_content="center">
<rectangle position="absolute" width="99%" height="2" color="#FFFFFF22" round="4" /> <rectangle position="absolute" width="99%" height="2" color="highlight(opacity-0.2)" round="4" />
</div> </div>
<!-- Left bottom side --> <!-- Left bottom side -->
<div margin_left="8"> <div margin_left="8">
<Button id="btn_audio" color="#FFFFFF00" border_color="#FFFFFF00" tooltip="AUDIO.VOLUME" tooltip_side="top"> <Button id="btn_audio" color="background(transparent)" border_color="background(transparent)" tooltip="AUDIO.VOLUME" tooltip_side="top">
<sprite src_builtin="dashboard/volume.svg" width="24" height="24" margin="8" /> <sprite src_builtin="dashboard/volume.svg" width="24" height="24" margin="8" />
</Button> </Button>
<Button id="btn_recenter" color="#FFFFFF00" border_color="#FFFFFF00" tooltip="ACTIONS.RECENTER_PLAYSPACE" tooltip_side="top"> <Button id="btn_recenter" color="background(transparent)" border_color="background(transparent)" tooltip="ACTIONS.RECENTER_PLAYSPACE" tooltip_side="top">
<sprite src_builtin="dashboard/recenter.svg" width="24" height="24" margin="8" /> <sprite src_builtin="dashboard/recenter.svg" width="24" height="24" margin="8" />
</Button> </Button>
</div> </div>
<!-- Right bottom side --> <!-- Right bottom side -->
<div margin_right="16"> <div margin_right="16">
<label id="label_time" size="16" weight="light" /> <label id="label_time" weight="bold" size="16" color="on_background" />
</div> </div>
</rectangle> </rectangle>
</div> </div>

View File

@ -1,11 +1,9 @@
<layout> <layout>
<include src="theme.xml" />
<macro name="dropdown_button" <macro name="dropdown_button"
flex_direction="row" flex_direction="row"
border="2" border="2"
color="#00000055" color="background"
border_color="#FFFFFF66" border_color="outline"
justify_content="space_between" /> justify_content="space_between" />
<!-- id, text, translation, tooltip, min_width --> <!-- id, text, translation, tooltip, min_width -->
@ -17,9 +15,9 @@
</div> </div>
<div gap="2"> <div gap="2">
<div padding_top="4" padding_bottom="4"> <div padding_top="4" padding_bottom="4">
<rectangle width="2" height="100%" color="#FFFFFF1A" /> <rectangle width="2" height="100%" color="outline" />
</div> </div>
<sprite margin_left="-4" width="30" height="30" color="~color_text" src_builtin="dashboard/down.svg" /> <sprite margin_left="-4" width="30" height="30" src_builtin="dashboard/down.svg" />
</div> </div>
</Button> </Button>
</template> </template>

View File

@ -1,12 +1,10 @@
<layout> <layout>
<include src="theme.xml" />
<macro name="group_box" <macro name="group_box"
flex_direction="column" flex_direction="column"
align_items="baseline" align_items="baseline"
border="2" border="2"
color="#00000055" color="background_contrast(opacity-0.5)"
border_color="#FFFFFF66" border_color="outline"
padding="12" padding="12"
gap="8" gap="8"
round="8" /> round="8" />
@ -14,9 +12,9 @@
<!-- src_builtin, 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_builtin="${src_builtin}" width="24" height="24" /> <sprite src_builtin="${src_builtin}" width="24" height="24" color="secondary" />
<label text="${text}" translation="${translation}" weight="bold" size="18" /> <label text="${text}" translation="${translation}" weight="bold" size="18" color="secondary" />
</div> </div>
<rectangle color="#FFFFFF44" width="100%" height="2" /> <rectangle color="outline" width="100%" height="2" />
</template> </template>
</layout> </layout>

View File

@ -2,7 +2,7 @@
<template name="LoadingWithText"> <template name="LoadingWithText">
<div id="root" width="100%" height="100%" align_items="center" justify_content="center"> <div id="root" width="100%" height="100%" align_items="center" justify_content="center">
<div flex_direction="row" gap="8" align_items="center" > <div flex_direction="row" gap="8" align_items="center" >
<sprite id="sprite_loading" src_builtin="dashboard/loading.svg" width="32" height="32"/> <sprite id="sprite_loading" src_builtin="dashboard/loading.svg" width="32" height="32" color="on_background"/>
<label translation="LOADING" weight="bold"/> <label translation="LOADING" weight="bold"/>
</div> </div>
</div> </div>
@ -10,7 +10,7 @@
<template name="LoadingWithoutText"> <template name="LoadingWithoutText">
<div id="root" width="100%" height="100%" align_items="center" justify_content="center"> <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"/> <sprite id="sprite_loading" src_builtin="dashboard/loading.svg" width="32" height="32" color="on_background"/>
</div> </div>
</template> </template>
</layout> </layout>

View File

@ -1,7 +1,5 @@
<layout> <layout>
<include src="theme.xml" /> <macro name="home_button" round="8" hover_color="primary" hover_border_color="on_primary" />
<macro name="home_button" round="8" color="#00000033" border_color="#FFFFFF77" hover_color="~color_accent" hover_border_color="#FFFFFF" />
<template name="MenuButton"> <template name="MenuButton">
<Button <Button

View File

@ -1,5 +1,5 @@
<layout> <layout>
<template name="Separator"> <template name="Separator">
<rectangle width="100%" height="1" color="#FFFFFF77" /> <rectangle width="100%" min_height="2" color="#FFFFFF10" />
</template> </template>
</layout> </layout>

View File

@ -1,14 +1,14 @@
<layout> <layout>
<include src="../theme.xml" /> <include src="../t_group_box.xml" />
<!-- tooltip, src, src_ext, name -->
<template name="AppEntry"> <template name="AppEntry">
<Button <Button
id="button" width="116" max_width="140" min_height="100" flex_grow="1" id="button" tooltip_side="bottom" tooltip="${tooltip}" width="116" max_width="140" min_height="100" flex_grow="1"
flex_direction="column" overflow="visible" align_items="center" justify_content="center" gap="4" flex_direction="column" overflow="visible" align_items="center" justify_content="center" gap="4"
color="#3385FF10"
> >
<div> <div>
<sprite src="${src}" src_ext="${src_ext}" width="64" height="64" /> <sprite src="${src}" src_ext="${src_ext}" width="64" height="64" color="#FFFFFF" parent_color="ignore" />
</div> </div>
<div align_items="center" justify_content="center"> <div align_items="center" justify_content="center">
<label width="116" weight="bold" text="${name}" size="12" wrap="1" align="center" padding_left="16" padding_right="16" /> <label width="116" weight="bold" text="${name}" size="12" wrap="1" align="center" padding_left="16" padding_right="16" />
@ -17,9 +17,9 @@
</template> </template>
<template name="CategoryText"> <template name="CategoryText">
<rectangle width="100%" flex_direction="column" round="8" border="2" gradient="vertical" color2="#4477FF09" color="#5588FF10" border_color="#FFFFFF00"> <rectangle width="100%" flex_direction="column" round="8" border="2" gradient="vertical" color2="background" color="background_variant" border_color="outline">
<label margin="8" text="${text}" weight="bold" size="22" /> <label margin="8" text="${text}" weight="bold" size="22" />
<rectangle height="2" color="~color_accent" margin_left="4" margin_right="4" /> <rectangle height="2" color="primary" margin_left="4" margin_right="4" />
</rectangle> </rectangle>
</template> </template>
@ -39,6 +39,15 @@
</rectangle> </rectangle>
</div> </div>
--> -->
<rectangle macro="group_box">
<div align_items="center" gap="8">
<sprite src_builtin="dashboard/heart_plus.svg" color="primary" min_width="24" min_height="24"/>
<label translation="PINNED_APPS"/>
</div>
<div id="pinned_apps_parent" flex_direction="row" flex_wrap="wrap" gap="4">
<!-- filled-in at runtime -->
</div>
</rectangle>
<div <div
id="app_list_parent" id="app_list_parent"
flex_direction="row" flex_direction="row"
@ -46,4 +55,4 @@
gap="4" gap="4"
/> />
</elements> </elements>
</layout> </layout>

View File

@ -5,7 +5,7 @@
<template name="Avatar"> <template name="Avatar">
<div flex_direction="column" gap="12" align_items="center"> <div flex_direction="column" gap="12" align_items="center">
<image src_builtin="${icon}" width="72" height="72" round="100%" position="relative"> <image src_builtin="${icon}" width="72" height="72" round="100%" position="relative">
<rectangle position="absolute" margin_left="-2" margin_top="-2" width="76" height="76" color="#FFFFFF" round="100%"/> <rectangle position="absolute" margin_left="-2" margin_top="-2" width="76" height="76" color="highlight" round="100%"/>
</image> </image>
<label size="14" text="${nickname}"/> <label size="14" text="${nickname}"/>
</div> </div>
@ -18,17 +18,17 @@
<div margin="32" flex_direction="column" align_items="center" justify_content="center" gap="16" height="100%"> <div margin="32" flex_direction="column" align_items="center" justify_content="center" gap="16" height="100%">
<div gap="16" align_items="center" width="100%"> <div gap="16" align_items="center" width="100%">
<sprite color="#FFAAAA" src_builtin="dashboard/donate.svg" min_width="64" min_height="64"/> <sprite color="#FFAAAA" src_builtin="dashboard/donate.svg" min_width="64" min_height="64"/>
<label wrap="1" size="20" translation="DONATE.DESCRIPTION"/> <label wrap="1" size="20" translation="DONATE.DESCRIPTION" color="on_background"/>
</div> </div>
<div flex_direction="column" gap="8" align_items="center" margin_top="16"> <div flex_direction="column" gap="8" align_items="center" margin_top="16">
<Button id="btn_donate" tooltip="DONATE.BUTTON_TOOLTIP" align_self="baseline"> <Button id="btn_donate" tooltip="DONATE.BUTTON_TOOLTIP" align_self="baseline">
<image src_builtin="dashboard/opencollective_logo.svg" width="252" height="42" margin="8" margin_left="16" margin_right="16"/> <image src_builtin="dashboard/opencollective_logo.svg" width="252" height="42" margin="8" margin_left="16" margin_right="16"/>
</Button> </Button>
<label text="opencollective.com/wayvr-org" color="~color-accent"/> <label text="opencollective.com/wayvr-org"/>
</div> </div>
<label weight="bold" size="24" translation="DONATE.THANK_YOU_WAYVR"/> <label weight="bold" size="24" translation="DONATE.THANK_YOU_WAYVR" color="on_background"/>
<div gap="32" margin_top="16"> <div gap="32" margin_top="16">
<Avatar icon="dashboard/avatars/galister.png" nickname="galister"/> <Avatar icon="dashboard/avatars/galister.png" nickname="galister"/>

View File

@ -9,7 +9,7 @@
flex_grow="1" flex_grow="1"
gap="24"> gap="24">
<image src_builtin="dashboard/splash.png" width="300" height="89" /> <image src_builtin="dashboard/splash.png" width="300" height="89" />
<label id="label_hello" size="32" weight="bold" /> <label id="label_hello" size="32" weight="bold" color="on_background" />
<!-- main button list --> <!-- main button list -->
<div flex_direction="row" gap="8"> <div flex_direction="row" gap="8">
@ -21,15 +21,15 @@
<div gap="8"> <div gap="8">
<Button id="btn_welcome_screen" macro="home_button" tooltip="APP_SETTINGS.SHOW_WELCOME_SCREEN"> <Button id="btn_welcome_screen" macro="home_button" tooltip="APP_SETTINGS.SHOW_WELCOME_SCREEN">
<sprite src_builtin="dashboard/welcome.svg" width="32" height="32" margin="16"/> <sprite src_builtin="dashboard/welcome.svg" width="32" height="32" margin="16" />
</Button> </Button>
<Button id="btn_website" macro="home_button" tooltip_str="wayvr.org"> <Button id="btn_website" macro="home_button" tooltip_str="wayvr.org">
<sprite src_builtin="dashboard/globe.svg" width="32" height="32" margin="16"/> <sprite src_builtin="dashboard/globe.svg" width="32" height="32" margin="16" />
</Button> </Button>
<Button id="btn_donate" macro="home_button" tooltip="DONATE.SUPPORT_US"> <Button id="btn_donate" macro="home_button" tooltip="DONATE.SUPPORT_US">
<sprite src_builtin="dashboard/opencollective.svg" width="32" height="32" margin="16"/> <sprite src_builtin="dashboard/opencollective.svg" width="32" height="32" margin="16" />
</Button> </Button>
</div> </div>
</div> </div>

View File

@ -2,8 +2,8 @@
<elements> <elements>
<div gap="4"> <div gap="4">
<Tabs id="tabs"> <Tabs id="tabs">
<Tab name="general_settings" translation="GENERAL_SETTINGS" sprite_src_builtin="dashboard/settings.svg" />
<Tab name="process_list" translation="PROCESS_LIST" sprite_src_builtin="dashboard/cpu.svg" /> <Tab name="process_list" translation="PROCESS_LIST" sprite_src_builtin="dashboard/cpu.svg" />
<Tab name="general_settings" translation="GENERAL_SETTINGS" sprite_src_builtin="dashboard/settings.svg" />
<Tab name="debug_timings" translation="DEBUG_INFO" sprite_src_builtin="dashboard/not_a_bug.svg" /> <Tab name="debug_timings" translation="DEBUG_INFO" sprite_src_builtin="dashboard/not_a_bug.svg" />
</Tabs> </Tabs>
<div gap="4" id="content" width="100%"> <div gap="4" id="content" width="100%">
@ -11,4 +11,4 @@
</div> </div>
</div> </div>
</elements> </elements>
</layout> </layout>

View File

@ -39,7 +39,7 @@
</template> </template>
<template name="DangerButton"> <template name="DangerButton">
<Button id="${id}" color="#AA3333" height="32" tooltip="${translation}_HELP" padding="4" gap="8"> <Button id="${id}" color="danger" border_color="danger(rgb-mult-0.5)" height="32" tooltip="${translation}_HELP" padding="4" gap="8">
<sprite src_builtin="${icon}" height="24" width="24" /> <sprite src_builtin="${icon}" height="24" width="24" />
<label align="left" translation="${translation}" weight="bold" min_width="200" /> <label align="left" translation="${translation}" weight="bold" min_width="200" />
</Button> </Button>
@ -47,7 +47,8 @@
<template name="AutostartApp"> <template name="AutostartApp">
<div id="${id}_root" flex_direction="row"> <div id="${id}_root" flex_direction="row">
<Button id="${id}" color="#AA3333" height="24" padding="4" margin_top="-2" margin_bottom="-2"> <Button id="${id}" color="#AA3333" height="24" padding="4" margin_top="-2" margin_bottom="-2"
hover_color="danger" hover_border_color="danger(rgb-mult-0.5)" >
<sprite src_builtin="dashboard/close.svg" height="20" width="20" /> <sprite src_builtin="dashboard/close.svg" height="20" width="20" />
</Button> </Button>
<div padding_left="8"> <div padding_left="8">
@ -56,17 +57,19 @@
</div> </div>
</template> </template>
<macro name="tab_style" color="background" />
<elements> <elements>
<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 macro="tab_style" 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 macro="tab_style" 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 macro="tab_style" 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 macro="tab_style" 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 macro="tab_style" 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 macro="tab_style" 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 macro="tab_style" 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 macro="tab_style" 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 --> <!-- filled-in at runtime -->

View File

@ -3,13 +3,13 @@
<template name="PageTitle"> <template name="PageTitle">
<div position="absolute" align_self="start" justify_self="center" width="100%" margin_top="16" gap="8" align_items="center" justify_content="center"> <div position="absolute" align_self="start" justify_self="center" width="100%" margin_top="16" gap="8" align_items="center" justify_content="center">
<sprite width="48" height="48" src_builtin="${icon}" color="#FFFFFF"/> <sprite width="48" height="48" src_builtin="${icon}" color="#FFFFFF"/>
<label size="28" weight="bold" translation="${translation}" shadow="#000000" shadow_x="3" shadow_y="3" /> <label size="28" weight="bold" translation="${translation}" color="#FFFFFF" shadow="#000000" shadow_x="3" shadow_y="3" />
</div> </div>
</template> </template>
<!-- text --> <!-- text -->
<template name="TextDesc"> <template name="TextDesc">
<label wrap="1" translation="${translation}" size="20" width="500" shadow="#000000" weight="bold"/> <label wrap="1" translation="${translation}" size="20" width="500" color="#FFFFFF" shadow="#000000" weight="bold"/>
</template> </template>
<template name="TextDescBig"> <template name="TextDescBig">

View File

@ -10,12 +10,12 @@
<label size="20" translation="WELCOME.GUIDE_INTRO"/> <label size="20" translation="WELCOME.GUIDE_INTRO"/>
<div gap="4" align_items="center" margin_top="8"> <div gap="4" align_items="center" margin_top="8">
<sprite min_width="16" min_height="16" src_builtin="dashboard/info.svg"/> <sprite min_width="16" min_height="16" src_builtin="dashboard/info.svg" color="on_background"/>
<label wrap="1" size="14" translation="WELCOME.REPOSITION_THIS_WINDOW_BY_GRABBING"/> <label wrap="1" size="14" translation="WELCOME.REPOSITION_THIS_WINDOW_BY_GRABBING"/>
</div> </div>
<div gap="4" align_items="center"> <div gap="4" align_items="center">
<sprite min_width="16" min_height="16" src_builtin="dashboard/info.svg"/> <sprite min_width="16" min_height="16" src_builtin="dashboard/info.svg" color="on_background"/>
<label wrap="1" size="14" translation="WELCOME.LAST_PAGE_INFO"/> <label wrap="1" size="14" translation="WELCOME.LAST_PAGE_INFO"/>
</div> </div>

View File

@ -6,12 +6,12 @@
<div macro="video_overlay_content"> <div macro="video_overlay_content">
<PageTitle icon="dashboard/click.svg" translation="WELCOME.LASER_COLORS"/> <PageTitle icon="dashboard/click.svg" translation="WELCOME.LASER_COLORS"/>
<div position="relative" flex_direction="row" gap="16" margin="16" align_self="end" justify_self="end" align_items="center"> <div position="relative" flex_direction="row" gap="16" margin="16" align_self="end" justify_self="end" align_items="center">
<rectangle flex_direction="column" gap="8" padding="8" round="4" border_color="~color_accent" border="2" > <rectangle flex_direction="column" gap="8" padding="8" round="4" border_color="outline" border="2" >
<label size="12" weight="bold" color="#00FFFF" translation="WELCOME.REGULAR_MODE_BLUE_LASER"/> <label size="12" weight="bold" color="#00FFFF" translation="WELCOME.REGULAR_MODE_BLUE_LASER"/>
<label size="12" weight="bold" color="#FFAA00" translation="WELCOME.RIGHT_CLICK_MODE_ORANGE_LASER"/> <label size="12" weight="bold" color="#FFAA00" translation="WELCOME.RIGHT_CLICK_MODE_ORANGE_LASER"/>
<label size="12" weight="bold" color="#FF88FF" translation="WELCOME.MIDDLE_CLICK_MODE_PURPLE_LASER"/> <label size="12" weight="bold" color="#FF88FF" translation="WELCOME.MIDDLE_CLICK_MODE_PURPLE_LASER"/>
</rectangle> </rectangle>
<label wrap="1" size="18" width="450" weight="bold" shadow="#000000" translation="WELCOME.LASER_FUNCTIONALITY"/> <label wrap="1" size="18" width="450" weight="bold" color="#ffffff" shadow="#000000" translation="WELCOME.LASER_FUNCTIONALITY"/>
</div> </div>
</div> </div>
</Video> </Video>

View File

@ -10,7 +10,7 @@
<label shadow="#000000" size="14" weight="bold" wrap="1" color="#aaffcc" translation="WELCOME.GRAB_ONE_HAND"/> <label shadow="#000000" size="14" weight="bold" wrap="1" color="#aaffcc" translation="WELCOME.GRAB_ONE_HAND"/>
<label shadow="#000000" size="14" weight="bold" wrap="1" color="#ccaaff" translation="WELCOME.GRAB_BOTH_HANDS"/> <label shadow="#000000" size="14" weight="bold" wrap="1" color="#ccaaff" translation="WELCOME.GRAB_BOTH_HANDS"/>
<div align_items="center" gap="8"> <div align_items="center" gap="8">
<sprite src_builtin="dashboard/info.svg" width="24" height="24" color="#ffff00"/> <sprite src_builtin="dashboard/info.svg" min_width="48" min_height="48" max_width="48" max_height="48" color="#ffff00"/>
<label shadow="#000000" size="16" weight="bold" wrap="1" color="#ffff00" translation="WELCOME.CENTER_MARKER"/> <label shadow="#000000" size="16" weight="bold" wrap="1" color="#ffff00" translation="WELCOME.CENTER_MARKER"/>
</div> </div>
</div> </div>

View File

@ -5,22 +5,22 @@
<div flex_direction="row" align_items="center"> <div flex_direction="row" align_items="center">
<image src_builtin="dashboard/check_3d.png" width="256" height="256"/> <image src_builtin="dashboard/check_3d.png" width="256" height="256"/>
<div flex_direction="column" gap="16" max_width="480"> <div flex_direction="column" gap="16" max_width="480">
<TextDescBig translation="WELCOME.ALL_SET" color="#AAFFAA"/> <TextDescBig translation="WELCOME.ALL_SET" color="tertiary"/>
<rectangle color="#AAFFAA33" width="100%" min_height="2"/> <rectangle color="#AAFFAA33" width="100%" min_height="2"/>
<label size="16" wrap="1" translation="WELCOME.BASICS"/> <label size="16" wrap="1" translation="WELCOME.BASICS"/>
<label size="16" wrap="1" translation="WELCOME.FEATURES_DOCUMENTATION"/> <label size="16" wrap="1" translation="WELCOME.FEATURES_DOCUMENTATION"/>
<Button id="btn_wayvr_org" align_self="baseline" min_height="32"> <Button id="btn_wayvr_org" align_self="baseline" min_height="32">
<sprite src_builtin="dashboard/globe.svg" width="24" height="24" color="~color_accent" margin="8"/> <sprite src_builtin="dashboard/globe.svg" width="24" height="24" margin="8"/>
<label size="18" weight="bold" text="wayvr.org" margin_right="8" /> <label size="18" weight="bold" text="wayvr.org" margin_right="8" />
</Button> </Button>
<rectangle color="#FFFFFF33" width="100%" min_height="2"/> <rectangle color="#FFFFFF33" width="100%" min_height="2"/>
<Button id="btn_home_screen" min_height="48"> <Button id="btn_home_screen" min_height="48" >
<sprite src_builtin="dashboard/check.svg" width="32" height="32" color="~color_accent" margin="8"/> <sprite src_builtin="dashboard/check.svg" width="32" height="32" margin="8"/>
<label size="20" weight="bold" translation="WELCOME.FINISH_TUTORIAL" margin_right="8" /> <label color="on_background" size="20" weight="bold" translation="WELCOME.FINISH_TUTORIAL" margin_right="8" />
</Button> </Button>
</div> </div>
</div> </div>

View File

@ -1,8 +0,0 @@
<layout>
<theme>
<var key="color_menu_dark" value="#0A0A0ACC" />
<var key="color_accent" value="#008cff" />
<var key="color_top_panel" value="#00000099" />
<var key="color_bottom_panel" value="#141e28" />
</theme>
</layout>

View File

@ -1,8 +1,8 @@
<layout> <layout>
<template name="Subtext"> <template name="Subtext">
<div flex_direction="row" gap="8"> <div flex_direction="row" gap="8" max_width="100%">
<label weight="bold" text="${title}" /> <label weight="bold" text="${title}" wrap="1" />
<label id="${label_id}" /> <label id="${label_id}" wrap="1" />
</div> </div>
</template> </template>
@ -15,12 +15,10 @@
<elements> <elements>
<div flex_direction="row" gap="16" flex_grow="1"> <div flex_direction="row" gap="16" flex_grow="1">
<rectangle macro="group_box" id="icon_parent" padding="16" color="#0033aa66" color2="#00000022" gradient="vertical" justify_content="center"> <rectangle macro="group_box" id="icon_parent" padding="16" color="background_variant(opacity-0.5)" color2="background(opacity-0.5)" gradient="vertical" justify_content="center"/>
<div flex_direction="column" gap="8" flex_grow="1">
</rectangle>
<div flex_direction="column" gap="8" flex_grow="1" max_width="720">
<label id="label_title" weight="bold" size="32" overflow="hidden" /> <label id="label_title" weight="bold" size="32" overflow="hidden" />
<Subtext label_id="label_exec" overflow="hidden" /> <Subtext label_id="label_exec"/>
<Separator /> <Separator />
<RadioGroup id="radio_compositor" flex_direction="row" gap="16"> <RadioGroup id="radio_compositor" flex_direction="row" gap="16">
<RadioBox translation="APP_LAUNCHER.MODE.NATIVE" value="Native" checked="1" /> <RadioBox translation="APP_LAUNCHER.MODE.NATIVE" value="Native" checked="1" />
@ -30,14 +28,14 @@
<label translation="APP_LAUNCHER.RES_TITLE" /> <label translation="APP_LAUNCHER.RES_TITLE" />
<RadioGroup id="radio_res" flex_direction="row" gap="16"> <RadioGroup id="radio_res" flex_direction="row" gap="16">
<RadioBox text="1440p" value="Res1440" /> <RadioBox text="1440p" value="Res1440" />
<RadioBox text="1080p" value="Res1080" checked="1" /> <RadioBox text="1080p" value="Res1080"/>
<RadioBox text="720p" value="Res720" /> <RadioBox text="720p" value="Res720" />
<RadioBox text="480p" value="Res480" /> <RadioBox text="480p" value="Res480" />
</RadioGroup> </RadioGroup>
<Separator /> <Separator />
<label translation="APP_LAUNCHER.ASPECT_TITLE" /> <label translation="APP_LAUNCHER.ASPECT_TITLE" />
<RadioGroup id="radio_orientation" flex_direction="row" gap="16"> <RadioGroup id="radio_orientation" flex_direction="row" gap="16">
<RadioBox translation="APP_LAUNCHER.ASPECT.WIDE" value="Wide" tooltip_str="16:9" checked="1" /> <RadioBox translation="APP_LAUNCHER.ASPECT.WIDE" value="Wide" tooltip_str="16:9"/>
<RadioBox translation="APP_LAUNCHER.ASPECT.SEMI_WIDE" value="SemiWide" tooltip_str="3:2" /> <RadioBox translation="APP_LAUNCHER.ASPECT.SEMI_WIDE" value="SemiWide" tooltip_str="3:2" />
<RadioBox translation="APP_LAUNCHER.ASPECT.SQUARE" value="Square" tooltip_str="1:1" /> <RadioBox translation="APP_LAUNCHER.ASPECT.SQUARE" value="Square" tooltip_str="1:1" />
<RadioBox translation="APP_LAUNCHER.ASPECT.SEMI_TALL" value="SemiTall" tooltip_str="2:3" /> <RadioBox translation="APP_LAUNCHER.ASPECT.SEMI_TALL" value="SemiTall" tooltip_str="2:3" />
@ -51,14 +49,22 @@
<RadioBox translation="APP_LAUNCHER.POS.STATIC" value="Static" tooltip_str="APP_LAUNCHER.POS.STATIC_HELP" /> <RadioBox translation="APP_LAUNCHER.POS.STATIC" value="Static" tooltip_str="APP_LAUNCHER.POS.STATIC_HELP" />
</RadioGroup --> </RadioGroup -->
<Separator /> <Separator />
<div flex_direction="row" justify_content="space_between" gap="16"> <CheckBox id="cb_autostart" translation="APP_LAUNCHER.AUTOSTART" align_self="baseline"/>
<CheckBox id="cb_autostart" translation="APP_LAUNCHER.AUTOSTART" /> <div flex_direction="row" align_items="end" justify_content="center" gap="8" margin_top="8">
<Button id="btn_launch" align_self="baseline" color="#44ce22FF" padding_top="4" padding_bottom="4" round="8" padding_right="12" min_height="40"> <Button id="btn_unpin" color="danger" padding_left="8" padding_right="8" round="8" min_height="40">
<sprite src_builtin="dashboard/heart_minus.svg" width="32" height="32" />
<label translation="REMOVE_FROM_FAVOURITES" weight="bold" size="17" />
</Button>
<Button id="btn_pin" color="secondary" padding_left="8" padding_right="8" round="8" min_height="40" tooltip="ADD_TO_FAVOURITES_HINT">
<sprite src_builtin="dashboard/heart_plus.svg" width="32" height="32" />
<label translation="ADD_TO_FAVOURITES" weight="bold" size="17" />
</Button>
<Button id="btn_launch" color="primary" padding_left="8" padding_right="8" round="8" min_width="200" min_height="40">
<sprite src_builtin="dashboard/play.svg" width="32" height="32" /> <sprite src_builtin="dashboard/play.svg" width="32" height="32" />
<label translation="APP_LAUNCHER.LAUNCH" weight="bold" size="17" shadow="#00000099" /> <label translation="APP_LAUNCHER.LAUNCH" weight="bold" size="17" />
</Button> </Button>
</div> </div>
</div> </div>
</div> </div>
</elements> </elements>
</layout> </layout>

View File

@ -5,8 +5,8 @@
<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_builtin="${device_icon}" width="16" height="16" /> <sprite src_builtin="${device_icon}" width="16" height="16" color="on_background" />
<label text="${device_name}" margin_right="8" size="12" weight="bold" /> <label text="${device_name}" margin_right="8" size="12" weight="bold" color="on_background" />
</div> </div>
<div width="100%" align_items="center"> <div width="100%" align_items="center">
<CheckBox id="checkbox" /> <CheckBox id="checkbox" />
@ -20,7 +20,7 @@
<template name="Card"> <template name="Card">
<rectangle macro="group_box"> <rectangle macro="group_box">
<div width="100%" align_items="center" justify_content="center"> <div width="100%" align_items="center" justify_content="center">
<label text="${card_name}" size="12" weight="bold" /> <label text="${card_name}" size="12" weight="bold" color="on_background" />
</div> </div>
<Button id="btn_card" text="${profile_name}" width="100%" height="32" /> <Button id="btn_card" text="${profile_name}" width="100%" height="32" />
</rectangle> </rectangle>
@ -29,7 +29,7 @@
<template name="SelectAudioProfileText"> <template name="SelectAudioProfileText">
<div align_items="center" gap="8"> <div align_items="center" gap="8">
<Button width="48" height="32" id="btn_back"> <Button width="48" height="32" id="btn_back">
<sprite src_builtin="dashboard/back.svg" width="24" height="24" /> <sprite src_builtin="dashboard/back.svg" width="24" height="24" color="on_background" />
</Button> </Button>
<label translation="AUDIO.SELECT_AUDIO_CARD_PROFILE" size="14" weight="bold" /> <label translation="AUDIO.SELECT_AUDIO_CARD_PROFILE" size="14" weight="bold" />
</div> </div>
@ -58,7 +58,8 @@
sprite_src_builtin="dashboard/magic_wand.svg" sprite_src_builtin="dashboard/magic_wand.svg"
min_width="32" min_width="32"
tooltip="AUDIO.AUTO_SWITCH_TO_VR_AUDIO" tooltip="AUDIO.AUTO_SWITCH_TO_VR_AUDIO"
color="~color_accent" color="primary"
sprite_color="on_primary"
tooltip_side="right" /> tooltip_side="right" />
<BottomButton id="btn_sinks" src_builtin="dashboard/volume.svg" translation="AUDIO.SPEAKERS" /> <BottomButton id="btn_sinks" src_builtin="dashboard/volume.svg" translation="AUDIO.SPEAKERS" />

View File

@ -14,8 +14,8 @@
<rectangle macro="group_box" id="${id}"> <rectangle macro="group_box" id="${id}">
<!-- top row --> <!-- top row -->
<div flex_direction="row" gap="8" align_items="center"> <div flex_direction="row" gap="8" align_items="center">
<sprite src_builtin="dashboard/controller.svg" width="24" height="24"/> <sprite src_builtin="dashboard/controller.svg" width="24" height="24" color="on_background"/>
<label translation="${translation}" color="~color_accent" weight="bold" size="14" /> <label translation="${translation}" color="primary" weight="bold" size="14" />
</div> </div>
<!-- filled-in at runtime --> <!-- filled-in at runtime -->
</rectangle> </rectangle>
@ -26,7 +26,7 @@
<div flex_direction="column" gap="8" align_items="center" overflow_y="scroll" width="100%" padding="16"> <div flex_direction="column" gap="8" align_items="center" overflow_y="scroll" width="100%" padding="16">
<div id="list_parent" flex_direction="column" gap="6" width="100%"></div> <div id="list_parent" flex_direction="column" gap="6" width="100%"></div>
</div> </div>
<rectangle color="#000000cc" flex_direction="row" gap="8" justify_content="end" padding="8"> <rectangle color="background_contrast" flex_direction="row" gap="8" justify_content="end" padding="8">
<Button id="btn_cancel" sprite_src_builtin="dashboard/cancel.svg" min_width="100" height="32" translation="APP_SETTINGS.CANCEL" /> <Button id="btn_cancel" sprite_src_builtin="dashboard/cancel.svg" min_width="100" height="32" translation="APP_SETTINGS.CANCEL" />
<Button id="btn_save" sprite_src_builtin="dashboard/check.svg" min_width="100" height="32" translation="APP_SETTINGS.SAVE" /> <Button id="btn_save" sprite_src_builtin="dashboard/check.svg" min_width="100" height="32" translation="APP_SETTINGS.SAVE" />
</rectangle> </rectangle>

View File

@ -0,0 +1,27 @@
<layout>
<template name="ColorPaletteButton">
<Button id="${id}" width="350" flex_direction="column" justify_content="space_around" tooltip="${tooltip}" tooltip_side="bottom"
color="${background}" hover_color="${background_variant}" border_color="${outline}" hover_border_color="${highlight}" padding="16" margin="4" >
<label text="${text}" size="24" weight="bold" color="${on_background}" parent_color="ignore"/>
<div flex_direction="row" width="100%" justify_content="space_evenly" padding_top="16" >
<rectangle round="100%" width="32" height="32" color="${primary}" border="2" border_color="${on_primary}" />
<rectangle round="100%" width="32" height="32" color="${secondary}" border="2" border_color="${on_secondary}" />
<rectangle round="100%" width="32" height="32" color="${tertiary}" border="2" border_color="${on_tertiary}" />
<rectangle round="100%" width="32" height="32" color="${danger}" border="2" border_color="${on_danger}" />
</div>
</Button>
</template>
<template name="CustomPaletteButton">
<Button id="custom_btn" width="350" flex_direction="column" justify_content="space_around" padding="16" margin="4" >
<label translation="APP_SETTINGS.COLOR_PALETTE_CUSTOM_URL" size="16" weight="bold" />
<label translation="APP_SETTINGS.COLOR_PALETTE_CUSTOM_URL2" size="14" color="on_background_variant" />
</Button>
</template>
<elements>
<div flex_direction="column" gap="8" width="100%" align_items="center">
<div id="list_parent" flex_grow="1" flex_wrap="wrap" flex_direction="row" gap="4" width="100%" justify_content="center"></div>
</div>
</elements>
</layout>

View File

@ -10,7 +10,7 @@
<div id="content" flex_direction="column" gap="8" width="100%"> <div id="content" flex_direction="column" gap="8" width="100%">
<label translation="DOWNLOADING_FILE" size="24" weight="bold"/> <label translation="DOWNLOADING_FILE" size="24" weight="bold"/>
<Separator/> <Separator/>
<label id="label_target_path" color="~color_text_translucent" /> <label id="label_target_path" color="on_background" />
<div gap="8" align_items="center"> <div gap="8" align_items="center">
<div id="loading_parent"/> <div id="loading_parent"/>

View File

@ -9,11 +9,11 @@
<label weight="bold" id="label_author" text="Unknown" /> <label weight="bold" id="label_author" text="Unknown" />
</div> </div>
<label id="label_description" wrap="1" text="No description available" /> <label id="label_description" wrap="1" text="No description available" />
<Button id="btn_launch" align_self="baseline" color="#44ce22FF" padding_top="4" padding_bottom="4" round="8" padding_right="12" min_width="200" min_height="40"> <Button id="btn_launch" align_self="baseline" color="primary" border_color="primary(rgb-mult-0.5)" padding_top="4" padding_bottom="4" round="8" padding_right="12" min_width="200" min_height="40">
<sprite src_builtin="dashboard/play.svg" width="32" height="32" /> <sprite src_builtin="dashboard/play.svg" width="32" height="32" />
<label text="Launch" weight="bold" size="17" shadow="#00000099" /> <label text="Launch" weight="bold" size="17" />
</Button> </Button>
</div> </div>
</div> </div>
</elements> </elements>
</layout> </layout>

View File

@ -2,7 +2,7 @@
<!-- id, text --> <!-- id, text -->
<template name="InputProfileButton"> <template name="InputProfileButton">
<Button id="${id}" width="350" gap="8" justify_content="start" > <Button id="${id}" width="350" gap="8" justify_content="start" >
<sprite margin_left="8" src_builtin="dashboard/controller.svg" min_width="24" min_height="24"/> <sprite margin_left="8" src_builtin="dashboard/controller.svg" min_width="24" min_height="24" />
<label text="${text}" weight="bold"/> <label text="${text}" weight="bold"/>
</Button> </Button>
</template> </template>

View File

@ -1,6 +1,4 @@
<layout> <layout>
<include src="../theme.xml" />
<elements> <elements>
<div <div
consume_mouse_events="1" consume_mouse_events="1"
@ -11,33 +9,33 @@
flex_direction="column" flex_direction="column"
position="absolute" position="absolute"
> >
<!-- Top black bar --> <!-- Top bar -->
<rectangle <rectangle
position="relative" position="relative"
color="#000000" color="background_contrast"
width="100%" height="48" width="100%" height="48"
> >
<!-- Shine effect at the top --> <!-- Shine effect at the top -->
<rectangle position="absolute" width="100%" height="2" round="4" color="#ffffff22" /> <rectangle position="absolute" width="100%" height="3" round="4" color="highlight(opacity-0.1)" />
<!-- Top bar contents --> <!-- Top bar contents -->
<div gap="16" align_items="center"> <div gap="16" align_items="center">
<!-- Back button --> <!-- Back button -->
<Button id="but_back" width="48" height="48" color="#ffffff00" border_color="#ffffff00"> <Button id="but_back" width="48" height="48" color="on_background_contrast(transparent)" border_color="on_background_contrast(transparent)">
<sprite src_builtin="dashboard/back.svg" width="24" height="24" /> <sprite src_builtin="dashboard/back.svg" width="24" height="24" />
</Button> </Button>
<!-- Title --> <!-- Title -->
<label id="popup_title" weight="bold" size="18" /> <label id="popup_title" color="on_background_contrast" weight="bold" size="18" />
</div> </div>
</rectangle> </rectangle>
<rectangle <rectangle
width="100%" width="100%"
height="100%" height="100%"
color="#010310fe" color="background_contrast(opacity-0.98)"
color2="#051c55fc" color2="background(opacity-0.99)"
gradient="vertical" gradient="vertical"
position="relative" position="relative"
> >

View File

@ -20,7 +20,7 @@
<div id="resolution_buttons" gap="8" flex_direction="row"> <div id="resolution_buttons" gap="8" flex_direction="row">
<!-- filled-in at runtime --> <!-- filled-in at runtime -->
</div> </div>
<image id="image" width="400" height="200" round="8" border="2" border_color="~color_accent"/> <image id="image" width="400" height="200" round="8" border="2" border_color="primary"/>
<label id="label_author" weight="bold"/> <label id="label_author" weight="bold"/>
<label id="label_description" wrap="1"/> <label id="label_description" wrap="1"/>
<Separator/> <Separator/>

View File

@ -34,7 +34,7 @@
--> -->
<template name="ResolutionPip"> <template name="ResolutionPip">
<rectangle color="${color}" padding_left="4" padding_right="4" padding_top="2" padding_bottom="2" round="3" align_self="start"> <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"/> <label text="${text}" weight="bold" size="12" color="#FFFFFF" shadow="#000000" shadow_x="2" shadow_y="2"/>
</rectangle> </rectangle>
</template> </template>
</layout> </layout>

View File

@ -182,7 +182,19 @@
"REMOVE_UNUSED_MODELS": "Sie schalten die Sprache-zu-Text-Funktion aus.\nMöchten Sie die heruntergeladenen Whisper-Modelle entfernen, um Speicherplatz freizugeben?" "REMOVE_UNUSED_MODELS": "Sie schalten die Sprache-zu-Text-Funktion aus.\nMöchten Sie die heruntergeladenen Whisper-Modelle entfernen, um Speicherplatz freizugeben?"
}, },
"WHISPER_MODEL": "Sprach-zu-Text-Modell", "WHISPER_MODEL": "Sprach-zu-Text-Modell",
"WHISPER_MODEL_HELP": "Zu verwendendes Whisper-Modell.\nGrößere Modelle liefern genauere\nErgebnisse, verbrauchen jedoch mehr VRAM." "WHISPER_MODEL_HELP": "Zu verwendendes Whisper-Modell.\nGrößere Modelle liefern genauere\nErgebnisse, verbrauchen jedoch mehr VRAM.",
"DEFAULT_OVERLAY_SCALE": "Standardgröße des Overlays",
"DEFAULT_OVERLAY_SCALE_HELP": "Neue Overlays werden in dieser Skalierung erstellt.",
"COLOR_PALETTE": "Farbpalette",
"COLOR_PALETTES": "Farbpaletten",
"COLOR_PALETTE_CURRENT": "Derzeit aktives Thema.",
"COLOR_PALETTE_ACTIVATE": "Klicken Sie hier, um dieses Thema zu verwenden.",
"APPLY_CHANGES_RESTART": "WayVR neu starten, um die Änderungen zu übernehmen?",
"COLOR_PALETTE_HELP": "Klicken, um Farbpaletten zu durchsuchen.\nUm eigene zu erstellen, besuche die Webseite!",
"COLOR_PALETTE_CUSTOM_URL": "Füge dein eigenes benutzerdefiniertes Thema hinzu!",
"COLOR_PALETTE_CUSTOM_URL2": "Klicken Sie hier, um das Web-Handbuch zu öffnen.",
"SNAP_ANGLE_DEG": "Snap-Winkel für Fensterneigung und Rollwinkel",
"SNAP_ANGLE_DEG_HELP": "Gegriffene Fenster rasten in diesem Winkel auf der Nick- und Rollachse ein."
}, },
"HELLO": "Hallo!", "HELLO": "Hallo!",
"AUDIO": { "AUDIO": {
@ -274,8 +286,6 @@
"WELCOME": { "WELCOME": {
"ALL_SET": "Du bist startklar!", "ALL_SET": "Du bist startklar!",
"BASICS": "Diese Grundlagen ermöglichen Ihnen einen einfachen Einstieg in die Nutzung von WayVR.", "BASICS": "Diese Grundlagen ermöglichen Ihnen einen einfachen Einstieg in die Nutzung von WayVR.",
"CENTER_MARKER": "Der Mittelpunkt-Marker bestimmt das Zentrum Ihres Sets.",
"DOUBLE_PRESS_TO_TOGGLE": "Drücken Sie zweimal B (auf Index) oder Y (auf Meta), um die Sichtbarkeit Ihres Arbeitsbereichs umzuschalten. Probieren Sie es jetzt aus.",
"FEATURES_DOCUMENTATION": "Es gibt viele weitere Funktionen, die es wert sind, angepasst zu werden. Weitere Informationen finden Sie in der Dokumentation unter", "FEATURES_DOCUMENTATION": "Es gibt viele weitere Funktionen, die es wert sind, angepasst zu werden. Weitere Informationen finden Sie in der Dokumentation unter",
"FINISH_TUTORIAL": "Tutorial beenden", "FINISH_TUTORIAL": "Tutorial beenden",
"GRAB_BOTH_HANDS": "Greifen Sie es mit beiden Händen, wenn Sie ein bestimmtes Overlay in Ihrem Set bewegen möchten.", "GRAB_BOTH_HANDS": "Greifen Sie es mit beiden Händen, wenn Sie ein bestimmtes Overlay in Ihrem Set bewegen möchten.",
@ -294,7 +304,15 @@
"RIGHT_CLICK_MODE_ORANGE_LASER": "— Rechtsklick-Modus: Oranger Laser", "RIGHT_CLICK_MODE_ORANGE_LASER": "— Rechtsklick-Modus: Oranger Laser",
"THANKS_FOR_INSTALLING": "Vielen Dank für die Installation von WayVR!", "THANKS_FOR_INSTALLING": "Vielen Dank für die Installation von WayVR!",
"WATCH": "Watch", "WATCH": "Watch",
"WORKING_SET": "Arbeits-Set" "WORKING_SET": "Arbeits-Set",
"DOUBLE_PRESS_TO_TOGGLE": "Drücke B (beim Index) oder Y (beim Meta) doppelt, um dein Set anzuzeigen, neu zu zentrieren oder auszublenden. Probier es jetzt aus.",
"CENTER_MARKER": "Der Zentrierungsmarker ist das, was bei der Neuzentrierung vor dir erscheint. Platziere Overlays darum herum!"
}, },
"DOWNLOAD": "Herunterladen" "DOWNLOAD": "Herunterladen",
"SAVED_TO_FAVOURITES": "Zu Favoriten hinzugefügt",
"ADD_TO_FAVOURITES": "Zu Favoriten hinzufügen",
"ADD_TO_FAVOURITES_HINT": "Voreinstellung speichern und an den App-Drawer anheften",
"REMOVE_FROM_FAVOURITES": "Aus Favoriten entfernen",
"EMPTY": "Leer",
"PINNED_APPS": "Angepinnte Anwendungen"
} }

View File

@ -96,6 +96,14 @@
"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", "COLOR_KEYING": "Color keying",
"COLOR_PALETTE": "Color palette",
"COLOR_PALETTE_HELP": "Click to browse color palettes.\nTo create custom ones, see website!",
"COLOR_PALETTES": "Color palettes",
"COLOR_PALETTE_CURRENT": "Currently active theme.",
"COLOR_PALETTE_CUSTOM_URL": "Add your own custom theme!",
"COLOR_PALETTE_CUSTOM_URL2": "Click to open web manual.",
"COLOR_PALETTE_ACTIVATE": "Click to use this theme.",
"APPLY_CHANGES_RESTART": "Restart WayVR to apply changes?",
"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",
@ -166,6 +174,8 @@
"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", "SELECT_VARIANT": "Select variant",
"SNAP_ANGLE_DEG": "Window pitch & roll snap angle",
"SNAP_ANGLE_DEG_HELP": "Grabbed windows snap to this angle on pitch and roll axis.",
"ENABLE_WATCH": "Enable watch", "ENABLE_WATCH": "Enable watch",
"SETS_ON_WATCH": "Sets on watch", "SETS_ON_WATCH": "Sets on watch",
"SHOW_WELCOME_SCREEN": "Show welcome screen", "SHOW_WELCOME_SCREEN": "Show welcome screen",
@ -240,8 +250,8 @@
"WELCOME": { "WELCOME": {
"ALL_SET": "You're all set!", "ALL_SET": "You're all set!",
"BASICS": "These basics will allow you to start using WayVR with ease.", "BASICS": "These basics will allow you to start using WayVR with ease.",
"CENTER_MARKER": "Center marker determines the center or your set.", "CENTER_MARKER": "Center marker is what will appear in front of you when recentering. Place overlays around it!",
"DOUBLE_PRESS_TO_TOGGLE": "Double-press B (on Index) or Y (on Meta) to toggle the visibility of your workspace. Try it now.", "DOUBLE_PRESS_TO_TOGGLE": "Double-press B (on Index) or Y (on Meta) to show, recenter or hide your set. Try it now.",
"FEATURES_DOCUMENTATION": "There are many other features worth customizing. For more information, check the documentation at", "FEATURES_DOCUMENTATION": "There are many other features worth customizing. For more information, check the documentation at",
"FINISH_TUTORIAL": "Finish tutorial", "FINISH_TUTORIAL": "Finish tutorial",
"GRAB_BOTH_HANDS": "Grab it with both hands if you want to move a specific overlay in your set.", "GRAB_BOTH_HANDS": "Grab it with both hands if you want to move a specific overlay in your set.",
@ -298,5 +308,11 @@
"TARGET_PATH": "Target path", "TARGET_PATH": "Target path",
"TERMINATE_PROCESS": "Terminate process", "TERMINATE_PROCESS": "Terminate process",
"VERSION": "Version", "VERSION": "Version",
"WIDTH": "Width" "WIDTH": "Width",
"SAVED_TO_FAVOURITES": "Saved to favourites",
"ADD_TO_FAVOURITES": "Add to favourites",
"ADD_TO_FAVOURITES_HINT": "Save preset and pin it to the app drawer",
"REMOVE_FROM_FAVOURITES": "Remove from favourites",
"EMPTY": "Empty",
"PINNED_APPS": "Pinned applications"
} }

View File

@ -182,7 +182,19 @@
"REMOVE_UNUSED_MODELS": "Estás desactivando la función de voz a texto.\n¿Te gustaría eliminar los modelos de Whisper descargados para liberar espacio?" "REMOVE_UNUSED_MODELS": "Estás desactivando la función de voz a texto.\n¿Te gustaría eliminar los modelos de Whisper descargados para liberar espacio?"
}, },
"WHISPER_MODEL": "Modelo de voz a texto", "WHISPER_MODEL": "Modelo de voz a texto",
"WHISPER_MODEL_HELP": "Modelo de Whisper a utilizar.\nLos modelos más grandes producen\nresultados más precisos, pero consumen más VRAM." "WHISPER_MODEL_HELP": "Modelo de Whisper a utilizar.\nLos modelos más grandes producen\nresultados más precisos, pero consumen más VRAM.",
"DEFAULT_OVERLAY_SCALE": "Tamaño de la superposición predeterminado",
"DEFAULT_OVERLAY_SCALE_HELP": "Genera nuevas capas de superposición con esta escala.",
"COLOR_PALETTE": "Paleta de colores",
"COLOR_PALETTES": "Paletas de colores",
"COLOR_PALETTE_CURRENT": "Tema actualmente activo.",
"COLOR_PALETTE_ACTIVATE": "Haz clic para usar este tema.",
"APPLY_CHANGES_RESTART": "¿Reiniciar WayVR para aplicar los cambios?",
"COLOR_PALETTE_HELP": "Haz clic para explorar paletas de colores.\nPara crear paletas personalizadas, ¡visita el sitio web!",
"COLOR_PALETTE_CUSTOM_URL": "¡Añade tu propio tema personalizado!",
"COLOR_PALETTE_CUSTOM_URL2": "Haz clic para abrir el manual web.",
"SNAP_ANGLE_DEG": "Ángulo de ajuste de cabeceo y alabeo de la ventana",
"SNAP_ANGLE_DEG_HELP": "Las ventanas agarradas se ajustan a este ángulo en los ejes de cabeceo y balanceo."
}, },
"HELLO": "¡Hola!", "HELLO": "¡Hola!",
"AUDIO": { "AUDIO": {
@ -274,8 +286,6 @@
"WELCOME": { "WELCOME": {
"ALL_SET": "¡Todo listo!", "ALL_SET": "¡Todo listo!",
"BASICS": "Estos conceptos básicos te permitirán empezar a usar WayVR con facilidad.", "BASICS": "Estos conceptos básicos te permitirán empezar a usar WayVR con facilidad.",
"CENTER_MARKER": "El marcador central determina el centro de tu set.",
"DOUBLE_PRESS_TO_TOGGLE": "Pulsa dos veces B (en Index) o Y (en Meta) para alternar la visibilidad de tu espacio de trabajo. Pruébalo ahora.",
"FEATURES_DOCUMENTATION": "Hay muchas otras funciones que merece la pena personalizar. Para más información, consulta la documentación en", "FEATURES_DOCUMENTATION": "Hay muchas otras funciones que merece la pena personalizar. Para más información, consulta la documentación en",
"FINISH_TUTORIAL": "Finalizar tutorial", "FINISH_TUTORIAL": "Finalizar tutorial",
"GRAB_BOTH_HANDS": "Agarra el elemento con ambas manos si quieres mover un overlay específico en tu set.", "GRAB_BOTH_HANDS": "Agarra el elemento con ambas manos si quieres mover un overlay específico en tu set.",
@ -294,7 +304,15 @@
"RIGHT_CLICK_MODE_ORANGE_LASER": "— Modo de clic derecho: Láser naranja", "RIGHT_CLICK_MODE_ORANGE_LASER": "— Modo de clic derecho: Láser naranja",
"THANKS_FOR_INSTALLING": "¡Gracias por instalar WayVR!", "THANKS_FOR_INSTALLING": "¡Gracias por instalar WayVR!",
"WATCH": "Watch", "WATCH": "Watch",
"WORKING_SET": "Set de trabajo" "WORKING_SET": "Set de trabajo",
"DOUBLE_PRESS_TO_TOGGLE": "Presiona dos veces B (en Index) o Y (en Meta) para mostrar, recentrar u ocultar tu set. Pruébalo ahora.",
"CENTER_MARKER": "El marcador de centrado es lo que aparecerá frente a ti al recentrar. ¡Coloca las capas alrededor de él!"
}, },
"DOWNLOAD": "Descargar" "DOWNLOAD": "Descargar",
"SAVED_TO_FAVOURITES": "Guardado en favoritos",
"ADD_TO_FAVOURITES": "Añadir a favoritos",
"ADD_TO_FAVOURITES_HINT": "Guarda el ajuste preestablecido y ánclalo al cajón de aplicaciones",
"REMOVE_FROM_FAVOURITES": "Eliminar de favoritos",
"EMPTY": "Vacío",
"PINNED_APPS": "Aplicaciones ancladas"
} }

View File

@ -203,7 +203,19 @@
"REMOVE_UNUSED_MODELS": "Stai disattivando la funzione di sintesi vocale.\nDesideri rimuovere i modelli Whisper scaricati per liberare spazio?" "REMOVE_UNUSED_MODELS": "Stai disattivando la funzione di sintesi vocale.\nDesideri rimuovere i modelli Whisper scaricati per liberare spazio?"
}, },
"WHISPER_MODEL": "Modello di sintesi vocale (speech-to-text)", "WHISPER_MODEL": "Modello di sintesi vocale (speech-to-text)",
"WHISPER_MODEL_HELP": "Modello Whisper da utilizzare.\nI modelli più grandi producono risultati\npiù accurati, ma consumano più VRAM." "WHISPER_MODEL_HELP": "Modello Whisper da utilizzare.\nI modelli più grandi producono risultati\npiù accurati, ma consumano più VRAM.",
"DEFAULT_OVERLAY_SCALE": "Dimensioni overlay predefinite",
"DEFAULT_OVERLAY_SCALE_HELP": "Genera nuovi overlay con questa scala.",
"COLOR_PALETTE": "Tavolozza colori",
"COLOR_PALETTES": "Tavolozze colori",
"COLOR_PALETTE_CURRENT": "Tema attualmente attivo.",
"COLOR_PALETTE_ACTIVATE": "Clicca per usare questo tema.",
"APPLY_CHANGES_RESTART": "Riavviare WayVR per applicare le modifiche?",
"COLOR_PALETTE_HELP": "Clicca per sfogliare le palette di colori.\nPer crearne di personalizzate, visita il sito web!",
"COLOR_PALETTE_CUSTOM_URL": "Aggiungi il tuo tema personalizzato!",
"COLOR_PALETTE_CUSTOM_URL2": "Clicca per aprire il manuale web.",
"SNAP_ANGLE_DEG": "Angolo di scatto beccheggio e rollio finestra",
"SNAP_ANGLE_DEG_HELP": "Le finestre afferrate si allineano a questo angolo sugli assi di beccheggio e rollio."
}, },
"APPLICATION_LAUNCHER": "Lanciatore applicazioni", "APPLICATION_LAUNCHER": "Lanciatore applicazioni",
"APPLICATION_STARTED": "Applicazione avviata", "APPLICATION_STARTED": "Applicazione avviata",
@ -274,8 +286,6 @@
"WELCOME": { "WELCOME": {
"ALL_SET": "Sei pronto!", "ALL_SET": "Sei pronto!",
"BASICS": "Queste nozioni di base ti permetteranno di iniziare a usare WayVR con facilità.", "BASICS": "Queste nozioni di base ti permetteranno di iniziare a usare WayVR con facilità.",
"CENTER_MARKER": "Il marker centrale determina il centro del tuo set.",
"DOUBLE_PRESS_TO_TOGGLE": "Premi due volte B (su Index) o Y (su Meta) per attivare/disattivare la visibilità del tuo workspace. Provalo subito.",
"FEATURES_DOCUMENTATION": "Ci sono molte altre funzioni che vale la pena personalizzare. Per ulteriori informazioni, consulta la documentazione all'indirizzo", "FEATURES_DOCUMENTATION": "Ci sono molte altre funzioni che vale la pena personalizzare. Per ulteriori informazioni, consulta la documentazione all'indirizzo",
"FINISH_TUTORIAL": "Termina tutorial", "FINISH_TUTORIAL": "Termina tutorial",
"GRAB_BOTH_HANDS": "Afferralo con entrambe le mani se vuoi spostare un overlay specifico nel tuo set.", "GRAB_BOTH_HANDS": "Afferralo con entrambe le mani se vuoi spostare un overlay specifico nel tuo set.",
@ -294,7 +304,15 @@
"RIGHT_CLICK_MODE_ORANGE_LASER": "— Modalità tasto destro: Laser arancione", "RIGHT_CLICK_MODE_ORANGE_LASER": "— Modalità tasto destro: Laser arancione",
"THANKS_FOR_INSTALLING": "Grazie per aver installato WayVR!", "THANKS_FOR_INSTALLING": "Grazie per aver installato WayVR!",
"WATCH": "Watch", "WATCH": "Watch",
"WORKING_SET": "Set di lavoro" "WORKING_SET": "Set di lavoro",
"DOUBLE_PRESS_TO_TOGGLE": "Premi due volte B (su Index) o Y (su Meta) per mostrare, ricentrare o nascondere il tuo set. Provalo ora.",
"CENTER_MARKER": "Il marker di centraggio è ciò che apparirà davanti a te durante il ricentramento. Posiziona gli overlay intorno ad esso!"
}, },
"DOWNLOAD": "Scarica" "DOWNLOAD": "Scarica",
"SAVED_TO_FAVOURITES": "Salvato nei preferiti",
"ADD_TO_FAVOURITES": "Aggiungi ai preferiti",
"ADD_TO_FAVOURITES_HINT": "Salva il preset e fissalo al cassetto delle app",
"REMOVE_FROM_FAVOURITES": "Rimuovi dai preferiti",
"EMPTY": "Vuoto",
"PINNED_APPS": "Applicazioni fissate"
} }

View File

@ -182,7 +182,19 @@
"REMOVE_UNUSED_MODELS": "文字起こし機能をオフにします。\n空き容量を増やすために、ダウンロード済みのモデルファイルを削除しますか" "REMOVE_UNUSED_MODELS": "文字起こし機能をオフにします。\n空き容量を増やすために、ダウンロード済みのモデルファイルを削除しますか"
}, },
"WHISPER_MODEL": "文字起こしモデル", "WHISPER_MODEL": "文字起こしモデル",
"WHISPER_MODEL_HELP": "使用するWhisperモデル。\nモデルが大きいほど精度は向上しますが、\nVRAMの消費量が増加します。" "WHISPER_MODEL_HELP": "使用するWhisperモデル。\nモデルが大きいほど精度は向上しますが、\nVRAMの消費量が増加します。",
"DEFAULT_OVERLAY_SCALE": "デフォルトのオーバーレイサイズ",
"DEFAULT_OVERLAY_SCALE_HELP": "新しいオーバーレイをこのスケールで生成します。",
"COLOR_PALETTE": "カラーパレット",
"COLOR_PALETTES": "カラーパレット",
"COLOR_PALETTE_CURRENT": "現在有効なテーマ。",
"COLOR_PALETTE_ACTIVATE": "クリックしてこのテーマを適用します。",
"APPLY_CHANGES_RESTART": "変更を適用するためにWayVRを再起動しますか",
"COLOR_PALETTE_HELP": "クリックしてカラーパレットを選択してください。\nカスタムパレットの作成については、ウェブサイトを参照してください",
"COLOR_PALETTE_CUSTOM_URL": "カスタムテーマを追加しましょう!",
"COLOR_PALETTE_CUSTOM_URL2": "クリックしてウェブマニュアルを開く。",
"SNAP_ANGLE_DEG": "ウィンドウのピッチ・ロールのスナップ角度",
"SNAP_ANGLE_DEG_HELP": "掴んでいるウィンドウが、ピッチ軸とロール軸でこの角度にスナップします。"
}, },
"HELLO": "こんにちは!", "HELLO": "こんにちは!",
"AUDIO": { "AUDIO": {
@ -274,8 +286,6 @@
"WELCOME": { "WELCOME": {
"ALL_SET": "準備完了です!", "ALL_SET": "準備完了です!",
"BASICS": "これらの基本操作を学ぶことで、WayVRを簡単に使い始めることができます。", "BASICS": "これらの基本操作を学ぶことで、WayVRを簡単に使い始めることができます。",
"CENTER_MARKER": "センターマーカーは、セットの中心を決定します。",
"DOUBLE_PRESS_TO_TOGGLE": "Bボタン (Index) または Yボタン (Meta) をダブルプレスして、ワークスペースの表示/非表示を切り替えます。今すぐ試してみてください。",
"FEATURES_DOCUMENTATION": "他にもカスタマイズする価値のある機能がたくさんあります。詳細については、以下のドキュメントを確認してください。", "FEATURES_DOCUMENTATION": "他にもカスタマイズする価値のある機能がたくさんあります。詳細については、以下のドキュメントを確認してください。",
"FINISH_TUTORIAL": "チュートリアルを終了する", "FINISH_TUTORIAL": "チュートリアルを終了する",
"GRAB_BOTH_HANDS": "セット内の特定のオーバーレイを移動させたい場合は、両手で掴んでください。", "GRAB_BOTH_HANDS": "セット内の特定のオーバーレイを移動させたい場合は、両手で掴んでください。",
@ -294,7 +304,15 @@
"RIGHT_CLICK_MODE_ORANGE_LASER": "— 右クリックモード:オレンジ色のレーザー", "RIGHT_CLICK_MODE_ORANGE_LASER": "— 右クリックモード:オレンジ色のレーザー",
"THANKS_FOR_INSTALLING": "WayVRをインストールしていただきありがとうございます", "THANKS_FOR_INSTALLING": "WayVRをインストールしていただきありがとうございます",
"WATCH": "Watch", "WATCH": "Watch",
"WORKING_SET": "ワーキングセット" "WORKING_SET": "ワーキングセット",
"DOUBLE_PRESS_TO_TOGGLE": "BボタンIndexまたはYボタンMetaを2回押して、セットの表示・中心合わせ・非表示を切り替えてください。今すぐ試してみましょう。",
"CENTER_MARKER": "リセンタリング時に目の前に表示されるのがセンターマーカーです。このマーカーを中心にオーバーレイを配置してください!"
}, },
"DOWNLOAD": "ダウンロード" "DOWNLOAD": "ダウンロード",
} "SAVED_TO_FAVOURITES": "お気に入りに保存しました",
"ADD_TO_FAVOURITES": "お気に入りに追加",
"ADD_TO_FAVOURITES_HINT": "プリセットを保存してアプリドロワーにピン留めします",
"REMOVE_FROM_FAVOURITES": "お気に入りから削除",
"EMPTY": "空",
"PINNED_APPS": "ピン留めされたアプリケーション"
}

View File

@ -177,7 +177,19 @@
"REMOVE_UNUSED_MODELS": "Wyłączasz funkcję zamiany mowy na tekst.\nCzy chcesz usunąć pobrane modele Whisper, aby zwolnić miejsce?" "REMOVE_UNUSED_MODELS": "Wyłączasz funkcję zamiany mowy na tekst.\nCzy chcesz usunąć pobrane modele Whisper, aby zwolnić miejsce?"
}, },
"WHISPER_MODEL": "Model mowy na tekst", "WHISPER_MODEL": "Model mowy na tekst",
"WHISPER_MODEL_HELP": "Model Whisper do wykorzystania.\nWiększe modele dają dokładniejsze\nwyniki, ale zużywają więcej pamięci VRAM." "WHISPER_MODEL_HELP": "Model Whisper do wykorzystania.\nWiększe modele dają dokładniejsze\nwyniki, ale zużywają więcej pamięci VRAM.",
"DEFAULT_OVERLAY_SCALE": "Domyślny rozmiar nakładki",
"DEFAULT_OVERLAY_SCALE_HELP": "Twórz nowe nakładki w tej skali.",
"COLOR_PALETTE": "Paleta kolorów",
"COLOR_PALETTES": "Palety kolorów",
"COLOR_PALETTE_CURRENT": "Obecnie aktywny motyw.",
"COLOR_PALETTE_ACTIVATE": "Kliknij, aby użyć tego motywu.",
"APPLY_CHANGES_RESTART": "Zrestartować WayVR, aby zastosować zmiany?",
"COLOR_PALETTE_HELP": "Kliknij, aby przeglądać palety kolorów.\nAby stworzyć własne, odwiedź stronę internetową!",
"COLOR_PALETTE_CUSTOM_URL": "Dodaj własny motyw!",
"COLOR_PALETTE_CUSTOM_URL2": "Kliknij, aby otworzyć instrukcję internetową.",
"SNAP_ANGLE_DEG": "Kąt przyciągania pochylenia i przechyłu okna",
"SNAP_ANGLE_DEG_HELP": "Chwycone okna przyciągają się do tego kąta na osi pochylenia i obrotu."
}, },
"APPLICATION_LAUNCHER": "Uruchamiacz aplikacji", "APPLICATION_LAUNCHER": "Uruchamiacz aplikacji",
"APPLICATIONS": "Aplikacje", "APPLICATIONS": "Aplikacje",
@ -274,8 +286,6 @@
"WELCOME": { "WELCOME": {
"ALL_SET": "Wszystko gotowe!", "ALL_SET": "Wszystko gotowe!",
"BASICS": "Te podstawowe informacje pozwolą Ci z łatwością zacząć korzystać z WayVR.", "BASICS": "Te podstawowe informacje pozwolą Ci z łatwością zacząć korzystać z WayVR.",
"CENTER_MARKER": "Znacznik środka określa środek Twojego zestawu.",
"DOUBLE_PRESS_TO_TOGGLE": "Naciśnij dwukrotnie przycisk B (na Index) lub Y (na Meta), aby przełączyć widoczność swojej przestrzeni roboczej. Spróbuj teraz.",
"FEATURES_DOCUMENTATION": "Istnieje wiele innych funkcji, które warto spersonalizować. Aby uzyskać więcej informacji, sprawdź dokumentację pod adresem:", "FEATURES_DOCUMENTATION": "Istnieje wiele innych funkcji, które warto spersonalizować. Aby uzyskać więcej informacji, sprawdź dokumentację pod adresem:",
"FINISH_TUTORIAL": "Zakończ samouczek", "FINISH_TUTORIAL": "Zakończ samouczek",
"GRAB_BOTH_HANDS": "Chwyć to obiema rękami, jeśli chcesz przenieść konkretny overlay ze swojego zestawu.", "GRAB_BOTH_HANDS": "Chwyć to obiema rękami, jeśli chcesz przenieść konkretny overlay ze swojego zestawu.",
@ -294,7 +304,15 @@
"RIGHT_CLICK_MODE_ORANGE_LASER": "— Tryb prawego przycisku: Pomarańczowy laser", "RIGHT_CLICK_MODE_ORANGE_LASER": "— Tryb prawego przycisku: Pomarańczowy laser",
"THANKS_FOR_INSTALLING": "Dziękujemy za zainstalowanie WayVR!", "THANKS_FOR_INSTALLING": "Dziękujemy za zainstalowanie WayVR!",
"WATCH": "Watch", "WATCH": "Watch",
"WORKING_SET": "Aktywny zestaw" "WORKING_SET": "Aktywny zestaw",
"DOUBLE_PRESS_TO_TOGGLE": "Naciśnij dwukrotnie przycisk B (na Index) lub Y (na Meta), aby wyświetlić, wycentrować lub ukryć swój zestaw. Wypróbuj to teraz.",
"CENTER_MARKER": "Znacznik środka to punkt, który pojawi się przed Tobą podczas centrowania. Umieść wokół niego nakładki!"
}, },
"DOWNLOAD": "Pobierz" "DOWNLOAD": "Pobierz",
"SAVED_TO_FAVOURITES": "Dodano do ulubionych",
"ADD_TO_FAVOURITES": "Dodaj do ulubionych",
"ADD_TO_FAVOURITES_HINT": "Zapisz ustawienie i przypnij je do szuflady aplikacji",
"REMOVE_FROM_FAVOURITES": "Usuń z ulubionych",
"EMPTY": "Puste",
"PINNED_APPS": "Przypięte aplikacje"
} }

View File

@ -203,7 +203,19 @@
"REMOVE_UNUSED_MODELS": "您正在关闭语音转文本功能。\n是否删除已下载的 Whisper 模型以释放空间?" "REMOVE_UNUSED_MODELS": "您正在关闭语音转文本功能。\n是否删除已下载的 Whisper 模型以释放空间?"
}, },
"WHISPER_MODEL": "语音转文本模型", "WHISPER_MODEL": "语音转文本模型",
"WHISPER_MODEL_HELP": "要使用的 Whisper 模型。\n模型越大结果越准确\n但消耗的显存 (VRAM) 越多。" "WHISPER_MODEL_HELP": "要使用的 Whisper 模型。\n模型越大结果越准确\n但消耗的显存 (VRAM) 越多。",
"DEFAULT_OVERLAY_SCALE": "默认叠加层大小",
"DEFAULT_OVERLAY_SCALE_HELP": "以该比例生成新的叠加层。",
"COLOR_PALETTE": "调色板",
"COLOR_PALETTES": "配色方案",
"COLOR_PALETTE_CURRENT": "当前激活的主题。",
"COLOR_PALETTE_ACTIVATE": "点击以使用此主题。",
"APPLY_CHANGES_RESTART": "重启 WayVR 以应用更改吗?",
"COLOR_PALETTE_HELP": "点击浏览配色方案。\n如需创建自定义配色请参阅网站",
"COLOR_PALETTE_CUSTOM_URL": "添加自定义主题!",
"COLOR_PALETTE_CUSTOM_URL2": "点击打开网页版手册。",
"SNAP_ANGLE_DEG": "窗口俯仰与翻滚捕捉角度",
"SNAP_ANGLE_DEG_HELP": "抓取的窗口在俯仰轴和翻滚轴上将对齐到此角度。"
}, },
"APPLICATION_LAUNCHER": "应用启动器", "APPLICATION_LAUNCHER": "应用启动器",
"APPLICATION_STARTED": "应用已启动", "APPLICATION_STARTED": "应用已启动",
@ -274,8 +286,6 @@
"WELCOME": { "WELCOME": {
"ALL_SET": "你已准备就绪!", "ALL_SET": "你已准备就绪!",
"BASICS": "这些基础知识将帮助您轻松开始使用 WayVR。", "BASICS": "这些基础知识将帮助您轻松开始使用 WayVR。",
"CENTER_MARKER": "中心标记确定了您 Set 的中心位置。",
"DOUBLE_PRESS_TO_TOGGLE": "双击 BIndex 控制器)或 YMeta 控制器)来切换工作区显示。立即尝试一下。",
"FEATURES_DOCUMENTATION": "还有许多其他值得自定义的功能。欲了解更多信息,请查看文档:", "FEATURES_DOCUMENTATION": "还有许多其他值得自定义的功能。欲了解更多信息,请查看文档:",
"FINISH_TUTORIAL": "完成教程", "FINISH_TUTORIAL": "完成教程",
"GRAB_BOTH_HANDS": "如果你想移动 Set 中的特定叠加层,请用双手抓取。", "GRAB_BOTH_HANDS": "如果你想移动 Set 中的特定叠加层,请用双手抓取。",
@ -294,7 +304,15 @@
"RIGHT_CLICK_MODE_ORANGE_LASER": "— 右键模式:橙色激光", "RIGHT_CLICK_MODE_ORANGE_LASER": "— 右键模式:橙色激光",
"THANKS_FOR_INSTALLING": "感谢安装 WayVR", "THANKS_FOR_INSTALLING": "感谢安装 WayVR",
"WATCH": "Watch", "WATCH": "Watch",
"WORKING_SET": "工作集" "WORKING_SET": "工作集",
"DOUBLE_PRESS_TO_TOGGLE": "双击 B 键 (Index) 或 Y 键 (Meta) 以显示、重新居中或隐藏您的集合。现在就试一下吧。",
"CENTER_MARKER": "中心标记是重新居中时出现在你面前的标志。请将覆盖层放置在它周围!"
}, },
"DOWNLOAD": "下载" "DOWNLOAD": "下载",
"SAVED_TO_FAVOURITES": "已保存至收藏夹",
"ADD_TO_FAVOURITES": "添加到收藏",
"ADD_TO_FAVOURITES_HINT": "保存预设并将其固定到应用抽屉",
"REMOVE_FROM_FAVOURITES": "从收藏中移除",
"EMPTY": "空",
"PINNED_APPS": "已固定应用程序"
} }

Binary file not shown.

View File

@ -2,10 +2,11 @@ use std::{path::PathBuf, rc::Rc};
use chrono::Timelike; use chrono::Timelike;
use glam::Vec2; use glam::Vec2;
use strum::EnumCount;
use wgui::{ use wgui::{
assets::{AssetPath, AssetProvider}, assets::{AssetPath, AssetProvider},
components::button::ComponentButton, components::button::ComponentButton,
event::StyleSetRequest, event::{CallbackDataCommon, StyleSetRequest},
font_config::WguiFontConfig, font_config::WguiFontConfig,
globals::WguiGlobals, globals::WguiGlobals,
i18n::Translation, i18n::Translation,
@ -21,8 +22,9 @@ use wgui::{
use wlx_common::{ use wlx_common::{
async_executor::AsyncExecutor, async_executor::AsyncExecutor,
audio, audio,
dash_interface::{BoxDashInterface, ConfigChangeKind, RecenterMode}, dash_interface::{BoxDashInterface, ConfigChangeKind, DashPlayspaceTask},
locale::WayVRLangProvider, locale::WayVRLangProvider,
palette::load_palette,
timestep::{self, Timestep}, timestep::{self, Timestep},
}; };
@ -61,6 +63,7 @@ pub struct Frontend<T> {
state: ParserState, state: ParserState,
current_tab: Option<Box<dyn Tab<T>>>, current_tab: Option<Box<dyn Tab<T>>>,
side_buttons: Vec<(TabType, Rc<ComponentButton>)>,
pub tasks: FrontendTasks, pub tasks: FrontendTasks,
@ -94,12 +97,14 @@ pub struct InitParams<'a, T> {
pub show_welcome: bool, pub show_welcome: bool,
pub has_monado: bool, pub has_monado: bool,
pub theme: Rc<WguiTheme>, pub theme: Rc<WguiTheme>,
pub color_palette: &'a str,
} }
#[derive(Clone)] #[derive(Clone)]
pub enum SoundType { pub enum SoundType {
Startup, Startup,
Launch, Launch,
Save,
} }
#[derive(Clone)] #[derive(Clone)]
@ -127,6 +132,8 @@ impl<T: 'static> Frontend<T> {
let font_binary_regular = assets.load_from_path_gzip("Quicksand-Regular.ttf.gz")?; let font_binary_regular = assets.load_from_path_gzip("Quicksand-Regular.ttf.gz")?;
let font_binary_light = assets.load_from_path_gzip("Quicksand-Light.ttf.gz")?; let font_binary_light = assets.load_from_path_gzip("Quicksand-Light.ttf.gz")?;
let palette = load_palette(params.color_palette);
let globals = WguiGlobals::new( let globals = WguiGlobals::new(
assets, assets,
params.lang_provider, params.lang_provider,
@ -137,6 +144,7 @@ impl<T: 'static> Frontend<T> {
family_name_monospace: "", family_name_monospace: "",
}, },
PathBuf::new(), //FIXME: pass from somewhere else PathBuf::new(), //FIXME: pass from somewhere else
palette,
)?; )?;
let (layout, state) = wgui::parser::new_layout_from_assets( let (layout, state) = wgui::parser::new_layout_from_assets(
@ -178,6 +186,7 @@ impl<T: 'static> Frontend<T> {
layout, layout,
state, state,
current_tab: None, current_tab: None,
side_buttons: Vec::with_capacity(TabType::COUNT),
globals, globals,
tasks, tasks,
ticks: 0, ticks: 0,
@ -216,6 +225,7 @@ impl<T: 'static> Frontend<T> {
let path = match sound_type { let path = match sound_type {
SoundType::Startup => "sound/startup.mp3", SoundType::Startup => "sound/startup.mp3",
SoundType::Launch => "sound/app_start.mp3", SoundType::Launch => "sound/app_start.mp3",
SoundType::Save => "sound/save.mp3",
}; };
// try loading a custom sound; if one doesn't exist (or it failed to load), use the built-in asset // try loading a custom sound; if one doesn't exist (or it failed to load), use the built-in asset
@ -350,8 +360,8 @@ impl<T: 'static> Frontend<T> {
(0.8666, 0.9333) (0.8666, 0.9333)
}; };
rect.params.color.a = alpha1; rect.params.color = rect.params.color.with_alpha(alpha1);
rect.params.color2.a = alpha2; rect.params.color2 = rect.params.color2.with_alpha(alpha2);
Ok(()) Ok(())
} }
@ -435,6 +445,15 @@ impl<T: 'static> Frontend<T> {
self.current_tab = Some(tab); self.current_tab = Some(tab);
let mut common = CallbackDataCommon {
state: &self.layout.state,
alterables: &mut self.layout.alterables,
};
for (t, btn) in self.side_buttons.iter() {
btn.set_sticky_state(&mut common, tab_type == *t);
}
Ok(()) Ok(())
} }
@ -450,34 +469,29 @@ impl<T: 'static> Frontend<T> {
// ################################ // ################################
// "Home" side button // "Home" side button
self.tasks.handle_button( let btn = self.state.fetch_component_as::<ComponentButton>("btn_side_home")?;
&self.state.fetch_component_as::<ComponentButton>("btn_side_home")?, self.tasks.handle_button(&btn, FrontendTask::SetTab(TabType::Home));
FrontendTask::SetTab(TabType::Home), self.side_buttons.push((TabType::Home, btn));
);
// "Apps" side button // "Apps" side button
self.tasks.handle_button( let btn = self.state.fetch_component_as::<ComponentButton>("btn_side_apps")?;
&self.state.fetch_component_as::<ComponentButton>("btn_side_apps")?, self.tasks.handle_button(&btn, FrontendTask::SetTab(TabType::Apps));
FrontendTask::SetTab(TabType::Apps), self.side_buttons.push((TabType::Apps, btn));
);
// "Games" side button // "Games" side button
self.tasks.handle_button( let btn = self.state.fetch_component_as::<ComponentButton>("btn_side_games")?;
&self.state.fetch_component_as::<ComponentButton>("btn_side_games")?, self.tasks.handle_button(&btn, FrontendTask::SetTab(TabType::Games));
FrontendTask::SetTab(TabType::Games), self.side_buttons.push((TabType::Games, btn));
);
// "Monado side button" // "Monado side button"
self.tasks.handle_button( let btn = self.state.fetch_component_as::<ComponentButton>("btn_side_monado")?;
&self.state.fetch_component_as::<ComponentButton>("btn_side_monado")?, self.tasks.handle_button(&btn, FrontendTask::SetTab(TabType::Monado));
FrontendTask::SetTab(TabType::Monado), self.side_buttons.push((TabType::Monado, btn));
);
// "Settings" side button // "Settings" side button
self.tasks.handle_button( let btn = self.state.fetch_component_as::<ComponentButton>("btn_side_settings")?;
&self.state.fetch_component_as::<ComponentButton>("btn_side_settings")?, self.tasks.handle_button(&btn, FrontendTask::SetTab(TabType::Settings));
FrontendTask::SetTab(TabType::Settings), self.side_buttons.push((TabType::Settings, btn));
);
// ################################ // ################################
// BOTTOM BAR BUTTONS // BOTTOM BAR BUTTONS
@ -539,7 +553,7 @@ impl<T: 'static> Frontend<T> {
} }
fn action_recenter_playspace(&mut self, data: &mut T) -> anyhow::Result<()> { fn action_recenter_playspace(&mut self, data: &mut T) -> anyhow::Result<()> {
self.interface.recenter_playspace(data, RecenterMode::Recenter)?; self.interface.playspace(data, DashPlayspaceTask::Recenter)?;
Ok(()) Ok(())
} }

View File

@ -3,29 +3,39 @@ 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, TemplateParams}, parser::{Fetchable, ParseDocumentParams, ParserState, TemplateParams},
task::Tasks,
}; };
use wlx_common::desktop_finder::DesktopEntry; use wlx_common::{config::PinnedApp, desktop_finder::DesktopEntry};
use crate::{ use crate::{
frontend::{Frontend, FrontendTasks}, frontend::{Frontend, FrontendTasks},
tab::{Tab, TabType}, tab::{Tab, TabType},
util::popup_manager::PopupHolder, util::{popup_manager::PopupHolder, wgui_simple},
views::{self}, views::{self},
}; };
struct State { struct State {
#[allow(dead_code)]
parser_state: ParserState,
view_launcher: PopupHolder<views::app_launcher::View>, view_launcher: PopupHolder<views::app_launcher::View>,
} }
pub struct TabApps<T> { #[derive(Clone)]
#[allow(dead_code)] enum Task {
parser_state: ParserState, RefreshPinnedApps,
}
pub struct TabApps<T> {
state: Rc<RefCell<State>>, state: Rc<RefCell<State>>,
app_list: AppList, app_list: AppList,
marker: PhantomData<T>, marker: PhantomData<T>,
pinned_apps_parent: WidgetID,
tasks: Tasks<Task>,
} }
impl<T> Tab<T> for TabApps<T> { impl<T> Tab<T> for TabApps<T> {
@ -34,9 +44,15 @@ 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 state = self.state.borrow_mut(); let mut state = self.state.borrow_mut();
self.app_list.tick(frontend, &self.state, &mut self.parser_state)?; self.app_list.tick(frontend, &self.tasks, &mut state, &self.state)?;
for task in self.tasks.drain() {
match task {
Task::RefreshPinnedApps => self.refresh_pinned_apps(&mut state, frontend, data)?,
}
}
state state
.view_launcher .view_launcher
@ -45,8 +61,85 @@ impl<T> Tab<T> for TabApps<T> {
} }
} }
fn find_entry_from_app_name<'a>(app_id: &str, entries: &'a [DesktopEntry]) -> Option<&'a DesktopEntry> {
entries.iter().find(|&entry| *entry.app_id == *app_id)
}
impl<T> TabApps<T> {
fn refresh_pinned_apps(&self, state: &mut State, frontend: &mut Frontend<T>, data: &mut T) -> anyhow::Result<()> {
frontend.layout.remove_children(self.pinned_apps_parent);
let globals = frontend.globals.clone();
let mut stale_entries = Vec::<Rc<str>>::new();
let mut pinned_desktop_entries = Vec::<(PinnedApp, &DesktopEntry)>::new();
// collect pinned desktop entries
{
let config = frontend.interface.general_config(data);
for pinned_app in &config.pinned_apps {
let Some(desktop_entry) = find_entry_from_app_name(&pinned_app.app_id, &self.app_list.all_entries) else {
stale_entries.push(pinned_app.app_id.clone());
continue;
};
pinned_desktop_entries.push((pinned_app.clone(), desktop_entry));
}
// cleanup:
// remove non-existent app ids from pinned apps
config.pinned_apps.retain(|pinned_app| {
for app_id in &stale_entries {
if *app_id == pinned_app.app_id {
return false;
}
}
true
});
}
if pinned_desktop_entries.is_empty() {
wgui_simple::create_label(
&mut frontend.layout,
self.pinned_apps_parent,
Translation::from_translation_key("EMPTY"),
)?;
}
// mount pinned desktop entries
for (pinned_app, desktop_entry) in pinned_desktop_entries {
let tooltip_string = format!(
"{}\n{}\n{}",
pinned_app.compositor_mode.as_ref(),
pinned_app.orientation_mode.as_ref(),
pinned_app.res_mode.as_ref()
);
let button = mount_entry(
frontend,
&mut state.parser_state,
&doc_params(frontend.globals.clone()),
self.pinned_apps_parent,
desktop_entry,
Some(tooltip_string),
)?;
button.on_click(on_app_click(
frontend.tasks.clone(),
self.tasks.clone(),
globals.clone(),
desktop_entry.clone(),
self.state.clone(),
Some(pinned_app.clone()),
));
}
Ok(())
}
}
struct AppList { struct AppList {
//data: Vec<ParserData>, //data: Vec<ParserData>,
all_entries: Vec<DesktopEntry>,
entries_to_mount: VecDeque<DesktopEntry>, entries_to_mount: VecDeque<DesktopEntry>,
list_parent: WidgetPair, list_parent: WidgetPair,
prev_category_name: String, prev_category_name: String,
@ -55,9 +148,11 @@ struct AppList {
// called after the user clicks any desktop entry // called after the user clicks any desktop entry
fn on_app_click( fn on_app_click(
frontend_tasks: FrontendTasks, frontend_tasks: FrontendTasks,
tasks: Tasks<Task>,
globals: WguiGlobals, globals: WguiGlobals,
entry: DesktopEntry, entry: DesktopEntry,
state: Rc<RefCell<State>>, state: Rc<RefCell<State>>,
pinned_app: Option<PinnedApp>,
) -> ButtonClickCallback { ) -> ButtonClickCallback {
Rc::new(move |_common, _evt| { Rc::new(move |_common, _evt| {
views::app_launcher::mount_popup( views::app_launcher::mount_popup(
@ -65,6 +160,8 @@ fn on_app_click(
globals.clone(), globals.clone(),
entry.clone(), entry.clone(),
state.borrow_mut().view_launcher.clone(), state.borrow_mut().view_launcher.clone(),
tasks.make_callback_box(Task::RefreshPinnedApps),
pinned_app.clone(),
); );
Ok(()) Ok(())
}) })
@ -81,20 +178,24 @@ 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 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 pinned_apps_parent = parser_state.fetch_widget(&frontend.layout.state, "pinned_apps_parent")?;
let state = Rc::new(RefCell::new(State { let state = Rc::new(RefCell::new(State {
view_launcher: Default::default(), view_launcher: Default::default(),
parser_state,
})); }));
let parser_state = wgui::parser::parse_from_assets(&doc_params(globals.clone()), &mut frontend.layout, parent_id)?; let entries: Vec<_> = frontend
let app_list_parent = parser_state.fetch_widget(&frontend.layout.state, "app_list_parent")?;
let mut entries_sorted: Vec<_> = frontend
.interface .interface
.desktop_finder(data) .desktop_finder(data)
.find_entries() .find_entries()
.into_values() .into_values()
.collect(); .collect();
let mut entries_sorted = entries.clone();
entries_sorted.sort_by(|a, b| { entries_sorted.sort_by(|a, b| {
let cat_name_a = get_category_name(a); let cat_name_a = get_category_name(a);
let cat_name_b = get_category_name(b); let cat_name_b = get_category_name(b);
@ -102,16 +203,21 @@ impl<T> TabApps<T> {
}); });
let app_list = AppList { let app_list = AppList {
all_entries: entries,
entries_to_mount: entries_sorted.drain(..).collect(), entries_to_mount: entries_sorted.drain(..).collect(),
list_parent: app_list_parent, list_parent: app_list_parent,
prev_category_name: String::new(), prev_category_name: String::new(),
}; };
let tasks = Tasks::<Task>::new();
tasks.push(Task::RefreshPinnedApps);
Ok(Self { Ok(Self {
app_list, app_list,
parser_state,
state, state,
tasks,
marker: PhantomData, marker: PhantomData,
pinned_apps_parent: pinned_apps_parent.id,
}) })
} }
} }
@ -225,81 +331,93 @@ fn get_category_name(entry: &DesktopEntry) -> &str {
} }
} }
impl AppList { fn mount_entry<T>(
fn mount_entry<T>( frontend: &mut Frontend<T>,
&mut self, parser_state: &mut ParserState,
frontend: &mut Frontend<T>, doc_params: &ParseDocumentParams,
parser_state: &mut ParserState, id_parent: WidgetID,
doc_params: &ParseDocumentParams, entry: &DesktopEntry,
entry: &DesktopEntry, tooltip: Option<String>,
) -> anyhow::Result<Rc<ComponentButton>> { ) -> anyhow::Result<Rc<ComponentButton>> {
let category_name = get_category_name(entry); {
if category_name != self.prev_category_name { let mut params = TemplateParams::new();
self.prev_category_name = String::from(category_name);
let mut params = TemplateParams::new();
params.insert("text", category_name);
parser_state.realize_template( if let Some(tooltip) = tooltip {
doc_params, params.insert_str("tooltip", tooltip);
"CategoryText", };
&mut frontend.layout,
self.list_parent.id,
params,
)?;
}
{ // entry icon
let mut params = TemplateParams::new(); params.insert_rc(
"src_ext",
entry
.icon_path
.as_ref()
.map_or_else(|| "".into(), |icon_path| icon_path.clone()),
);
// entry icon // entry fallback (question mark) icon
params.insert_rc( params.insert(
"src_ext", "src",
entry if entry.icon_path.is_none() {
.icon_path "dashboard/terminal.svg"
.as_ref() } else {
.map_or_else(|| "".into(), |icon_path| icon_path.clone()), ""
); },
);
params.insert("name", &entry.app_name);
// entry fallback (question mark) icon let data = parser_state.realize_template(doc_params, "AppEntry", &mut frontend.layout, id_parent, params)?;
params.insert(
"src",
if entry.icon_path.is_none() {
"dashboard/terminal.svg"
} else {
""
},
);
params.insert("name", &entry.app_name);
let data = parser_state.realize_template( data.fetch_component_as::<ComponentButton>("button")
doc_params,
"AppEntry",
&mut frontend.layout,
self.list_parent.id,
params,
)?;
data.fetch_component_as::<ComponentButton>("button")
}
} }
}
impl AppList {
fn tick<T>( fn tick<T>(
&mut self, &mut self,
frontend: &mut Frontend<T>, frontend: &mut Frontend<T>,
state: &Rc<RefCell<State>>, tasks: &Tasks<Task>,
parser_state: &mut ParserState, state: &mut State,
rc_state: &Rc<RefCell<State>>,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let parser_state = &mut state.parser_state;
// load 30 entries for a single frame at most // load 30 entries for a single frame at most
for _ in 0..30 { 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 category_name = get_category_name(&entry);
if category_name != self.prev_category_name {
self.prev_category_name = String::from(category_name);
let mut params = TemplateParams::new();
params.insert("text", category_name);
parser_state.realize_template(
&doc_params(globals.clone()),
"CategoryText",
&mut frontend.layout,
self.list_parent.id,
params,
)?;
}
let button = mount_entry(
frontend,
parser_state,
&doc_params(globals.clone()),
self.list_parent.id,
&entry,
None,
)?;
button.on_click(on_app_click( button.on_click(on_app_click(
frontend.tasks.clone(), frontend.tasks.clone(),
tasks.clone(),
globals.clone(), globals.clone(),
entry.clone(), entry.clone(),
state.clone(), rc_state.clone(),
None,
)); ));
} else { } else {
break; break;

View File

@ -1,3 +1,5 @@
use strum::EnumCount;
use crate::frontend::Frontend; use crate::frontend::Frontend;
pub mod apps; pub mod apps;
@ -8,7 +10,7 @@ pub mod monado;
pub mod settings; pub mod settings;
pub mod welcome; pub mod welcome;
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug, PartialEq, Eq, EnumCount)]
pub enum TabType { pub enum TabType {
Home, Home,
Apps, Apps,

View File

@ -28,16 +28,16 @@ use crate::{
#[derive(Clone)] #[derive(Clone)]
enum TabNameEnum { enum TabNameEnum {
GeneralSettings,
ProcessList, ProcessList,
GeneralSettings,
DebugTimings, DebugTimings,
} }
impl TabNameEnum { impl TabNameEnum {
fn from_string(s: &str) -> Option<Self> { fn from_string(s: &str) -> Option<Self> {
match s { match s {
"general_settings" => Some(TabNameEnum::GeneralSettings),
"process_list" => Some(TabNameEnum::ProcessList), "process_list" => Some(TabNameEnum::ProcessList),
"general_settings" => Some(TabNameEnum::GeneralSettings),
"debug_timings" => Some(TabNameEnum::DebugTimings), "debug_timings" => Some(TabNameEnum::DebugTimings),
_ => None, _ => None,
} }
@ -126,8 +126,8 @@ struct SubtabDebugTimings {
#[allow(clippy::large_enum_variant)] #[allow(clippy::large_enum_variant)]
enum Subtab { enum Subtab {
Empty, Empty,
GeneralSettings(SubtabGeneralSettings),
ProcessList(SubtabProcessList), ProcessList(SubtabProcessList),
GeneralSettings(SubtabGeneralSettings),
DebugTimings(SubtabDebugTimings), DebugTimings(SubtabDebugTimings),
} }
@ -184,6 +184,10 @@ impl<T> Tab<T> for TabMonado<T> {
Task::SetTab(tab) => { Task::SetTab(tab) => {
frontend.layout.remove_children(self.id_content); frontend.layout.remove_children(self.id_content);
match tab { match tab {
TabNameEnum::ProcessList => {
self.tasks.push(Task::ProcessListRefresh);
self.subtab = Subtab::ProcessList(SubtabProcessList::new(self.id_content, frontend)?)
}
TabNameEnum::GeneralSettings => { TabNameEnum::GeneralSettings => {
self.subtab = Subtab::GeneralSettings(SubtabGeneralSettings::new( self.subtab = Subtab::GeneralSettings(SubtabGeneralSettings::new(
self.id_content, self.id_content,
@ -192,10 +196,6 @@ impl<T> Tab<T> for TabMonado<T> {
&self.tasks, &self.tasks,
)?) )?)
} }
TabNameEnum::ProcessList => {
self.tasks.push(Task::ProcessListRefresh);
self.subtab = Subtab::ProcessList(SubtabProcessList::new(self.id_content, frontend)?)
}
TabNameEnum::DebugTimings => { TabNameEnum::DebugTimings => {
self.subtab = Subtab::DebugTimings(SubtabDebugTimings::new(self.id_content, frontend, &self.tasks)?) self.subtab = Subtab::DebugTimings(SubtabDebugTimings::new(self.id_content, frontend, &self.tasks)?)
} }
@ -206,7 +206,6 @@ impl<T> Tab<T> for TabMonado<T> {
match &mut self.subtab { match &mut self.subtab {
Subtab::Empty => {} Subtab::Empty => {}
Subtab::GeneralSettings(_) => {}
Subtab::ProcessList(_) => { Subtab::ProcessList(_) => {
// every few seconds // every few seconds
if let Subtab::ProcessList(_) = &self.subtab if let Subtab::ProcessList(_) = &self.subtab
@ -215,6 +214,7 @@ impl<T> Tab<T> for TabMonado<T> {
self.tasks.push(Task::ProcessListRefresh); self.tasks.push(Task::ProcessListRefresh);
} }
} }
Subtab::GeneralSettings(_) => {}
Subtab::DebugTimings(timings) => { Subtab::DebugTimings(timings) => {
timings.update(&self.tasks, data, frontend)?; timings.update(&self.tasks, data, frontend)?;
} }

View File

@ -3,15 +3,15 @@ use std::{marker::PhantomData, rc::Rc, str::FromStr};
use strum::{AsRefStr, EnumProperty, EnumString}; use strum::{AsRefStr, EnumProperty, EnumString};
use wgui::{ use wgui::{
assets::AssetPath, assets::AssetPath,
color::WguiColorName,
components::tabs::ComponentTabs, components::tabs::ComponentTabs,
drawing,
event::StyleSetRequest, event::StyleSetRequest,
globals::WguiGlobals, globals::WguiGlobals,
i18n::Translation, i18n::Translation,
layout::{Layout, WidgetID}, layout::{Layout, WidgetID},
log::LogErr, log::LogErr,
parser::{Fetchable, ParseDocumentParams, ParserState}, parser::{Fetchable, ParseDocumentParams, ParserState},
renderer_vk::text::{FontWeight, TextStyle}, renderer_vk::text::{FontWeight, TextStyle, WguiTextShadow},
taffy::{self, prelude::length}, taffy::{self, prelude::length},
task::Tasks, task::Tasks,
widget::{ widget::{
@ -23,7 +23,7 @@ use wgui::{
use wlx_common::{ use wlx_common::{
config::GeneralConfig, config::GeneralConfig,
config_io::ConfigRoot, config_io::ConfigRoot,
dash_interface::{ConfigChangeKind, InterfaceFeats, RecenterMode}, dash_interface::{ConfigChangeKind, DashPlayspaceTask, InterfaceFeats},
}; };
use crate::{ use crate::{
@ -43,7 +43,7 @@ mod tab_space_drag;
mod tab_troubleshooting; mod tab_troubleshooting;
#[derive(Clone)] #[derive(Clone)]
enum TabNameEnum { pub(crate) enum TabNameEnum {
AutostartApps, AutostartApps,
Controls, Controls,
Features, Features,
@ -71,7 +71,7 @@ impl TabNameEnum {
} }
#[derive(Clone)] #[derive(Clone)]
enum Task { pub(crate) enum Task {
ClearPipewireTokens, ClearPipewireTokens,
ClearSavedState, ClearSavedState,
DeleteAllConfigs, DeleteAllConfigs,
@ -201,7 +201,7 @@ impl<T> Tab<T> for TabSettings<T> {
std::fs::create_dir(&path)?; std::fs::create_dir(&path)?;
} }
Task::ResetPlayspace => { Task::ResetPlayspace => {
frontend.interface.recenter_playspace(data, RecenterMode::Reset)?; frontend.interface.playspace(data, DashPlayspaceTask::Reset)?;
return Ok(()); return Ok(());
} }
Task::RestartSoftware => { Task::RestartSoftware => {
@ -291,7 +291,7 @@ impl<T> Tab<T> for TabSettings<T> {
// Sorted alphabetically // Sorted alphabetically
#[allow(clippy::enum_variant_names)] #[allow(clippy::enum_variant_names)]
#[derive(Clone, Copy, Eq, PartialEq, AsRefStr, EnumString)] #[derive(Clone, Copy, Eq, PartialEq, AsRefStr, EnumString)]
enum SettingType { pub(crate) enum SettingType {
AllowSliding, AllowSliding,
BlockGameInput, BlockGameInput,
BlockGameInputIgnoreWatch, BlockGameInputIgnoreWatch,
@ -320,6 +320,7 @@ enum SettingType {
PointerLerpFactor, PointerLerpFactor,
ScreenRenderDown, ScreenRenderDown,
ScrollSpeed, ScrollSpeed,
SnapAngleDeg,
EnableWatch, EnableWatch,
SetsOnWatch, SetsOnWatch,
SpaceDragMultiplier, SpaceDragMultiplier,
@ -389,6 +390,7 @@ impl SettingType {
Self::LongPressDuration => &mut config.long_press_duration, Self::LongPressDuration => &mut config.long_press_duration,
Self::PointerLerpFactor => &mut config.pointer_lerp_factor, Self::PointerLerpFactor => &mut config.pointer_lerp_factor,
Self::ScrollSpeed => &mut config.scroll_speed, Self::ScrollSpeed => &mut config.scroll_speed,
Self::SnapAngleDeg => &mut config.snap_angle_deg,
Self::SpaceDragMultiplier => &mut config.space_drag_multiplier, Self::SpaceDragMultiplier => &mut config.space_drag_multiplier,
Self::SpaceGravityDamping => &mut config.space_gravity_damping, Self::SpaceGravityDamping => &mut config.space_gravity_damping,
Self::SpaceGravityFlingStrength => &mut config.space_gravity_fling_strength, Self::SpaceGravityFlingStrength => &mut config.space_gravity_fling_strength,
@ -500,6 +502,7 @@ impl SettingType {
Self::ScrollSpeed => Ok("APP_SETTINGS.SCROLL_SPEED"), Self::ScrollSpeed => Ok("APP_SETTINGS.SCROLL_SPEED"),
Self::EnableWatch => Ok("APP_SETTINGS.ENABLE_WATCH"), Self::EnableWatch => Ok("APP_SETTINGS.ENABLE_WATCH"),
Self::SetsOnWatch => Ok("APP_SETTINGS.SETS_ON_WATCH"), Self::SetsOnWatch => Ok("APP_SETTINGS.SETS_ON_WATCH"),
Self::SnapAngleDeg => Ok("APP_SETTINGS.SNAP_ANGLE_DEG"),
Self::SpaceDragMultiplier => Ok("APP_SETTINGS.SPACE_DRAG_MULTIPLIER"), Self::SpaceDragMultiplier => Ok("APP_SETTINGS.SPACE_DRAG_MULTIPLIER"),
Self::SpaceDragUnlocked => Ok("APP_SETTINGS.SPACE_DRAG_UNLOCKED"), Self::SpaceDragUnlocked => Ok("APP_SETTINGS.SPACE_DRAG_UNLOCKED"),
Self::SpaceGravityDamping => Ok("APP_SETTINGS.SPACE_GRAVITY_DAMPING"), Self::SpaceGravityDamping => Ok("APP_SETTINGS.SPACE_GRAVITY_DAMPING"),
@ -536,6 +539,7 @@ impl SettingType {
Self::KeyboardMiddleClick => Some("APP_SETTINGS.KEYBOARD_MIDDLE_CLICK_HELP"), Self::KeyboardMiddleClick => Some("APP_SETTINGS.KEYBOARD_MIDDLE_CLICK_HELP"),
Self::LeftHandedMouse => Some("APP_SETTINGS.LEFT_HANDED_MOUSE_HELP"), Self::LeftHandedMouse => Some("APP_SETTINGS.LEFT_HANDED_MOUSE_HELP"),
Self::ScreenRenderDown => Some("APP_SETTINGS.SCREEN_RENDER_DOWN_HELP"), Self::ScreenRenderDown => Some("APP_SETTINGS.SCREEN_RENDER_DOWN_HELP"),
Self::SnapAngleDeg => Some("APP_SETTINGS.SNAP_ANGLE_DEG_HELP"),
Self::SpaceGravityDamping => Some("APP_SETTINGS.SPACE_GRAVITY_DAMPING_HELP"), Self::SpaceGravityDamping => Some("APP_SETTINGS.SPACE_GRAVITY_DAMPING_HELP"),
Self::SpaceGravityFlingStrength => Some("APP_SETTINGS.SPACE_GRAVITY_FLING_STRENGTH_HELP"), Self::SpaceGravityFlingStrength => Some("APP_SETTINGS.SPACE_GRAVITY_FLING_STRENGTH_HELP"),
Self::SpaceGravityFloorHeight => Some("APP_SETTINGS.SPACE_GRAVITY_FLOOR_HEIGHT_HELP"), Self::SpaceGravityFloorHeight => Some("APP_SETTINGS.SPACE_GRAVITY_FLOOR_HEIGHT_HELP"),
@ -580,7 +584,7 @@ pub fn horiz_cell(layout: &mut Layout, parent: WidgetID) -> anyhow::Result<Widge
WidgetDiv::create(), WidgetDiv::create(),
taffy::Style { taffy::Style {
flex_direction: taffy::FlexDirection::Row, flex_direction: taffy::FlexDirection::Row,
align_items: Some(taffy::AlignItems::Center), align_items: Some(taffy::AlignItems::CENTER),
gap: length(8.0), gap: length(8.0),
..Default::default() ..Default::default()
}, },
@ -597,11 +601,17 @@ fn mount_requires_restart(layout: &mut Layout, parent: WidgetID) -> anyhow::Resu
content, content,
style: TextStyle { style: TextStyle {
wrap: false, wrap: false,
color: Some(drawing::Color::new(1.0, 0.5, 0.5, 1.0)), color: Some(WguiColorName::Danger.into()),
weight: Some(FontWeight::Bold), weight: Some(FontWeight::Bold),
size: Some(10.0), size: Some(10.0),
shadow: Some(WguiTextShadow {
x: 2.0,
y: 2.0,
..Default::default()
}),
..Default::default() ..Default::default()
}, },
..Default::default()
}, },
); );

View File

@ -4,6 +4,7 @@ use wgui::{
components::button::ComponentButton, globals::WguiGlobals, layout::WidgetID, parser::Fetchable, task::Tasks, components::button::ComponentButton, globals::WguiGlobals, layout::WidgetID, parser::Fetchable, task::Tasks,
}; };
use crate::tab::settings::{horiz_cell, mount_requires_restart};
use crate::util::popup_manager::PopupHolder; use crate::util::popup_manager::PopupHolder;
use crate::{ use crate::{
@ -56,13 +57,15 @@ fn create_input_profiles_button(
let id = mp.idx.to_string(); let id = mp.idx.to_string();
mp.idx += 1; mp.idx += 1;
let id_cell = horiz_cell(mp.layout, parent)?;
let mut params = TemplateParams::new(); let mut params = TemplateParams::new();
params.insert("id", &id); params.insert("id", &id);
params.insert("translation", "APP_SETTINGS.INPUT_PROFILES"); params.insert("translation", "APP_SETTINGS.INPUT_PROFILES");
params.insert("icon", "dashboard/controller.svg"); params.insert("icon", "dashboard/controller.svg");
mp.parser_state mp.parser_state
.instantiate_template(mp.doc_params, "ButtonText", mp.layout, parent, params)?; .instantiate_template(mp.doc_params, "ButtonText", mp.layout, id_cell, params)?;
let btn = mp.parser_state.fetch_component_as::<ComponentButton>(&id)?; let btn = mp.parser_state.fetch_component_as::<ComponentButton>(&id)?;
btn.on_click(Rc::new({ btn.on_click(Rc::new({
@ -73,6 +76,8 @@ fn create_input_profiles_button(
} }
})); }));
mount_requires_restart(mp.layout, id_cell)?;
Ok(()) Ok(())
} }
@ -99,6 +104,7 @@ impl State {
options_checkbox(par.mp, c, SettingType::InvertScrollDirectionY)?; 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::ScrollSpeed, 0.1, 5.0, 0.1)?;
options_slider_f32(par.mp, c, SettingType::LongPressDuration, 0.1, 2.0, 0.1)?; options_slider_f32(par.mp, c, SettingType::LongPressDuration, 0.1, 2.0, 0.1)?;
options_slider_f32(par.mp, c, SettingType::SnapAngleDeg, 0., 45., 5.)?;
if par.feats.openxr { if par.feats.openxr {
options_slider_f32(par.mp, c, SettingType::PointerLerpFactor, 0.1, 1.0, 0.1)?; options_slider_f32(par.mp, c, SettingType::PointerLerpFactor, 0.1, 1.0, 0.1)?;

View File

@ -1,20 +1,70 @@
use crate::tab::settings::{ use std::rc::Rc;
SettingType, SettingsMountParams, SettingsTab,
macros::{options_category, options_checkbox, options_dropdown, options_slider_f32}, use wgui::{
components::button::ComponentButton,
globals::WguiGlobals,
layout::WidgetID,
parser::{Fetchable, TemplateParams},
task::Tasks,
}; };
pub struct State {} use crate::{
frontend::FrontendTasks,
tab::settings::{
SettingType, SettingsMountParams, SettingsTab, Task as SettingsTask, horiz_cell,
macros::{options_category, options_checkbox, options_dropdown, options_slider_f32},
mount_requires_restart,
},
util::popup_manager::PopupHolder,
views::{ViewUpdateParams, color_palettes},
};
impl SettingsTab for State {} #[derive(Clone)]
enum Task {
OpenColorPalettes,
}
pub struct State {
popup_color_palettes: PopupHolder<color_palettes::View>,
frontend_tasks: FrontendTasks,
globals: WguiGlobals,
tasks: Tasks<Task>,
settings_tasks: Tasks<SettingsTask>,
}
impl SettingsTab for State {
fn update(&mut self, par: &mut ViewUpdateParams) -> anyhow::Result<()> {
self.popup_color_palettes.update(par)?;
for task in self.tasks.drain() {
match task {
Task::OpenColorPalettes => {
color_palettes::mount_popup(
self.frontend_tasks.clone(),
self.globals.clone(),
self.popup_color_palettes.clone(),
self.settings_tasks.clone(),
par.general_config.color_palette.clone(),
);
}
}
}
Ok(())
}
}
impl State { impl State {
pub fn mount(par: SettingsMountParams) -> anyhow::Result<State> { pub fn mount(par: SettingsMountParams) -> anyhow::Result<State> {
let tasks = Tasks::<Task>::new();
let popup = PopupHolder::<color_palettes::View>::default();
let c = options_category( let c = options_category(
par.mp, par.mp,
par.id_parent, par.id_parent,
"APP_SETTINGS.LOOK_AND_FEEL", "APP_SETTINGS.LOOK_AND_FEEL",
"dashboard/palette.svg", "dashboard/palette.svg",
)?; )?;
create_color_palettes_button(par.mp, c, tasks.clone(), &popup)?;
options_dropdown::<wlx_common::locale::Language>(par.mp, c, &SettingType::Language)?; options_dropdown::<wlx_common::locale::Language>(par.mp, c, &SettingType::Language)?;
options_checkbox(par.mp, c, SettingType::OpaqueBackground)?; options_checkbox(par.mp, c, SettingType::OpaqueBackground)?;
options_checkbox(par.mp, c, SettingType::HideUsername)?; options_checkbox(par.mp, c, SettingType::HideUsername)?;
@ -26,6 +76,45 @@ impl State {
options_checkbox(par.mp, c, SettingType::EnableWatch)?; options_checkbox(par.mp, c, SettingType::EnableWatch)?;
options_checkbox(par.mp, c, SettingType::SetsOnWatch)?; options_checkbox(par.mp, c, SettingType::SetsOnWatch)?;
options_checkbox(par.mp, c, SettingType::Clock12h)?; options_checkbox(par.mp, c, SettingType::Clock12h)?;
Ok(State {}) Ok(State {
popup_color_palettes: popup,
frontend_tasks: par.frontend_tasks.clone(),
globals: par.mp.doc_params.globals.clone(),
tasks,
settings_tasks: par.mp.tasks.clone(),
})
} }
} }
fn create_color_palettes_button(
mp: &mut crate::tab::settings::macros::MacroParams,
parent: WidgetID,
tasks: Tasks<Task>,
_popup: &PopupHolder<color_palettes::View>,
) -> anyhow::Result<()> {
let id = mp.idx.to_string();
mp.idx += 1;
let id_cell = horiz_cell(mp.layout, parent)?;
let mut params = TemplateParams::new();
params.insert("id", &id);
params.insert("translation", "APP_SETTINGS.COLOR_PALETTE");
params.insert("icon", "dashboard/palette.svg");
mp.parser_state
.instantiate_template(mp.doc_params, "ButtonText", mp.layout, id_cell, params)?;
let btn = mp.parser_state.fetch_component_as::<ComponentButton>(&id)?;
btn.on_click(Rc::new({
let tasks = tasks.clone();
move |_common, _e| {
tasks.push(Task::OpenColorPalettes);
Ok(())
}
}));
mount_requires_restart(mp.layout, id_cell)?;
Ok(())
}

View File

@ -110,7 +110,14 @@ impl<T> TabWelcome<T> {
for i in 0..PAGE_COUNT { for i in 0..PAGE_COUNT {
let mut params = TemplateParams::new(); let mut params = TemplateParams::new();
let is_selected = i == self.current_page; let is_selected = i == self.current_page;
params.insert("COLOR", if is_selected { "#FFFFFF" } else { "#FFFFFF11" }); params.insert(
"COLOR",
if is_selected {
"primary"
} else {
"on_background(opacity-0.25)"
},
);
self self
.state .state

View File

@ -1,14 +1,14 @@
use crate::util::{networking::http_client, steam_utils::AppID}; use crate::util::{networking::http_client, steam_utils::AppID};
use anyhow::Context; use anyhow::Context;
use serde::Deserialize; use serde::Deserialize;
use wlx_common::{async_executor::AsyncExecutor, cache_dir}; use wlx_common::cache_dir;
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)
pub compressed_image_data: Vec<u8>, pub compressed_image_data: Vec<u8>,
} }
pub async fn request_image(executor: AsyncExecutor, app_id: AppID) -> anyhow::Result<CoverArt> { pub async fn request_image(app_id: AppID) -> anyhow::Result<CoverArt> {
let cache_file_path = format!("cover_arts/{}.bin", app_id); let cache_file_path = format!("cover_arts/{}.bin", app_id);
// check if file already exists in cache directory // check if file already exists in cache directory
@ -23,7 +23,7 @@ pub async fn request_image(executor: AsyncExecutor, app_id: AppID) -> anyhow::Re
app_id app_id
); );
match http_client::get_simple(&executor, &url).await { match http_client::get_simple(&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?;
@ -55,11 +55,7 @@ pub struct AppDetailsJSONData {
pub developers: Vec<String>, // ["Valve"] pub developers: Vec<String>, // ["Valve"]
} }
async fn get_app_details_json_internal( async fn get_app_details_json_internal(cache_file_path: &str, app_id: AppID) -> anyhow::Result<AppDetailsJSONData> {
executor: AsyncExecutor,
cache_file_path: &str,
app_id: AppID,
) -> anyhow::Result<AppDetailsJSONData> {
// check if file already exists in cache directory // check if file already exists in cache directory
if let Some(data) = cache_dir::get_data(cache_file_path).await { if let Some(data) = cache_dir::get_data(cache_file_path).await {
return Ok(serde_json::from_value(serde_json::from_slice(&data)?)?); return Ok(serde_json::from_value(serde_json::from_slice(&data)?)?);
@ -68,7 +64,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_simple(&executor, &url).await?; let response = http_client::get_simple(&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")?;
@ -88,10 +84,10 @@ async fn get_app_details_json_internal(
Ok(app_details) Ok(app_details)
} }
pub async fn get_app_details_json(executor: AsyncExecutor, app_id: AppID) -> Option<AppDetailsJSONData> { pub async fn get_app_details_json(app_id: AppID) -> Option<AppDetailsJSONData> {
let cache_file_path = format!("app_details/{}.json", app_id); let cache_file_path = format!("app_details/{}.json", app_id);
match get_app_details_json_internal(executor, &cache_file_path, app_id).await { match get_app_details_json_internal(&cache_file_path, app_id).await {
Ok(r) => Some(r), Ok(r) => Some(r),
Err(e) => { Err(e) => {
log::error!("Failed to get app details: {:?}", e); log::error!("Failed to get app details: {:?}", e);

View File

@ -1,19 +1,15 @@
//
// example smol+hyper usage derived from
// https://github.com/smol-rs/smol/blob/master/examples/hyper-client.rs
// under Apache-2.0 + MIT license.
// Repository URL: https://github.com/smol-rs/smol
//
use anyhow::Context as _; use anyhow::Context as _;
use async_native_tls::TlsStream; use smol::{
use http_body_util::{BodyStream, Empty}; Task,
use hyper::Request; channel::{Receiver, bounded},
use smol::{net::TcpStream, prelude::*}; io::AsyncWriteExt as _,
use std::convert::TryInto; };
use std::pin::Pin; use std::{io::Read as _, path::Path, str, sync::OnceLock};
use std::task::{Context, Poll};
use wlx_common::async_executor::AsyncExecutor; const IO_BUFFER_SIZE: usize = 256 * 1024;
const CHANNEL_CAPACITY: usize = 4;
const MAX_INITIAL_ALLOCATION: usize = 8 * 1024 * 1024;
pub struct HttpClientResponse { pub struct HttpClientResponse {
pub data: Vec<u8>, pub data: Vec<u8>,
} }
@ -21,276 +17,224 @@ pub struct HttpClientResponse {
impl HttpClientResponse { impl HttpClientResponse {
pub fn into_json<T>(self) -> anyhow::Result<T> pub fn into_json<T>(self) -> anyhow::Result<T>
where where
T: for<'a> serde::Deserialize<'a>, T: serde::de::DeserializeOwned,
{ {
let utf8 = str::from_utf8(&self.data)?; let utf8 = str::from_utf8(&self.data)?;
Ok(serde_json::from_str::<T>(utf8)?) Ok(serde_json::from_str(utf8)?)
} }
} }
#[derive(Debug, Clone, Copy)]
pub struct ProgressFuncData { pub struct ProgressFuncData {
pub bytes_downloaded: u64, pub bytes_downloaded: u64,
pub file_size: u64, pub file_size: u64,
} }
pub type ProgressFunc = Box<dyn Fn(ProgressFuncData)>; pub type ProgressFunc<'a> = Box<dyn FnMut(ProgressFuncData) + 'a>;
pub struct GetParams<'a> { pub struct GetParams<'a> {
pub executor: &'a AsyncExecutor,
pub url: &'a str, pub url: &'a str,
pub on_progress: Option<ProgressFunc>, pub on_progress: Option<ProgressFunc<'a>>,
} }
pub async fn get(params: GetParams<'_>) -> anyhow::Result<HttpClientResponse> { struct DownloadStream {
let url: hyper::Uri = params.url.try_into()?; file_size: u64,
let req = Request::builder() chunks: Receiver<Vec<u8>>,
.header( worker: Task<anyhow::Result<()>>,
hyper::header::HOST,
url.authority().context("invalid authority")?.clone().as_str(),
)
.uri(url)
.body(Empty::new())?;
let resp = fetch_and_follow_redirects(params.executor, req, params.on_progress, &params.url).await?;
Ok(resp)
} }
async fn fetch_and_follow_redirects( static HTTP_AGENT: OnceLock<ureq::Agent> = OnceLock::new();
executor: &AsyncExecutor,
req: hyper::Request<Empty<&'static [u8]>>,
mut on_progress: Option<ProgressFunc>,
initial_url: &str,
) -> anyhow::Result<HttpClientResponse> {
log::info!("fetching URL \"{}\"", initial_url);
let resp = fetch(executor, req.clone()).await?; fn http_agent() -> &'static ureq::Agent {
let status = resp.status(); HTTP_AGENT.get_or_init(|| {
ureq::Agent::config_builder()
.max_redirects(10)
.http_status_as_error(false)
.build()
.new_agent()
})
}
if status.is_redirection() { /// Starts a blocking HTTP request and streams its body through a bounded
let next_req = follow_single_redirect(&req, &resp).await?; /// asynchronous channel.
///
/// The request, TLS operations, and all BodyReader reads remain inside one
/// blocking worker. Only owned byte chunks cross back to the async task.
async fn start_download(url: &str, allow_missing_content_length: bool) -> anyhow::Result<DownloadStream> {
let url = url.to_owned();
let agent = http_agent().clone();
let max_redirects = 10; let (metadata_tx, metadata_rx) = bounded::<u64>(1);
let mut current_req = next_req;
let mut redirects = 0;
loop {
let resp = fetch(executor, current_req.clone()).await?;
let resp_status = resp.status();
if resp_status.is_success() { let (chunk_tx, chunk_rx) = bounded::<Vec<u8>>(CHANNEL_CAPACITY);
let (parts, body) = resp.into_parts();
let mut bytes_downloaded: u64 = 0; let worker = smol::unblock(move || -> anyhow::Result<()> {
let mut file_size: u64 = 1; log::info!("fetching URL \"{}\"", url);
if let Some(val) = parts.headers.get("Content-Length") let response = agent
&& let Ok(str) = val.to_str() .get(&url)
&& let Ok(s) = str.parse() .header("Accept-Encoding", "identity")
{ .call()
file_size = s; .with_context(|| format!("failed to fetch URL \"{url}\""))?;
}
let data = BodyStream::new(body) if !response.status().is_success() {
.try_fold(Vec::new(), |mut body, chunk| { anyhow::bail!("non-200 HTTP response: {}", response.status().as_u16(),);
if let Some(chunk) = chunk.data_ref() { }
bytes_downloaded += chunk.len() as u64;
body.extend_from_slice(chunk);
if let Some(on_progress) = &mut on_progress { let file_size = match response.body().content_length() {
on_progress(ProgressFuncData { Some(file_size) => file_size,
bytes_downloaded,
file_size,
})
}
}
Ok(body)
})
.await?;
return Ok(HttpClientResponse { data }); None if allow_missing_content_length => 0,
} else if resp_status.is_redirection() {
redirects += 1; None => {
if redirects >= max_redirects { anyhow::bail!("HTTP response has no Content-Length header");
anyhow::bail!("too many redirects");
}
current_req = follow_single_redirect(&current_req, &resp).await?;
} else {
anyhow::bail!("non-200 HTTP response: {}", resp_status.as_str());
} }
};
if metadata_tx.send_blocking(file_size).is_err() {
return Ok(());
}
drop(metadata_tx);
let mut reader = response.into_body().into_reader();
let mut buffer = [0_u8; IO_BUFFER_SIZE];
loop {
let count = reader.read(&mut buffer).with_context(|| {
format!(
"failed while reading HTTP response body \
from \"{url}\""
)
})?;
if count == 0 {
break;
}
let chunk = buffer[..count].to_vec();
if chunk_tx.send_blocking(chunk).is_err() {
return Ok(());
}
}
Ok(())
});
let file_size = match metadata_rx.recv().await {
Ok(file_size) => file_size,
Err(_) => {
worker.await?;
anyhow::bail!("HTTP worker stopped before providing response metadata");
}
};
Ok(DownloadStream {
file_size,
chunks: chunk_rx,
worker,
})
}
/// Downloads a response into memory.
///
/// This fails if the server does not provide a Content-Length header.
pub async fn get(mut params: GetParams<'_>) -> anyhow::Result<HttpClientResponse> {
let DownloadStream {
file_size,
chunks,
worker,
} = start_download(params.url, false).await?;
let initial_capacity = usize::try_from(file_size).unwrap_or(0).min(MAX_INITIAL_ALLOCATION);
let mut data = Vec::with_capacity(initial_capacity);
let mut bytes_downloaded = 0_u64;
while let Ok(chunk) = chunks.recv().await {
bytes_downloaded += chunk.len() as u64;
data.extend_from_slice(&chunk);
if let Some(on_progress) = params.on_progress.as_mut() {
on_progress(ProgressFuncData {
bytes_downloaded,
file_size,
});
} }
} }
if !resp.status().is_success() { worker.await?;
// non-200 HTTP response
anyhow::bail!("non-200 HTTP response: {}", resp.status().as_str()); if bytes_downloaded != file_size {
anyhow::bail!(
"HTTP response size mismatch: expected {} bytes, received {}",
file_size,
bytes_downloaded,
);
} }
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 data = BodyStream::new(body)
.try_fold(Vec::new(), |mut body, chunk| {
if let Some(chunk) = chunk.data_ref() {
bytes_downloaded += chunk.len() as u64;
body.extend_from_slice(chunk);
if let Some(on_progress) = &mut on_progress {
on_progress(ProgressFuncData {
bytes_downloaded,
file_size,
})
}
}
Ok(body)
})
.await?;
Ok(HttpClientResponse { data }) Ok(HttpClientResponse { data })
} }
fn uri_try_from_str(s: &str) -> anyhow::Result<hyper::http::uri::PathAndQuery> { /// Downloads a response directly to a file.
use std::convert::TryInto; ///
let uri: hyper::Uri = s.try_into().context("invalid URI")?; /// Unlike `get`, this permits responses without a Content-Length header. In
uri /// that case, `ProgressFuncData::file_size` is zero.
.path_and_query() ///
.ok_or_else(|| anyhow::anyhow!("URI has no path and query component")) /// An existing file is truncated. If the download fails after the file is
.cloned() /// created, a partial file may remain at `path`.
} pub async fn download_to_file(mut params: GetParams<'_>, path: impl AsRef<Path>) -> anyhow::Result<()> {
let path = path.as_ref().to_owned();
async fn follow_single_redirect( let DownloadStream {
req: &hyper::Request<Empty<&'static [u8]>>, file_size,
resp: &hyper::Response<hyper::body::Incoming>, chunks,
) -> anyhow::Result<hyper::Request<Empty<&'static [u8]>>> { worker,
let location = resp } = start_download(params.url, true).await?;
.headers()
.get(hyper::header::LOCATION)
.ok_or_else(|| anyhow::anyhow!("redirect response has no Location header"))?;
let location_str = location.to_str().context("invalid redirect location header")?; let mut file = smol::fs::File::create(&path)
.await
.with_context(|| format!("failed to create download file {:?}", path,))?;
// resolve relative urls against the original url. let mut bytes_downloaded = 0_u64;
let original_uri = req.uri().clone();
let next_url: hyper::Uri = if location_str.starts_with("http://") || location_str.starts_with("https://") {
hyper::Uri::try_from(location_str).context("invalid redirect location")?
} else {
let mut parts = original_uri.into_parts();
parts.path_and_query = uri_try_from_str(location_str)
.context("invalid redirect location")?
.into();
hyper::Uri::from_parts(parts).context("failed to construct redirect URI")?
};
log::info!("redirecting to \"{}\"", next_url); while let Ok(chunk) = chunks.recv().await {
file
.write_all(&chunk)
.await
.with_context(|| format!("failed to write download file {:?}", path,))?;
let next_req = Request::builder() bytes_downloaded += chunk.len() as u64;
.header(
hyper::header::HOST,
next_url.authority().context("invalid authority")?.as_str(),
)
.uri(next_url)
.body(Empty::new())?;
Ok(next_req) if let Some(on_progress) = params.on_progress.as_mut() {
} on_progress(ProgressFuncData {
bytes_downloaded,
pub async fn get_simple(executor: &AsyncExecutor, url: &str) -> anyhow::Result<HttpClientResponse> { file_size,
get(GetParams { });
executor,
url,
on_progress: None,
})
.await
}
async fn fetch(
ex: &AsyncExecutor,
req: hyper::Request<http_body_util::Empty<&'static [u8]>>,
) -> anyhow::Result<hyper::Response<hyper::body::Incoming>> {
let io = {
let host = req.uri().host().context("cannot parse host")?;
match req.uri().scheme_str() {
Some("http") => {
let stream = {
let port = req.uri().port_u16().unwrap_or(80);
smol::net::TcpStream::connect((host, port)).await?
};
SmolStream::Plain(stream)
}
Some("https") => {
// In case of HTTPS, establish a secure TLS connection first.
let stream = {
let port = req.uri().port_u16().unwrap_or(443);
smol::net::TcpStream::connect((host, port)).await?
};
let stream = async_native_tls::connect(host, stream).await?;
SmolStream::Tls(stream)
}
scheme => anyhow::bail!("unsupported scheme: {:?}", scheme),
}
};
// Spawn the HTTP/1 connection.
let (mut sender, conn) = hyper::client::conn::http1::handshake(smol_hyper::rt::FuturesIo::new(io)).await?;
ex.spawn(async move {
if let Err(e) = conn.await {
println!("Connection failed: {:?}", e);
}
})
.detach();
// Get the result
let result = sender.send_request(req).await?;
Ok(result)
}
/// A TCP or TCP+TLS connection.
enum SmolStream {
/// A plain TCP connection.
Plain(TcpStream),
/// A TCP connection secured by TLS.
Tls(TlsStream<TcpStream>),
}
impl AsyncRead for SmolStream {
fn poll_read(mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8]) -> Poll<smol::io::Result<usize>> {
match &mut *self {
SmolStream::Plain(stream) => Pin::new(stream).poll_read(cx, buf),
SmolStream::Tls(stream) => Pin::new(stream).poll_read(cx, buf),
}
}
}
impl AsyncWrite for SmolStream {
fn poll_write(mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> Poll<smol::io::Result<usize>> {
match &mut *self {
SmolStream::Plain(stream) => Pin::new(stream).poll_write(cx, buf),
SmolStream::Tls(stream) => Pin::new(stream).poll_write(cx, buf),
} }
} }
fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<smol::io::Result<()>> { worker.await?;
match &mut *self {
SmolStream::Plain(stream) => Pin::new(stream).poll_close(cx), file
SmolStream::Tls(stream) => Pin::new(stream).poll_close(cx), .flush()
} .await
.with_context(|| format!("failed to flush download file {:?}", path,))?;
if file_size != 0 && bytes_downloaded != file_size {
anyhow::bail!(
"HTTP response size mismatch: expected {} bytes, received {}",
file_size,
bytes_downloaded,
);
} }
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<smol::io::Result<()>> { Ok(())
match &mut *self { }
SmolStream::Plain(stream) => Pin::new(stream).poll_flush(cx),
SmolStream::Tls(stream) => Pin::new(stream).poll_flush(cx), pub async fn get_simple(url: &str) -> anyhow::Result<HttpClientResponse> {
} get(GetParams { url, on_progress: None }).await
}
} }

View File

@ -1,16 +1,11 @@
use std::rc::Rc; use std::rc::Rc;
use wgui::{globals::WguiGlobals, renderer_vk::text::custom_glyph::CustomGlyphData}; use wgui::{globals::WguiGlobals, renderer_vk::text::custom_glyph::CustomGlyphData};
use wlx_common::async_executor::AsyncExecutor;
use crate::util::networking::http_client; use crate::util::networking::http_client;
pub async fn fetch_to_glyph_data( pub async fn fetch_to_glyph_data(globals: &WguiGlobals, url: &str) -> anyhow::Result<(CustomGlyphData, Rc<Vec<u8>>)> {
globals: &WguiGlobals, let res = http_client::get_simple(url).await?;
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)?; let glyph_data = CustomGlyphData::from_bytes_raster(globals, url, &res.data)?;
Ok((glyph_data, Rc::new(res.data))) Ok((glyph_data, Rc::new(res.data)))
} }

View File

@ -1,7 +1,7 @@
use crate::util::networking::{self, WAYVR_SKYMAPS_ROOT, http_client}; use crate::util::networking::{self, WAYVR_SKYMAPS_ROOT, http_client};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::path::PathBuf; use std::path::PathBuf;
use wlx_common::{async_executor::AsyncExecutor, config_io}; use wlx_common::config_io;
pub type SkymapUuid = uuid::Uuid; pub type SkymapUuid = uuid::Uuid;
#[derive(Copy, Clone, Serialize, Deserialize, Debug)] #[derive(Copy, Clone, Serialize, Deserialize, Debug)]
@ -160,10 +160,10 @@ impl SkymapCatalog {
} }
} }
pub async fn request_catalog(executor: &AsyncExecutor) -> anyhow::Result<SkymapCatalog> { pub async fn request_catalog() -> anyhow::Result<SkymapCatalog> {
log::info!("Fetching skymap list"); log::info!("Fetching skymap list");
let res = http_client::get_simple(executor, &format!("{}/catalog.json", networking::WAYVR_SKYMAPS_ROOT)).await?; let res = http_client::get_simple(&format!("{}/catalog.json", networking::WAYVR_SKYMAPS_ROOT)).await?;
let catalog = res.into_json::<SkymapCatalog>()?; let catalog = res.into_json::<SkymapCatalog>()?;
catalog.validate()?; catalog.validate()?;

View File

@ -1,10 +1,9 @@
use std::{cell::RefCell, collections::VecDeque, rc::Rc};
use glam::{Mat4, Vec3}; use glam::{Mat4, Vec3};
use std::{cell::RefCell, collections::VecDeque, rc::Rc};
use wgui::{ use wgui::{
animation::{Animation, AnimationEasing}, animation::{Animation, AnimationEasing},
color::{WguiColor, WguiColorName},
components::tooltip::{TOOLTIP_BORDER_COLOR, TOOLTIP_COLOR}, components::tooltip::{TOOLTIP_BORDER_COLOR, TOOLTIP_COLOR},
drawing::Color,
i18n::Translation, i18n::Translation,
layout::{Layout, LayoutTask, LayoutTasks, WidgetID}, layout::{Layout, LayoutTask, LayoutTasks, WidgetID},
renderer_vk::{ renderer_vk::{
@ -69,8 +68,8 @@ impl ToastManager {
width: percent(1.0), width: percent(1.0),
height: percent(0.8), height: percent(0.8),
}, },
align_items: Some(taffy::AlignItems::End), align_items: Some(taffy::AlignItems::END),
justify_content: Some(taffy::JustifyContent::Center), justify_content: Some(taffy::JustifyContent::CENTER),
..Default::default() ..Default::default()
}, },
)?; )?;
@ -78,8 +77,8 @@ impl ToastManager {
let (rect, _) = layout.add_child( let (rect, _) = layout.add_child(
root.id, root.id,
WidgetRectangle::create(WidgetRectangleParams { WidgetRectangle::create(WidgetRectangleParams {
color: TOOLTIP_COLOR, color: TOOLTIP_COLOR.into(),
border_color: TOOLTIP_BORDER_COLOR, border_color: TOOLTIP_BORDER_COLOR.into(),
border: 2.0, border: 2.0,
round: WLength::Percent(1.0), round: WLength::Percent(1.0),
..Default::default() ..Default::default()
@ -111,6 +110,7 @@ impl ToastManager {
wrap: true, wrap: true,
..Default::default() ..Default::default()
}, },
..Default::default()
}, },
); );
let (label, _) = layout.add_child(rect.id, label, taffy::Style { ..Default::default() })?; let (label, _) = layout.add_child(rect.id, label, taffy::Style { ..Default::default() })?;
@ -132,11 +132,15 @@ impl ToastManager {
} }
let rect = data.obj.get_as_mut::<WidgetRectangle>().unwrap(); let rect = data.obj.get_as_mut::<WidgetRectangle>().unwrap();
rect.params.color.a = opacity; rect.params.color = rect.params.color.with_alpha(opacity);
rect.params.border_color.a = opacity; rect.params.border_color = rect.params.border_color.with_alpha(opacity);
let mut label = common.state.widgets.get_as::<WidgetLabel>(label.id).unwrap(); let mut label = common.state.widgets.get_as::<WidgetLabel>(label.id).unwrap();
label.set_color(common, Color::new(1.0, 1.0, 1.0, opacity), true); label.set_color(
common,
WguiColor::from(WguiColorName::OnBackgroundVariant).with_alpha(opacity),
true,
);
common.alterables.mark_redraw(); common.alterables.mark_redraw();
}), }),
)); ));

View File

@ -2,8 +2,8 @@ use glam::{Mat4, Vec2};
use wgui::{ use wgui::{
animation::{Animation, AnimationEasing}, animation::{Animation, AnimationEasing},
assets::AssetPath, assets::AssetPath,
color::WguiColorName,
components::{self, button::ButtonClickCallback}, components::{self, button::ButtonClickCallback},
drawing,
i18n::Translation, i18n::Translation,
layout::{Layout, LayoutTask, WidgetID}, layout::{Layout, LayoutTask, WidgetID},
parser::{Fetchable, ParseDocumentParams}, parser::{Fetchable, ParseDocumentParams},
@ -55,6 +55,7 @@ pub fn create_label(layout: &mut Layout, id_parent: WidgetID, content: Translati
wrap: true, wrap: true,
..Default::default() ..Default::default()
}, },
..Default::default()
}, },
); );
@ -70,10 +71,11 @@ pub fn create_label_error(layout: &mut Layout, parent: WidgetID, content: String
content: Translation::from_raw_text_string(content), content: Translation::from_raw_text_string(content),
style: TextStyle { style: TextStyle {
wrap: true, wrap: true,
color: Some(drawing::Color::new(1.0, 0.5, 0.0, 1.0)), color: Some(WguiColorName::Danger.into()),
weight: Some(FontWeight::Bold), weight: Some(FontWeight::Bold),
..Default::default() ..Default::default()
}, },
..Default::default()
}, },
); );
@ -84,8 +86,9 @@ pub fn create_label_error(layout: &mut Layout, parent: WidgetID, content: String
pub fn create_icon(layout: &mut Layout, id_parent: WidgetID, size: Vec2, path: AssetPath) -> anyhow::Result<WidgetID> { pub fn create_icon(layout: &mut Layout, id_parent: WidgetID, size: Vec2, path: AssetPath) -> anyhow::Result<WidgetID> {
let widget_sprite = WidgetSprite::create(WidgetSpriteParams { let widget_sprite = WidgetSprite::create(WidgetSpriteParams {
color: None, color: Some(WguiColorName::OnBackground.into()),
glyph_data: Some(CustomGlyphData::from_assets(&layout.state.globals, path)?), glyph_data: Some(CustomGlyphData::from_assets(&layout.state.globals, path)?),
..Default::default()
}); });
let size = taffy::Size { let size = taffy::Size {

View File

@ -1,10 +1,11 @@
use std::{collections::HashMap, rc::Rc, str::FromStr}; use std::{collections::HashMap, rc::Rc, str::FromStr};
use strum::VariantNames;
use strum::{AsRefStr, EnumString, VariantNames};
use wayvr_ipc::packet_client::{PositionMode, WvrProcessLaunchParams}; use wayvr_ipc::packet_client::{PositionMode, WvrProcessLaunchParams};
use wgui::{ use wgui::{
assets::AssetPath, assets::AssetPath,
components::{button::ComponentButton, checkbox::ComponentCheckbox, radio_group::ComponentRadioGroup}, components::{
ComponentTrait, button::ComponentButton, checkbox::ComponentCheckbox, radio_group::ComponentRadioGroup,
},
globals::WguiGlobals, globals::WguiGlobals,
i18n::Translation, i18n::Translation,
layout::{Layout, WidgetID}, layout::{Layout, WidgetID},
@ -12,7 +13,11 @@ use wgui::{
task::Tasks, task::Tasks,
widget::label::WidgetLabel, widget::label::WidgetLabel,
}; };
use wlx_common::{config::GeneralConfig, dash_interface::BoxDashInterface, desktop_finder::DesktopEntry}; use wlx_common::{
config::{AppCompositorMode, AppOrientationMode, AppPosMode, AppResMode, GeneralConfig, PinnedApp},
dash_interface::BoxDashInterface,
desktop_finder::DesktopEntry,
};
use crate::{ use crate::{
frontend::{FrontendTask, FrontendTasks, SoundType}, frontend::{FrontendTask, FrontendTasks, SoundType},
@ -20,51 +25,23 @@ use crate::{
views::{ViewTrait, ViewUpdateParams}, views::{ViewTrait, ViewUpdateParams},
}; };
#[derive(Clone, Copy, Eq, PartialEq, EnumString, VariantNames, AsRefStr)]
enum PosMode {
Floating,
Anchored,
Static,
}
#[derive(Clone, Copy, Eq, PartialEq, EnumString, VariantNames, AsRefStr)]
enum ResMode {
Res1440,
Res1080,
Res720,
Res480,
}
#[derive(Clone, Copy, Eq, PartialEq, EnumString, VariantNames, AsRefStr)]
enum OrientationMode {
Wide,
SemiWide,
Square,
SemiTall,
Tall,
}
#[derive(Clone, Copy, Eq, PartialEq, EnumString, VariantNames, AsRefStr)]
enum CompositorMode {
Cage,
Native,
}
#[derive(Clone)] #[derive(Clone)]
enum Task { enum Task {
SetCompositor(CompositorMode), SetCompositor(AppCompositorMode),
SetRes(ResMode), SetRes(AppResMode),
SetOrientation(OrientationMode), SetOrientation(AppOrientationMode),
SetAutoStart(bool), SetAutoStart(bool),
Launch, Launch,
PinApp,
UnpinApp,
} }
struct LaunchParams<'a, T> { struct LaunchParams<'a, T> {
application: &'a DesktopEntry, application: &'a DesktopEntry,
compositor_mode: CompositorMode, compositor_mode: AppCompositorMode,
pos_mode: PosMode, pos_mode: AppPosMode,
res_mode: ResMode, res_mode: AppResMode,
orientation_mode: OrientationMode, orientation_mode: AppOrientationMode,
globals: &'a WguiGlobals, globals: &'a WguiGlobals,
frontend_tasks: &'a FrontendTasks, frontend_tasks: &'a FrontendTasks,
interface: &'a mut BoxDashInterface<T>, interface: &'a mut BoxDashInterface<T>,
@ -88,14 +65,17 @@ pub struct View {
#[allow(dead_code)] #[allow(dead_code)]
radio_orientation: Rc<ComponentRadioGroup>, radio_orientation: Rc<ComponentRadioGroup>,
compositor_mode: CompositorMode, compositor_mode: AppCompositorMode,
pos_mode: PosMode, pos_mode: AppPosMode,
res_mode: ResMode, res_mode: AppResMode,
orientation_mode: OrientationMode, orientation_mode: AppOrientationMode,
pinned_app: Option<PinnedApp>,
auto_start: bool, auto_start: bool,
on_launched: Option<Box<dyn FnOnce()>>, on_close_request: Option<Box<dyn FnOnce()>>,
on_app_pins_changed: Option<Box<dyn Fn()>>,
} }
pub struct Params<'a> { pub struct Params<'a> {
@ -105,7 +85,9 @@ 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 FnOnce()>, pub on_close_request: Box<dyn FnOnce()>,
pub on_app_pins_changed: Box<dyn Fn()>,
pub pinned_app: Option<PinnedApp>,
} }
impl ViewTrait for View { impl ViewTrait for View {
@ -131,6 +113,8 @@ impl View {
let cb_autostart = state.fetch_component_as::<ComponentCheckbox>("cb_autostart")?; let cb_autostart = state.fetch_component_as::<ComponentCheckbox>("cb_autostart")?;
let btn_launch = state.fetch_component_as::<ComponentButton>("btn_launch")?; let btn_launch = state.fetch_component_as::<ComponentButton>("btn_launch")?;
let btn_pin = state.fetch_component_as::<ComponentButton>("btn_pin")?;
let btn_unpin = state.fetch_component_as::<ComponentButton>("btn_unpin")?;
{ {
let mut label_exec = state.fetch_widget_as::<WidgetLabel>(&params.layout.state, "label_exec")?; let mut label_exec = state.fetch_widget_as::<WidgetLabel>(&params.layout.state, "label_exec")?;
@ -145,6 +129,16 @@ impl View {
tasks.handle_button(&btn_launch, Task::Launch); tasks.handle_button(&btn_launch, Task::Launch);
if params.pinned_app.is_some() {
// "Unpin app"
tasks.handle_button(&btn_unpin, Task::UnpinApp);
params.layout.remove_widget(btn_pin.base().get_id());
} else {
// "Pin app"
tasks.handle_button(&btn_pin, Task::PinApp);
params.layout.remove_widget(btn_unpin.base().get_id());
}
let id_icon_parent = state.get_widget_id("icon_parent")?; let id_icon_parent = state.get_widget_id("icon_parent")?;
// app icon // app icon
@ -160,28 +154,35 @@ impl View {
)?; )?;
} }
let compositor_mode = if params.config.xwayland_by_default { let compositor_mode = match &params.pinned_app {
CompositorMode::Cage Some(pinned_app) => pinned_app.compositor_mode,
} else { None => {
CompositorMode::Native if params.config.xwayland_by_default {
AppCompositorMode::Cage
} else {
AppCompositorMode::Native
}
}
}; };
radio_compositor.set_value_simple(compositor_mode.as_ref())?;
tasks.push(Task::SetCompositor(compositor_mode));
let res_mode = ResMode::Res1080;
// TODO: configurable defaults ? // TODO: configurable defaults ?
//radio_res.set_value(res_mode.as_ref())?; let mut res_mode = AppResMode::Res1080;
//tasks.push(Task::SetRes(res_mode)); let mut orientation_mode = AppOrientationMode::Wide;
let pos_mode = AppPosMode::Anchored;
let orientation_mode = OrientationMode::Wide; if let Some(pinned_app) = &params.pinned_app {
// TODO: configurable defaults ? res_mode = pinned_app.res_mode;
//radio_orientation.set_value(orientation_mode.as_ref())?; orientation_mode = pinned_app.orientation_mode;
//tasks.push(Task::SetOrientation(orientation_mode)); }
let pos_mode = PosMode::Anchored; // update radios
// TODO: configurable defaults ? {
//radio_pos.set_value(pos_mode.as_ref())?; let mut common = params.layout.common();
//tasks.push(Task::SetPos(pos_mode)); // TODO: pos_mode is disabled as for now
radio_compositor.set_value(&mut common, compositor_mode.as_ref())?;
radio_res.set_value(&mut common, res_mode.as_ref())?;
radio_orientation.set_value(&mut common, orientation_mode.as_ref())?;
}
let auto_start = false; let auto_start = false;
@ -189,11 +190,11 @@ impl View {
let tasks = tasks.clone(); let tasks = tasks.clone();
Box::new(move |_, ev| { Box::new(move |_, ev| {
if let Some(mode) = ev.value.and_then(|v| { if let Some(mode) = ev.value.and_then(|v| {
CompositorMode::from_str(&v) AppCompositorMode::from_str(&v)
.inspect_err(|_| { .inspect_err(|_| {
log::error!( log::error!(
"Invalid value for compositor: '{v}'. Valid values are: {:?}", "Invalid value for compositor: '{v}'. Valid values are: {:?}",
ResMode::VARIANTS AppResMode::VARIANTS
) )
}) })
.ok() .ok()
@ -208,11 +209,11 @@ impl View {
let tasks = tasks.clone(); let tasks = tasks.clone();
Box::new(move |_, ev| { Box::new(move |_, ev| {
if let Some(mode) = ev.value.and_then(|v| { if let Some(mode) = ev.value.and_then(|v| {
ResMode::from_str(&v) AppResMode::from_str(&v)
.inspect_err(|_| { .inspect_err(|_| {
log::error!( log::error!(
"Invalid value for resolution: '{v}'. Valid values are: {:?}", "Invalid value for resolution: '{v}'. Valid values are: {:?}",
ResMode::VARIANTS AppResMode::VARIANTS
) )
}) })
.ok() .ok()
@ -246,11 +247,11 @@ impl View {
let tasks = tasks.clone(); let tasks = tasks.clone();
Box::new(move |_, ev| { Box::new(move |_, ev| {
if let Some(mode) = ev.value.and_then(|v| { if let Some(mode) = ev.value.and_then(|v| {
OrientationMode::from_str(&v) AppOrientationMode::from_str(&v)
.inspect_err(|_| { .inspect_err(|_| {
log::error!( log::error!(
"Invalid value for orientation: '{v}'. Valid values are: {:?}", "Invalid value for orientation: '{v}'. Valid values are: {:?}",
OrientationMode::VARIANTS AppOrientationMode::VARIANTS
) )
}) })
.ok() .ok()
@ -290,7 +291,9 @@ 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: Some(params.on_launched), on_close_request: Some(params.on_close_request),
on_app_pins_changed: Some(params.on_app_pins_changed),
pinned_app: params.pinned_app,
}) })
} }
@ -307,6 +310,14 @@ impl View {
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),
Task::PinApp => {
self.action_pin_app(interface.general_config(data));
interface.config_changed(data, Default::default());
}
Task::UnpinApp => {
self.action_unpin_app(interface.general_config(data));
interface.config_changed(data, Default::default());
}
} }
} }
} }
@ -314,6 +325,49 @@ impl View {
Ok(()) Ok(())
} }
fn close(&mut self) {
if let Some(c) = self.on_close_request.take() {
c();
}
}
fn action_unpin_app(&mut self, config: &mut GeneralConfig) {
let Some(pinned_app) = &self.pinned_app else {
unreachable!();
};
self.frontend_tasks.push(FrontendTask::PlaySound(SoundType::Save));
config.pinned_apps.retain(|p| p != pinned_app);
if let Some(c) = &self.on_app_pins_changed {
c();
}
self.close();
}
fn action_pin_app(&mut self, config: &mut GeneralConfig) {
self
.frontend_tasks
.push(FrontendTask::PushToast(Translation::from_translation_key(
"SAVED_TO_FAVOURITES",
)));
self.frontend_tasks.push(FrontendTask::PlaySound(SoundType::Save));
config.pinned_apps.push(PinnedApp {
app_id: self.entry.app_id.clone(),
compositor_mode: self.compositor_mode,
pos_mode: self.pos_mode,
orientation_mode: self.orientation_mode,
res_mode: self.res_mode,
});
if let Some(c) = &self.on_app_pins_changed {
c();
}
self.close();
}
fn action_launch<T>(&mut self, interface: &mut BoxDashInterface<T>, data: &mut T) { fn action_launch<T>(&mut self, interface: &mut BoxDashInterface<T>, data: &mut T) {
View::try_launch(LaunchParams { View::try_launch(LaunchParams {
application: &self.entry, application: &self.entry,
@ -326,7 +380,7 @@ impl View {
auto_start: self.auto_start, auto_start: self.auto_start,
interface, interface,
data, data,
on_launched: self.on_launched.take(), on_launched: self.on_close_request.take(),
}); });
} }
@ -347,7 +401,7 @@ impl View {
fn launch<T>(mut 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 == AppCompositorMode::Native {
// This list could be larger, feel free to expand it // This list could be larger, feel free to expand it
env.push("QT_QPA_PLATFORM=wayland".into()); env.push("QT_QPA_PLATFORM=wayland".into());
env.push("GDK_BACKEND=wayland".into()); env.push("GDK_BACKEND=wayland".into());
@ -357,19 +411,19 @@ impl View {
} }
let args = match params.compositor_mode { let args = match params.compositor_mode {
CompositorMode::Cage => format!("-- {} {}", params.application.exec_path, params.application.exec_args), AppCompositorMode::Cage => format!("-- {} {}", params.application.exec_path, params.application.exec_args),
CompositorMode::Native => params.application.exec_args.to_string(), AppCompositorMode::Native => params.application.exec_args.to_string(),
}; };
let exec = match params.compositor_mode { let exec = match params.compositor_mode {
CompositorMode::Cage => "cage".to_string(), AppCompositorMode::Cage => "cage".to_string(),
CompositorMode::Native => params.application.exec_path.to_string(), AppCompositorMode::Native => params.application.exec_path.to_string(),
}; };
let pos_mode = match params.pos_mode { let pos_mode = match params.pos_mode {
PosMode::Floating => PositionMode::Float, AppPosMode::Floating => PositionMode::Float,
PosMode::Anchored => PositionMode::Anchor, AppPosMode::Anchored => PositionMode::Anchor,
PosMode::Static => PositionMode::Static, AppPosMode::Static => PositionMode::Static,
}; };
let mut userdata = HashMap::new(); let mut userdata = HashMap::new();
@ -408,20 +462,20 @@ impl View {
Ok(()) Ok(())
} }
fn calculate_resolution(res_mode: ResMode, orientation_mode: OrientationMode) -> [u32; 2] { fn calculate_resolution(res_mode: AppResMode, orientation_mode: AppOrientationMode) -> [u32; 2] {
let total_pixels = match res_mode { let total_pixels = match res_mode {
ResMode::Res1440 => 2560 * 1440, AppResMode::Res1440 => 2560 * 1440,
ResMode::Res1080 => 1920 * 1080, AppResMode::Res1080 => 1920 * 1080,
ResMode::Res720 => 1280 * 720, AppResMode::Res720 => 1280 * 720,
ResMode::Res480 => 854 * 480, AppResMode::Res480 => 854 * 480,
}; };
let (ratio_w, ratio_h) = match orientation_mode { let (ratio_w, ratio_h) = match orientation_mode {
OrientationMode::Wide => (16, 9), AppOrientationMode::Wide => (16, 9),
OrientationMode::SemiWide => (3, 2), AppOrientationMode::SemiWide => (3, 2),
OrientationMode::Square => (1, 1), AppOrientationMode::Square => (1, 1),
OrientationMode::SemiTall => (2, 3), AppOrientationMode::SemiTall => (2, 3),
OrientationMode::Tall => (9, 16), AppOrientationMode::Tall => (9, 16),
}; };
let k = ((total_pixels as f64) / (ratio_w * ratio_h) as f64).sqrt(); let k = ((total_pixels as f64) / (ratio_w * ratio_h) as f64).sqrt();
@ -433,13 +487,20 @@ impl View {
} }
} }
pub fn mount_popup(frontend_tasks: FrontendTasks, globals: WguiGlobals, entry: DesktopEntry, popup: PopupHolder<View>) { pub fn mount_popup(
frontend_tasks: FrontendTasks,
globals: WguiGlobals,
entry: DesktopEntry,
popup: PopupHolder<View>,
on_app_pins_changed: Box<dyn Fn()>,
pinned_app: Option<PinnedApp>,
) {
frontend_tasks frontend_tasks
.clone() .clone()
.push(FrontendTask::MountPopupOnce(MountPopupOnceParams::new( .push(FrontendTask::MountPopupOnce(MountPopupOnceParams::new(
Translation::from_raw_text(&entry.app_name), Translation::from_raw_text(&entry.app_name),
Box::new(move |data| { Box::new(move |data| {
let on_launched = popup.get_close_callback(data.layout); let on_close_request = popup.get_close_callback(data.layout);
let view = View::new(Params { let view = View::new(Params {
entry: entry.clone(), entry: entry.clone(),
globals: &globals, globals: &globals,
@ -447,7 +508,9 @@ pub fn mount_popup(frontend_tasks: FrontendTasks, globals: WguiGlobals, entry: D
parent_id: data.id_content, parent_id: data.id_content,
frontend_tasks: &frontend_tasks, frontend_tasks: &frontend_tasks,
config: data.config, config: data.config,
on_launched, on_close_request,
on_app_pins_changed,
pinned_app,
})?; })?;
popup.set_view(data.handle, view, None); popup.set_view(data.handle, view, None);

View File

@ -2,6 +2,7 @@ use std::rc::Rc;
use wgui::{ use wgui::{
assets::AssetPath, assets::AssetPath,
color::{WguiColor, WguiColorName},
components::{ components::{
self, self,
button::{ButtonClickCallback, ComponentButton}, button::{ButtonClickCallback, ComponentButton},
@ -110,11 +111,13 @@ struct MultiSelectorParams<'a> {
} }
fn mount_multi_selector(params: MultiSelectorParams) -> anyhow::Result<()> { fn mount_multi_selector(params: MultiSelectorParams) -> anyhow::Result<()> {
let accent_color = params.ess.layout.state.theme.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;
let color = if highlighted { Some(accent_color) } else { None }; let color = if highlighted {
Some(WguiColor::from(WguiColorName::Primary))
} else {
None
};
// button // button
let (_, button) = components::button::construct( let (_, button) = components::button::construct(
@ -664,11 +667,11 @@ impl View {
let mut perform = |btn_num: u8, btn: &Rc<ComponentButton>| { let mut perform = |btn_num: u8, btn: &Rc<ComponentButton>| {
let color = if num == btn_num { let color = if num == btn_num {
com.state.theme.accent_color WguiColorName::Primary
} else { } else {
com.state.theme.button_color WguiColorName::BackgroundVariant
}; };
btn.set_color(&mut com, color); btn.set_color(&mut com, color.into());
}; };
perform(0, &self.btn_sinks); perform(0, &self.btn_sinks);

View File

@ -0,0 +1,322 @@
use std::{rc::Rc, sync::Arc};
use wgui::{
assets::AssetPath,
color::WguiColorName,
components::button::ComponentButton,
globals::WguiGlobals,
i18n::Translation,
layout::{Layout, WidgetID},
log::LogErr,
palette::PALETTES,
parser::{Fetchable, ParseDocumentParams, TemplateParams},
task::Tasks,
};
use wlx_common::{
dash_interface::ConfigChangeKind,
palette::{list_palette_files, load_custom_palette},
};
use crate::{
frontend::{FrontendTask, FrontendTasks},
tab::settings::Task as SettingsTask,
util::popup_manager::{MountPopupOnceParams, PopupHolder},
views::{self, ViewTrait, ViewUpdateParams},
};
#[derive(Clone)]
enum Task {
SelectPalette(String),
CustomPaletteUrl,
Restart,
Cancel,
}
pub struct Params<'a> {
pub globals: WguiGlobals,
pub layout: &'a mut Layout,
pub parent_id: WidgetID,
pub frontend_tasks: &'a FrontendTasks,
pub settings_tasks: Tasks<SettingsTask>,
pub current_palette: Arc<str>,
}
pub struct View {
tasks: Tasks<Task>,
frontend_tasks: FrontendTasks,
globals: WguiGlobals,
popup_dialog: PopupHolder<views::dialog_box::View>,
settings_tasks: Tasks<SettingsTask>,
chosen_palette: Option<Arc<str>>,
}
impl ViewTrait for View {
fn update(&mut self, par: &mut ViewUpdateParams) -> anyhow::Result<()> {
self.popup_dialog.update(par)?;
for task in self.tasks.drain() {
match task {
Task::SelectPalette(profile) => {
self.chosen_palette = Some(profile.into());
self.show_restart_dialog_box()?;
}
Task::Cancel => {
let close_dialog = self.popup_dialog.get_close_callback(par.layout);
close_dialog();
}
Task::Restart => {
if let Some(palette) = self.chosen_palette.take() {
par.general_config.color_palette = palette;
par.config_change_kind.replace(ConfigChangeKind::WguiThemeChange);
}
self.settings_tasks.push(SettingsTask::RestartSoftware);
}
Task::CustomPaletteUrl => {
self.frontend_tasks.push(FrontendTask::OpenURL(
"https://wayvr.org/docs/basics/customization/".into(),
));
}
}
}
Ok(())
}
}
macro_rules! insert_colors {
(
$params:expr,
$palette:expr,
$( $key:literal => $color:ident ),* $(,)?
) => {
$(
$params.insert_str(
$key,
WguiColorName::$color
.to_wgui_color()
.resolve($palette)
.to_hex()
);
)*
};
}
impl View {
pub fn new(params: Params) -> anyhow::Result<Self> {
let doc_params = &ParseDocumentParams {
globals: params.globals.clone(),
path: AssetPath::BuiltIn("gui/view/color_palettes.xml"),
extra: Default::default(),
};
let mut parser_state = wgui::parser::parse_from_assets(doc_params, params.layout, params.parent_id)?;
let list_parent = parser_state.fetch_widget(&params.layout.state, "list_parent")?.id;
let tasks = Tasks::new();
let popup_dialog = PopupHolder::<views::dialog_box::View>::default();
for (idx, name) in list_palette_files().into_iter().enumerate() {
let Ok(palette) = load_custom_palette(&name).log_warn("Could not load custom color palette") else {
continue;
};
let id = format!("profile_custom_{idx}");
let is_current = &*params.current_palette == name.as_str();
let mut cell_params = TemplateParams::new();
cell_params.insert("id", &id);
let display_name = &name[..name.len() - 5];
if is_current {
cell_params.insert_str("text", format!("{display_name}"));
cell_params.insert("tooltip", "APP_SETTINGS.COLOR_PALETTE_CURRENT");
} else {
cell_params.insert("text", display_name);
cell_params.insert("tooltip", "APP_SETTINGS.COLOR_PALETTE_ACTIVATE");
}
insert_colors!(
cell_params,
&palette,
"primary" => Primary,
"on_primary" => OnPrimary,
"secondary" => Secondary,
"on_secondary" => OnSecondary,
"tertiary" => Tertiary,
"on_tertiary" => OnTertiary,
"danger" => Danger,
"on_danger" => OnDanger,
"background" => Background,
"on_background" => OnBackground,
"background_variant" => BackgroundVariant,
"outline" => Outline,
"highlight" => Highlight,
);
parser_state.instantiate_template(
doc_params,
"ColorPaletteButton",
params.layout,
list_parent,
cell_params,
)?;
if !is_current {
let btn = parser_state.fetch_component_as::<ComponentButton>(&id)?;
let tasks_clone = tasks.clone();
btn.on_click(Rc::new({
move |_common, _e| {
tasks_clone.push(Task::SelectPalette(name.to_string()));
Ok(())
}
}));
}
}
for (idx, (name, palette)) in PALETTES.iter().enumerate() {
let id = format!("profile_builtin_{idx}");
let is_current = &*params.current_palette == *name;
let mut cell_params = TemplateParams::new();
cell_params.insert("id", &id);
if is_current {
cell_params.insert_str("text", format!("{name}"));
cell_params.insert("tooltip", "APP_SETTINGS.COLOR_PALETTE_CURRENT");
} else {
cell_params.insert("text", name);
cell_params.insert("tooltip", "APP_SETTINGS.COLOR_PALETTE_ACTIVATE");
}
insert_colors!(
cell_params,
palette,
"primary" => Primary,
"on_primary" => OnPrimary,
"secondary" => Secondary,
"on_secondary" => OnSecondary,
"tertiary" => Tertiary,
"on_tertiary" => OnTertiary,
"danger" => Danger,
"on_danger" => OnDanger,
"background" => Background,
"on_background" => OnBackground,
"background_variant" => BackgroundVariant,
"outline" => Outline,
"highlight" => Highlight,
);
parser_state.instantiate_template(
doc_params,
"ColorPaletteButton",
params.layout,
list_parent,
cell_params,
)?;
if !is_current {
let btn = parser_state.fetch_component_as::<ComponentButton>(&id)?;
let tasks_clone = tasks.clone();
btn.on_click(Rc::new({
move |_common, _e| {
tasks_clone.push(Task::SelectPalette(name.to_string()));
Ok(())
}
}));
}
}
parser_state.instantiate_template(
doc_params,
"CustomPaletteButton",
params.layout,
list_parent,
TemplateParams::default(),
)?;
let btn = parser_state.fetch_component_as::<ComponentButton>("custom_btn")?;
let tasks_clone = tasks.clone();
btn.on_click(Rc::new({
move |_common, _e| {
tasks_clone.push(Task::CustomPaletteUrl);
Ok(())
}
}));
Ok(Self {
tasks,
frontend_tasks: params.frontend_tasks.clone(),
globals: params.globals.clone(),
popup_dialog,
settings_tasks: params.settings_tasks,
chosen_palette: None,
})
}
fn show_restart_dialog_box(&mut self) -> anyhow::Result<()> {
const ACTION_RESTART: &str = "restart";
const ACTION_CANCEL: &str = "cancel";
let tasks = self.tasks.clone();
views::dialog_box::mount_popup(
self.popup_dialog.clone(),
self.frontend_tasks.clone(),
views::dialog_box::Params {
globals: self.globals.clone(),
message: Translation::from_translation_key("APP_SETTINGS.APPLY_CHANGES_RESTART"),
entries: vec![
views::dialog_box::ButtonEntry {
content: Translation::from_translation_key("APP_SETTINGS.CANCEL"),
icon: "dashboard/close.svg",
action: ACTION_CANCEL,
},
views::dialog_box::ButtonEntry {
content: Translation::from_translation_key("APP_SETTINGS.RESTART_SOFTWARE"),
icon: "dashboard/refresh.svg",
action: ACTION_RESTART,
},
],
on_action_click: Box::new(move |action| match action {
ACTION_RESTART => {
tasks.push(Task::Restart);
}
ACTION_CANCEL => {
tasks.push(Task::Cancel);
}
_ => unreachable!(),
}),
},
);
Ok(())
}
}
pub fn mount_popup(
frontend_tasks: FrontendTasks,
globals: WguiGlobals,
popup: PopupHolder<View>,
settings_tasks: Tasks<SettingsTask>,
current_palette: Arc<str>,
) {
frontend_tasks
.clone()
.push(FrontendTask::MountPopupOnce(MountPopupOnceParams::new(
Translation::from_translation_key("APP_SETTINGS.COLOR_PALETTES"),
Box::new(move |data| {
let view = View::new(Params {
globals: globals.clone(),
layout: data.layout,
parent_id: data.id_content,
frontend_tasks: &frontend_tasks,
settings_tasks,
current_palette,
})?;
popup.set_view(data.handle, view, None);
Ok(popup.get_close_callback(data.layout))
}),
Default::default(), /* extra */
)));
}

View File

@ -69,13 +69,7 @@ impl ViewTrait for View {
if let Some(on_downloaded) = self.on_downloaded.take() { if let Some(on_downloaded) = self.on_downloaded.take() {
self self
.executor .executor
.spawn(View::download( .spawn(View::download(self.tasks.clone(), url, path, on_downloaded))
self.tasks.clone(),
self.executor.clone(),
url,
path,
on_downloaded,
))
.detach(); .detach();
} }
} }
@ -189,37 +183,12 @@ impl View {
async fn download( async fn download(
tasks: Tasks<Task>, tasks: Tasks<Task>,
executor: AsyncExecutor,
url: String, url: String,
target_path: PathBuf, target_path: PathBuf,
on_downloaded: Box<dyn FnOnce()>, on_downloaded: Box<dyn FnOnce()>,
) -> Option<()> { ) -> Option<()> {
tasks.push(Task::SetStatusText(String::from("Connecting to the server..."))); 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 parent directory if it doesn't exist yet // create parent directory if it doesn't exist yet
if let Some(parent) = target_path.parent() { if let Some(parent) = target_path.parent() {
handle_async_result( handle_async_result(
@ -229,10 +198,28 @@ impl View {
)?; )?;
} }
// start downloading from the server with progress reporting
handle_async_result( handle_async_result(
"File write failed", "Download failed",
&tasks, &tasks,
smol::fs::write(target_path, res.data).await, http_client::download_to_file(
http_client::GetParams {
url: &url,
on_progress: Some(Box::new({
let tasks = tasks.clone();
move |data: ProgressFuncData| {
tasks.push(Task::SetStatusText(format!(
"{}/{} MiB ({}%)",
data.bytes_downloaded / 1024 / 1024,
data.file_size / 1024 / 1024,
(data.bytes_downloaded as f32 / data.file_size as f32 * 100.0).round()
)))
}
})),
},
&target_path,
)
.await,
)?; )?;
tasks.push(Task::SetStatusText(String::from("Download finished"))); tasks.push(Task::SetStatusText(String::from("Download finished")));

View File

@ -11,7 +11,7 @@ use wgui::{
globals::WguiGlobals, globals::WguiGlobals,
i18n::Translation, i18n::Translation,
layout::{Layout, WidgetID}, layout::{Layout, WidgetID},
renderer_vk::text::{FontWeight, HorizontalAlign, TextShadow, TextStyle, custom_glyph::CustomGlyphData}, renderer_vk::text::{FontWeight, HorizontalAlign, TextStyle, WguiTextShadow, custom_glyph::CustomGlyphData},
taffy::{ taffy::{
self, AlignItems, AlignSelf, JustifyContent, JustifySelf, self, AlignItems, AlignSelf, JustifyContent, JustifySelf,
prelude::{auto, length, percent}, prelude::{auto, length, percent},
@ -61,12 +61,8 @@ const GAME_COVER_SIZE_X: f32 = 140.0;
const GAME_COVER_SIZE_Y: f32 = 210.0; const GAME_COVER_SIZE_Y: f32 = 210.0;
impl View { impl View {
async fn request_cover_image( async fn request_cover_image(manifest: steam_utils::AppManifest, on_loaded: Box<dyn FnOnce(CoverArt)>) {
executor: AsyncExecutor, let cover_art = match cached_fetcher::request_image(manifest.app_id.clone()).await {
manifest: steam_utils::AppManifest,
on_loaded: Box<dyn FnOnce(CoverArt)>,
) {
let cover_art = match cached_fetcher::request_image(executor, manifest.app_id.clone()).await {
Ok(cover_art) => cover_art, Ok(cover_art) => cover_art,
Err(e) => { Err(e) => {
log::error!("request_cover_image failed: {:?}", e); log::error!("request_cover_image failed: {:?}", e);
@ -107,16 +103,17 @@ impl View {
content: Translation::from_raw_text(text), content: Translation::from_raw_text(text),
style: TextStyle { style: TextStyle {
weight: Some(FontWeight::Bold), weight: Some(FontWeight::Bold),
color: Some(drawing::Color::new(1.0, 1.0, 1.0, 1.0).into()),
wrap: true, wrap: true,
size: Some(16.0), size: Some(16.0),
align: Some(HorizontalAlign::Center), align: Some(HorizontalAlign::Center),
shadow: Some(TextShadow { shadow: Some(WguiTextShadow {
color: drawing::Color::new(0.0, 0.0, 0.0, 1.0), color: drawing::Color::new(0.0, 0.0, 0.0, 1.0).into(),
x: 2.0, ..Default::default()
y: 2.0,
}), }),
..Default::default() ..Default::default()
}, },
..Default::default()
}, },
); );
@ -125,8 +122,8 @@ impl View {
label, label,
taffy::Style { taffy::Style {
position: taffy::Position::Absolute, position: taffy::Position::Absolute,
align_self: Some(AlignSelf::Baseline), align_self: Some(AlignSelf::BASELINE),
justify_self: Some(JustifySelf::Center), justify_self: Some(JustifySelf::CENTER),
margin: taffy::Rect { margin: taffy::Rect {
top: length(32.0), top: length(32.0),
bottom: auto(), bottom: auto(),
@ -173,9 +170,9 @@ impl View {
let (widget_button, button) = components::button::construct( let (widget_button, button) = components::button::construct(
params.ess, params.ess,
components::button::Params { components::button::Params {
color: Some(drawing::Color::new(1.0, 1.0, 1.0, 0.0)), color: Some(drawing::Color::new(1.0, 1.0, 1.0, 0.0).into()),
border_color: Some(BORDER_COLOR_DEFAULT), border_color: Some(BORDER_COLOR_DEFAULT.into()),
hover_border_color: Some(BORDER_COLOR_HOVERED), hover_border_color: Some(BORDER_COLOR_HOVERED.into()),
round: WLength::Units(12.0), round: WLength::Units(12.0),
border: 2.0, border: 2.0,
tooltip: Some(TooltipInfo { tooltip: Some(TooltipInfo {
@ -184,8 +181,8 @@ impl View {
}), }),
style: taffy::Style { style: taffy::Style {
position: taffy::Position::Relative, position: taffy::Position::Relative,
align_items: Some(taffy::AlignItems::Center), align_items: Some(AlignItems::CENTER),
justify_content: Some(taffy::JustifyContent::Center), justify_content: Some(JustifyContent::CENTER),
size: taffy::Size { size: taffy::Size {
width: length(GAME_COVER_SIZE_X * params.scale), width: length(GAME_COVER_SIZE_X * params.scale),
height: length(GAME_COVER_SIZE_Y * params.scale), height: length(GAME_COVER_SIZE_Y * params.scale),
@ -206,16 +203,16 @@ impl View {
height: percent(1.0), height: percent(1.0),
}, },
padding: taffy::Rect::length(2.0), padding: taffy::Rect::length(2.0),
align_items: Some(AlignItems::Center), align_items: Some(AlignItems::CENTER),
justify_content: Some(JustifyContent::Center), justify_content: Some(JustifyContent::CENTER),
..Default::default() ..Default::default()
}, },
)?; )?;
let rect_gradient = |color: drawing::Color, color2: drawing::Color| { let rect_gradient = |color: drawing::Color, color2: drawing::Color| {
rectangle::WidgetRectangle::create(rectangle::WidgetRectangleParams { rectangle::WidgetRectangle::create(rectangle::WidgetRectangleParams {
color, color: color.into(),
color2, color2: color2.into(),
round: WLength::Units(12.0), round: WLength::Units(12.0),
gradient: GradientMode::Vertical, gradient: GradientMode::Vertical,
..Default::default() ..Default::default()
@ -239,7 +236,7 @@ impl View {
drawing::Color::new(1.0, 1.0, 1.0, 0.2), drawing::Color::new(1.0, 1.0, 1.0, 0.2),
drawing::Color::new(1.0, 1.0, 1.0, 0.02), drawing::Color::new(1.0, 1.0, 1.0, 0.02),
), ),
rect_gradient_style(taffy::AlignSelf::Baseline, 0.05), rect_gradient_style(taffy::AlignSelf::BASELINE, 0.05),
)?; )?;
// not optimal, this forces us to create a new pass for every created cover art just to overlay various rectangles at the top of the image cover art // not optimal, this forces us to create a new pass for every created cover art just to overlay various rectangles at the top of the image cover art
@ -252,7 +249,7 @@ impl View {
drawing::Color::new(1.0, 1.0, 1.0, 0.15), drawing::Color::new(1.0, 1.0, 1.0, 0.15),
drawing::Color::new(1.0, 1.0, 1.0, 0.0), drawing::Color::new(1.0, 1.0, 1.0, 0.0),
), ),
rect_gradient_style(taffy::AlignSelf::Baseline, 0.5), rect_gradient_style(taffy::AlignSelf::BASELINE, 0.5),
)?; )?;
// bottom black gradient // bottom black gradient
@ -262,7 +259,7 @@ impl View {
drawing::Color::new(0.0, 0.0, 0.0, 0.0), drawing::Color::new(0.0, 0.0, 0.0, 0.0),
drawing::Color::new(0.0, 0.0, 0.0, 0.25), drawing::Color::new(0.0, 0.0, 0.0, 0.25),
), ),
rect_gradient_style(taffy::AlignSelf::End, 0.5), rect_gradient_style(taffy::AlignSelf::END, 0.5),
)?; )?;
// bottom shadow // bottom shadow
@ -272,7 +269,7 @@ impl View {
drawing::Color::new(0.0, 0.0, 0.0, 0.1), drawing::Color::new(0.0, 0.0, 0.0, 0.1),
drawing::Color::new(0.0, 0.0, 0.0, 0.9), drawing::Color::new(0.0, 0.0, 0.0, 0.9),
), ),
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 { let id_loading = wgui_simple::create_loading(wgui_simple::CreateLoadingParams {
@ -285,7 +282,6 @@ impl View {
params params
.executor .executor
.spawn(View::request_cover_image( .spawn(View::request_cover_image(
params.executor.clone(),
params.manifest.clone(), params.manifest.clone(),
Box::new(params.on_loaded), Box::new(params.on_loaded),
)) ))

View File

@ -75,8 +75,8 @@ impl ViewTrait for View {
} }
impl View { impl View {
async fn fetch_details(executor: AsyncExecutor, tasks: Tasks<Task>, app_id: AppID) { async fn fetch_details(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(app_id).await else {
return; return;
}; };
@ -103,7 +103,7 @@ impl View {
let tasks = Tasks::new(); let tasks = Tasks::new();
// fetch details from the web // fetch details from the web
let fut = View::fetch_details(params.executor.clone(), tasks.clone(), params.manifest.app_id.clone()); let fut = View::fetch_details(tasks.clone(), params.manifest.app_id.clone());
params.executor.spawn(fut).detach(); params.executor.spawn(fut).detach();
let id_cover_art_parent = state.get_widget_id("cover_art_parent")?; let id_cover_art_parent = state.get_widget_id("cover_art_parent")?;

View File

@ -3,6 +3,7 @@ use wlx_common::{async_executor::AsyncExecutor, config::GeneralConfig, dash_inte
pub mod app_launcher; pub mod app_launcher;
pub mod audio_settings; pub mod audio_settings;
pub mod bindings; pub mod bindings;
pub mod color_palettes;
pub mod dialog_box; pub mod dialog_box;
pub mod download_file; pub mod download_file;
pub mod game_cover; pub mod game_cover;

View File

@ -11,8 +11,8 @@ use crate::{
use anyhow::Context; use anyhow::Context;
use wgui::{ use wgui::{
assets::AssetPath, assets::AssetPath,
color::WguiColorName,
components::button::ComponentButton, components::button::ComponentButton,
drawing::Color,
globals::WguiGlobals, globals::WguiGlobals,
i18n::Translation, i18n::Translation,
layout::{Layout, WidgetID}, layout::{Layout, WidgetID},
@ -86,7 +86,7 @@ fn mount_resolution_button(
let button = data.fetch_component_as::<ComponentButton>("button")?; let button = data.fetch_component_as::<ComponentButton>("button")?;
if already_downloaded { if already_downloaded {
button.set_color(&mut layout.common(), Color::new(0.0, 0.4, 0.0, 1.0)); // green button.set_color(&mut layout.common(), WguiColorName::Tertiary.into()); // green
} }
tasks.handle_button(&button, Task::ResolutionClicked(res)); tasks.handle_button(&button, Task::ResolutionClicked(res));
Ok(()) Ok(())

View File

@ -119,8 +119,8 @@ impl ViewTrait for View {
} }
impl View { impl View {
async fn skymap_catalog_request_wrapper(tasks: Tasks<Task>, executor: AsyncExecutor) { async fn skymap_catalog_request_wrapper(tasks: Tasks<Task>) {
let res = networking::skymap_catalog::request_catalog(&executor).await; let res = networking::skymap_catalog::request_catalog().await;
tasks.push(Task::SetSkymapCatalog(Rc::new(res))) tasks.push(Task::SetSkymapCatalog(Rc::new(res)))
} }
@ -131,7 +131,7 @@ impl View {
with_text: true, with_text: true,
})?; })?;
let tasks = Tasks::<Task>::new(); let tasks = Tasks::<Task>::new();
let fut = View::skymap_catalog_request_wrapper(tasks.clone(), par.executor.clone()); let fut = View::skymap_catalog_request_wrapper(tasks.clone());
par.executor.spawn(fut).detach(); par.executor.spawn(fut).detach();
Ok(Self { Ok(Self {
id_parent: par.parent_id, id_parent: par.parent_id,
@ -147,15 +147,10 @@ impl View {
}) })
} }
async fn request_skymap_preview( async fn request_skymap_preview(globals: WguiGlobals, entry: SkymapCatalogEntry, tasks: Tasks<Task>) {
globals: WguiGlobals,
executor: AsyncExecutor,
entry: SkymapCatalogEntry,
tasks: Tasks<Task>,
) {
tasks.push(Task::SetSkymapPreview(( tasks.push(Task::SetSkymapPreview((
entry.uuid, entry.uuid,
networking::image_fetch::fetch_to_glyph_data(&globals, &executor, &entry.files.get_url_preview()) networking::image_fetch::fetch_to_glyph_data(&globals, &entry.files.get_url_preview())
.await .await
.ok(), .ok(),
))); )));
@ -192,12 +187,7 @@ impl View {
let id_list = parser_state.fetch_widget(&layout.state, "list")?.id; let id_list = parser_state.fetch_widget(&layout.state, "list")?.id;
for entry in &catalog.entries { for entry in &catalog.entries {
let task = View::request_skymap_preview( let task = View::request_skymap_preview(self.globals.clone(), entry.clone(), self.tasks.clone());
self.globals.clone(),
self.executor.clone(),
entry.clone(),
self.tasks.clone(),
);
let skymap_uuid = entry.uuid; let skymap_uuid = entry.uuid;

View File

@ -8,14 +8,16 @@ authors = ["galister", "oo8dev"]
repository = "https://github.com/wlx-team/wayvr" repository = "https://github.com/wlx-team/wayvr"
[dependencies] [dependencies]
anyhow = { workspace = true }
glam = { workspace = true }
log = { workspace = true }
rust-embed = "8.9.0"
tracing-subscriber = { version = "0.3.22", features = ["env-filter"] }
wgui = { path = "../wgui/" }
winit = "0.30.12"
vulkano = { workspace = true }
vulkano-shaders = { workspace = true }
dash-frontend = { path = "../dash-frontend/" } dash-frontend = { path = "../dash-frontend/" }
wgui = { path = "../wgui/" }
wlx-common = { path = "../wlx-common" } wlx-common = { path = "../wlx-common" }
anyhow.workspace = true
glam.workspace = true
log.workspace = true
rust-embed.workspace = true
tracing-subscriber.workspace = true
vulkano.workspace = true
vulkano-shaders.workspace = true
winit.workspace = true
ureq = "3.3.0"

View File

@ -1,8 +0,0 @@
<layout>
<!-- TODO: deprecate this in favor of theme.xml -->
<theme>
<var key="color_bg" value="#24273a" />
<var key="color_bg_active" value="#44475a" />
<var key="text_color" value="#dddddd" />
</theme>
</layout>

View File

@ -10,6 +10,7 @@ use wgui::{
font_config::WguiFontConfig, font_config::WguiFontConfig,
globals::WguiGlobals, globals::WguiGlobals,
layout::{Layout, LayoutParams, LayoutUpdateParams}, layout::{Layout, LayoutParams, LayoutUpdateParams},
palette::WguiColorPalette,
parser::{ParseDocumentParams, ParserState}, parser::{ParseDocumentParams, ParserState},
}; };
use wlx_common::locale::WayVRLangProvider; use wlx_common::locale::WayVRLangProvider;
@ -30,12 +31,14 @@ impl TestbedAny {
}; };
let lang_provider = WayVRLangProvider::default(); let lang_provider = WayVRLangProvider::default();
let palette_name = std::env::var("PALETTE").unwrap_or_else(|_| "Default".to_string());
let globals = WguiGlobals::new( let globals = WguiGlobals::new(
assets, assets,
&lang_provider, &lang_provider,
&WguiFontConfig::default(), &WguiFontConfig::default(),
PathBuf::new(), // cwd PathBuf::new(), // cwd
WguiColorPalette::get_builtin(&palette_name),
)?; )?;
let (layout, state) = wgui::parser::new_layout_from_assets( let (layout, state) = wgui::parser::new_layout_from_assets(

View File

@ -13,6 +13,7 @@ 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 lang_provider = WayVRLangProvider::default();
let palette_name = std::env::var("PALETTE").unwrap_or_else(|_| "Default".to_string());
let frontend = frontend::Frontend::new(frontend::InitParams { let frontend = frontend::Frontend::new(frontend::InitParams {
interface: Box::new(interface), interface: Box::new(interface),
@ -20,6 +21,7 @@ impl TestbedDashboard {
has_monado: true, has_monado: true,
lang_provider: &lang_provider, lang_provider: &lang_provider,
theme: Rc::new(WguiTheme::default()), theme: Rc::new(WguiTheme::default()),
color_palette: &palette_name,
})?; })?;
Ok(Self { frontend }) Ok(Self { frontend })
} }

View File

@ -17,6 +17,7 @@ use wgui::{
globals::WguiGlobals, globals::WguiGlobals,
i18n::Translation, i18n::Translation,
layout::{Layout, LayoutParams, LayoutUpdateParams, Widget}, layout::{Layout, LayoutParams, LayoutUpdateParams, Widget},
palette::WguiColorPalette,
parser::{Fetchable, ParseDocumentExtra, ParseDocumentParams, ParserState}, parser::{Fetchable, ParseDocumentExtra, ParseDocumentParams, ParserState},
taffy::{self, prelude::length}, taffy::{self, prelude::length},
task::Tasks, task::Tasks,
@ -86,12 +87,14 @@ 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 lang_provider = WayVRLangProvider::default();
let palette_name = std::env::var("PALETTE").unwrap_or_else(|_| "Default".to_string());
let globals = WguiGlobals::new( let globals = WguiGlobals::new(
assets, assets,
&lang_provider, &lang_provider,
&WguiFontConfig::default(), &WguiFontConfig::default(),
PathBuf::new(), // cwd PathBuf::new(), // cwd
WguiColorPalette::get_builtin(&palette_name),
)?; )?;
let extra = ParseDocumentExtra { let extra = ParseDocumentExtra {
@ -116,9 +119,9 @@ impl TestbedGeneric {
color = color.mult_rgb(mult_f32); color = color.mult_rgb(mult_f32);
let mut rect = par.get_widget_as::<WidgetRectangle>().unwrap(); let mut rect = par.get_widget_as::<WidgetRectangle>().unwrap();
rect.params.color = color; rect.params.color = color.into();
})), })),
dev_mode: false, ..Default::default()
}; };
let (layout, parser_state) = wgui::parser::new_layout_from_assets( let (layout, parser_state) = wgui::parser::new_layout_from_assets(

View File

@ -8,18 +8,19 @@ authors = ["galister", "oo8dev"]
repository = "https://github.com/wlx-team/wayvr" repository = "https://github.com/wlx-team/wayvr"
[dependencies] [dependencies]
anyhow = "1.0.93" anyhow.workspace = true
async-channel.workspace = true
async-io.workspace = true
bytes.workspace = true bytes.workspace = true
log = "0.4.22" interprocess.workspace = true
log.workspace = true
serde.workspace = true serde.workspace = true
smallvec = "1.13.2" smallvec.workspace = true
# client-only deps # client-only deps
interprocess = { version = "2.2.2", features = ["tokio"], optional = true }
serde_json.workspace = true serde_json.workspace = true
tokio = { version = "1.43.1", features = ["macros"], optional = true } smol = { workspace = true, optional = true }
tokio-util = { version = "0.7.13", optional = true }
[features] [features]
default = ["client"] default = ["client"]
client = ["dep:interprocess", "dep:tokio", "dep:tokio-util"] client = ["dep:smol"]

View File

@ -1,16 +1,12 @@
use bytes::BufMut; use bytes::BufMut;
use interprocess::local_socket::{ use interprocess::local_socket::{self, ToNsName};
self, GenericNamespaced,
tokio::{Stream, prelude::*},
};
use serde::Serialize; use serde::Serialize;
use smallvec::SmallVec; use smallvec::SmallVec;
use smol::io::AsyncReadExt;
use smol::io::AsyncWriteExt;
use smol::lock::Mutex;
use std::os::unix::net::UnixStream as StdUnixStream;
use std::sync::{Arc, Weak}; use std::sync::{Arc, Weak};
use tokio::{
io::{AsyncReadExt, AsyncWriteExt},
sync::Mutex,
};
use tokio_util::sync::CancellationToken;
use crate::{ use crate::{
gen_id, gen_id,
@ -43,10 +39,10 @@ type SignalFunc = Box<dyn FnMut(&packet_server::PacketServer) -> bool + Send>;
pub struct WayVRClient { pub struct WayVRClient {
receiver: ReceiverMutex, receiver: ReceiverMutex,
sender: SenderMutex, sender: SenderMutex,
cancel_token: CancellationToken, cancel_tx: async_channel::Sender<()>,
exiting: bool, exiting: bool,
queued_packets: QueuedPacketVec, queued_packets: QueuedPacketVec,
pub auth: Option<AuthInfo>, // Some if authenticated pub auth: Option<AuthInfo>,
pub on_signal: Option<SignalFunc>, pub on_signal: Option<SignalFunc>,
} }
@ -67,8 +63,26 @@ pub async fn send_packet(sender: &SenderMutex, data: &[u8]) -> anyhow::Result<()
pub type WayVRClientMutex = Arc<Mutex<WayVRClient>>; pub type WayVRClientMutex = Arc<Mutex<WayVRClient>>;
pub type WayVRClientWeak = Weak<Mutex<WayVRClient>>; pub type WayVRClientWeak = Weak<Mutex<WayVRClient>>;
type ReceiverMutex = Arc<Mutex<local_socket::tokio::RecvHalf>>; type ReceiverMutex = Arc<Mutex<smol::net::unix::UnixStream>>;
type SenderMutex = Arc<Mutex<local_socket::tokio::SendHalf>>; type SenderMutex = Arc<Mutex<smol::net::unix::UnixStream>>;
fn interprocess_stream_to_async(
stream: local_socket::Stream,
) -> anyhow::Result<smol::net::unix::UnixStream> {
use std::os::unix::io::OwnedFd;
let uds_stream: interprocess::os::unix::uds_local_socket::Stream = match stream {
local_socket::Stream::UdSocket(s) => s,
#[allow(unreachable_patterns)]
_ => anyhow::bail!("Unsupported socket type"),
};
let owned_fd: OwnedFd = uds_stream.into();
let std_stream = StdUnixStream::from(owned_fd);
smol::net::unix::UnixStream::try_from(std_stream)
.map_err(|e| anyhow::anyhow!("Failed to wrap socket for async I/O: {}", e))
}
async fn client_runner(client: WayVRClientMutex) -> anyhow::Result<()> { async fn client_runner(client: WayVRClientMutex) -> anyhow::Result<()> {
loop { loop {
@ -79,7 +93,7 @@ async fn client_runner(client: WayVRClientMutex) -> anyhow::Result<()> {
type Payload = SmallVec<[u8; 64]>; type Payload = SmallVec<[u8; 64]>;
async fn read_payload( async fn read_payload(
conn: &mut local_socket::tokio::RecvHalf, conn: &mut smol::net::unix::UnixStream,
size: u32, size: u32,
) -> anyhow::Result<Payload> { ) -> anyhow::Result<Payload> {
let mut payload = Payload::new(); let mut payload = Payload::new();
@ -122,32 +136,32 @@ impl WayVRClient {
pub async fn new(client_name: &str) -> anyhow::Result<WayVRClientMutex> { pub async fn new(client_name: &str) -> anyhow::Result<WayVRClientMutex> {
let printname = "/tmp/wayvr_ipc.sock"; let printname = "/tmp/wayvr_ipc.sock";
let name = printname.to_ns_name::<GenericNamespaced>()?;
let stream = match Stream::connect(name).await { let name = printname
Ok(c) => c, .to_ns_name::<local_socket::GenericNamespaced>()
Err(e) => { .map_err(|e| anyhow::anyhow!("Failed to create socket name: {}", e))?;
anyhow::bail!("Failed to connect to the WayVR IPC: {}", e) let opts = local_socket::ConnectOptions::new().name(name);
} let ip_stream = opts
}; .connect_sync()
let (receiver, sender) = stream.split(); .map_err(|e| anyhow::anyhow!("Failed to connect to the WayVR IPC: {}", e))?;
let receiver = Arc::new(Mutex::new(receiver)); let async_stream = interprocess_stream_to_async(ip_stream)?;
let sender = Arc::new(Mutex::new(sender)); let receiver = Arc::new(Mutex::new(async_stream.clone()));
let sender = Arc::new(Mutex::new(async_stream));
let cancel_token = CancellationToken::new(); let (cancel_tx, cancel_rx) = async_channel::bounded(1);
let client = Arc::new(Mutex::new(Self { let client = Arc::new(Mutex::new(Self {
receiver, receiver,
sender: sender.clone(), sender: sender.clone(),
cancel_tx,
exiting: false, exiting: false,
cancel_token: cancel_token.clone(),
queued_packets: QueuedPacketVec::new(), queued_packets: QueuedPacketVec::new(),
auth: None, auth: None,
on_signal: None, on_signal: None,
})); }));
WayVRClient::start_runner(client.clone(), cancel_token); WayVRClient::start_runner(client.clone(), cancel_rx);
// Send handshake to the server // Send handshake to the server
send_packet( send_packet(
@ -163,17 +177,31 @@ impl WayVRClient {
Ok(client) Ok(client)
} }
fn start_runner(client: WayVRClientMutex, cancel_token: CancellationToken) { fn start_runner(client: WayVRClientMutex, cancel_rx: async_channel::Receiver<()>) {
tokio::spawn(async move { smol::spawn(async move {
tokio::select! { let cancel_fut = async {
_ = cancel_token.cancelled() => { cancel_rx
log::info!("Exiting IPC runner gracefully"); .recv()
} .await
e = client_runner(client.clone()) => { .map_err(|_| anyhow::anyhow!("cancelled"))
log::info!("IPC Runner failed: {:?}", e); };
let result = smol::future::race(client_runner(client.clone()), cancel_fut).await;
match result {
Ok(()) => {
log::info!("IPC Runner completed");
}
Err(e) => {
if e.to_string() == "cancelled" {
log::info!("Exiting IPC runner gracefully");
} else {
log::info!("IPC Runner failed: {:?}", e);
} }
}
} }
}); })
.detach();
} }
async fn tick(client_mtx: WayVRClientMutex) -> anyhow::Result<()> { async fn tick(client_mtx: WayVRClientMutex) -> anyhow::Result<()> {
@ -185,7 +213,9 @@ impl WayVRClient {
// read packet // read packet
let packet = { let packet = {
let mut receiver = receiver.lock().await; let mut receiver = receiver.lock().await;
let packet_size = receiver.read_u32().await?; let mut size_buf = [0u8; 4];
receiver.read_exact(&mut size_buf).await?;
let packet_size = u32::from_be_bytes(size_buf);
log::trace!("packet size {}", packet_size); log::trace!("packet size {}", packet_size);
if packet_size > 128 * 1024 { if packet_size > 128 * 1024 {
anyhow::bail!("packet size too large (> 128 KiB)"); anyhow::bail!("packet size too large (> 128 KiB)");
@ -442,6 +472,6 @@ impl WayVRClient {
impl Drop for WayVRClient { impl Drop for WayVRClient {
fn drop(&mut self) { fn drop(&mut self) {
self.exiting = true; self.exiting = true;
self.cancel_token.cancel(); let _ = self.cancel_tx.try_send(());
} }
} }

View File

@ -1,25 +1,29 @@
use std::sync::Arc; use async_channel::{Receiver, Sender};
use tokio::sync::Notify;
// Copyable wrapped Notify struct for easier usage // Copyable wrapped Notify struct for easier usage
#[derive(Default, Clone)] #[derive(Clone)]
pub struct Notifier { pub struct Notifier {
notifier: Arc<Notify>, sender: Sender<()>,
receiver: Receiver<()>,
} }
impl Notifier { impl Notifier {
pub fn new() -> Self { pub fn new() -> Self {
Self { let (sender, receiver) = async_channel::bounded(1);
notifier: Arc::new(Notify::new()), Self { sender, receiver }
}
} }
pub fn notify(&self) { pub fn notify(&self) {
self.notifier.notify_waiters(); let _ = self.sender.try_send(());
} }
pub async fn wait(&self) { pub async fn wait(&self) {
self.notifier.notified().await; let _ = self.receiver.recv().await;
}
}
impl Default for Notifier {
fn default() -> Self {
Self::new()
} }
} }

View File

@ -1,6 +1,6 @@
[package] [package]
name = "wayvr" name = "wayvr"
version = "26.7.0" version = "26.7.1"
edition = "2024" edition = "2024"
license = "GPL-3.0-only" license = "GPL-3.0-only"
authors = ["galister", "oo8dev"] authors = ["galister", "oo8dev"]
@ -27,10 +27,14 @@ wlx-capture = { path = "../wlx-capture" }
wlx-common = { path = "../wlx-common" } wlx-common = { path = "../wlx-common" }
anyhow.workspace = true anyhow.workspace = true
chrono = { workspace = true, features = ["unstable-locales"] }
clap.workspace = true clap.workspace = true
dbus.workspace = true
glam = { workspace = true, features = ["mint", "serde"] } glam = { workspace = true, features = ["mint", "serde"] }
idmap = { workspace = true, features = ["serde"] } idmap = { workspace = true, features = ["serde"] }
idmap-derive.workspace = true idmap-derive.workspace = true
interprocess.workspace = true
libc.workspace = true
log.workspace = true log.workspace = true
pipewire = { workspace = true, optional = true } pipewire = { workspace = true, optional = true }
regex.workspace = true regex.workspace = true
@ -39,68 +43,86 @@ serde = { workspace = true, features = ["rc"] }
serde_json.workspace = true serde_json.workspace = true
serde_json5.workspace = true serde_json5.workspace = true
slotmap.workspace = true slotmap.workspace = true
smallvec.workspace = true
smol = { workspace = true }
strum.workspace = true strum.workspace = true
tracing-subscriber.workspace = true
uuid = { workspace = true }
vulkano.workspace = true vulkano.workspace = true
vulkano-shaders.workspace = true vulkano-shaders.workspace = true
xdg.workspace = true
ash = "^0.38.0" # must match vulkano
bytes = { workspace = true }
chrono = { version = "0.4.42", features = ["unstable-locales"] }
chrono-tz = "0.10.4"
config = "0.15.19"
dbus = { version = "0.9.9" }
futures = "0.3.31"
image_dds = { version = "0.7.2", default-features = false, features = [
"ddsfile",
] }
input-linux = "0.7.1"
interprocess = { version = "2.2.3" }
json = { version = "0.12.4", optional = true }
json5 = "1.3.0"
libc = "0.2.178"
libmonado = { git = "https://github.com/wayvr-org/libmonado-rs.git", rev = "6f66b26930c24a8a2fc57ddcd85704784894c750", optional = true }
log-panics = { version = "2.1.0", features = ["with-backtrace"] }
mint = "0.5.9"
openxr = { version = "0.21.0", features = ["linked", "mint"], optional = true }
ovr_overlay = { git = "https://github.com/galister/ovr_overlay_oyasumi", rev = "e477bd2a9e04293ea68c1e7529ef2cb131f32acc", features = [
"ovr_input",
"ovr_system",
], optional = true }
prost = { version = "0.14.3", optional = true }
pure-rust-locales = "0.8.2"
rosc = { version = "0.11.4", optional = true }
serde_yaml = "0.9.34"
signal-hook = "0.3.18"
smallvec = "1.15.1"
smithay = { version = "0.7.0", default-features = false, features = [
"backend_vulkan",
"desktop",
"wayland_frontend",
"xwayland",
] }
smol = { workspace = true }
sysinfo = { version = "0.37" }
thiserror = "2.0"
tracing = "0.1.43"
tracing-subscriber = { version = "0.3.22", features = ["env-filter"] }
uuid = { workspace = true }
wayland-client = { workspace = true } wayland-client = { workspace = true }
wayland-protocols = { workspace = true } wayland-protocols = { workspace = true }
wayland-protocols-misc = { workspace = true } wayland-protocols-misc = { workspace = true }
winit = { version = "0.30.12", optional = true } winit = { workspace = true, optional = true }
xcb = { version = "1.6.0", features = [ xdg.workspace = true
"as-raw-xcb-connection",
], optional = true }
xkbcommon = { version = "0.8.0" } # 0.9.0 breaks keymap import on some distros
whisper-rs = { version = "0.16.0", features = ["vulkan"], optional = true }
smithay-clipboard = "0.7.3"
calloop = { version = "0.14", optional = true } ash = "^0.38.0" # must match vulkano
calloop-wayland-source = { version = "0.4", optional = true } bytes = { workspace = true }
chrono-tz = "0.10.4"
config = "0.15.25"
futures = "0.3.32"
image_dds = {
version = "0.7.2",
default-features = false,
features = [
"ddsfile",
]
}
input-linux = "0.7.1"
json = { version = "0.12.4", optional = true }
json5 = "1.3.1"
libmonado = {
git = "https://github.com/wayvr-org/libmonado-rs.git",
rev = "6f66b26930c24a8a2fc57ddcd85704784894c750",
optional = true
}
log-panics = { version = "2.1.0", features = ["with-backtrace"] }
mint = "0.5.9"
openxr = { version = "0.21.1", features = ["linked", "mint"], optional = true }
ovr_overlay = {
git = "https://github.com/galister/ovr_overlay_oyasumi",
rev = "e477bd2a9e04293ea68c1e7529ef2cb131f32acc",
features = [
"ovr_input",
"ovr_system",
],
optional = true
}
prost = { version = "0.14.4", optional = true }
pure-rust-locales = "0.8.2"
rosc = { version = "0.11.4", optional = true }
serde_yaml = "0.9.33"
signal-hook = "0.4.4"
smithay = {
version = "0.7.0",
default-features = false,
features = [
"backend_vulkan",
"desktop",
"wayland_frontend",
"xwayland",
]
}
smithay-clipboard = "0.7.3"
sysinfo = { version = "0.39.6" }
thiserror = "2.0.18"
tracing = "0.1.44"
whisper-rs = { version = "0.16.0", features = ["vulkan"], optional = true }
xcb = {
version = "1.7.0",
features = [
"as-raw-xcb-connection",
],
optional = true
}
xkbcommon = {
version = "0.8.0"
} # 0.9.0 breaks keymap import on some distros
calloop = { version = "0.14.4", optional = true }
calloop-wayland-source = { version = "0.4.1", optional = true }
[build-dependencies] [build-dependencies]
regex.workspace = true regex.workspace = true
[features] [features]
@ -113,6 +135,11 @@ osc = ["dep:rosc"]
pipewire = ["wlx-capture/pipewire"] pipewire = ["wlx-capture/pipewire"]
uidev = ["dep:winit"] uidev = ["dep:winit"]
wayland = ["pipewire", "wlx-capture/wlr", "xkbcommon/wayland"] wayland = ["pipewire", "wlx-capture/wlr", "xkbcommon/wayland"]
whisper = ["dep:pipewire", "dep:whisper-rs", "dep:calloop", "dep:calloop-wayland-source"] whisper = [
"dep:calloop",
"dep:calloop-wayland-source",
"dep:pipewire",
"dep:whisper-rs"
]
x11 = ["dep:xcb", "wlx-capture/xshm", "xkbcommon/x11"] x11 = ["dep:xcb", "wlx-capture/xshm", "xkbcommon/x11"]
xcb = ["dep:xcb"] xcb = ["dep:xcb"]

6
wayvr/run Executable file
View File

@ -0,0 +1,6 @@
#!/bin/bash
cargo run --profile=plain -- --show --replace

View File

@ -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="m9.55 18l-5.7-5.7l1.425-1.425L9.55 15.15l9.175-9.175L20.15 7.4z"/></svg>

After

Width:  |  Height:  |  Size: 300 B

View File

@ -10,18 +10,18 @@
round="8" round="8"
align_items="center" align_items="center"
justify_content="space_between" justify_content="space_between"
color="~color_bg" color="background"
border_color="~color_accent" border_color="outline"
> >
<label id="label_title" margin_left="8" color="~color_text" size="22" weight="bold" /> <label id="label_title" margin_left="8" color="on_background" size="22" weight="bold" />
<div gap="4"> <div gap="4">
<Button macro="window_button" _release="::EditToggle" <Button macro="window_button" _release="::EditToggle">
border_color="~color_faded_translucent" color="~color_faded_50" color2="~color_faded_10">
<sprite macro="window_button_icon" src="watch/edit.svg" /> <sprite macro="window_button_icon" src="watch/edit.svg" />
</Button> </Button>
<Button macro="window_button" _release="::DecorCloseWindow" <Button macro="window_button" _release="::DecorCloseWindow"
border_color="~color_danger_translucent" color="~color_danger_50" color2="~color_danger_10"> color="danger" border_color="danger(rgb-mult-0.5)"
hover_color="danger(rgb-add-0.2)" hover_border_color="danger">
<sprite macro="window_button_icon" src="edit/close.svg" /> <sprite macro="window_button_icon" src="edit/close.svg" />
</Button> </Button>
</div> </div>

View File

@ -1,33 +1,28 @@
<layout> <layout>
<macro name="button_style"
margin="2" overflow="hidden" box_sizing="border_box"
border_color="~color_accent" border="2" round="8" color="~color_accent_50" color2="~color_accent_10" gradient="vertical"
align_items="center" justify_content="center" />
<macro name="button_style" <macro name="button_style"
margin="2" overflow="hidden" box_sizing="border_box" align_items="center" justify_content="center" margin="2" overflow="hidden" box_sizing="border_box" align_items="center" justify_content="center"
border="2" round="50%" padding="8" gradient="vertical" tooltip_side="bottom" /> border="2" round="50%" padding="8" gradient="vertical" tooltip_side="bottom" />
<template name="TopButton"> <template name="TopButton">
<Button id="${id}" macro="button_style" tooltip="${tooltip}" _press="${press}" _release="${release}" sticky="${sticky}" border_color="~color_accent_translucent" color="~color_accent_50" color2="~color_accent_10"> <Button id="${id}" macro="button_style" tooltip="${tooltip}" _press="${press}" _release="${release}" sticky="${sticky}">
<sprite id="${id}_sprite" width="48" height="48" src="${src}" /> <sprite id="${id}_sprite" width="48" height="48" src="${src}" />
</Button> </Button>
</template> </template>
<template name="TopButtonDanger"> <template name="TopButtonDanger">
<Button macro="button_style" tooltip="${tooltip}" _release="${release}" border_color="~color_danger_translucent" color="~color_danger_50" color2="~color_danger_10"> <Button macro="button_style" tooltip="${tooltip}" _release="${release}" border_color="danger(rgb-mult-0.5)" color="danger" hover_color="danger(rgb-add-0.2)" hover_border_color="danger">
<sprite width="48" height="48" src="${src}" /> <sprite width="48" height="48" src="${src}" />
</Button> </Button>
</template> </template>
<template name="TopButtonFaded"> <template name="TopButtonTertiary">
<Button macro="button_style" tooltip="${tooltip}" _press="${press}" _release="${release}" border_color="~color_faded_translucent" color="~color_faded_50" color2="~color_faded_10"> <Button macro="button_style" tooltip="${tooltip}" _press="${press}" _release="${release}" color="tertiary" border_color="tertiary(rgb-mult-0.5)" hover_color="tertiary(rgb-add-0.2)" hover_border_color="tertiary">
<sprite width="48" height="48" src="${src}" /> <sprite width="48" height="48" src="${src}" />
</Button> </Button>
</template> </template>
<template name="PosButton"> <template name="PosButton">
<Button id="${id}" macro="button_style" tooltip="${tooltip}" _press="${press}" _release="${release}" border_color="~color_accent_translucent" color="~color_accent_50" color2="~color_accent_10"> <Button id="${id}" macro="button_style" tooltip="${tooltip}" _press="${press}" _release="${release}" >
<sprite id="${id}_sprite" width="40" height="40" src="${src}" /> <sprite id="${id}_sprite" width="40" height="40" src="${src}" />
</Button> </Button>
</template> </template>
@ -37,7 +32,7 @@
<rectangle id="shadow" width="100%" height="100%" padding="4" gap="4" align_items="center" justify_content="space_between" color="#000000c0" flex_direction="column"> <rectangle id="shadow" width="100%" height="100%" padding="4" gap="4" align_items="center" justify_content="space_between" color="#000000c0" flex_direction="column">
<div /> <div />
<div flex_direction="column"> <div flex_direction="column">
<rectangle padding="16" gap="8" round="32" color="~color_bg" border="2" border_color="~color_accent" justify_content="center"> <rectangle padding="16" gap="8" round="32" color="background(opacity-0.9)" border="2" border_color="outline" justify_content="center">
<div flex_direction="column" gap="8"> <div flex_direction="column" gap="8">
<div flex_direction="row" gap="4"> <div flex_direction="row" gap="4">
<TopButton sticky="0" id="top_lock" src="edit/lock_open.svg" tooltip="EDIT_MODE.LOCK_INTERACTION" press="::EditModeToggleLock" /> <TopButton sticky="0" id="top_lock" src="edit/lock_open.svg" tooltip="EDIT_MODE.LOCK_INTERACTION" press="::EditModeToggleLock" />
@ -51,7 +46,7 @@
<!-- TopButton sticky="0" id="top_resize" src="edit/resize.svg" tooltip="EDIT_MODE.RESIZE_PRESS_AND_DRAG" / --> <!-- TopButton sticky="0" id="top_resize" src="edit/resize.svg" tooltip="EDIT_MODE.RESIZE_PRESS_AND_DRAG" / -->
<TopButtonDanger src="edit/delete.svg" tooltip="EDIT_MODE.DELETE" release="::EditModeDelete" /> <TopButtonDanger src="edit/delete.svg" tooltip="EDIT_MODE.DELETE" release="::EditModeDelete" />
<div width="8" height="100%" /> <div width="8" height="100%" />
<TopButtonFaded src="watch/edit.svg" tooltip="EDIT_MODE.LEAVE" press="::EditToggle" /> <TopButtonTertiary src="edit/check.svg" tooltip="EDIT_MODE.LEAVE" press="::EditToggle" />
</div> </div>
<div id="tab_none" display="none" height="100"> <div id="tab_none" display="none" height="100">
<label align="center" translation="EDIT_MODE.HINT_POINT_WINDOW" /> <label align="center" translation="EDIT_MODE.HINT_POINT_WINDOW" />
@ -129,7 +124,7 @@
</div> </div>
<div width="100%" justify_content="end" flex_direction="row"> <div width="100%" justify_content="end" flex_direction="row">
<div> <div>
<Button id="resize_corner" macro="button_style" tooltip="EDIT_MODE.RESIZE_CORNER" tooltip_side="left" _press="::EditModeResizeStart" _release="::EditModeResizeStop" border_color="~color_accent_translucent" color="~color_accent_50" color2="~color_accent_10"> <Button id="resize_corner" macro="button_style" tooltip="EDIT_MODE.RESIZE_CORNER" tooltip_side="left" _press="::EditModeResizeStart" _release="::EditModeResizeStop">
<sprite width="48" height="48" src="edit/resize-corner.svg" /> <sprite width="48" height="48" src="edit/resize-corner.svg" />
</Button> </Button>
</div> </div>

View File

@ -6,25 +6,25 @@
padding="16" padding="16"
flex_direction="column" flex_direction="column"
gap="8" gap="8"
color="~color_bg" border_color="~color_accent" border="2" round="8"> color="background" border_color="outline" border="2" round="8">
<div flex_direction="row" padding="8" justify_content="space_between"> <div flex_direction="row" padding="8" justify_content="space_between">
<div flex_direction="column" align_items="center" gap="8" max_width="160" min_width="160"> <div flex_direction="column" align_items="center" gap="8" max_width="160" min_width="160">
<sprite color="~color_text" src="grab-help/controller-grip-joy.svg" min_width="128" min_height="128" max_width="128" max_height="128" flex_grow="1" /> <sprite src="grab-help/controller-grip-joy.svg" min_width="128" min_height="128" max_width="128" max_height="128" flex_grow="1" color="primary" />
<label wrap="1" size="25" padding_left="16" padding_right="16" translation="GRAB.ADJUST_DISTANCE" align="center" /> <label wrap="1" size="25" padding_left="16" padding_right="16" translation="GRAB.ADJUST_DISTANCE" align="center" />
</div> </div>
<div flex_direction="column" align_items="center" gap="8" max_width="160" min_width="160"> <div flex_direction="column" align_items="center" gap="8" max_width="160" min_width="160">
<sprite color="~color_text" src="grab-help/controller-grip-trigger-joy.svg" min_width="128" min_height="128" max_width="128" max_height="128" flex_grow="1" /> <sprite src="grab-help/controller-grip-trigger-joy.svg" min_width="128" min_height="128" max_width="128" max_height="128" flex_grow="1" color="primary" />
<label wrap="1" size="25" padding_left="16" padding_right="16" translation="GRAB.ADJUST_SIZE" align="center" /> <label wrap="1" size="25" padding_left="16" padding_right="16" translation="GRAB.ADJUST_SIZE" align="center" />
</div> </div>
<div flex_direction="column" align_items="center" gap="8" max_width="160" min_width="160"> <div flex_direction="column" align_items="center" gap="8" max_width="160" min_width="160">
<sprite color="~color_text" src="grab-help/controller-grip-b.svg" min_width="128" min_height="128" max_width="128" max_height="128" flex_grow="1" /> <sprite src="grab-help/controller-grip-b.svg" min_width="128" min_height="128" max_width="128" max_height="128" flex_grow="1" color="primary" />
<label wrap="1" size="25" padding_left="16" padding_right="16" translation="GRAB.UNRESTRICTED_MOVEMENT" align="center" /> <label wrap="1" size="25" padding_left="16" padding_right="16" translation="GRAB.UNRESTRICTED_MOVEMENT" align="center" />
</div> </div>
</div> </div>
<!-- A simple separator --> <!-- A simple separator -->
<rectangle width="100%" height="2" color="~color_accent" /> <rectangle width="100%" height="2" color="outline" />
<div align_items="center" padding="8" > <div align_items="center" padding="8" >
<label display="none" id="grabbing_watch" wrap="1" size="20" padding_right="16" translation="GRAB.GRABBING_WATCH" /> <label display="none" id="grabbing_watch" wrap="1" size="20" padding_right="16" translation="GRAB.GRABBING_WATCH" />

View File

@ -1,20 +1,24 @@
<layout> <layout>
<macro name="keycap_rect" <macro name="keycap_rect"
margin="2" width="100%" overflow="hidden" box_sizing="border_box" margin="2" width="100%" overflow="hidden" box_sizing="border_box"
border_color="~color_accent_translucent" border="2" round="6" color="~color_accent_40" color2="~color_accent_10" gradient="vertical" border_color="outline" border="2" round="6" color="background_variant" color2="background" gradient="vertical"
align_items="center" justify_content="center" /> align_items="center" justify_content="center" />
<macro name="tray_rect" <macro name="tray_rect"
margin="2" width="100%" overflow="hidden" box_sizing="border_box" margin="2" width="100%" overflow="hidden" box_sizing="border_box"
border_color="~color_accent_translucent" border="2" round="6" color="~color_bg" color2="~color_accent_10" gradient="vertical" border_color="outline" border="2" round="6" color="background" color2="background_variant" gradient="vertical"
align_items="center" justify_content="center" /> align_items="center" justify_content="center" />
<macro name="keycap_div" <macro name="keycap_div"
width="${width}" height="${height}" min_width="${width}" min_height="${height}" max_width="${width}" max_height="${height}" width="${width}" height="${height}" min_width="${width}" min_height="${height}" max_width="${width}" max_height="${height}"
/> />
<theme>
<var key="text_size" value="20" />
</theme>
<template name="VerticalSeparator"> <template name="VerticalSeparator">
<rectangle width="2" height="100%" color="~color_accent" /> <rectangle width="2" height="100%" color="primary" />
</template> </template>
<!-- The keyboard is build from the xkb keymap. This file is for customizing the keycaps. --> <!-- The keyboard is build from the xkb keymap. This file is for customizing the keycaps. -->
@ -24,7 +28,7 @@
<template name="KeySpecial"> <template name="KeySpecial">
<div macro="keycap_div"> <div macro="keycap_div">
<rectangle id="${id}" macro="keycap_rect"> <rectangle id="${id}" macro="keycap_rect">
<sprite color="~color_text" width="24" height="24" src="keyboard/${text}.svg" /> <sprite color="on_background" width="24" height="24" src="keyboard/${text}.svg" />
</rectangle> </rectangle>
</div> </div>
</template> </template>
@ -34,7 +38,7 @@
<template name="KeyLetter"> <template name="KeyLetter">
<div macro="keycap_div"> <div macro="keycap_div">
<rectangle id="${id}" macro="keycap_rect"> <rectangle id="${id}" macro="keycap_rect">
<label text="${text}" size="18" /> <label text="${text}" size="~text_size" />
</rectangle> </rectangle>
</div> </div>
</template> </template>
@ -44,8 +48,8 @@
<template name="KeyLetterAltGr"> <template name="KeyLetterAltGr">
<div macro="keycap_div"> <div macro="keycap_div">
<rectangle id="${id}" macro="keycap_rect" gap="3"> <rectangle id="${id}" macro="keycap_rect" gap="3">
<label text="${text}" size="18" /> <label text="${text}" size="~text_size" />
<label color="~color_text_translucent" text="${text_altgr}" size="18" /> <label color="on_background_variant" text="${text_altgr}" size="~text_size" />
</rectangle> </rectangle>
</div> </div>
</template> </template>
@ -55,8 +59,8 @@
<template name="KeySymbol"> <template name="KeySymbol">
<div macro="keycap_div"> <div macro="keycap_div">
<rectangle id="${id}" macro="keycap_rect" gap="3"> <rectangle id="${id}" macro="keycap_rect" gap="3">
<label color="~color_text_translucent" text="${text_shift}" size="18" /> <label color="on_background_variant" text="${text_shift}" size="~text_size" />
<label text="${text}" size="18" /> <label text="${text}" size="~text_size" />
</rectangle> </rectangle>
</div> </div>
</template> </template>
@ -67,14 +71,14 @@
<div macro="keycap_div"> <div macro="keycap_div">
<rectangle id="${id}" macro="keycap_rect" flex_direction="row" flex_wrap="wrap" padding="4"> <rectangle id="${id}" macro="keycap_rect" flex_direction="row" flex_wrap="wrap" padding="4">
<div width="50%" height="50%" align_items="center" justify_content="center"> <div width="50%" height="50%" align_items="center" justify_content="center">
<label color="~color_text_translucent" text="${text_shift}" size="18" /> <label color="on_background_variant" text="${text_shift}" size="~text_size" />
</div> </div>
<div width="50%" height="50%" align_items="center" justify_content="center" /> <div width="50%" height="50%" align_items="center" justify_content="center" />
<div width="50%" height="50%" align_items="center" justify_content="center"> <div width="50%" height="50%" align_items="center" justify_content="center">
<label text="${text}" size="18" /> <label text="${text}" size="~text_size" />
</div> </div>
<div width="50%" height="50%" align_items="center" justify_content="center"> <div width="50%" height="50%" align_items="center" justify_content="center">
<label color="~color_text_translucent" text="${text_altgr}" size="18" /> <label color="on_background_variant" text="${text_altgr}" size="~text_size" />
</div> </div>
</rectangle> </rectangle>
</div> </div>
@ -90,10 +94,10 @@
</div> </div>
</template> </template>
<macro name="button_style" border="2" border_color="~color_accent_translucent" color="~color_bg" round="6" <macro name="button_style" border="2" round="6"
align_items="center" justify_content="center" padding="6" width="60" height="60" overflow="visible"/> align_items="center" justify_content="center" padding="6" width="60" height="60" overflow="visible" />
<macro name="bg_rect" width="100%" color="~color_bg" round="10" border="2" border_color="~color_accent" /> <macro name="bg_rect" width="100%" color="background_contrast" round="10" border="2" border_color="primary" />
<blueprint name="menu_app"> <blueprint name="menu_app">
<context_menu > <context_menu >
@ -108,7 +112,7 @@
<!-- An app with a single icon. --> <!-- An app with a single icon. -->
<template name="App"> <template name="App">
<Button macro="button_style" id="overlay_${idx}" tooltip_str="${name}" _context_name="${name}" _press_left="::ContextMenuOpen menu_app" _press_right="::OverlayToggle ${name}"> <Button macro="button_style" id="overlay_${idx}" tooltip_str="${name}" _context_name="${name}" _press_left="::ContextMenuOpen menu_app" _press_right="::OverlayToggle ${name}">
<sprite width="38" height="38" color="~color_text" src_ext="${icon}" /> <sprite width="38" height="38" src_ext="${icon}" parent_color="ignore" />
</Button> </Button>
</template> </template>
@ -123,9 +127,9 @@
<!-- A screen with a shortened connector name, e.g. "H1" for HDMI-A-1 or "D2" for DP-2 --> <!-- A screen with a shortened connector name, e.g. "H1" for HDMI-A-1 or "D2" for DP-2 -->
<template name="Screen"> <template name="Screen">
<Button macro="button_style" id="overlay_${idx}" tooltip_str="${name}" _context_name="${name}" _press_left="::ContextMenuOpen menu_screen" _press_right="::OverlayToggle ${name}"> <Button macro="button_style" id="overlay_${idx}" tooltip_str="${name}" _context_name="${name}" _press_left="::ContextMenuOpen menu_screen" _press_right="::OverlayToggle ${name}">
<sprite width="38" height="38" color="~color_text" src_builtin="edit/screen.svg" /> <sprite width="38" height="38" src_builtin="edit/screen.svg" />
<div position="absolute" margin_top="-7" margin_left="-1"> <div position="absolute" margin_top="-7" margin_left="-1">
<label text="${display}" size="18" color="~color_faded_20" weight="bold" /> <label text="${display}" size="~text_size" weight="bold" parent_color="background" />
</div> </div>
</Button> </Button>
</template> </template>
@ -141,7 +145,7 @@
<template name="Panel"> <template name="Panel">
<Button macro="button_style" id="overlay_${idx}" tooltip_str="${name}" _context_name="${name}" _press_left="::ContextMenuOpen menu_panel" _press_right="::OverlayToggle ${name}"> <Button macro="button_style" id="overlay_${idx}" tooltip_str="${name}" _context_name="${name}" _press_left="::ContextMenuOpen menu_panel" _press_right="::OverlayToggle ${name}">
<sprite width="38" height="38" color="~color_text" src="${icon}" /> <sprite width="38" height="38" src="${icon}" />
</Button> </Button>
</template> </template>
@ -156,9 +160,9 @@
<template name="Mirror"> <template name="Mirror">
<Button macro="button_style" id="overlay_${idx}" tooltip_str="${name}" _context_name="${name}" _press_left="::ContextMenuOpen menu_mirror" _press_right="::OverlayToggle ${name}"> <Button macro="button_style" id="overlay_${idx}" tooltip_str="${name}" _context_name="${name}" _press_left="::ContextMenuOpen menu_mirror" _press_right="::OverlayToggle ${name}">
<sprite width="38" height="38" color="~color_text" src_builtin="edit/mirror.svg" /> <sprite width="38" height="38" src_builtin="edit/mirror.svg" />
<div position="absolute" margin_top="5" margin_left="13"> <div position="absolute" margin_top="5" margin_left="13">
<label text="${display}" size="20" color="~color_faded_20" weight="bold" /> <label text="${display}" size="20" weight="bold" parent_color="background" />
</div> </div>
</Button> </Button>
</template> </template>
@ -185,18 +189,18 @@
<template name="Set"> <template name="Set">
<Button macro="button_style" id="set_${idx}" _press="::SetSwitch ${idx}" tooltip="WATCH.SWITCH_TO_SET" tooltip_side="bottom"> <Button macro="button_style" id="set_${idx}" _press="::SetSwitch ${idx}" tooltip="WATCH.SWITCH_TO_SET" tooltip_side="bottom">
<sprite width="38" height="38" color="~color_text" src_builtin="watch/set2.svg" /> <sprite width="38" height="38" src_builtin="watch/set2.svg" />
<div position="absolute" margin_top="10" margin_left="-7"> <div position="absolute" margin_top="10" margin_left="-7">
<label text="${display}" size="20" color="~color_faded_20" weight="bold" /> <label text="${display}" size="20" weight="bold" parent_color="background" />
</div> </div>
</Button> </Button>
</template> </template>
<template name="Clock"> <template name="Clock">
<div flex_direction="column" gap="3" align_items="center"> <div flex_direction="column" gap="3" align_items="center">
<label text="23:59" _source="clock" _display="time" size="21" weight="bold" /> <label text="23:59" _source="clock" _display="time" size="21" weight="bold" color="primary" />
<label text="Tuesday" _source="clock" _display="dow" size="15" /> <label text="Tuesday" _source="clock" _display="dow" size="15" color="on_background_variant" />
<label text="22/2/2022" _source="clock" _display="date" size="15" /> <label text="22/2/2022" _source="clock" _display="date" size="15" color="on_background_variant" />
</div> </div>
</template> </template>
@ -205,7 +209,7 @@
<rectangle macro="bg_rect" padding="10" align_items="center" justify_content="space_between"> <rectangle macro="bg_rect" padding="10" align_items="center" justify_content="space_between">
<div gap="10"> <div gap="10">
<Button macro="button_style" id="btn_dashboard" _press="::DashToggle"> <Button macro="button_style" id="btn_dashboard" _press="::DashToggle">
<sprite width="38" height="38" color="~color_text" src="watch/wayvr_dashboard_mono.svg" /> <sprite width="38" height="38" src="watch/wayvr_dashboard_mono.svg" />
</Button> </Button>
<VerticalSeparator /> <VerticalSeparator />
<div id="panels_root" gap="6"> <div id="panels_root" gap="6">
@ -216,7 +220,7 @@
</div> </div>
<div id="tray_root" flex_direction="row" gap="10"> <div id="tray_root" flex_direction="row" gap="10">
<Button macro="button_style" _press="::ContextMenuOpen menu_burger"> <Button macro="button_style" _press="::ContextMenuOpen menu_burger">
<sprite width="38" height="38" color="~color_text" src_builtin="keyboard/burger.svg" /> <sprite width="38" height="38" src_builtin="keyboard/burger.svg" />
</Button> </Button>
<VerticalSeparator /> <VerticalSeparator />

View File

@ -0,0 +1,18 @@
<layout>
<elements>
<rectangle width="1280" height="720" padding="10" align_items="center" justify_content="space_evenly" flex_direction="column" color="background" color2="background_variant" border="2" border_color="primary" gradient="radial">
<div width="100%" align_items="center">
<label width="100%" id="status" color="secondary" text="" size="36" align="center" />
</div>
<div width="100%" align_items="center" flex_direction="column" gap="16">
<div width="100%" align_items="center">
<label width="100%" color="on_background" text="~name" size="48" align="center" />
</div>
<div width="100%" align_items="center">
<label width="100%" color="on_background_variant" text="~description" size="36" align="center" wrap="1" />
</div>
</div>
</rectangle>
</elements>
</layout>

View File

@ -6,22 +6,22 @@
padding="16" padding="16"
flex_direction="column" flex_direction="column"
gap="8" gap="8"
color="#000000c0" border_color="~color_accent" border="2" round="8"> color="background" border_color="outline" border="2" round="8">
<!-- Toast title --> <!-- Toast title -->
<div flex_direction="row" align_items="center" gap="8"> <div flex_direction="row" align_items="center" gap="8">
<sprite src="icons/bell.svg" min_width="32" min_height="32" max_width="32" max_height="32" flex_grow="1" /> <sprite src="icons/bell.svg" color="primary" min_width="32" min_height="32" max_width="32" max_height="32" flex_grow="1" />
<label wrap="1" id="toast_title" weight="bold" size="25" padding_left="16" padding_right="16" /> <label wrap="1" id="toast_title" color="primary" weight="bold" size="25" padding_left="16" padding_right="16" />
</div> </div>
<!-- A simple separator --> <!-- A simple separator -->
<rectangle width="100%" height="2" color="~color_accent" /> <rectangle width="100%" height="2" color="outline" />
<div> <div>
<!-- Toast message --> <!-- Toast message -->
<label id="toast_body" wrap="1" size="20" padding_left="16" padding_right="16" /> <label id="toast_body" color="on_background" wrap="1" size="20" padding_left="16" padding_right="16" />
</div> </div>
</rectangle> </rectangle>
</div> </div>
</elements> </elements>
</layout> </layout>

View File

@ -3,25 +3,20 @@
<var key="clock0_size" value="46" /> <var key="clock0_size" value="46" />
<var key="clock0_date_size" value="16" /> <var key="clock0_date_size" value="16" />
<var key="clock0_dow_size" value="16" /> <var key="clock0_dow_size" value="16" />
<var key="clock_alt1_color" value="#8bd5ca" />
<var key="clock_alt2_color" value="#b7bdf8" />
<var key="clock_alt_size" value="24" /> <var key="clock_alt_size" value="24" />
<var key="clock_alt_tz_size" value="14" /> <var key="clock_alt_tz_size" value="14" />
</theme> </theme>
<macro name="decorative_rect" <macro name="decorative_rect"
padding="8" color="~color_bg" padding="8" color="background"
border="2" border_color="~color_accent" round="8" /> border="2" border_color="outline" round="8" />
<macro name="button_style" <macro name="button_style" padding="8" border="2" round="8" align_items="center" justify_content="center" />
padding="8"
border_color="~color_accent_translucent" border="2" round="8" color="~color_accent_40" color2="~color_accent_10" gradient="vertical"
align_items="center" justify_content="center" />
<template name="Device"> <template name="Device">
<rectangle macro="decorative_rect" padding_top="4" padding_bottom="4" align_items="center" gap="8"> <rectangle macro="decorative_rect" padding_top="4" padding_bottom="4" align_items="center" gap="8">
<sprite width="32" height="32" src_builtin="${src}" color="~color_text" /> <sprite width="32" height="32" src_builtin="${src}" color="on_background" />
<label _source="battery" _device="${idx}" size="24" weight="bold" /> <label _source="battery" _device="${idx}" size="24" weight="bold" _low_color="danger" _normal_color="primary" _charging_color="tertiary" />
</rectangle> </rectangle>
</template> </template>
@ -40,36 +35,36 @@
<template name="Screen"> <template name="Screen">
<Button macro="button_style" id="overlay_${idx}" tooltip_str="${name}" _press="::OverlayToggle ${name}"> <Button macro="button_style" id="overlay_${idx}" tooltip_str="${name}" _press="::OverlayToggle ${name}">
<sprite width="38" height="38" color="~color_text" src_builtin="edit/screen.svg" /> <sprite width="38" height="38" src_builtin="edit/screen.svg" />
<div position="absolute" margin_top="-7" margin_left="-1"> <div position="absolute" margin_top="-7" margin_left="-1">
<label text="${display}" size="18" color="~color_faded_20" weight="bold" /> <label text="${display}" size="18" weight="bold" parent_color="background" />
</div> </div>
</Button> </Button>
</template> </template>
<template name="Panel"> <template name="Panel">
<Button macro="button_style" id="overlay_${idx}" tooltip_str="${name}" _press="::OverlayToggle ${name}"> <Button macro="button_style" id="overlay_${idx}" tooltip_str="${name}" _press="::OverlayToggle ${name}">
<sprite width="38" height="38" color="~color_text" src_builtin="edit/panel.svg" /> <sprite width="38" height="38" src_builtin="edit/panel.svg" />
</Button> </Button>
</template> </template>
<template name="Mirror"> <template name="Mirror">
<Button macro="button_style" id="overlay_${idx}" tooltip_str="${name}" _press="::OverlayToggle ${name}"> <Button macro="button_style" id="overlay_${idx}" tooltip_str="${name}" _press="::OverlayToggle ${name}">
<sprite width="38" height="38" color="~color_text" src_builtin="edit/mirror.svg" /> <sprite width="38" height="38" src_builtin="edit/mirror.svg" />
<div position="absolute" margin_top="5" margin_left="13"> <div position="absolute" margin_top="5" margin_left="13">
<label text="${display}" size="20" color="~color_faded_20" weight="bold" /> <label text="${display}" size="20" weight="bold" parent_color="background" />
</div> </div>
</Button> </Button>
</template> </template>
<template name="App"> <template name="App">
<Button macro="button_style" id="overlay_${idx}" tooltip_str="${name}" _press="::OverlayToggle ${name}"> <Button macro="button_style" id="overlay_${idx}" tooltip_str="${name}" _press="::OverlayToggle ${name}">
<sprite width="38" height="38" color="~color_text" src_ext="${icon}" /> <sprite width="38" height="38" src_ext="${icon}" />
</Button> </Button>
</template> </template>
<template name="Set"> <template name="Set">
<Button macro="button_style" id="set_${idx}" _press="::SetToggle ${idx}" tooltip="WATCH.SWITCH_TO_SET" tooltip_side="top"> <Button macro="button_style" id="set_${idx}" _press="::SetToggle ${idx}" tooltip="WATCH.SWITCH_TO_SET" tooltip_side="top">
<sprite width="38" height="38" color="~color_text" src_builtin="watch/set2.svg" /> <sprite width="38" height="38" src_builtin="watch/set2.svg" />
<div position="absolute" margin_top="9"> <div position="absolute" margin_top="9">
<label text="${display}" size="24" color="#00050F" weight="bold" /> <label text="${display}" size="24" weight="bold" parent_color="background" />
</div> </div>
</Button> </Button>
</template> </template>
@ -77,28 +72,28 @@
<template name="Clock"> <template name="Clock">
<div gap="12" flex_direction="column" padding="4"> <div gap="12" flex_direction="column" padding="4">
<div flex_direction="column" > <div flex_direction="column" >
<label text="11:22 PM" _source="clock" _display="time" color="~color_text" size="~clock0_size" weight="bold" align="center" /> <label text="11:22 PM" _source="clock" _display="time" color="on_background" size="~clock0_size" weight="bold" align="center" />
<div padding_left="2" gap="16" flex_direction="row" justify_content="center"> <div padding_left="2" gap="16" flex_direction="row" justify_content="center">
<label text="Tue" _source="clock" _display="dow_short" color="~color_text" size="~clock0_dow_size" weight="bold" /> <label text="Tue" _source="clock" _display="dow_short" color="on_background_variant" size="~clock0_dow_size" weight="bold" />
<label text="22/2/2022" _source="clock" _display="date" color="~color_text" size="~clock0_date_size" weight="bold" /> <label text="22/2/2022" _source="clock" _display="date" color="on_background_variant" size="~clock0_date_size" weight="bold" />
</div> </div>
</div> </div>
<div flex_direction="row" gap="8" justify_content="space_around"> <div flex_direction="row" gap="8" justify_content="space_around">
<!-- Timezone names here are only placeholders. Set your timezones via ~/.config/wayvr/conf.d --> <!-- Timezone names here are only placeholders. Set your timezones via ~/.config/wayvr/conf.d -->
<div flex_direction="column"> <div flex_direction="column">
<label text="Paris" _source="clock" _display="name" _timezone="0" color="~clock_alt1_color" size="~clock_alt_tz_size" weight="bold" align="center" /> <label text="Paris" _source="clock" _display="name" _timezone="0" color="secondary" size="~clock_alt_tz_size" weight="bold" align="center" />
<label text="23:59" _source="clock" _display="time" _timezone="0" color="~clock_alt1_color" size="~clock_alt_size" weight="bold" align="center" /> <label text="23:59" _source="clock" _display="time" _timezone="0" color="secondary" size="~clock_alt_size" weight="bold" align="center" />
</div> </div>
<div flex_direction="column"> <div flex_direction="column">
<label text="New York" _source="clock" _display="name" _timezone="1" color="~clock_alt2_color" size="~clock_alt_tz_size" weight="bold" align="center" /> <label text="New York" _source="clock" _display="name" _timezone="1" color="tertiary" size="~clock_alt_tz_size" weight="bold" align="center" />
<label text="23:59" _source="clock" _display="time" _timezone="1" color="~clock_alt2_color" size="~clock_alt_size" weight="bold" align="center" /> <label text="23:59" _source="clock" _display="time" _timezone="1" color="tertiary" size="~clock_alt_size" weight="bold" align="center" />
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<template name="VerticalSeparator"> <template name="VerticalSeparator">
<rectangle width="2" height="100%" color="~color_accent" /> <rectangle width="2" height="100%" color="outline" />
</template> </template>
<elements> <elements>
@ -128,18 +123,18 @@
<div flex_direction="column" gap="8"> <div flex_direction="column" gap="8">
<div gap="8"> <div gap="8">
<Button id="btn_keyboard" macro="button_style" _press="::OverlayToggle kbd" tooltip="EDIT_MODE.KEYBOARD" tooltip_side="left"> <Button id="btn_keyboard" macro="button_style" _press="::OverlayToggle kbd" tooltip="EDIT_MODE.KEYBOARD" tooltip_side="left">
<sprite src_builtin="watch/keyboard.svg" color="~color_text" width="40" height="40" /> <sprite src_builtin="watch/keyboard.svg" width="40" height="40" />
</Button> </Button>
<Button id="btn_edit_mode" macro="button_style" _press="::EditToggle" tooltip="WATCH.EDIT_MODE" tooltip_side="left"> <Button id="btn_edit_mode" macro="button_style" _press="::EditToggle" tooltip="WATCH.EDIT_MODE" tooltip_side="left">
<sprite color="~color_text" width="40" height="40" src="watch/edit.svg" /> <sprite width="40" height="40" src="watch/edit.svg" />
</Button> </Button>
</div> </div>
<div gap="8"> <div gap="8">
<Button macro="button_style" _press="::PlayspaceRecenter" tooltip="WATCH.RECENTER" tooltip_side="left"> <Button macro="button_style" _press="::PlayspaceRecenter" tooltip="WATCH.RECENTER" tooltip_side="left">
<sprite width="40" height="40" color="~color_text" src="watch/recenter.svg" /> <sprite width="40" height="40" src="watch/recenter.svg" />
</Button> </Button>
<Button macro="button_style" _press="::PlayspaceFixFloor" tooltip="WATCH.FIX_FLOOR" tooltip_side="left"> <Button macro="button_style" _press="::PlayspaceFixFloor" tooltip="WATCH.FIX_FLOOR" tooltip_side="left">
<sprite width="40" height="40" color="~color_text" src="watch/fix-floor.svg" /> <sprite width="40" height="40" src="watch/fix-floor.svg" />
</Button> </Button>
</div> </div>
</div> </div>
@ -149,7 +144,7 @@
<div flex_direction="row" gap="8"> <div flex_direction="row" gap="8">
<div gap="4"> <div gap="4">
<Button id="btn_dashboard" macro="button_style" _press="::DashToggle" tooltip="WATCH.DASHBOARD" tooltip_side="top"> <Button id="btn_dashboard" macro="button_style" _press="::DashToggle" tooltip="WATCH.DASHBOARD" tooltip_side="top">
<sprite color="~color_text" width="40" height="40" src="watch/wayvr_dashboard_mono.svg" /> <sprite width="40" height="40" src="watch/wayvr_dashboard_mono.svg" />
</Button> </Button>
</div> </div>
<VerticalSeparator /> <VerticalSeparator />

View File

@ -1,11 +1,11 @@
<layout> <layout>
<macro name="button_style" border="2" border_color="~color_accent_translucent" color="~color_bg" round="6" <macro name="button_style" border="2" round="6"
align_items="center" justify_content="center" padding="6" width="60" height="60" overflow="visible"/> align_items="center" justify_content="center" padding="6" width="60" height="60" overflow="visible" />
<macro name="bg_rect" width="100%" color="~color_bg" round="10" border="2" border_color="~color_accent" /> <macro name="bg_rect" width="100%" color="background" border_color="outline" round="10" border="2" />
<template name="VerticalSeparator"> <template name="VerticalSeparator">
<rectangle width="2" height="100%" color="~color_accent" /> <rectangle width="2" height="100%" color="outline" />
</template> </template>
<elements> <elements>
@ -13,27 +13,28 @@
<rectangle macro="bg_rect" padding="10" align_items="center" justify_content="space_between"> <rectangle macro="bg_rect" padding="10" align_items="center" justify_content="space_between">
<div gap="10"> <div gap="10">
<Button macro="button_style" _press="::WhisperTranscribeStart" _release="::WhisperTranscribeStop" tooltip="WHISPER.TRANSCRIBE" tooltip_side="right"> <Button macro="button_style" _press="::WhisperTranscribeStart" _release="::WhisperTranscribeStop" tooltip="WHISPER.TRANSCRIBE" tooltip_side="right">
<sprite width="38" height="38" color="~color_text" src="icons/mic.svg" /> <sprite width="38" height="38" src="icons/mic.svg" />
</Button> </Button>
<VerticalSeparator /> <VerticalSeparator />
<Button macro="button_style" _press="::WhisperPaste" tooltip="WHISPER.PASTE" tooltip_side="right"> <Button macro="button_style" _press="::WhisperPaste" tooltip="WHISPER.PASTE" tooltip_side="right">
<sprite width="38" height="38" color="~color_text" src_builtin="icons/paste.svg" /> <sprite width="38" height="38" src_builtin="icons/paste.svg" />
</Button> </Button>
<Button macro="button_style" _press="::WhisperPasteAndGo" tooltip="WHISPER.PASTE_AND_GO" tooltip_side="right"> <Button macro="button_style" _press="::WhisperPasteAndGo" tooltip="WHISPER.PASTE_AND_GO" tooltip_side="right">
<sprite width="38" height="38" color="~color_text" src_builtin="icons/paste-go.svg" /> <sprite width="38" height="38" src_builtin="icons/paste-go.svg" />
</Button> </Button>
<VerticalSeparator /> <VerticalSeparator />
<Button macro="button_style" _press="::SendKey Return down" _release="::SendKey Return up" tooltip="WHISPER.ENTER_KEY" tooltip_side="right"> <Button macro="button_style" _press="::SendKey Return down" _release="::SendKey Return up" tooltip="WHISPER.ENTER_KEY" tooltip_side="right">
<sprite width="38" height="38" color="~color_text" src_builtin="keyboard/return.svg" /> <sprite width="38" height="38" src_builtin="keyboard/return.svg" />
</Button> </Button>
<VerticalSeparator /> <VerticalSeparator />
<Button macro="button_style" _press="::WhisperSendOSC" tooltip="WHISPER.SEND_OSC" id="btn_osc_send" tooltip_side="right"> <Button macro="button_style" _press="::WhisperSendOSC" tooltip="WHISPER.SEND_OSC" id="btn_osc_send" tooltip_side="right">
<sprite width="38" height="38" color="~color_text" src_builtin="icons/chat.svg" /> <sprite width="38" height="38" src_builtin="icons/chat.svg" />
</Button> </Button>
</div> </div>
<div gap="10"> <div gap="10">
<Button macro="button_style" _press="::WhisperUnloadAndClose" tooltip="WHISPER.UNLOAD_AND_CLOSE" tooltip_side="left"> <Button macro="button_style" _press="::WhisperUnloadAndClose" tooltip="WHISPER.UNLOAD_AND_CLOSE" tooltip_side="left"
<sprite width="38" height="38" color="~color_text" src_builtin="edit/close.svg" /> hover_color="danger" hover_border_color="danger(rgb-mult-0.5)">
<sprite width="38" height="38" src_builtin="edit/close.svg" />
</Button> </Button>
</div> </div>
</rectangle> </rectangle>

View File

@ -109,11 +109,12 @@
"NO_SET_SELECTED": "Kein Set ausgewählt.", "NO_SET_SELECTED": "Kein Set ausgewählt.",
"LAST_EXISTING_SET": "Dies ist das letzte vorhandene Set.", "LAST_EXISTING_SET": "Dies ist das letzte vorhandene Set.",
"EMPTY_SET": "Leeres Set!", "EMPTY_SET": "Leeres Set!",
"LETS_ADD_OVERLAYS": "Lass uns ein paar Overlays von der Uhr hinzufügen!",
"ONE_CONTROLLER_ON_FLOOR": "Lege einen Controller auf den Boden!", "ONE_CONTROLLER_ON_FLOOR": "Lege einen Controller auf den Boden!",
"CANNOT_ADD_SET": "Satz kann nicht hinzugefügt werden!", "CANNOT_ADD_SET": "Satz kann nicht hinzugefügt werden!",
"MAXIMUM_SETS_REACHED": "Maximale Anzahl an Sets erreicht.", "MAXIMUM_SETS_REACHED": "Maximale Anzahl an Sets erreicht.",
"FIXING_FLOOR_IN_X_SECS": "Boden wird in {SECONDS} Sekunde(n) repariert..." "FIXING_FLOOR_IN_X_SECS": "Boden wird in {SECONDS} Sekunde(n) repariert...",
"TITLE_SCREENCAST_FAIL": "Bildschirmübertragungsquelle konnte nicht gestartet werden!",
"LETS_ADD_OVERLAYS": "Lass uns einige Overlays aus der Taskleiste hinzufügen!"
}, },
"DONE": "Erledigt", "DONE": "Erledigt",
"WHISPER": { "WHISPER": {
@ -127,5 +128,12 @@
"INITIAL_TEXT": "Halten Sie die Mikrofontaste gedrückt, um mit der Transkription zu beginnen...", "INITIAL_TEXT": "Halten Sie die Mikrofontaste gedrückt, um mit der Transkription zu beginnen...",
"INIT_ERROR": "Fehler beim Starten der Whisper-Engine", "INIT_ERROR": "Fehler beim Starten der Whisper-Engine",
"ENTER_KEY": "Enter-Taste" "ENTER_KEY": "Enter-Taste"
},
"SCREENCAST": {
"PENDING": "Bitte warten...",
"QUEUED": "Warten auf die Bestätigung des Screen-Cast-Pop-ups eines anderen Overlays.",
"WAITING_FOR_USER_DESC": "Überprüfen Sie Ihren Desktop auf ein Pop-up zur Bildschirmübertragung.\nWählen Sie Folgendes aus:",
"WAITING_FOR_USER": "Überprüfen Sie Ihren Desktop auf ein Pop-up zur Bildschirmübertragung.",
"FAIL": "Bildschirmübertragungsquelle konnte nicht initialisiert werden."
} }
} }

View File

@ -89,7 +89,15 @@
"GRABBING_WATCH": "To swap hands, move the watch in front and grab it with the other hand.", "GRABBING_WATCH": "To swap hands, move the watch in front and grab it with the other hand.",
"UNRESTRICTED_MOVEMENT": "Unrestricted movement" "UNRESTRICTED_MOVEMENT": "Unrestricted movement"
}, },
"SCREENCAST": {
"PENDING": "Please wait...",
"QUEUED": "Waiting for a different overlay's screen cast pop-up to be approved.",
"WAITING_FOR_USER_DESC": "Check your dekstop for a screen cast pop-up.\nSelect the following:",
"WAITING_FOR_USER": "Check your desktop for a screen cast pop-up.",
"FAIL": "Could not initialize screen cast source."
},
"TOAST": { "TOAST": {
"TITLE_SCREENCAST_FAIL": "Could not start screen cast source!",
"CANNOT_ADD_SET": "Cannot add set!", "CANNOT_ADD_SET": "Cannot add set!",
"CANNOT_REMOVE_SET": "Cannot remove set!", "CANNOT_REMOVE_SET": "Cannot remove set!",
"DEFAULT_TITLE": "Notification", "DEFAULT_TITLE": "Notification",
@ -97,7 +105,7 @@
"ERROR": "Error", "ERROR": "Error",
"FIXING_FLOOR_IN_X_SECS": "Fixing floor in {SECONDS} second(s)...", "FIXING_FLOOR_IN_X_SECS": "Fixing floor in {SECONDS} second(s)...",
"LAST_EXISTING_SET": "This is the last existing set.", "LAST_EXISTING_SET": "This is the last existing set.",
"LETS_ADD_OVERLAYS": "Let's add some overlays from the watch!", "LETS_ADD_OVERLAYS": "Let's add some overlays from the taskbar!",
"MAXIMUM_SETS_REACHED": "Maximum number of sets reached.", "MAXIMUM_SETS_REACHED": "Maximum number of sets reached.",
"NO_SET_SELECTED": "No set is selected.", "NO_SET_SELECTED": "No set is selected.",
"ONE_CONTROLLER_ON_FLOOR": "Place one controller on the floor!" "ONE_CONTROLLER_ON_FLOOR": "Place one controller on the floor!"

View File

@ -109,11 +109,12 @@
"NO_SET_SELECTED": "No se ha seleccionado ningún conjunto.", "NO_SET_SELECTED": "No se ha seleccionado ningún conjunto.",
"LAST_EXISTING_SET": "Este es el último conjunto existente.", "LAST_EXISTING_SET": "Este es el último conjunto existente.",
"EMPTY_SET": "¡Conjunto vacío!", "EMPTY_SET": "¡Conjunto vacío!",
"LETS_ADD_OVERLAYS": "¡Añadamos algunos overlays desde el reloj!",
"ONE_CONTROLLER_ON_FLOOR": "¡Coloca un mando en el suelo!", "ONE_CONTROLLER_ON_FLOOR": "¡Coloca un mando en el suelo!",
"CANNOT_ADD_SET": "¡No se puede agregar el conjunto!", "CANNOT_ADD_SET": "¡No se puede agregar el conjunto!",
"MAXIMUM_SETS_REACHED": "Se ha alcanzado el número máximo de sets.", "MAXIMUM_SETS_REACHED": "Se ha alcanzado el número máximo de sets.",
"FIXING_FLOOR_IN_X_SECS": "Corrigiendo el suelo en {SECONDS} segundo(s)..." "FIXING_FLOOR_IN_X_SECS": "Corrigiendo el suelo en {SECONDS} segundo(s)...",
"TITLE_SCREENCAST_FAIL": "¡No se pudo iniciar la fuente de transmisión de pantalla!",
"LETS_ADD_OVERLAYS": "¡Añadamos algunas superposiciones desde la barra de tareas!"
}, },
"DONE": "Hecho", "DONE": "Hecho",
"WHISPER": { "WHISPER": {
@ -127,5 +128,12 @@
"INITIAL_TEXT": "Mantén presionado el botón del micrófono para comenzar a transcribir...", "INITIAL_TEXT": "Mantén presionado el botón del micrófono para comenzar a transcribir...",
"INIT_ERROR": "Error al iniciar el motor de Whisper", "INIT_ERROR": "Error al iniciar el motor de Whisper",
"ENTER_KEY": "Tecla Enter" "ENTER_KEY": "Tecla Enter"
},
"SCREENCAST": {
"PENDING": "Por favor, espere...",
"QUEUED": "Esperando a que se apruebe la ventana emergente de la transmisión de pantalla de otra superposición.",
"WAITING_FOR_USER_DESC": "Busca la ventana emergente de transmisión de pantalla en tu escritorio.\nSelecciona lo siguiente:",
"WAITING_FOR_USER": "Revisa tu escritorio para ver la ventana emergente de transmisión de pantalla.",
"FAIL": "No se pudo inicializar la fuente de transmisión de pantalla."
} }
} }

View File

@ -97,9 +97,10 @@
"NO_SET_SELECTED": "Nessun set è selezionato.", "NO_SET_SELECTED": "Nessun set è selezionato.",
"LAST_EXISTING_SET": "Questo è l'ultimo set esistente.", "LAST_EXISTING_SET": "Questo è l'ultimo set esistente.",
"EMPTY_SET": "Set vuoto!", "EMPTY_SET": "Set vuoto!",
"LETS_ADD_OVERLAYS": "Aggiungiamo degli overlay dal Watch!",
"ONE_CONTROLLER_ON_FLOOR": "Posiziona un controller a terra!", "ONE_CONTROLLER_ON_FLOOR": "Posiziona un controller a terra!",
"FIXING_FLOOR_IN_X_SECS": "Correzione del pavimento in {SECONDS} secondo/i..." "FIXING_FLOOR_IN_X_SECS": "Correzione del pavimento in {SECONDS} secondo/i...",
"TITLE_SCREENCAST_FAIL": "Impossibile avviare la sorgente dello screen cast!",
"LETS_ADD_OVERLAYS": "Aggiungiamo alcuni overlay dalla barra delle applicazioni!"
}, },
"WATCH": { "WATCH": {
"ADD_NEW_SET": "Aggiungi un nuovo set", "ADD_NEW_SET": "Aggiungi un nuovo set",
@ -125,5 +126,12 @@
"INITIAL_TEXT": "Tieni premuto il tasto del microfono per iniziare la trascrizione...", "INITIAL_TEXT": "Tieni premuto il tasto del microfono per iniziare la trascrizione...",
"INIT_ERROR": "Errore durante l'avvio del motore Whisper", "INIT_ERROR": "Errore durante l'avvio del motore Whisper",
"ENTER_KEY": "Tasto Invio" "ENTER_KEY": "Tasto Invio"
},
"SCREENCAST": {
"PENDING": "Attendi prego...",
"QUEUED": "In attesa dell'approvazione del pop-up di screen cast di un altro overlay.",
"WAITING_FOR_USER_DESC": "Controlla il desktop per un pop-up di condivisione schermo.\nSeleziona quanto segue:",
"WAITING_FOR_USER": "Controlla il desktop per un pop-up di condivisione schermo.",
"FAIL": "Impossibile inizializzare la sorgente di trasmissione dello schermo."
} }
} }

View File

@ -107,11 +107,12 @@
"NO_SET_SELECTED": "セットが選択されていません。", "NO_SET_SELECTED": "セットが選択されていません。",
"LAST_EXISTING_SET": "これが最後の設定です。", "LAST_EXISTING_SET": "これが最後の設定です。",
"EMPTY_SET": "空のセットです!", "EMPTY_SET": "空のセットです!",
"LETS_ADD_OVERLAYS": "ウォッチからオーバーレイを追加しましょう!",
"ONE_CONTROLLER_ON_FLOOR": "コントローラーを床に置いてください!", "ONE_CONTROLLER_ON_FLOOR": "コントローラーを床に置いてください!",
"CANNOT_ADD_SET": "セットを追加できません!", "CANNOT_ADD_SET": "セットを追加できません!",
"MAXIMUM_SETS_REACHED": "最大セット数に達しました。", "MAXIMUM_SETS_REACHED": "最大セット数に達しました。",
"FIXING_FLOOR_IN_X_SECS": "{SECONDS}秒で床を固定中..." "FIXING_FLOOR_IN_X_SECS": "{SECONDS}秒で床を固定中...",
"TITLE_SCREENCAST_FAIL": "スクリーンキャストのソースを開始できませんでした!",
"LETS_ADD_OVERLAYS": "タスクバーからオーバーレイを追加しましょう!"
}, },
"DONE": "完了", "DONE": "完了",
"WHISPER": { "WHISPER": {
@ -125,5 +126,12 @@
"INITIAL_TEXT": "マイクボタンを押し続けて文字起こしを開始してください...", "INITIAL_TEXT": "マイクボタンを押し続けて文字起こしを開始してください...",
"INIT_ERROR": "Whisperエンジンの起動中にエラーが発生しました", "INIT_ERROR": "Whisperエンジンの起動中にエラーが発生しました",
"ENTER_KEY": "Enterキー" "ENTER_KEY": "Enterキー"
},
"SCREENCAST": {
"PENDING": "少々お待ちください...",
"QUEUED": "別のオーバーレイのスクリーンキャスト許可ポップアップの応答を待っています。",
"WAITING_FOR_USER_DESC": "デスクトップのスクリーンキャストのポップアップを確認してください。\n以下を選択してください",
"WAITING_FOR_USER": "デスクトップにスクリーンキャストのポップアップが表示されていないか確認してください。",
"FAIL": "スクリーンキャストのソースを初期化できませんでした。"
} }
} }

View File

@ -107,11 +107,12 @@
"NO_SET_SELECTED": "Nie wybrano żadnego zestawu.", "NO_SET_SELECTED": "Nie wybrano żadnego zestawu.",
"LAST_EXISTING_SET": "To jest ostatni istniejący zestaw.", "LAST_EXISTING_SET": "To jest ostatni istniejący zestaw.",
"EMPTY_SET": "Pusty zestaw!", "EMPTY_SET": "Pusty zestaw!",
"LETS_ADD_OVERLAYS": "Dodajmy kilka nakładek z zegarka!",
"ONE_CONTROLLER_ON_FLOOR": "Umieść jeden kontroler na podłodze!", "ONE_CONTROLLER_ON_FLOOR": "Umieść jeden kontroler na podłodze!",
"CANNOT_ADD_SET": "Nie można dodać zestawu!", "CANNOT_ADD_SET": "Nie można dodać zestawu!",
"MAXIMUM_SETS_REACHED": "Osiągnięto maksymalną liczbę zestawów.", "MAXIMUM_SETS_REACHED": "Osiągnięto maksymalną liczbę zestawów.",
"FIXING_FLOOR_IN_X_SECS": "Naprawianie podłogi za {SECONDS} sekund(y)..." "FIXING_FLOOR_IN_X_SECS": "Naprawianie podłogi za {SECONDS} sekund(y)...",
"TITLE_SCREENCAST_FAIL": "Nie udało się uruchomić źródła przesyłania obrazu z ekranu!",
"LETS_ADD_OVERLAYS": "Dodajmy kilka nakładek z paska zadań!"
}, },
"DONE": "Gotowe", "DONE": "Gotowe",
"WHISPER": { "WHISPER": {
@ -125,5 +126,12 @@
"INITIAL_TEXT": "Naciśnij i przytrzymaj przycisk mikrofonu, aby rozpocząć transkrypcję...", "INITIAL_TEXT": "Naciśnij i przytrzymaj przycisk mikrofonu, aby rozpocząć transkrypcję...",
"INIT_ERROR": "Błąd podczas uruchamiania silnika Whisper", "INIT_ERROR": "Błąd podczas uruchamiania silnika Whisper",
"ENTER_KEY": "Klawisz Enter" "ENTER_KEY": "Klawisz Enter"
},
"SCREENCAST": {
"PENDING": "Proszę czekać...",
"QUEUED": "Oczekiwanie na zatwierdzenie okna wysyłania obrazu z innego nakładki.",
"WAITING_FOR_USER_DESC": "Sprawdź na pulpicie okno wyboru ekranu do transmitowania.\nWybierz następujące:",
"WAITING_FOR_USER": "Sprawdź na pulpicie powiadomienie o przesyłaniu ekranu.",
"FAIL": "Nie udało się zainicjować źródła przesyłania obrazu z ekranu."
} }
} }

View File

@ -97,9 +97,10 @@
"NO_SET_SELECTED": "未选择集合。", "NO_SET_SELECTED": "未选择集合。",
"LAST_EXISTING_SET": "这是最后一个现有的集合。", "LAST_EXISTING_SET": "这是最后一个现有的集合。",
"EMPTY_SET": "空集合!", "EMPTY_SET": "空集合!",
"LETS_ADD_OVERLAYS": "让我们从手表添加一些覆盖层吧!",
"ONE_CONTROLLER_ON_FLOOR": "请将一个控制器放在地面上!", "ONE_CONTROLLER_ON_FLOOR": "请将一个控制器放在地面上!",
"FIXING_FLOOR_IN_X_SECS": "{SECONDS}秒内固定地面..." "FIXING_FLOOR_IN_X_SECS": "{SECONDS}秒内固定地面...",
"TITLE_SCREENCAST_FAIL": "无法启动屏幕投影源!",
"LETS_ADD_OVERLAYS": "让我们从任务栏添加一些覆盖层吧!"
}, },
"WATCH": { "WATCH": {
"ADD_NEW_SET": "添加新集合", "ADD_NEW_SET": "添加新集合",
@ -125,5 +126,12 @@
"INITIAL_TEXT": "按住麦克风按钮开始转录...", "INITIAL_TEXT": "按住麦克风按钮开始转录...",
"INIT_ERROR": "启动 Whisper 引擎时出错", "INIT_ERROR": "启动 Whisper 引擎时出错",
"ENTER_KEY": "回车键" "ENTER_KEY": "回车键"
},
"SCREENCAST": {
"PENDING": "请稍候...",
"QUEUED": "等待另一个覆盖层的屏幕投射确认弹窗。",
"WAITING_FOR_USER_DESC": "请检查桌面上的屏幕投射弹出窗口。\n选择以下选项",
"WAITING_FOR_USER": "请检查桌面上的屏幕投射弹出窗口。",
"FAIL": "无法初始化屏幕镜像源。"
} }
} }

View File

@ -888,7 +888,12 @@ where
app.anchor.translation = app.anchor.translation =
pointer.pose.transform_point3a(grab_data.offset.translation); pointer.pose.transform_point3a(grab_data.offset.translation);
let scale = scalar_scale(&app.anchor); let scale = scalar_scale(&app.anchor);
realign(&mut app.anchor, &app.input_state.hmd, scale); realign(
&mut app.anchor,
&app.input_state.hmd,
scale,
app.session.config.snap_angle_deg,
);
} }
} else { } else {
// single grab resize // single grab resize
@ -912,7 +917,12 @@ where
let scale = scalar_scale(&overlay_state.transform); let scale = scalar_scale(&overlay_state.transform);
overlay_state.transform.translation = overlay_state.transform.translation =
pointer.pose.transform_point3a(grab_data.offset.translation); pointer.pose.transform_point3a(grab_data.offset.translation);
realign(&mut overlay_state.transform, &app.input_state.hmd, scale); realign(
&mut overlay_state.transform,
&app.input_state.hmd,
scale,
app.session.config.snap_angle_deg,
);
} }
overlay.config.pause_movement = true; overlay.config.pause_movement = true;
overlay.config.dirty = true; overlay.config.dirty = true;

View File

@ -112,7 +112,9 @@ pub fn openvr_run(args: &Args) -> Result<(), BackendError> {
app.late_init(); app.late_init();
let _ = install_manifest(&mut app_mgr); if args.install {
let _ = install_manifest(&mut app_mgr);
}
let mut overlays = OverlayWindowManager::<OpenVrOverlayData>::new(&mut app, args.headless)?; let mut overlays = OverlayWindowManager::<OpenVrOverlayData>::new(&mut app, args.headless)?;

View File

@ -4,10 +4,12 @@ use ovr_overlay::{
compositor::CompositorManager, compositor::CompositorManager,
sys::{EChaperoneConfigFile, ETrackingUniverseOrigin, HmdMatrix34_t}, sys::{EChaperoneConfigFile, ETrackingUniverseOrigin, HmdMatrix34_t},
}; };
use wgui::log::LogErr;
use wlx_common::config::GeneralConfig;
use crate::{ use crate::{
backend::{input::InputState, playspace_common, task::PlayspaceTask}, backend::{input::InputState, playspace_common, task::PlayspaceTask},
state::AppState, state::{AppState, PlayspaceState, load_playspace_state, save_playspace_state},
windowing::manager::OverlayWindowManager, windowing::manager::OverlayWindowManager,
}; };
@ -23,14 +25,18 @@ pub(super) struct PlayspaceMover {
universe: ETrackingUniverseOrigin, universe: ETrackingUniverseOrigin,
drag: Option<MoverData<Vec3A>>, drag: Option<MoverData<Vec3A>>,
rotate: Option<MoverData<Quat>>, rotate: Option<MoverData<Quat>>,
floor_y: Option<f32>,
playspace_state: PlayspaceState,
} }
impl PlayspaceMover { impl PlayspaceMover {
pub const fn new() -> Self { pub fn new() -> Self {
Self { Self {
universe: ETrackingUniverseOrigin::TrackingUniverseRawAndUncalibrated, universe: ETrackingUniverseOrigin::TrackingUniverseRawAndUncalibrated,
drag: None, drag: None,
rotate: None, rotate: None,
floor_y: None,
playspace_state: load_playspace_state().unwrap_or_default(),
} }
} }
@ -50,6 +56,9 @@ impl PlayspaceMover {
PlayspaceTask::Recenter => { PlayspaceTask::Recenter => {
self.recenter(chaperone_mgr, &app.input_state); self.recenter(chaperone_mgr, &app.input_state);
} }
PlayspaceTask::SaveCenter => {
self.save_center(chaperone_mgr);
}
} }
} }
@ -177,13 +186,10 @@ impl PlayspaceMover {
} }
pub fn reset_offset(&mut self, chaperone_mgr: &mut ChaperoneSetupManager, input: &InputState) { pub fn reset_offset(&mut self, chaperone_mgr: &mut ChaperoneSetupManager, input: &InputState) {
let mut height = 1.6; let height = match self.floor_y {
if let Some(mat) = get_working_copy(&self.universe, chaperone_mgr) { Some(y) => y,
height = input.hmd.translation.y - mat.translation.y; None => input.hmd.translation.y - 1.7,
if self.universe == ETrackingUniverseOrigin::TrackingUniverseStanding { };
apply_chaperone_transform(mat, chaperone_mgr);
}
}
let xform = if self.universe == ETrackingUniverseOrigin::TrackingUniverseSeated { let xform = if self.universe == ETrackingUniverseOrigin::TrackingUniverseSeated {
Affine3A::from_translation(Vec3::NEG_Y * height) Affine3A::from_translation(Vec3::NEG_Y * height)
@ -213,6 +219,7 @@ impl PlayspaceMover {
}; };
let offset = y1.min(y2) - 0.03; let offset = y1.min(y2) - 0.03;
mat.translation.y += offset; mat.translation.y += offset;
self.floor_y = Some(mat.translation.y);
set_working_copy(&self.universe, chaperone_mgr, &mat); set_working_copy(&self.universe, chaperone_mgr, &mat);
chaperone_mgr.commit_working_copy(EChaperoneConfigFile::EChaperoneConfigFile_Live); chaperone_mgr.commit_working_copy(EChaperoneConfigFile::EChaperoneConfigFile_Live);
@ -228,17 +235,6 @@ impl PlayspaceMover {
} }
pub fn recenter(&mut self, chaperone_mgr: &mut ChaperoneSetupManager, input: &InputState) { pub fn recenter(&mut self, chaperone_mgr: &mut ChaperoneSetupManager, input: &InputState) {
let Some(mut mat) = get_working_copy(&self.universe, chaperone_mgr) else {
log::warn!("Can't recenter - failed to get zero pose");
return;
};
mat.translation.x += input.hmd.translation.x;
mat.translation.z += input.hmd.translation.z;
set_working_copy(&self.universe, chaperone_mgr, &mat);
chaperone_mgr.commit_working_copy(EChaperoneConfigFile::EChaperoneConfigFile_Live);
if self.drag.is_some() { if self.drag.is_some() {
log::info!("Space drag interrupted by recenter"); log::info!("Space drag interrupted by recenter");
self.drag = None; self.drag = None;
@ -247,6 +243,45 @@ impl PlayspaceMover {
log::info!("Space rotate interrupted by recenter"); log::info!("Space rotate interrupted by recenter");
self.rotate = None; self.rotate = None;
} }
let Some(mat) = get_working_copy(&self.universe, chaperone_mgr) else {
log::warn!("Can't recenter - failed to get zero pose");
return;
};
let cur_rot: Quat = Quat::from_affine3(&mat);
let cur_pos: Vec3 = Vec3::from(mat.translation);
let mut stage_offset = Affine3A::from_rotation_translation(cur_rot, cur_pos);
let horiz_hmd_pos = Vec3::new(input.hmd.translation.x, 0.0, input.hmd.translation.z);
let fwd = input.hmd.transform_vector3a(Vec3A::NEG_Z);
let horiz_len_sq = fwd.x.mul_add(fwd.x, fwd.z * fwd.z);
let hmd_yaw = if horiz_len_sq > f32::EPSILON {
let yaw = (-fwd.x).atan2(-fwd.z);
Quat::from_rotation_y(yaw)
} else {
Quat::IDENTITY
};
let recenter_offset = Affine3A::from_rotation_translation(hmd_yaw, horiz_hmd_pos);
stage_offset *= recenter_offset;
let (_, new_rot, new_pos) = stage_offset.to_scale_rotation_translation();
let new_mat = if horiz_len_sq > f32::EPSILON {
Affine3A::from_rotation_translation(new_rot, new_pos)
} else {
let mut m = mat;
m.translation = new_pos.into();
m
};
set_working_copy(&self.universe, chaperone_mgr, &new_mat);
chaperone_mgr.commit_working_copy(EChaperoneConfigFile::EChaperoneConfigFile_Live);
} }
pub fn playspace_changed( pub fn playspace_changed(
@ -277,6 +312,26 @@ impl PlayspaceMover {
pub fn get_universe(&self) -> ETrackingUniverseOrigin { pub fn get_universe(&self) -> ETrackingUniverseOrigin {
self.universe.clone() self.universe.clone()
} }
pub fn save_center(&mut self, chaperone_mgr: &mut ChaperoneSetupManager) {
if self.drag.is_some() {
log::info!("Space drag interrupted by save center");
self.drag = None;
}
if self.rotate.is_some() {
log::info!("Space rotate interrupted by save center");
self.rotate = None;
}
let Some(mat) = get_working_copy(&self.universe, chaperone_mgr) else {
log::warn!("Can't save center - failed to get zero pose");
return;
};
self.playspace_state.openvr_space_center = mat;
let _ =
save_playspace_state(&self.playspace_state).log_err("Could not save playspace state");
}
} }
const fn universe_str(universe: &ETrackingUniverseOrigin) -> &'static str { const fn universe_str(universe: &ETrackingUniverseOrigin) -> &'static str {

View File

@ -87,7 +87,13 @@ impl OverlayWindowData<OpenXrOverlayData> {
smallvec![(swapchain.get_subimage(0), EyeVisibility::BOTH),] smallvec![(swapchain.get_subimage(0), EyeVisibility::BOTH),]
}; };
let transform = state.transform * self.config.backend.frame_meta().unwrap().transform; // contract let transform = state.transform
* self
.config
.backend
.frame_meta()
.map(|x| x.transform)
.unwrap_or_default(); // contract
let aspect_ratio = swapchain.extent[0] as f32 / swapchain.extent[1] as f32; let aspect_ratio = swapchain.extent[0] as f32 / swapchain.extent[1] as f32;
let (scale_x, scale_y) = if aspect_ratio < 1.0 { let (scale_x, scale_y) = if aspect_ratio < 1.0 {

View File

@ -1,4 +1,4 @@
use glam::{Affine3A, Quat, Vec3A, vec3a}; use glam::{Affine3A, Quat, Vec3, Vec3A, vec3a};
use libmonado::{MndResult, Monado, Pose, ReferenceSpaceType}; use libmonado::{MndResult, Monado, Pose, ReferenceSpaceType};
use wgui::log::LogErr; use wgui::log::LogErr;
@ -8,7 +8,7 @@ use crate::{
playspace_common::{self, SpaceGravity, SpaceGravityUpdateParams}, playspace_common::{self, SpaceGravity, SpaceGravityUpdateParams},
task::PlayspaceTask, task::PlayspaceTask,
}, },
state::AppState, state::{AppState, PlayspaceState, load_playspace_state},
windowing::manager::OverlayWindowManager, windowing::manager::OverlayWindowManager,
}; };
@ -25,6 +25,7 @@ pub(super) struct PlayspaceMover {
rotate: Option<MoverData<Quat>>, rotate: Option<MoverData<Quat>>,
gravity: SpaceGravity, gravity: SpaceGravity,
floor_y: f32, floor_y: f32,
playspace_state: PlayspaceState,
} }
impl PlayspaceMover { impl PlayspaceMover {
@ -42,6 +43,7 @@ impl PlayspaceMover {
rotate: None, rotate: None,
gravity: SpaceGravity::new(), gravity: SpaceGravity::new(),
floor_y: pose.position.y, floor_y: pose.position.y,
playspace_state: load_playspace_state().unwrap_or_default(),
}) })
} }
@ -60,6 +62,7 @@ impl PlayspaceMover {
PlayspaceTask::Recenter => { PlayspaceTask::Recenter => {
self.recenter(&app.input_state, &mut monado.ipc); self.recenter(&app.input_state, &mut monado.ipc);
} }
PlayspaceTask::SaveCenter => { /* OpenVR only */ }
} }
} }
@ -244,8 +247,34 @@ impl PlayspaceMover {
return; return;
}; };
pose.position.x += input.hmd.translation.x; let cur_rot: Quat = pose.orientation.into();
pose.position.z += input.hmd.translation.z; let cur_pos: Vec3 = pose.position.into();
let mut stage_offset = Affine3A::from_rotation_translation(cur_rot, cur_pos);
let horiz_hmd_pos = Vec3::new(input.hmd.translation.x, 0.0, input.hmd.translation.z);
let fwd = input.hmd.transform_vector3a(Vec3A::NEG_Z);
let horiz_len_sq = fwd.x.mul_add(fwd.x, fwd.z * fwd.z);
let hmd_yaw = if horiz_len_sq > f32::EPSILON {
let yaw = (-fwd.x).atan2(-fwd.z);
Quat::from_rotation_y(yaw)
} else {
Quat::IDENTITY
};
let recenter_offset = Affine3A::from_rotation_translation(hmd_yaw, horiz_hmd_pos);
stage_offset *= recenter_offset;
let (_, new_rot, new_pos) = stage_offset.to_scale_rotation_translation();
pose.position = new_pos.into();
if horiz_len_sq > f32::EPSILON {
pose.orientation = new_rot.into();
}
let _ = monado let _ = monado
.set_reference_space_offset(ReferenceSpaceType::Stage, pose) .set_reference_space_offset(ReferenceSpaceType::Stage, pose)
@ -299,6 +328,28 @@ impl PlayspaceMover {
.set_reference_space_offset(ReferenceSpaceType::Stage, pose) .set_reference_space_offset(ReferenceSpaceType::Stage, pose)
.inspect_err(|e| log::warn!("Could not fix floor due to libmonado error: {e:?}")); .inspect_err(|e| log::warn!("Could not fix floor due to libmonado error: {e:?}"));
} }
pub fn save_center(&mut self, monado: &mut Monado) {
if self.drag.is_some() {
log::info!("Space drag interrupted by save center");
self.drag = None;
}
if self.rotate.is_some() {
log::info!("Space rotate interrupted by save center");
self.rotate = None;
}
let Ok(mut pose) = monado
.get_reference_space_offset(ReferenceSpaceType::Stage)
.inspect_err(|e| log::warn!("Could not fix floor due to libmonado error: {e:?}"))
else {
return;
};
self.playspace_state.openxr_space_center = mat;
let _ =
save_playspace_state(&self.playspace_state).log_err("Could not save playspace state");
}
} }
fn apply_offset(transform: Affine3A, monado: &mut Monado) { fn apply_offset(transform: Affine3A, monado: &mut Monado) {

View File

@ -63,6 +63,8 @@ pub enum PlayspaceTask {
Recenter, Recenter,
Reset, Reset,
FixFloor, FixFloor,
#[cfg(feature = "openvr")]
SaveCenter,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]

View File

@ -97,7 +97,7 @@ impl WayVRCompositor {
) -> anyhow::Result<Self> { ) -> anyhow::Result<Self> {
let (wayland_env, listener) = create_wayland_listener()?; let (wayland_env, listener) = create_wayland_listener()?;
let xwayland_satellite = Command::new("xwayland-satellite") let xwayland_satellite = Command::new(bundled_executable("xwayland-satellite"))
.arg(wayland_env.display_num_string()) .arg(wayland_env.display_num_string())
.env("WAYLAND_DISPLAY", wayland_env.wayland_display_num_string()) .env("WAYLAND_DISPLAY", wayland_env.wayland_display_num_string())
.spawn() .spawn()
@ -392,3 +392,11 @@ fn accumulate_discrete_scroll(acc: &mut f32, delta_v120: i32) -> i32 {
steps steps
} }
/// Runs executable from APPDIR, falling back to PATH
fn bundled_executable(name: impl AsRef<std::path::Path>) -> PathBuf {
match std::env::var_os("APPDIR") {
Some(appdir) => PathBuf::from(appdir).join("usr").join("bin").join(name),
None => PathBuf::from(name.as_ref()),
}
}

Some files were not shown because too many files have changed in this diff Show More