mirror of https://github.com/wayvr-org/wayvr.git
Compare commits
77 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
4652f708b0 | |
|
|
e1879ca891 | |
|
|
786660a4d5 | |
|
|
9e43b9b3ad | |
|
|
7c16748f88 | |
|
|
a29451ceaa | |
|
|
d93b74cc8a | |
|
|
2def8c3a6d | |
|
|
30d786eaa1 | |
|
|
779baaf86c | |
|
|
7c7e7edbfb | |
|
|
b1e09910a2 | |
|
|
5407e53700 | |
|
|
e895a61ea0 | |
|
|
4ed52f0214 | |
|
|
fcc84e56b3 | |
|
|
c74c51f7cd | |
|
|
acaabead52 | |
|
|
fd8a327395 | |
|
|
952b6e10b6 | |
|
|
245666ab06 | |
|
|
227e3851bc | |
|
|
4e9b893304 | |
|
|
d311813899 | |
|
|
075a6dd63b | |
|
|
83b9a1920f | |
|
|
635cfef63e | |
|
|
a8262c1de4 | |
|
|
a3e3101ab0 | |
|
|
28442ac6a8 | |
|
|
d6268c0c20 | |
|
|
5c0ae395a2 | |
|
|
962ed32452 | |
|
|
f3438b1867 | |
|
|
e0ab4be8e3 | |
|
|
c4655b7b9a | |
|
|
c32e3ef86e | |
|
|
2ea3b222f3 | |
|
|
6f5be3a157 | |
|
|
db436d4630 | |
|
|
895e95dcf4 | |
|
|
0516270dcb | |
|
|
28a9ed2c27 | |
|
|
b1fb0d9347 | |
|
|
031157ea8c | |
|
|
6a064248dc | |
|
|
7ab47ada4b | |
|
|
1febd4ed0d | |
|
|
f2e2c37108 | |
|
|
b3e4e37992 | |
|
|
fb71975019 | |
|
|
02f1de59b2 | |
|
|
40430ea94e | |
|
|
867c343c9b | |
|
|
2e7a528857 | |
|
|
0a7f8340d7 | |
|
|
e79dfcb719 | |
|
|
cb99676230 | |
|
|
bc0fad60d5 | |
|
|
3d2689c931 | |
|
|
dc58c1a555 | |
|
|
a4ce23881a | |
|
|
62c210fc1d | |
|
|
c3cbc43f6f | |
|
|
155340b78d | |
|
|
d7bd243c41 | |
|
|
3c7e91c28c | |
|
|
a00f2ab8db | |
|
|
b83c54334f | |
|
|
f3dc87fa64 | |
|
|
9f854d0649 | |
|
|
000bc3c166 | |
|
|
1820e2b152 | |
|
|
a10b20aea9 | |
|
|
80237d36cd | |
|
|
cfeef62215 | |
|
|
730b546e84 |
|
|
@ -29,6 +29,9 @@ jobs:
|
|||
- name: Cargo Build
|
||||
run: |
|
||||
../.github/workflows/scripts/appimage_build_wlx.sh
|
||||
- name: Build xwayland-satellite
|
||||
run: |
|
||||
../.github/workflows/scripts/appimage_build_satellite.sh
|
||||
- name: Package AppImage
|
||||
run: |
|
||||
../.github/workflows/scripts/appimage_package.sh
|
||||
|
|
|
|||
|
|
@ -27,6 +27,9 @@ jobs:
|
|||
- name: Cargo Build
|
||||
run: |
|
||||
../.github/workflows/scripts/appimage_build_wlx.sh
|
||||
- name: Build xwayland-satellite
|
||||
run: |
|
||||
../.github/workflows/scripts/appimage_build_satellite.sh
|
||||
- name: Package AppImage
|
||||
run: |
|
||||
../.github/workflows/scripts/appimage_package.sh
|
||||
|
|
|
|||
|
|
@ -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
|
||||
File diff suppressed because it is too large
Load Diff
24
Cargo.toml
24
Cargo.toml
|
|
@ -14,11 +14,17 @@ members = [
|
|||
|
||||
[workspace.dependencies]
|
||||
anyhow = "1.0.100"
|
||||
async-channel = "2.3"
|
||||
async-io = "2.1"
|
||||
bytes = { version = "1.11.1" }
|
||||
chrono = "0.4.42"
|
||||
clap = { version = "4.5.53", features = ["derive"] }
|
||||
dbus = { version = "0.9.12" }
|
||||
glam = { version = "0.30.9", features = ["mint", "serde"] }
|
||||
idmap = "0.2.2"
|
||||
idmap-derive = "0.2.22"
|
||||
interprocess = { version = "2.2.3" }
|
||||
libc = "0.2.178"
|
||||
log = "0.4.29"
|
||||
pipewire = { version = "0.10", features = ["v0_3_33", "v0_3_65"] }
|
||||
regex = "1.12.2"
|
||||
|
|
@ -27,20 +33,30 @@ serde = { version = "1", features = ["derive"] }
|
|||
serde_json = "1.0.145"
|
||||
serde_json5 = "0.2.1"
|
||||
slotmap = "1.1.1"
|
||||
smallvec = "1.15.2"
|
||||
smol = "2.0.2"
|
||||
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"] }
|
||||
vulkano = { version = "0.35.2", default-features = false, features = [
|
||||
"macros",
|
||||
] }
|
||||
vulkano = {
|
||||
version = "0.35.2",
|
||||
default-features = false,
|
||||
features = [
|
||||
"macros",
|
||||
]
|
||||
}
|
||||
vulkano-shaders = "0.35.0"
|
||||
wayland-client = { version = "0.31" }
|
||||
wayland-protocols = { version = "0.32" }
|
||||
wayland-protocols-misc = { version = "0.3.12" }
|
||||
winit = "0.30.12"
|
||||
xdg = "3.0.0"
|
||||
|
||||
[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]
|
||||
opt-level = 1
|
||||
|
|
|
|||
55
README.md
55
README.md
|
|
@ -84,7 +84,9 @@ Show and hide the working set using:
|
|||
- 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)
|
||||
|
||||
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
|
||||
|
||||
|
|
@ -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.
|
||||
|
||||
### 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
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
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 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
|
||||
|
||||
- Use the BLUE laser when typing regularly.
|
||||
|
|
@ -146,12 +161,9 @@ Typing
|
|||
|
||||
### 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)
|
||||
|
||||
OpenXR (Monado/WiVRn): See [Docs: OpenXR Bindings](https://wayvr.org/docs/various/openxr-bindings/)
|
||||
|
||||
If your controllers are not supported, please reach out. \
|
||||
We would like to work with you and include additional bindings.
|
||||
- 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)
|
||||
- Monado/WiVRn: See WayVR Dashboard → Settings → Controls.
|
||||
|
||||
## Customization
|
||||
|
||||
|
|
@ -171,14 +183,16 @@ Check [here](https://wayvr.org/docs/various/troubleshooting/) for tips.
|
|||
|
||||
## 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`
|
||||
- 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 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:
|
||||
|
||||
- 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.
|
||||
- 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:
|
||||
|
||||
- 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.
|
||||
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`
|
||||
|
||||
### X11 limitations
|
||||
|
||||
|
|
|
|||
|
|
@ -8,27 +8,32 @@ authors = ["galister", "oo8dev"]
|
|||
repository = "https://github.com/wlx-team/wayvr"
|
||||
|
||||
[dependencies]
|
||||
wayvr-ipc = { path = "../wayvr-ipc", default-features = false }
|
||||
wgui = { path = "../wgui/" }
|
||||
wlx-common = { path = "../wlx-common" }
|
||||
|
||||
anyhow.workspace = true
|
||||
async-native-tls = "0.5.0"
|
||||
chrono = "0.4.42"
|
||||
chrono.workspace = true
|
||||
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
|
||||
lz4_flex = { version = "0.13.1", features = ["frame"] }
|
||||
rust-embed.workspace = true
|
||||
serde = { workspace = true, features = ["rc"] }
|
||||
serde_json.workspace = true
|
||||
smol = { workspace = true }
|
||||
smol-hyper = "0.1.1"
|
||||
strum.workspace = true
|
||||
uuid.workspace = true
|
||||
wayvr-ipc = { path = "../wayvr-ipc", default-features = false }
|
||||
wgui = { path = "../wgui/" }
|
||||
wlx-common = { path = "../wlx-common" }
|
||||
xdg.workspace = true
|
||||
|
||||
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]
|
||||
default = ["monado"]
|
||||
monado = []
|
||||
|
|
|
|||
|
|
@ -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 |
|
|
@ -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 |
|
|
@ -1,6 +1,4 @@
|
|||
<layout>
|
||||
<include src="theme.xml" />
|
||||
|
||||
<theme>
|
||||
<var key="side_size" value="48" />
|
||||
<var key="top_size" value="40" />
|
||||
|
|
@ -14,10 +12,8 @@
|
|||
round="100%"
|
||||
width="~side_button_size"
|
||||
height="~side_button_size"
|
||||
color="#44444400"
|
||||
hover_color="#333333ff"
|
||||
border_color="#00000000"
|
||||
hover_border_color="#555555ff"
|
||||
color="background_contrast(transparent)"
|
||||
border_color="background_contrast"
|
||||
tooltip="${tooltip}"
|
||||
tooltip_side="${tooltip_side}"
|
||||
>
|
||||
|
|
@ -39,7 +35,7 @@
|
|||
<rectangle
|
||||
width="100%"
|
||||
round="100%"
|
||||
color="~color_menu_dark"
|
||||
color="background_contrast"
|
||||
flex_direction="column"
|
||||
justify_content="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_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" />
|
||||
<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" />
|
||||
</rectangle>
|
||||
</div>
|
||||
|
|
@ -65,13 +61,14 @@
|
|||
<div position="relative" width="100%" height="~top_size" min_height="~top_size" max_height="~top_size" justify_content="end">
|
||||
<!-- Title bar -->
|
||||
<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">
|
||||
<sprite id="sprite_titlebar_icon" width="24" height="24" />
|
||||
<label id="label_titlebar_title" weight="bold" size="16" text="Title" />
|
||||
<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" color="on_background_contrast" />
|
||||
<label id="label_titlebar_title" weight="bold" size="16" text="Title" color="on_background_contrast" />
|
||||
</rectangle>
|
||||
</div>
|
||||
<!-- 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" />
|
||||
</Button>
|
||||
</div>
|
||||
|
|
@ -80,8 +77,8 @@
|
|||
<!-- color and color2 alpha will be modified at runtime -->
|
||||
<rectangle
|
||||
id="rect_content"
|
||||
color2="#0d131a00"
|
||||
color="#252f5300"
|
||||
color2="background_contrast"
|
||||
color="background"
|
||||
gradient="vertical"
|
||||
round="8"
|
||||
flex_grow="1"
|
||||
|
|
@ -92,7 +89,7 @@
|
|||
<!-- radial gradient -->
|
||||
<rectangle
|
||||
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
|
||||
|
|
@ -120,36 +117,36 @@
|
|||
flex_direction="row"
|
||||
align_items="center"
|
||||
justify_content="space_between"
|
||||
color="~color_bottom_panel"
|
||||
color="background"
|
||||
>
|
||||
<!-- left gradient -->
|
||||
<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>
|
||||
|
||||
<!-- right gradient -->
|
||||
<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>
|
||||
|
||||
<!-- top shine -->
|
||||
<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>
|
||||
|
||||
<!-- Left bottom side -->
|
||||
<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" />
|
||||
</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" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<!-- Right bottom side -->
|
||||
<div margin_right="16">
|
||||
<label id="label_time" size="16" weight="light" />
|
||||
<label id="label_time" weight="bold" size="16" color="on_background" />
|
||||
</div>
|
||||
</rectangle>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
<layout>
|
||||
<include src="theme.xml" />
|
||||
|
||||
<macro name="dropdown_button"
|
||||
flex_direction="row"
|
||||
border="2"
|
||||
color="#00000055"
|
||||
border_color="#FFFFFF66"
|
||||
color="background"
|
||||
border_color="outline"
|
||||
justify_content="space_between" />
|
||||
|
||||
<!-- id, text, translation, tooltip, min_width -->
|
||||
|
|
@ -17,9 +15,9 @@
|
|||
</div>
|
||||
<div gap="2">
|
||||
<div padding_top="4" padding_bottom="4">
|
||||
<rectangle width="2" height="100%" color="#FFFFFF1A" />
|
||||
<rectangle width="2" height="100%" color="outline" />
|
||||
</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>
|
||||
</Button>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
<layout>
|
||||
<include src="theme.xml" />
|
||||
|
||||
<macro name="group_box"
|
||||
flex_direction="column"
|
||||
align_items="baseline"
|
||||
border="2"
|
||||
color="#00000055"
|
||||
border_color="#FFFFFF66"
|
||||
color="background_contrast(opacity-0.5)"
|
||||
border_color="outline"
|
||||
padding="12"
|
||||
gap="8"
|
||||
round="8" />
|
||||
|
|
@ -14,9 +12,9 @@
|
|||
<!-- src_builtin, text, translation -->
|
||||
<template name="GroupBoxTitle">
|
||||
<div flex_direction="row" align_items="center" gap="8">
|
||||
<sprite src_builtin="${src_builtin}" width="24" height="24" />
|
||||
<label text="${text}" translation="${translation}" weight="bold" size="18" />
|
||||
<sprite src_builtin="${src_builtin}" width="24" height="24" color="secondary" />
|
||||
<label text="${text}" translation="${translation}" weight="bold" size="18" color="secondary" />
|
||||
</div>
|
||||
<rectangle color="#FFFFFF44" width="100%" height="2" />
|
||||
<rectangle color="outline" width="100%" height="2" />
|
||||
</template>
|
||||
</layout>
|
||||
</layout>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
<template name="LoadingWithText">
|
||||
<div id="root" width="100%" height="100%" align_items="center" justify_content="center">
|
||||
<div flex_direction="row" gap="8" align_items="center" >
|
||||
<sprite id="sprite_loading" src_builtin="dashboard/loading.svg" width="32" height="32"/>
|
||||
<sprite id="sprite_loading" src_builtin="dashboard/loading.svg" width="32" height="32" color="on_background"/>
|
||||
<label translation="LOADING" weight="bold"/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
<template name="LoadingWithoutText">
|
||||
<div id="root" width="100%" height="100%" align_items="center" justify_content="center">
|
||||
<sprite id="sprite_loading" src_builtin="dashboard/loading.svg" width="32" height="32"/>
|
||||
<sprite id="sprite_loading" src_builtin="dashboard/loading.svg" width="32" height="32" color="on_background"/>
|
||||
</div>
|
||||
</template>
|
||||
</layout>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
<layout>
|
||||
<include src="theme.xml" />
|
||||
|
||||
<macro name="home_button" round="8" color="#00000033" border_color="#FFFFFF77" hover_color="~color_accent" hover_border_color="#FFFFFF" />
|
||||
<macro name="home_button" round="8" hover_color="primary" hover_border_color="on_primary" />
|
||||
|
||||
<template name="MenuButton">
|
||||
<Button
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<layout>
|
||||
<template name="Separator">
|
||||
<rectangle width="100%" height="1" color="#FFFFFF77" />
|
||||
<rectangle width="100%" min_height="2" color="#FFFFFF10" />
|
||||
</template>
|
||||
</layout>
|
||||
</layout>
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
<layout>
|
||||
<include src="../theme.xml" />
|
||||
<include src="../t_group_box.xml" />
|
||||
|
||||
<!-- tooltip, src, src_ext, name -->
|
||||
<template name="AppEntry">
|
||||
<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"
|
||||
color="#3385FF10"
|
||||
>
|
||||
<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 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" />
|
||||
|
|
@ -17,9 +17,9 @@
|
|||
</template>
|
||||
|
||||
<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" />
|
||||
<rectangle height="2" color="~color_accent" margin_left="4" margin_right="4" />
|
||||
<rectangle height="2" color="primary" margin_left="4" margin_right="4" />
|
||||
</rectangle>
|
||||
</template>
|
||||
|
||||
|
|
@ -39,6 +39,15 @@
|
|||
</rectangle>
|
||||
</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
|
||||
id="app_list_parent"
|
||||
flex_direction="row"
|
||||
|
|
@ -46,4 +55,4 @@
|
|||
gap="4"
|
||||
/>
|
||||
</elements>
|
||||
</layout>
|
||||
</layout>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
<template name="Avatar">
|
||||
<div flex_direction="column" gap="12" align_items="center">
|
||||
<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>
|
||||
<label size="14" text="${nickname}"/>
|
||||
</div>
|
||||
|
|
@ -18,17 +18,17 @@
|
|||
<div margin="32" flex_direction="column" align_items="center" justify_content="center" gap="16" height="100%">
|
||||
<div gap="16" align_items="center" width="100%">
|
||||
<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 flex_direction="column" gap="8" align_items="center" margin_top="16">
|
||||
<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"/>
|
||||
</Button>
|
||||
<label text="opencollective.com/wayvr-org" color="~color-accent"/>
|
||||
<label text="opencollective.com/wayvr-org"/>
|
||||
</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">
|
||||
<Avatar icon="dashboard/avatars/galister.png" nickname="galister"/>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
flex_grow="1"
|
||||
gap="24">
|
||||
<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 -->
|
||||
<div flex_direction="row" gap="8">
|
||||
|
|
@ -21,15 +21,15 @@
|
|||
|
||||
<div gap="8">
|
||||
<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 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 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>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
<elements>
|
||||
<div gap="4">
|
||||
<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="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" />
|
||||
</Tabs>
|
||||
<div gap="4" id="content" width="100%">
|
||||
|
|
@ -11,4 +11,4 @@
|
|||
</div>
|
||||
</div>
|
||||
</elements>
|
||||
</layout>
|
||||
</layout>
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@
|
|||
</template>
|
||||
|
||||
<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" />
|
||||
<label align="left" translation="${translation}" weight="bold" min_width="200" />
|
||||
</Button>
|
||||
|
|
@ -47,7 +47,8 @@
|
|||
|
||||
<template name="AutostartApp">
|
||||
<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" />
|
||||
</Button>
|
||||
<div padding_left="8">
|
||||
|
|
@ -56,17 +57,19 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<macro name="tab_style" color="background" />
|
||||
|
||||
<elements>
|
||||
<div gap="4">
|
||||
<Tabs id="tabs">
|
||||
<Tab name="look_and_feel" translation="APP_SETTINGS.LOOK_AND_FEEL" sprite_src_builtin="dashboard/palette.svg" />
|
||||
<Tab name="skybox" translation="APP_SETTINGS.SKYBOX" sprite_src_builtin="dashboard/globe.svg" />
|
||||
<Tab name="features" translation="APP_SETTINGS.FEATURES" sprite_src_builtin="dashboard/options.svg" />
|
||||
<Tab name="controls" translation="APP_SETTINGS.CONTROLS" sprite_src_builtin="dashboard/controller.svg" />
|
||||
<Tab name="space_drag" translation="APP_SETTINGS.SPACE_DRAG" sprite_src_builtin="dashboard/drag.svg" />
|
||||
<Tab name="misc" translation="APP_SETTINGS.MISC" sprite_src_builtin="dashboard/blocks.svg" />
|
||||
<Tab name="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="look_and_feel" translation="APP_SETTINGS.LOOK_AND_FEEL" sprite_src_builtin="dashboard/palette.svg" />
|
||||
<Tab macro="tab_style" name="skybox" translation="APP_SETTINGS.SKYBOX" sprite_src_builtin="dashboard/globe.svg" />
|
||||
<Tab macro="tab_style" name="features" translation="APP_SETTINGS.FEATURES" sprite_src_builtin="dashboard/options.svg" />
|
||||
<Tab macro="tab_style" name="controls" translation="APP_SETTINGS.CONTROLS" sprite_src_builtin="dashboard/controller.svg" />
|
||||
<Tab macro="tab_style" name="space_drag" translation="APP_SETTINGS.SPACE_DRAG" sprite_src_builtin="dashboard/drag.svg" />
|
||||
<Tab macro="tab_style" name="misc" translation="APP_SETTINGS.MISC" sprite_src_builtin="dashboard/blocks.svg" />
|
||||
<Tab macro="tab_style" name="autostart_apps" translation="APP_SETTINGS.AUTOSTART_APPS" sprite_src_builtin="dashboard/apps.svg" />
|
||||
<Tab macro="tab_style" name="troubleshooting" translation="APP_SETTINGS.TROUBLESHOOTING" sprite_src_builtin="dashboard/cpu.svg" />
|
||||
</Tabs>
|
||||
<div flex_wrap="wrap" justify_content="stretch" gap="4" id="settings_root" width="100%">
|
||||
<!-- filled-in at runtime -->
|
||||
|
|
|
|||
|
|
@ -3,13 +3,13 @@
|
|||
<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">
|
||||
<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>
|
||||
</template>
|
||||
|
||||
<!-- text -->
|
||||
<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 name="TextDescBig">
|
||||
|
|
|
|||
|
|
@ -10,12 +10,12 @@
|
|||
<label size="20" translation="WELCOME.GUIDE_INTRO"/>
|
||||
|
||||
<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"/>
|
||||
</div>
|
||||
|
||||
<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"/>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -6,12 +6,12 @@
|
|||
<div macro="video_overlay_content">
|
||||
<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">
|
||||
<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="#FFAA00" translation="WELCOME.RIGHT_CLICK_MODE_ORANGE_LASER"/>
|
||||
<label size="12" weight="bold" color="#FF88FF" translation="WELCOME.MIDDLE_CLICK_MODE_PURPLE_LASER"/>
|
||||
</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>
|
||||
</Video>
|
||||
|
|
|
|||
|
|
@ -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="#ccaaff" translation="WELCOME.GRAB_BOTH_HANDS"/>
|
||||
<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"/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -5,22 +5,22 @@
|
|||
<div flex_direction="row" align_items="center">
|
||||
<image src_builtin="dashboard/check_3d.png" width="256" height="256"/>
|
||||
<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"/>
|
||||
|
||||
<label size="16" wrap="1" translation="WELCOME.BASICS"/>
|
||||
<label size="16" wrap="1" translation="WELCOME.FEATURES_DOCUMENTATION"/>
|
||||
<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" />
|
||||
</Button>
|
||||
|
||||
<rectangle color="#FFFFFF33" width="100%" min_height="2"/>
|
||||
|
||||
<Button id="btn_home_screen" min_height="48">
|
||||
<sprite src_builtin="dashboard/check.svg" width="32" height="32" color="~color_accent" margin="8"/>
|
||||
<label size="20" weight="bold" translation="WELCOME.FINISH_TUTORIAL" margin_right="8" />
|
||||
<Button id="btn_home_screen" min_height="48" >
|
||||
<sprite src_builtin="dashboard/check.svg" width="32" height="32" margin="8"/>
|
||||
<label color="on_background" size="20" weight="bold" translation="WELCOME.FINISH_TUTORIAL" margin_right="8" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
<layout>
|
||||
<template name="Subtext">
|
||||
<div flex_direction="row" gap="8">
|
||||
<label weight="bold" text="${title}" />
|
||||
<label id="${label_id}" />
|
||||
<div flex_direction="row" gap="8" max_width="100%">
|
||||
<label weight="bold" text="${title}" wrap="1" />
|
||||
<label id="${label_id}" wrap="1" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -15,12 +15,10 @@
|
|||
|
||||
<elements>
|
||||
<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>
|
||||
<div flex_direction="column" gap="8" flex_grow="1" max_width="720">
|
||||
<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">
|
||||
<label id="label_title" weight="bold" size="32" overflow="hidden" />
|
||||
<Subtext label_id="label_exec" overflow="hidden" />
|
||||
<Subtext label_id="label_exec"/>
|
||||
<Separator />
|
||||
<RadioGroup id="radio_compositor" flex_direction="row" gap="16">
|
||||
<RadioBox translation="APP_LAUNCHER.MODE.NATIVE" value="Native" checked="1" />
|
||||
|
|
@ -30,14 +28,14 @@
|
|||
<label translation="APP_LAUNCHER.RES_TITLE" />
|
||||
<RadioGroup id="radio_res" flex_direction="row" gap="16">
|
||||
<RadioBox text="1440p" value="Res1440" />
|
||||
<RadioBox text="1080p" value="Res1080" checked="1" />
|
||||
<RadioBox text="1080p" value="Res1080"/>
|
||||
<RadioBox text="720p" value="Res720" />
|
||||
<RadioBox text="480p" value="Res480" />
|
||||
</RadioGroup>
|
||||
<Separator />
|
||||
<label translation="APP_LAUNCHER.ASPECT_TITLE" />
|
||||
<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.SQUARE" value="Square" tooltip_str="1:1" />
|
||||
<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" />
|
||||
</RadioGroup -->
|
||||
<Separator />
|
||||
<div flex_direction="row" justify_content="space_between" gap="16">
|
||||
<CheckBox id="cb_autostart" translation="APP_LAUNCHER.AUTOSTART" />
|
||||
<Button id="btn_launch" align_self="baseline" color="#44ce22FF" padding_top="4" padding_bottom="4" round="8" padding_right="12" min_height="40">
|
||||
<CheckBox id="cb_autostart" translation="APP_LAUNCHER.AUTOSTART" align_self="baseline"/>
|
||||
<div flex_direction="row" align_items="end" justify_content="center" gap="8" margin_top="8">
|
||||
<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" />
|
||||
<label translation="APP_LAUNCHER.LAUNCH" weight="bold" size="17" shadow="#00000099" />
|
||||
<label translation="APP_LAUNCHER.LAUNCH" weight="bold" size="17" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</elements>
|
||||
</layout>
|
||||
</layout>
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@
|
|||
<template name="DeviceSlider">
|
||||
<rectangle macro="group_box">
|
||||
<div width="100%" align_items="center" justify_content="center" gap="8">
|
||||
<sprite src_builtin="${device_icon}" width="16" height="16" />
|
||||
<label text="${device_name}" margin_right="8" size="12" weight="bold" />
|
||||
<sprite src_builtin="${device_icon}" width="16" height="16" color="on_background" />
|
||||
<label text="${device_name}" margin_right="8" size="12" weight="bold" color="on_background" />
|
||||
</div>
|
||||
<div width="100%" align_items="center">
|
||||
<CheckBox id="checkbox" />
|
||||
|
|
@ -20,7 +20,7 @@
|
|||
<template name="Card">
|
||||
<rectangle macro="group_box">
|
||||
<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>
|
||||
<Button id="btn_card" text="${profile_name}" width="100%" height="32" />
|
||||
</rectangle>
|
||||
|
|
@ -29,7 +29,7 @@
|
|||
<template name="SelectAudioProfileText">
|
||||
<div align_items="center" gap="8">
|
||||
<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>
|
||||
<label translation="AUDIO.SELECT_AUDIO_CARD_PROFILE" size="14" weight="bold" />
|
||||
</div>
|
||||
|
|
@ -58,7 +58,8 @@
|
|||
sprite_src_builtin="dashboard/magic_wand.svg"
|
||||
min_width="32"
|
||||
tooltip="AUDIO.AUTO_SWITCH_TO_VR_AUDIO"
|
||||
color="~color_accent"
|
||||
color="primary"
|
||||
sprite_color="on_primary"
|
||||
tooltip_side="right" />
|
||||
|
||||
<BottomButton id="btn_sinks" src_builtin="dashboard/volume.svg" translation="AUDIO.SPEAKERS" />
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@
|
|||
<rectangle macro="group_box" id="${id}">
|
||||
<!-- top row -->
|
||||
<div flex_direction="row" gap="8" align_items="center">
|
||||
<sprite src_builtin="dashboard/controller.svg" width="24" height="24"/>
|
||||
<label translation="${translation}" color="~color_accent" weight="bold" size="14" />
|
||||
<sprite src_builtin="dashboard/controller.svg" width="24" height="24" color="on_background"/>
|
||||
<label translation="${translation}" color="primary" weight="bold" size="14" />
|
||||
</div>
|
||||
<!-- filled-in at runtime -->
|
||||
</rectangle>
|
||||
|
|
@ -26,7 +26,7 @@
|
|||
<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>
|
||||
<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_save" sprite_src_builtin="dashboard/check.svg" min_width="100" height="32" translation="APP_SETTINGS.SAVE" />
|
||||
</rectangle>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -10,7 +10,7 @@
|
|||
<div id="content" flex_direction="column" gap="8" width="100%">
|
||||
<label translation="DOWNLOADING_FILE" size="24" weight="bold"/>
|
||||
<Separator/>
|
||||
<label id="label_target_path" color="~color_text_translucent" />
|
||||
<label id="label_target_path" color="on_background" />
|
||||
|
||||
<div gap="8" align_items="center">
|
||||
<div id="loading_parent"/>
|
||||
|
|
|
|||
|
|
@ -9,11 +9,11 @@
|
|||
<label weight="bold" id="label_author" text="Unknown" />
|
||||
</div>
|
||||
<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" />
|
||||
<label text="Launch" weight="bold" size="17" shadow="#00000099" />
|
||||
<label text="Launch" weight="bold" size="17" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</elements>
|
||||
</layout>
|
||||
</layout>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
<!-- id, text -->
|
||||
<template name="InputProfileButton">
|
||||
<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"/>
|
||||
</Button>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
<layout>
|
||||
<include src="../theme.xml" />
|
||||
|
||||
<elements>
|
||||
<div
|
||||
consume_mouse_events="1"
|
||||
|
|
@ -11,33 +9,33 @@
|
|||
flex_direction="column"
|
||||
position="absolute"
|
||||
>
|
||||
<!-- Top black bar -->
|
||||
<!-- Top bar -->
|
||||
<rectangle
|
||||
position="relative"
|
||||
color="#000000"
|
||||
color="background_contrast"
|
||||
width="100%" height="48"
|
||||
>
|
||||
|
||||
<!-- 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 -->
|
||||
<div gap="16" align_items="center">
|
||||
<!-- 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" />
|
||||
</Button>
|
||||
|
||||
<!-- Title -->
|
||||
<label id="popup_title" weight="bold" size="18" />
|
||||
<label id="popup_title" color="on_background_contrast" weight="bold" size="18" />
|
||||
</div>
|
||||
</rectangle>
|
||||
|
||||
<rectangle
|
||||
width="100%"
|
||||
height="100%"
|
||||
color="#010310fe"
|
||||
color2="#051c55fc"
|
||||
color="background_contrast(opacity-0.98)"
|
||||
color2="background(opacity-0.99)"
|
||||
gradient="vertical"
|
||||
position="relative"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
<div id="resolution_buttons" gap="8" flex_direction="row">
|
||||
<!-- filled-in at runtime -->
|
||||
</div>
|
||||
<image id="image" width="400" height="200" round="8" border="2" border_color="~color_accent"/>
|
||||
<image id="image" width="400" height="200" round="8" border="2" border_color="primary"/>
|
||||
<label id="label_author" weight="bold"/>
|
||||
<label id="label_description" wrap="1"/>
|
||||
<Separator/>
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@
|
|||
-->
|
||||
<template name="ResolutionPip">
|
||||
<rectangle color="${color}" padding_left="4" padding_right="4" padding_top="2" padding_bottom="2" round="3" align_self="start">
|
||||
<label text="${text}" weight="bold" size="12" shadow="#000000" shadow_x="2" shadow_y="2"/>
|
||||
<label text="${text}" weight="bold" size="12" color="#FFFFFF" shadow="#000000" shadow_x="2" shadow_y="2"/>
|
||||
</rectangle>
|
||||
</template>
|
||||
</layout>
|
||||
|
|
|
|||
|
|
@ -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?"
|
||||
},
|
||||
"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!",
|
||||
"AUDIO": {
|
||||
|
|
@ -274,8 +286,6 @@
|
|||
"WELCOME": {
|
||||
"ALL_SET": "Du bist startklar!",
|
||||
"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",
|
||||
"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.",
|
||||
|
|
@ -294,7 +304,15 @@
|
|||
"RIGHT_CLICK_MODE_ORANGE_LASER": "— Rechtsklick-Modus: Oranger Laser",
|
||||
"THANKS_FOR_INSTALLING": "Vielen Dank für die Installation von WayVR!",
|
||||
"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"
|
||||
}
|
||||
|
|
@ -96,6 +96,14 @@
|
|||
"CAPTURE_METHOD": "Wayland screen capture",
|
||||
"CAPTURE_METHOD_HELP": "Try changing this if you are\nexperiencing black or glitchy screens",
|
||||
"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_HELP": "Prompt for screen selection on next start",
|
||||
"CLEAR_SAVED_STATE": "Clear saved state",
|
||||
|
|
@ -166,6 +174,8 @@
|
|||
"SCREEN_RENDER_DOWN_HELP": "Helps with aliasing on high-res screens",
|
||||
"SCROLL_SPEED": "Scroll speed",
|
||||
"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",
|
||||
"SETS_ON_WATCH": "Sets on watch",
|
||||
"SHOW_WELCOME_SCREEN": "Show welcome screen",
|
||||
|
|
@ -240,8 +250,8 @@
|
|||
"WELCOME": {
|
||||
"ALL_SET": "You're all set!",
|
||||
"BASICS": "These basics will allow you to start using WayVR with ease.",
|
||||
"CENTER_MARKER": "Center marker determines the center or your set.",
|
||||
"DOUBLE_PRESS_TO_TOGGLE": "Double-press B (on Index) or Y (on Meta) to toggle the visibility of your workspace. Try it now.",
|
||||
"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 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",
|
||||
"FINISH_TUTORIAL": "Finish tutorial",
|
||||
"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",
|
||||
"TERMINATE_PROCESS": "Terminate process",
|
||||
"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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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?"
|
||||
},
|
||||
"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!",
|
||||
"AUDIO": {
|
||||
|
|
@ -274,8 +286,6 @@
|
|||
"WELCOME": {
|
||||
"ALL_SET": "¡Todo listo!",
|
||||
"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",
|
||||
"FINISH_TUTORIAL": "Finalizar tutorial",
|
||||
"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",
|
||||
"THANKS_FOR_INSTALLING": "¡Gracias por instalar WayVR!",
|
||||
"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"
|
||||
}
|
||||
|
|
@ -203,7 +203,19 @@
|
|||
"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_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_STARTED": "Applicazione avviata",
|
||||
|
|
@ -274,8 +286,6 @@
|
|||
"WELCOME": {
|
||||
"ALL_SET": "Sei pronto!",
|
||||
"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",
|
||||
"FINISH_TUTORIAL": "Termina tutorial",
|
||||
"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",
|
||||
"THANKS_FOR_INSTALLING": "Grazie per aver installato WayVR!",
|
||||
"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"
|
||||
}
|
||||
|
|
@ -182,7 +182,19 @@
|
|||
"REMOVE_UNUSED_MODELS": "文字起こし機能をオフにします。\n空き容量を増やすために、ダウンロード済みのモデルファイルを削除しますか?"
|
||||
},
|
||||
"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": "こんにちは!",
|
||||
"AUDIO": {
|
||||
|
|
@ -274,8 +286,6 @@
|
|||
"WELCOME": {
|
||||
"ALL_SET": "準備完了です!",
|
||||
"BASICS": "これらの基本操作を学ぶことで、WayVRを簡単に使い始めることができます。",
|
||||
"CENTER_MARKER": "センターマーカーは、セットの中心を決定します。",
|
||||
"DOUBLE_PRESS_TO_TOGGLE": "Bボタン (Index) または Yボタン (Meta) をダブルプレスして、ワークスペースの表示/非表示を切り替えます。今すぐ試してみてください。",
|
||||
"FEATURES_DOCUMENTATION": "他にもカスタマイズする価値のある機能がたくさんあります。詳細については、以下のドキュメントを確認してください。",
|
||||
"FINISH_TUTORIAL": "チュートリアルを終了する",
|
||||
"GRAB_BOTH_HANDS": "セット内の特定のオーバーレイを移動させたい場合は、両手で掴んでください。",
|
||||
|
|
@ -294,7 +304,15 @@
|
|||
"RIGHT_CLICK_MODE_ORANGE_LASER": "— 右クリックモード:オレンジ色のレーザー",
|
||||
"THANKS_FOR_INSTALLING": "WayVRをインストールしていただきありがとうございます!",
|
||||
"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": "ピン留めされたアプリケーション"
|
||||
}
|
||||
|
|
@ -177,7 +177,19 @@
|
|||
"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_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",
|
||||
"APPLICATIONS": "Aplikacje",
|
||||
|
|
@ -274,8 +286,6 @@
|
|||
"WELCOME": {
|
||||
"ALL_SET": "Wszystko gotowe!",
|
||||
"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:",
|
||||
"FINISH_TUTORIAL": "Zakończ samouczek",
|
||||
"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",
|
||||
"THANKS_FOR_INSTALLING": "Dziękujemy za zainstalowanie WayVR!",
|
||||
"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"
|
||||
}
|
||||
|
|
@ -203,7 +203,19 @@
|
|||
"REMOVE_UNUSED_MODELS": "您正在关闭语音转文本功能。\n是否删除已下载的 Whisper 模型以释放空间?"
|
||||
},
|
||||
"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_STARTED": "应用已启动",
|
||||
|
|
@ -274,8 +286,6 @@
|
|||
"WELCOME": {
|
||||
"ALL_SET": "你已准备就绪!",
|
||||
"BASICS": "这些基础知识将帮助您轻松开始使用 WayVR。",
|
||||
"CENTER_MARKER": "中心标记确定了您 Set 的中心位置。",
|
||||
"DOUBLE_PRESS_TO_TOGGLE": "双击 B(Index 控制器)或 Y(Meta 控制器)来切换工作区显示。立即尝试一下。",
|
||||
"FEATURES_DOCUMENTATION": "还有许多其他值得自定义的功能。欲了解更多信息,请查看文档:",
|
||||
"FINISH_TUTORIAL": "完成教程",
|
||||
"GRAB_BOTH_HANDS": "如果你想移动 Set 中的特定叠加层,请用双手抓取。",
|
||||
|
|
@ -294,7 +304,15 @@
|
|||
"RIGHT_CLICK_MODE_ORANGE_LASER": "— 右键模式:橙色激光",
|
||||
"THANKS_FOR_INSTALLING": "感谢安装 WayVR!",
|
||||
"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.
|
|
@ -2,10 +2,11 @@ use std::{path::PathBuf, rc::Rc};
|
|||
|
||||
use chrono::Timelike;
|
||||
use glam::Vec2;
|
||||
use strum::EnumCount;
|
||||
use wgui::{
|
||||
assets::{AssetPath, AssetProvider},
|
||||
components::button::ComponentButton,
|
||||
event::StyleSetRequest,
|
||||
event::{CallbackDataCommon, StyleSetRequest},
|
||||
font_config::WguiFontConfig,
|
||||
globals::WguiGlobals,
|
||||
i18n::Translation,
|
||||
|
|
@ -21,8 +22,9 @@ use wgui::{
|
|||
use wlx_common::{
|
||||
async_executor::AsyncExecutor,
|
||||
audio,
|
||||
dash_interface::{BoxDashInterface, ConfigChangeKind, RecenterMode},
|
||||
dash_interface::{BoxDashInterface, ConfigChangeKind, DashPlayspaceTask},
|
||||
locale::WayVRLangProvider,
|
||||
palette::load_palette,
|
||||
timestep::{self, Timestep},
|
||||
};
|
||||
|
||||
|
|
@ -61,6 +63,7 @@ pub struct Frontend<T> {
|
|||
state: ParserState,
|
||||
|
||||
current_tab: Option<Box<dyn Tab<T>>>,
|
||||
side_buttons: Vec<(TabType, Rc<ComponentButton>)>,
|
||||
|
||||
pub tasks: FrontendTasks,
|
||||
|
||||
|
|
@ -94,12 +97,14 @@ pub struct InitParams<'a, T> {
|
|||
pub show_welcome: bool,
|
||||
pub has_monado: bool,
|
||||
pub theme: Rc<WguiTheme>,
|
||||
pub color_palette: &'a str,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum SoundType {
|
||||
Startup,
|
||||
Launch,
|
||||
Save,
|
||||
}
|
||||
|
||||
#[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_light = assets.load_from_path_gzip("Quicksand-Light.ttf.gz")?;
|
||||
|
||||
let palette = load_palette(params.color_palette);
|
||||
|
||||
let globals = WguiGlobals::new(
|
||||
assets,
|
||||
params.lang_provider,
|
||||
|
|
@ -137,6 +144,7 @@ impl<T: 'static> Frontend<T> {
|
|||
family_name_monospace: "",
|
||||
},
|
||||
PathBuf::new(), //FIXME: pass from somewhere else
|
||||
palette,
|
||||
)?;
|
||||
|
||||
let (layout, state) = wgui::parser::new_layout_from_assets(
|
||||
|
|
@ -178,6 +186,7 @@ impl<T: 'static> Frontend<T> {
|
|||
layout,
|
||||
state,
|
||||
current_tab: None,
|
||||
side_buttons: Vec::with_capacity(TabType::COUNT),
|
||||
globals,
|
||||
tasks,
|
||||
ticks: 0,
|
||||
|
|
@ -216,6 +225,7 @@ impl<T: 'static> Frontend<T> {
|
|||
let path = match sound_type {
|
||||
SoundType::Startup => "sound/startup.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
|
||||
|
|
@ -350,8 +360,8 @@ impl<T: 'static> Frontend<T> {
|
|||
(0.8666, 0.9333)
|
||||
};
|
||||
|
||||
rect.params.color.a = alpha1;
|
||||
rect.params.color2.a = alpha2;
|
||||
rect.params.color = rect.params.color.with_alpha(alpha1);
|
||||
rect.params.color2 = rect.params.color2.with_alpha(alpha2);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -435,6 +445,15 @@ impl<T: 'static> Frontend<T> {
|
|||
|
||||
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(())
|
||||
}
|
||||
|
||||
|
|
@ -450,34 +469,29 @@ impl<T: 'static> Frontend<T> {
|
|||
// ################################
|
||||
|
||||
// "Home" side button
|
||||
self.tasks.handle_button(
|
||||
&self.state.fetch_component_as::<ComponentButton>("btn_side_home")?,
|
||||
FrontendTask::SetTab(TabType::Home),
|
||||
);
|
||||
let btn = self.state.fetch_component_as::<ComponentButton>("btn_side_home")?;
|
||||
self.tasks.handle_button(&btn, FrontendTask::SetTab(TabType::Home));
|
||||
self.side_buttons.push((TabType::Home, btn));
|
||||
|
||||
// "Apps" side button
|
||||
self.tasks.handle_button(
|
||||
&self.state.fetch_component_as::<ComponentButton>("btn_side_apps")?,
|
||||
FrontendTask::SetTab(TabType::Apps),
|
||||
);
|
||||
let btn = self.state.fetch_component_as::<ComponentButton>("btn_side_apps")?;
|
||||
self.tasks.handle_button(&btn, FrontendTask::SetTab(TabType::Apps));
|
||||
self.side_buttons.push((TabType::Apps, btn));
|
||||
|
||||
// "Games" side button
|
||||
self.tasks.handle_button(
|
||||
&self.state.fetch_component_as::<ComponentButton>("btn_side_games")?,
|
||||
FrontendTask::SetTab(TabType::Games),
|
||||
);
|
||||
let btn = self.state.fetch_component_as::<ComponentButton>("btn_side_games")?;
|
||||
self.tasks.handle_button(&btn, FrontendTask::SetTab(TabType::Games));
|
||||
self.side_buttons.push((TabType::Games, btn));
|
||||
|
||||
// "Monado side button"
|
||||
self.tasks.handle_button(
|
||||
&self.state.fetch_component_as::<ComponentButton>("btn_side_monado")?,
|
||||
FrontendTask::SetTab(TabType::Monado),
|
||||
);
|
||||
let btn = self.state.fetch_component_as::<ComponentButton>("btn_side_monado")?;
|
||||
self.tasks.handle_button(&btn, FrontendTask::SetTab(TabType::Monado));
|
||||
self.side_buttons.push((TabType::Monado, btn));
|
||||
|
||||
// "Settings" side button
|
||||
self.tasks.handle_button(
|
||||
&self.state.fetch_component_as::<ComponentButton>("btn_side_settings")?,
|
||||
FrontendTask::SetTab(TabType::Settings),
|
||||
);
|
||||
let btn = self.state.fetch_component_as::<ComponentButton>("btn_side_settings")?;
|
||||
self.tasks.handle_button(&btn, FrontendTask::SetTab(TabType::Settings));
|
||||
self.side_buttons.push((TabType::Settings, btn));
|
||||
|
||||
// ################################
|
||||
// BOTTOM BAR BUTTONS
|
||||
|
|
@ -539,7 +553,7 @@ impl<T: 'static> Frontend<T> {
|
|||
}
|
||||
|
||||
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(())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,29 +3,39 @@ use wgui::{
|
|||
assets::AssetPath,
|
||||
components::button::{ButtonClickCallback, ComponentButton},
|
||||
globals::WguiGlobals,
|
||||
i18n::Translation,
|
||||
layout::{WidgetID, WidgetPair},
|
||||
parser::{Fetchable, ParseDocumentParams, ParserState, TemplateParams},
|
||||
task::Tasks,
|
||||
};
|
||||
use wlx_common::desktop_finder::DesktopEntry;
|
||||
use wlx_common::{config::PinnedApp, desktop_finder::DesktopEntry};
|
||||
|
||||
use crate::{
|
||||
frontend::{Frontend, FrontendTasks},
|
||||
tab::{Tab, TabType},
|
||||
util::popup_manager::PopupHolder,
|
||||
util::{popup_manager::PopupHolder, wgui_simple},
|
||||
views::{self},
|
||||
};
|
||||
|
||||
struct State {
|
||||
#[allow(dead_code)]
|
||||
parser_state: ParserState,
|
||||
view_launcher: PopupHolder<views::app_launcher::View>,
|
||||
}
|
||||
|
||||
pub struct TabApps<T> {
|
||||
#[allow(dead_code)]
|
||||
parser_state: ParserState,
|
||||
#[derive(Clone)]
|
||||
enum Task {
|
||||
RefreshPinnedApps,
|
||||
}
|
||||
|
||||
pub struct TabApps<T> {
|
||||
state: Rc<RefCell<State>>,
|
||||
app_list: AppList,
|
||||
marker: PhantomData<T>,
|
||||
|
||||
pinned_apps_parent: WidgetID,
|
||||
|
||||
tasks: Tasks<Task>,
|
||||
}
|
||||
|
||||
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<()> {
|
||||
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
|
||||
.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 {
|
||||
//data: Vec<ParserData>,
|
||||
all_entries: Vec<DesktopEntry>,
|
||||
entries_to_mount: VecDeque<DesktopEntry>,
|
||||
list_parent: WidgetPair,
|
||||
prev_category_name: String,
|
||||
|
|
@ -55,9 +148,11 @@ struct AppList {
|
|||
// called after the user clicks any desktop entry
|
||||
fn on_app_click(
|
||||
frontend_tasks: FrontendTasks,
|
||||
tasks: Tasks<Task>,
|
||||
globals: WguiGlobals,
|
||||
entry: DesktopEntry,
|
||||
state: Rc<RefCell<State>>,
|
||||
pinned_app: Option<PinnedApp>,
|
||||
) -> ButtonClickCallback {
|
||||
Rc::new(move |_common, _evt| {
|
||||
views::app_launcher::mount_popup(
|
||||
|
|
@ -65,6 +160,8 @@ fn on_app_click(
|
|||
globals.clone(),
|
||||
entry.clone(),
|
||||
state.borrow_mut().view_launcher.clone(),
|
||||
tasks.make_callback_box(Task::RefreshPinnedApps),
|
||||
pinned_app.clone(),
|
||||
);
|
||||
Ok(())
|
||||
})
|
||||
|
|
@ -81,20 +178,24 @@ fn doc_params(globals: WguiGlobals) -> ParseDocumentParams<'static> {
|
|||
impl<T> TabApps<T> {
|
||||
pub fn new(frontend: &mut Frontend<T>, parent_id: WidgetID, data: &mut T) -> anyhow::Result<Self> {
|
||||
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 {
|
||||
view_launcher: Default::default(),
|
||||
parser_state,
|
||||
}));
|
||||
|
||||
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 mut entries_sorted: Vec<_> = frontend
|
||||
let entries: Vec<_> = frontend
|
||||
.interface
|
||||
.desktop_finder(data)
|
||||
.find_entries()
|
||||
.into_values()
|
||||
.collect();
|
||||
|
||||
let mut entries_sorted = entries.clone();
|
||||
entries_sorted.sort_by(|a, b| {
|
||||
let cat_name_a = get_category_name(a);
|
||||
let cat_name_b = get_category_name(b);
|
||||
|
|
@ -102,16 +203,21 @@ impl<T> TabApps<T> {
|
|||
});
|
||||
|
||||
let app_list = AppList {
|
||||
all_entries: entries,
|
||||
entries_to_mount: entries_sorted.drain(..).collect(),
|
||||
list_parent: app_list_parent,
|
||||
prev_category_name: String::new(),
|
||||
};
|
||||
|
||||
let tasks = Tasks::<Task>::new();
|
||||
tasks.push(Task::RefreshPinnedApps);
|
||||
|
||||
Ok(Self {
|
||||
app_list,
|
||||
parser_state,
|
||||
state,
|
||||
tasks,
|
||||
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>(
|
||||
&mut self,
|
||||
frontend: &mut Frontend<T>,
|
||||
parser_state: &mut ParserState,
|
||||
doc_params: &ParseDocumentParams,
|
||||
entry: &DesktopEntry,
|
||||
) -> anyhow::Result<Rc<ComponentButton>> {
|
||||
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);
|
||||
fn mount_entry<T>(
|
||||
frontend: &mut Frontend<T>,
|
||||
parser_state: &mut ParserState,
|
||||
doc_params: &ParseDocumentParams,
|
||||
id_parent: WidgetID,
|
||||
entry: &DesktopEntry,
|
||||
tooltip: Option<String>,
|
||||
) -> anyhow::Result<Rc<ComponentButton>> {
|
||||
{
|
||||
let mut params = TemplateParams::new();
|
||||
|
||||
parser_state.realize_template(
|
||||
doc_params,
|
||||
"CategoryText",
|
||||
&mut frontend.layout,
|
||||
self.list_parent.id,
|
||||
params,
|
||||
)?;
|
||||
}
|
||||
if let Some(tooltip) = tooltip {
|
||||
params.insert_str("tooltip", tooltip);
|
||||
};
|
||||
|
||||
{
|
||||
let mut params = TemplateParams::new();
|
||||
// entry icon
|
||||
params.insert_rc(
|
||||
"src_ext",
|
||||
entry
|
||||
.icon_path
|
||||
.as_ref()
|
||||
.map_or_else(|| "".into(), |icon_path| icon_path.clone()),
|
||||
);
|
||||
|
||||
// entry icon
|
||||
params.insert_rc(
|
||||
"src_ext",
|
||||
entry
|
||||
.icon_path
|
||||
.as_ref()
|
||||
.map_or_else(|| "".into(), |icon_path| icon_path.clone()),
|
||||
);
|
||||
// entry fallback (question mark) icon
|
||||
params.insert(
|
||||
"src",
|
||||
if entry.icon_path.is_none() {
|
||||
"dashboard/terminal.svg"
|
||||
} else {
|
||||
""
|
||||
},
|
||||
);
|
||||
params.insert("name", &entry.app_name);
|
||||
|
||||
// entry fallback (question mark) icon
|
||||
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(doc_params, "AppEntry", &mut frontend.layout, id_parent, params)?;
|
||||
|
||||
let data = parser_state.realize_template(
|
||||
doc_params,
|
||||
"AppEntry",
|
||||
&mut frontend.layout,
|
||||
self.list_parent.id,
|
||||
params,
|
||||
)?;
|
||||
|
||||
data.fetch_component_as::<ComponentButton>("button")
|
||||
}
|
||||
data.fetch_component_as::<ComponentButton>("button")
|
||||
}
|
||||
}
|
||||
|
||||
impl AppList {
|
||||
fn tick<T>(
|
||||
&mut self,
|
||||
frontend: &mut Frontend<T>,
|
||||
state: &Rc<RefCell<State>>,
|
||||
parser_state: &mut ParserState,
|
||||
tasks: &Tasks<Task>,
|
||||
state: &mut State,
|
||||
rc_state: &Rc<RefCell<State>>,
|
||||
) -> anyhow::Result<()> {
|
||||
let parser_state = &mut state.parser_state;
|
||||
|
||||
// load 30 entries for a single frame at most
|
||||
for _ in 0..30 {
|
||||
if let Some(entry) = self.entries_to_mount.pop_front() {
|
||||
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(
|
||||
frontend.tasks.clone(),
|
||||
tasks.clone(),
|
||||
globals.clone(),
|
||||
entry.clone(),
|
||||
state.clone(),
|
||||
rc_state.clone(),
|
||||
None,
|
||||
));
|
||||
} else {
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
use strum::EnumCount;
|
||||
|
||||
use crate::frontend::Frontend;
|
||||
|
||||
pub mod apps;
|
||||
|
|
@ -8,7 +10,7 @@ pub mod monado;
|
|||
pub mod settings;
|
||||
pub mod welcome;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, EnumCount)]
|
||||
pub enum TabType {
|
||||
Home,
|
||||
Apps,
|
||||
|
|
|
|||
|
|
@ -28,16 +28,16 @@ use crate::{
|
|||
|
||||
#[derive(Clone)]
|
||||
enum TabNameEnum {
|
||||
GeneralSettings,
|
||||
ProcessList,
|
||||
GeneralSettings,
|
||||
DebugTimings,
|
||||
}
|
||||
|
||||
impl TabNameEnum {
|
||||
fn from_string(s: &str) -> Option<Self> {
|
||||
match s {
|
||||
"general_settings" => Some(TabNameEnum::GeneralSettings),
|
||||
"process_list" => Some(TabNameEnum::ProcessList),
|
||||
"general_settings" => Some(TabNameEnum::GeneralSettings),
|
||||
"debug_timings" => Some(TabNameEnum::DebugTimings),
|
||||
_ => None,
|
||||
}
|
||||
|
|
@ -126,8 +126,8 @@ struct SubtabDebugTimings {
|
|||
#[allow(clippy::large_enum_variant)]
|
||||
enum Subtab {
|
||||
Empty,
|
||||
GeneralSettings(SubtabGeneralSettings),
|
||||
ProcessList(SubtabProcessList),
|
||||
GeneralSettings(SubtabGeneralSettings),
|
||||
DebugTimings(SubtabDebugTimings),
|
||||
}
|
||||
|
||||
|
|
@ -184,6 +184,10 @@ impl<T> Tab<T> for TabMonado<T> {
|
|||
Task::SetTab(tab) => {
|
||||
frontend.layout.remove_children(self.id_content);
|
||||
match tab {
|
||||
TabNameEnum::ProcessList => {
|
||||
self.tasks.push(Task::ProcessListRefresh);
|
||||
self.subtab = Subtab::ProcessList(SubtabProcessList::new(self.id_content, frontend)?)
|
||||
}
|
||||
TabNameEnum::GeneralSettings => {
|
||||
self.subtab = Subtab::GeneralSettings(SubtabGeneralSettings::new(
|
||||
self.id_content,
|
||||
|
|
@ -192,10 +196,6 @@ impl<T> Tab<T> for TabMonado<T> {
|
|||
&self.tasks,
|
||||
)?)
|
||||
}
|
||||
TabNameEnum::ProcessList => {
|
||||
self.tasks.push(Task::ProcessListRefresh);
|
||||
self.subtab = Subtab::ProcessList(SubtabProcessList::new(self.id_content, frontend)?)
|
||||
}
|
||||
TabNameEnum::DebugTimings => {
|
||||
self.subtab = Subtab::DebugTimings(SubtabDebugTimings::new(self.id_content, frontend, &self.tasks)?)
|
||||
}
|
||||
|
|
@ -206,7 +206,6 @@ impl<T> Tab<T> for TabMonado<T> {
|
|||
|
||||
match &mut self.subtab {
|
||||
Subtab::Empty => {}
|
||||
Subtab::GeneralSettings(_) => {}
|
||||
Subtab::ProcessList(_) => {
|
||||
// every few seconds
|
||||
if let Subtab::ProcessList(_) = &self.subtab
|
||||
|
|
@ -215,6 +214,7 @@ impl<T> Tab<T> for TabMonado<T> {
|
|||
self.tasks.push(Task::ProcessListRefresh);
|
||||
}
|
||||
}
|
||||
Subtab::GeneralSettings(_) => {}
|
||||
Subtab::DebugTimings(timings) => {
|
||||
timings.update(&self.tasks, data, frontend)?;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,15 +3,15 @@ use std::{marker::PhantomData, rc::Rc, str::FromStr};
|
|||
use strum::{AsRefStr, EnumProperty, EnumString};
|
||||
use wgui::{
|
||||
assets::AssetPath,
|
||||
color::WguiColorName,
|
||||
components::tabs::ComponentTabs,
|
||||
drawing,
|
||||
event::StyleSetRequest,
|
||||
globals::WguiGlobals,
|
||||
i18n::Translation,
|
||||
layout::{Layout, WidgetID},
|
||||
log::LogErr,
|
||||
parser::{Fetchable, ParseDocumentParams, ParserState},
|
||||
renderer_vk::text::{FontWeight, TextStyle},
|
||||
renderer_vk::text::{FontWeight, TextStyle, WguiTextShadow},
|
||||
taffy::{self, prelude::length},
|
||||
task::Tasks,
|
||||
widget::{
|
||||
|
|
@ -23,7 +23,7 @@ use wgui::{
|
|||
use wlx_common::{
|
||||
config::GeneralConfig,
|
||||
config_io::ConfigRoot,
|
||||
dash_interface::{ConfigChangeKind, InterfaceFeats, RecenterMode},
|
||||
dash_interface::{ConfigChangeKind, DashPlayspaceTask, InterfaceFeats},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
|
|
@ -43,7 +43,7 @@ mod tab_space_drag;
|
|||
mod tab_troubleshooting;
|
||||
|
||||
#[derive(Clone)]
|
||||
enum TabNameEnum {
|
||||
pub(crate) enum TabNameEnum {
|
||||
AutostartApps,
|
||||
Controls,
|
||||
Features,
|
||||
|
|
@ -71,7 +71,7 @@ impl TabNameEnum {
|
|||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
enum Task {
|
||||
pub(crate) enum Task {
|
||||
ClearPipewireTokens,
|
||||
ClearSavedState,
|
||||
DeleteAllConfigs,
|
||||
|
|
@ -201,7 +201,7 @@ impl<T> Tab<T> for TabSettings<T> {
|
|||
std::fs::create_dir(&path)?;
|
||||
}
|
||||
Task::ResetPlayspace => {
|
||||
frontend.interface.recenter_playspace(data, RecenterMode::Reset)?;
|
||||
frontend.interface.playspace(data, DashPlayspaceTask::Reset)?;
|
||||
return Ok(());
|
||||
}
|
||||
Task::RestartSoftware => {
|
||||
|
|
@ -291,7 +291,7 @@ impl<T> Tab<T> for TabSettings<T> {
|
|||
// Sorted alphabetically
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
#[derive(Clone, Copy, Eq, PartialEq, AsRefStr, EnumString)]
|
||||
enum SettingType {
|
||||
pub(crate) enum SettingType {
|
||||
AllowSliding,
|
||||
BlockGameInput,
|
||||
BlockGameInputIgnoreWatch,
|
||||
|
|
@ -320,6 +320,7 @@ enum SettingType {
|
|||
PointerLerpFactor,
|
||||
ScreenRenderDown,
|
||||
ScrollSpeed,
|
||||
SnapAngleDeg,
|
||||
EnableWatch,
|
||||
SetsOnWatch,
|
||||
SpaceDragMultiplier,
|
||||
|
|
@ -389,6 +390,7 @@ impl SettingType {
|
|||
Self::LongPressDuration => &mut config.long_press_duration,
|
||||
Self::PointerLerpFactor => &mut config.pointer_lerp_factor,
|
||||
Self::ScrollSpeed => &mut config.scroll_speed,
|
||||
Self::SnapAngleDeg => &mut config.snap_angle_deg,
|
||||
Self::SpaceDragMultiplier => &mut config.space_drag_multiplier,
|
||||
Self::SpaceGravityDamping => &mut config.space_gravity_damping,
|
||||
Self::SpaceGravityFlingStrength => &mut config.space_gravity_fling_strength,
|
||||
|
|
@ -500,6 +502,7 @@ impl SettingType {
|
|||
Self::ScrollSpeed => Ok("APP_SETTINGS.SCROLL_SPEED"),
|
||||
Self::EnableWatch => Ok("APP_SETTINGS.ENABLE_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::SpaceDragUnlocked => Ok("APP_SETTINGS.SPACE_DRAG_UNLOCKED"),
|
||||
Self::SpaceGravityDamping => Ok("APP_SETTINGS.SPACE_GRAVITY_DAMPING"),
|
||||
|
|
@ -536,6 +539,7 @@ impl SettingType {
|
|||
Self::KeyboardMiddleClick => Some("APP_SETTINGS.KEYBOARD_MIDDLE_CLICK_HELP"),
|
||||
Self::LeftHandedMouse => Some("APP_SETTINGS.LEFT_HANDED_MOUSE_HELP"),
|
||||
Self::ScreenRenderDown => Some("APP_SETTINGS.SCREEN_RENDER_DOWN_HELP"),
|
||||
Self::SnapAngleDeg => Some("APP_SETTINGS.SNAP_ANGLE_DEG_HELP"),
|
||||
Self::SpaceGravityDamping => Some("APP_SETTINGS.SPACE_GRAVITY_DAMPING_HELP"),
|
||||
Self::SpaceGravityFlingStrength => Some("APP_SETTINGS.SPACE_GRAVITY_FLING_STRENGTH_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(),
|
||||
taffy::Style {
|
||||
flex_direction: taffy::FlexDirection::Row,
|
||||
align_items: Some(taffy::AlignItems::Center),
|
||||
align_items: Some(taffy::AlignItems::CENTER),
|
||||
gap: length(8.0),
|
||||
..Default::default()
|
||||
},
|
||||
|
|
@ -597,11 +601,17 @@ fn mount_requires_restart(layout: &mut Layout, parent: WidgetID) -> anyhow::Resu
|
|||
content,
|
||||
style: TextStyle {
|
||||
wrap: false,
|
||||
color: Some(drawing::Color::new(1.0, 0.5, 0.5, 1.0)),
|
||||
color: Some(WguiColorName::Danger.into()),
|
||||
weight: Some(FontWeight::Bold),
|
||||
size: Some(10.0),
|
||||
shadow: Some(WguiTextShadow {
|
||||
x: 2.0,
|
||||
y: 2.0,
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ use wgui::{
|
|||
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::{
|
||||
|
|
@ -56,13 +57,15 @@ fn create_input_profiles_button(
|
|||
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.INPUT_PROFILES");
|
||||
params.insert("icon", "dashboard/controller.svg");
|
||||
|
||||
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)?;
|
||||
btn.on_click(Rc::new({
|
||||
|
|
@ -73,6 +76,8 @@ fn create_input_profiles_button(
|
|||
}
|
||||
}));
|
||||
|
||||
mount_requires_restart(mp.layout, id_cell)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -99,6 +104,7 @@ impl State {
|
|||
options_checkbox(par.mp, c, SettingType::InvertScrollDirectionY)?;
|
||||
options_slider_f32(par.mp, c, SettingType::ScrollSpeed, 0.1, 5.0, 0.1)?;
|
||||
options_slider_f32(par.mp, c, SettingType::LongPressDuration, 0.1, 2.0, 0.1)?;
|
||||
options_slider_f32(par.mp, c, SettingType::SnapAngleDeg, 0., 45., 5.)?;
|
||||
|
||||
if par.feats.openxr {
|
||||
options_slider_f32(par.mp, c, SettingType::PointerLerpFactor, 0.1, 1.0, 0.1)?;
|
||||
|
|
|
|||
|
|
@ -1,20 +1,70 @@
|
|||
use crate::tab::settings::{
|
||||
SettingType, SettingsMountParams, SettingsTab,
|
||||
macros::{options_category, options_checkbox, options_dropdown, options_slider_f32},
|
||||
use std::rc::Rc;
|
||||
|
||||
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 {
|
||||
pub fn mount(par: SettingsMountParams) -> anyhow::Result<State> {
|
||||
let tasks = Tasks::<Task>::new();
|
||||
let popup = PopupHolder::<color_palettes::View>::default();
|
||||
|
||||
let c = options_category(
|
||||
par.mp,
|
||||
par.id_parent,
|
||||
"APP_SETTINGS.LOOK_AND_FEEL",
|
||||
"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_checkbox(par.mp, c, SettingType::OpaqueBackground)?;
|
||||
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::SetsOnWatch)?;
|
||||
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(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -110,7 +110,14 @@ impl<T> TabWelcome<T> {
|
|||
for i in 0..PAGE_COUNT {
|
||||
let mut params = TemplateParams::new();
|
||||
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
|
||||
.state
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
use crate::util::{networking::http_client, steam_utils::AppID};
|
||||
use anyhow::Context;
|
||||
use serde::Deserialize;
|
||||
use wlx_common::{async_executor::AsyncExecutor, cache_dir};
|
||||
use wlx_common::cache_dir;
|
||||
|
||||
pub struct CoverArt {
|
||||
// can be empty in case if data couldn't be fetched (use a fallback image then)
|
||||
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);
|
||||
|
||||
// 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
|
||||
);
|
||||
|
||||
match http_client::get_simple(&executor, &url).await {
|
||||
match http_client::get_simple(&url).await {
|
||||
Ok(response) => {
|
||||
log::info!("Success");
|
||||
cache_dir::set_data(&cache_file_path, &response.data).await?;
|
||||
|
|
@ -55,11 +55,7 @@ pub struct AppDetailsJSONData {
|
|||
pub developers: Vec<String>, // ["Valve"]
|
||||
}
|
||||
|
||||
async fn get_app_details_json_internal(
|
||||
executor: AsyncExecutor,
|
||||
cache_file_path: &str,
|
||||
app_id: AppID,
|
||||
) -> anyhow::Result<AppDetailsJSONData> {
|
||||
async fn get_app_details_json_internal(cache_file_path: &str, app_id: AppID) -> anyhow::Result<AppDetailsJSONData> {
|
||||
// check if file already exists in cache directory
|
||||
if let Some(data) = cache_dir::get_data(cache_file_path).await {
|
||||
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
|
||||
log::info!("Fetching app detail ID {}", 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 root = serde_json::from_str::<serde_json::Value>(&res_utf8)?;
|
||||
let body = root.get(&app_id).context("invalid body")?;
|
||||
|
|
@ -88,10 +84,10 @@ async fn get_app_details_json_internal(
|
|||
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);
|
||||
|
||||
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),
|
||||
Err(e) => {
|
||||
log::error!("Failed to get app details: {:?}", e);
|
||||
|
|
|
|||
|
|
@ -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 async_native_tls::TlsStream;
|
||||
use http_body_util::{BodyStream, Empty};
|
||||
use hyper::Request;
|
||||
use smol::{net::TcpStream, prelude::*};
|
||||
use std::convert::TryInto;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use wlx_common::async_executor::AsyncExecutor;
|
||||
use smol::{
|
||||
Task,
|
||||
channel::{Receiver, bounded},
|
||||
io::AsyncWriteExt as _,
|
||||
};
|
||||
use std::{io::Read as _, path::Path, str, sync::OnceLock};
|
||||
|
||||
const IO_BUFFER_SIZE: usize = 256 * 1024;
|
||||
const CHANNEL_CAPACITY: usize = 4;
|
||||
const MAX_INITIAL_ALLOCATION: usize = 8 * 1024 * 1024;
|
||||
|
||||
pub struct HttpClientResponse {
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
|
@ -21,276 +17,224 @@ pub struct HttpClientResponse {
|
|||
impl HttpClientResponse {
|
||||
pub fn into_json<T>(self) -> anyhow::Result<T>
|
||||
where
|
||||
T: for<'a> serde::Deserialize<'a>,
|
||||
T: serde::de::DeserializeOwned,
|
||||
{
|
||||
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 bytes_downloaded: 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 executor: &'a AsyncExecutor,
|
||||
pub url: &'a str,
|
||||
pub on_progress: Option<ProgressFunc>,
|
||||
pub on_progress: Option<ProgressFunc<'a>>,
|
||||
}
|
||||
|
||||
pub async fn get(params: GetParams<'_>) -> anyhow::Result<HttpClientResponse> {
|
||||
let url: hyper::Uri = params.url.try_into()?;
|
||||
let req = Request::builder()
|
||||
.header(
|
||||
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, ¶ms.url).await?;
|
||||
|
||||
Ok(resp)
|
||||
struct DownloadStream {
|
||||
file_size: u64,
|
||||
chunks: Receiver<Vec<u8>>,
|
||||
worker: Task<anyhow::Result<()>>,
|
||||
}
|
||||
|
||||
async fn fetch_and_follow_redirects(
|
||||
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);
|
||||
static HTTP_AGENT: OnceLock<ureq::Agent> = OnceLock::new();
|
||||
|
||||
let resp = fetch(executor, req.clone()).await?;
|
||||
let status = resp.status();
|
||||
fn http_agent() -> &'static ureq::Agent {
|
||||
HTTP_AGENT.get_or_init(|| {
|
||||
ureq::Agent::config_builder()
|
||||
.max_redirects(10)
|
||||
.http_status_as_error(false)
|
||||
.build()
|
||||
.new_agent()
|
||||
})
|
||||
}
|
||||
|
||||
if status.is_redirection() {
|
||||
let next_req = follow_single_redirect(&req, &resp).await?;
|
||||
/// Starts a blocking HTTP request and streams its body through a bounded
|
||||
/// 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 mut current_req = next_req;
|
||||
let mut redirects = 0;
|
||||
loop {
|
||||
let resp = fetch(executor, current_req.clone()).await?;
|
||||
let resp_status = resp.status();
|
||||
let (metadata_tx, metadata_rx) = bounded::<u64>(1);
|
||||
|
||||
if resp_status.is_success() {
|
||||
let (parts, body) = resp.into_parts();
|
||||
let (chunk_tx, chunk_rx) = bounded::<Vec<u8>>(CHANNEL_CAPACITY);
|
||||
|
||||
let mut bytes_downloaded: u64 = 0;
|
||||
let mut file_size: u64 = 1;
|
||||
let worker = smol::unblock(move || -> anyhow::Result<()> {
|
||||
log::info!("fetching URL \"{}\"", url);
|
||||
|
||||
if let Some(val) = parts.headers.get("Content-Length")
|
||||
&& let Ok(str) = val.to_str()
|
||||
&& let Ok(s) = str.parse()
|
||||
{
|
||||
file_size = s;
|
||||
}
|
||||
let response = agent
|
||||
.get(&url)
|
||||
.header("Accept-Encoding", "identity")
|
||||
.call()
|
||||
.with_context(|| format!("failed to fetch URL \"{url}\""))?;
|
||||
|
||||
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 !response.status().is_success() {
|
||||
anyhow::bail!("non-200 HTTP response: {}", response.status().as_u16(),);
|
||||
}
|
||||
|
||||
if let Some(on_progress) = &mut on_progress {
|
||||
on_progress(ProgressFuncData {
|
||||
bytes_downloaded,
|
||||
file_size,
|
||||
})
|
||||
}
|
||||
}
|
||||
Ok(body)
|
||||
})
|
||||
.await?;
|
||||
let file_size = match response.body().content_length() {
|
||||
Some(file_size) => file_size,
|
||||
|
||||
return Ok(HttpClientResponse { data });
|
||||
} else if resp_status.is_redirection() {
|
||||
redirects += 1;
|
||||
if redirects >= max_redirects {
|
||||
anyhow::bail!("too many redirects");
|
||||
}
|
||||
current_req = follow_single_redirect(¤t_req, &resp).await?;
|
||||
} else {
|
||||
anyhow::bail!("non-200 HTTP response: {}", resp_status.as_str());
|
||||
None if allow_missing_content_length => 0,
|
||||
|
||||
None => {
|
||||
anyhow::bail!("HTTP response has no Content-Length header");
|
||||
}
|
||||
};
|
||||
|
||||
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() {
|
||||
// non-200 HTTP response
|
||||
anyhow::bail!("non-200 HTTP response: {}", resp.status().as_str());
|
||||
worker.await?;
|
||||
|
||||
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 })
|
||||
}
|
||||
|
||||
fn uri_try_from_str(s: &str) -> anyhow::Result<hyper::http::uri::PathAndQuery> {
|
||||
use std::convert::TryInto;
|
||||
let uri: hyper::Uri = s.try_into().context("invalid URI")?;
|
||||
uri
|
||||
.path_and_query()
|
||||
.ok_or_else(|| anyhow::anyhow!("URI has no path and query component"))
|
||||
.cloned()
|
||||
}
|
||||
/// Downloads a response directly to a file.
|
||||
///
|
||||
/// Unlike `get`, this permits responses without a Content-Length header. In
|
||||
/// that case, `ProgressFuncData::file_size` is zero.
|
||||
///
|
||||
/// An existing file is truncated. If the download fails after the file is
|
||||
/// 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(
|
||||
req: &hyper::Request<Empty<&'static [u8]>>,
|
||||
resp: &hyper::Response<hyper::body::Incoming>,
|
||||
) -> anyhow::Result<hyper::Request<Empty<&'static [u8]>>> {
|
||||
let location = resp
|
||||
.headers()
|
||||
.get(hyper::header::LOCATION)
|
||||
.ok_or_else(|| anyhow::anyhow!("redirect response has no Location header"))?;
|
||||
let DownloadStream {
|
||||
file_size,
|
||||
chunks,
|
||||
worker,
|
||||
} = start_download(params.url, true).await?;
|
||||
|
||||
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 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")?
|
||||
};
|
||||
let mut bytes_downloaded = 0_u64;
|
||||
|
||||
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()
|
||||
.header(
|
||||
hyper::header::HOST,
|
||||
next_url.authority().context("invalid authority")?.as_str(),
|
||||
)
|
||||
.uri(next_url)
|
||||
.body(Empty::new())?;
|
||||
bytes_downloaded += chunk.len() as u64;
|
||||
|
||||
Ok(next_req)
|
||||
}
|
||||
|
||||
pub async fn get_simple(executor: &AsyncExecutor, url: &str) -> anyhow::Result<HttpClientResponse> {
|
||||
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),
|
||||
if let Some(on_progress) = params.on_progress.as_mut() {
|
||||
on_progress(ProgressFuncData {
|
||||
bytes_downloaded,
|
||||
file_size,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<smol::io::Result<()>> {
|
||||
match &mut *self {
|
||||
SmolStream::Plain(stream) => Pin::new(stream).poll_close(cx),
|
||||
SmolStream::Tls(stream) => Pin::new(stream).poll_close(cx),
|
||||
}
|
||||
worker.await?;
|
||||
|
||||
file
|
||||
.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<()>> {
|
||||
match &mut *self {
|
||||
SmolStream::Plain(stream) => Pin::new(stream).poll_flush(cx),
|
||||
SmolStream::Tls(stream) => Pin::new(stream).poll_flush(cx),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_simple(url: &str) -> anyhow::Result<HttpClientResponse> {
|
||||
get(GetParams { url, on_progress: None }).await
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,11 @@
|
|||
use std::rc::Rc;
|
||||
|
||||
use wgui::{globals::WguiGlobals, renderer_vk::text::custom_glyph::CustomGlyphData};
|
||||
use wlx_common::async_executor::AsyncExecutor;
|
||||
|
||||
use crate::util::networking::http_client;
|
||||
|
||||
pub async fn fetch_to_glyph_data(
|
||||
globals: &WguiGlobals,
|
||||
executor: &AsyncExecutor,
|
||||
url: &str,
|
||||
) -> anyhow::Result<(CustomGlyphData, Rc<Vec<u8>>)> {
|
||||
let res = http_client::get_simple(executor, url).await?;
|
||||
pub async fn fetch_to_glyph_data(globals: &WguiGlobals, url: &str) -> anyhow::Result<(CustomGlyphData, Rc<Vec<u8>>)> {
|
||||
let res = http_client::get_simple(url).await?;
|
||||
let glyph_data = CustomGlyphData::from_bytes_raster(globals, url, &res.data)?;
|
||||
Ok((glyph_data, Rc::new(res.data)))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use crate::util::networking::{self, WAYVR_SKYMAPS_ROOT, http_client};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::PathBuf;
|
||||
use wlx_common::{async_executor::AsyncExecutor, config_io};
|
||||
use wlx_common::config_io;
|
||||
pub type SkymapUuid = uuid::Uuid;
|
||||
|
||||
#[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");
|
||||
|
||||
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>()?;
|
||||
catalog.validate()?;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
use std::{cell::RefCell, collections::VecDeque, rc::Rc};
|
||||
|
||||
use glam::{Mat4, Vec3};
|
||||
use std::{cell::RefCell, collections::VecDeque, rc::Rc};
|
||||
use wgui::{
|
||||
animation::{Animation, AnimationEasing},
|
||||
color::{WguiColor, WguiColorName},
|
||||
components::tooltip::{TOOLTIP_BORDER_COLOR, TOOLTIP_COLOR},
|
||||
drawing::Color,
|
||||
i18n::Translation,
|
||||
layout::{Layout, LayoutTask, LayoutTasks, WidgetID},
|
||||
renderer_vk::{
|
||||
|
|
@ -69,8 +68,8 @@ impl ToastManager {
|
|||
width: percent(1.0),
|
||||
height: percent(0.8),
|
||||
},
|
||||
align_items: Some(taffy::AlignItems::End),
|
||||
justify_content: Some(taffy::JustifyContent::Center),
|
||||
align_items: Some(taffy::AlignItems::END),
|
||||
justify_content: Some(taffy::JustifyContent::CENTER),
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
|
|
@ -78,8 +77,8 @@ impl ToastManager {
|
|||
let (rect, _) = layout.add_child(
|
||||
root.id,
|
||||
WidgetRectangle::create(WidgetRectangleParams {
|
||||
color: TOOLTIP_COLOR,
|
||||
border_color: TOOLTIP_BORDER_COLOR,
|
||||
color: TOOLTIP_COLOR.into(),
|
||||
border_color: TOOLTIP_BORDER_COLOR.into(),
|
||||
border: 2.0,
|
||||
round: WLength::Percent(1.0),
|
||||
..Default::default()
|
||||
|
|
@ -111,6 +110,7 @@ impl ToastManager {
|
|||
wrap: true,
|
||||
..Default::default()
|
||||
},
|
||||
..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();
|
||||
rect.params.color.a = opacity;
|
||||
rect.params.border_color.a = opacity;
|
||||
rect.params.color = rect.params.color.with_alpha(opacity);
|
||||
rect.params.border_color = rect.params.border_color.with_alpha(opacity);
|
||||
|
||||
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();
|
||||
}),
|
||||
));
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ use glam::{Mat4, Vec2};
|
|||
use wgui::{
|
||||
animation::{Animation, AnimationEasing},
|
||||
assets::AssetPath,
|
||||
color::WguiColorName,
|
||||
components::{self, button::ButtonClickCallback},
|
||||
drawing,
|
||||
i18n::Translation,
|
||||
layout::{Layout, LayoutTask, WidgetID},
|
||||
parser::{Fetchable, ParseDocumentParams},
|
||||
|
|
@ -55,6 +55,7 @@ pub fn create_label(layout: &mut Layout, id_parent: WidgetID, content: Translati
|
|||
wrap: true,
|
||||
..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),
|
||||
style: TextStyle {
|
||||
wrap: true,
|
||||
color: Some(drawing::Color::new(1.0, 0.5, 0.0, 1.0)),
|
||||
color: Some(WguiColorName::Danger.into()),
|
||||
weight: Some(FontWeight::Bold),
|
||||
..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> {
|
||||
let widget_sprite = WidgetSprite::create(WidgetSpriteParams {
|
||||
color: None,
|
||||
color: Some(WguiColorName::OnBackground.into()),
|
||||
glyph_data: Some(CustomGlyphData::from_assets(&layout.state.globals, path)?),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let size = taffy::Size {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
use std::{collections::HashMap, rc::Rc, str::FromStr};
|
||||
|
||||
use strum::{AsRefStr, EnumString, VariantNames};
|
||||
use strum::VariantNames;
|
||||
use wayvr_ipc::packet_client::{PositionMode, WvrProcessLaunchParams};
|
||||
use wgui::{
|
||||
assets::AssetPath,
|
||||
components::{button::ComponentButton, checkbox::ComponentCheckbox, radio_group::ComponentRadioGroup},
|
||||
components::{
|
||||
ComponentTrait, button::ComponentButton, checkbox::ComponentCheckbox, radio_group::ComponentRadioGroup,
|
||||
},
|
||||
globals::WguiGlobals,
|
||||
i18n::Translation,
|
||||
layout::{Layout, WidgetID},
|
||||
|
|
@ -12,7 +13,11 @@ use wgui::{
|
|||
task::Tasks,
|
||||
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::{
|
||||
frontend::{FrontendTask, FrontendTasks, SoundType},
|
||||
|
|
@ -20,51 +25,23 @@ use crate::{
|
|||
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)]
|
||||
enum Task {
|
||||
SetCompositor(CompositorMode),
|
||||
SetRes(ResMode),
|
||||
SetOrientation(OrientationMode),
|
||||
SetCompositor(AppCompositorMode),
|
||||
SetRes(AppResMode),
|
||||
SetOrientation(AppOrientationMode),
|
||||
SetAutoStart(bool),
|
||||
Launch,
|
||||
PinApp,
|
||||
UnpinApp,
|
||||
}
|
||||
|
||||
struct LaunchParams<'a, T> {
|
||||
application: &'a DesktopEntry,
|
||||
compositor_mode: CompositorMode,
|
||||
pos_mode: PosMode,
|
||||
res_mode: ResMode,
|
||||
orientation_mode: OrientationMode,
|
||||
compositor_mode: AppCompositorMode,
|
||||
pos_mode: AppPosMode,
|
||||
res_mode: AppResMode,
|
||||
orientation_mode: AppOrientationMode,
|
||||
globals: &'a WguiGlobals,
|
||||
frontend_tasks: &'a FrontendTasks,
|
||||
interface: &'a mut BoxDashInterface<T>,
|
||||
|
|
@ -88,14 +65,17 @@ pub struct View {
|
|||
#[allow(dead_code)]
|
||||
radio_orientation: Rc<ComponentRadioGroup>,
|
||||
|
||||
compositor_mode: CompositorMode,
|
||||
pos_mode: PosMode,
|
||||
res_mode: ResMode,
|
||||
orientation_mode: OrientationMode,
|
||||
compositor_mode: AppCompositorMode,
|
||||
pos_mode: AppPosMode,
|
||||
res_mode: AppResMode,
|
||||
orientation_mode: AppOrientationMode,
|
||||
|
||||
pinned_app: Option<PinnedApp>,
|
||||
|
||||
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> {
|
||||
|
|
@ -105,7 +85,9 @@ pub struct Params<'a> {
|
|||
pub parent_id: WidgetID,
|
||||
pub config: &'a GeneralConfig,
|
||||
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 {
|
||||
|
|
@ -131,6 +113,8 @@ impl View {
|
|||
let cb_autostart = state.fetch_component_as::<ComponentCheckbox>("cb_autostart")?;
|
||||
|
||||
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>(¶ms.layout.state, "label_exec")?;
|
||||
|
|
@ -145,6 +129,16 @@ impl View {
|
|||
|
||||
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")?;
|
||||
|
||||
// app icon
|
||||
|
|
@ -160,28 +154,35 @@ impl View {
|
|||
)?;
|
||||
}
|
||||
|
||||
let compositor_mode = if params.config.xwayland_by_default {
|
||||
CompositorMode::Cage
|
||||
} else {
|
||||
CompositorMode::Native
|
||||
let compositor_mode = match ¶ms.pinned_app {
|
||||
Some(pinned_app) => pinned_app.compositor_mode,
|
||||
None => {
|
||||
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 ?
|
||||
//radio_res.set_value(res_mode.as_ref())?;
|
||||
//tasks.push(Task::SetRes(res_mode));
|
||||
let mut res_mode = AppResMode::Res1080;
|
||||
let mut orientation_mode = AppOrientationMode::Wide;
|
||||
let pos_mode = AppPosMode::Anchored;
|
||||
|
||||
let orientation_mode = OrientationMode::Wide;
|
||||
// TODO: configurable defaults ?
|
||||
//radio_orientation.set_value(orientation_mode.as_ref())?;
|
||||
//tasks.push(Task::SetOrientation(orientation_mode));
|
||||
if let Some(pinned_app) = ¶ms.pinned_app {
|
||||
res_mode = pinned_app.res_mode;
|
||||
orientation_mode = pinned_app.orientation_mode;
|
||||
}
|
||||
|
||||
let pos_mode = PosMode::Anchored;
|
||||
// TODO: configurable defaults ?
|
||||
//radio_pos.set_value(pos_mode.as_ref())?;
|
||||
//tasks.push(Task::SetPos(pos_mode));
|
||||
// update radios
|
||||
{
|
||||
let mut common = params.layout.common();
|
||||
// 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;
|
||||
|
||||
|
|
@ -189,11 +190,11 @@ impl View {
|
|||
let tasks = tasks.clone();
|
||||
Box::new(move |_, ev| {
|
||||
if let Some(mode) = ev.value.and_then(|v| {
|
||||
CompositorMode::from_str(&v)
|
||||
AppCompositorMode::from_str(&v)
|
||||
.inspect_err(|_| {
|
||||
log::error!(
|
||||
"Invalid value for compositor: '{v}'. Valid values are: {:?}",
|
||||
ResMode::VARIANTS
|
||||
AppResMode::VARIANTS
|
||||
)
|
||||
})
|
||||
.ok()
|
||||
|
|
@ -208,11 +209,11 @@ impl View {
|
|||
let tasks = tasks.clone();
|
||||
Box::new(move |_, ev| {
|
||||
if let Some(mode) = ev.value.and_then(|v| {
|
||||
ResMode::from_str(&v)
|
||||
AppResMode::from_str(&v)
|
||||
.inspect_err(|_| {
|
||||
log::error!(
|
||||
"Invalid value for resolution: '{v}'. Valid values are: {:?}",
|
||||
ResMode::VARIANTS
|
||||
AppResMode::VARIANTS
|
||||
)
|
||||
})
|
||||
.ok()
|
||||
|
|
@ -246,11 +247,11 @@ impl View {
|
|||
let tasks = tasks.clone();
|
||||
Box::new(move |_, ev| {
|
||||
if let Some(mode) = ev.value.and_then(|v| {
|
||||
OrientationMode::from_str(&v)
|
||||
AppOrientationMode::from_str(&v)
|
||||
.inspect_err(|_| {
|
||||
log::error!(
|
||||
"Invalid value for orientation: '{v}'. Valid values are: {:?}",
|
||||
OrientationMode::VARIANTS
|
||||
AppOrientationMode::VARIANTS
|
||||
)
|
||||
})
|
||||
.ok()
|
||||
|
|
@ -290,7 +291,9 @@ impl View {
|
|||
entry: params.entry,
|
||||
frontend_tasks: params.frontend_tasks.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::SetAutoStart(auto_start) => self.auto_start = auto_start,
|
||||
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(())
|
||||
}
|
||||
|
||||
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) {
|
||||
View::try_launch(LaunchParams {
|
||||
application: &self.entry,
|
||||
|
|
@ -326,7 +380,7 @@ impl View {
|
|||
auto_start: self.auto_start,
|
||||
interface,
|
||||
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<()> {
|
||||
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
|
||||
env.push("QT_QPA_PLATFORM=wayland".into());
|
||||
env.push("GDK_BACKEND=wayland".into());
|
||||
|
|
@ -357,19 +411,19 @@ impl View {
|
|||
}
|
||||
|
||||
let args = match params.compositor_mode {
|
||||
CompositorMode::Cage => format!("-- {} {}", params.application.exec_path, params.application.exec_args),
|
||||
CompositorMode::Native => params.application.exec_args.to_string(),
|
||||
AppCompositorMode::Cage => format!("-- {} {}", params.application.exec_path, params.application.exec_args),
|
||||
AppCompositorMode::Native => params.application.exec_args.to_string(),
|
||||
};
|
||||
|
||||
let exec = match params.compositor_mode {
|
||||
CompositorMode::Cage => "cage".to_string(),
|
||||
CompositorMode::Native => params.application.exec_path.to_string(),
|
||||
AppCompositorMode::Cage => "cage".to_string(),
|
||||
AppCompositorMode::Native => params.application.exec_path.to_string(),
|
||||
};
|
||||
|
||||
let pos_mode = match params.pos_mode {
|
||||
PosMode::Floating => PositionMode::Float,
|
||||
PosMode::Anchored => PositionMode::Anchor,
|
||||
PosMode::Static => PositionMode::Static,
|
||||
AppPosMode::Floating => PositionMode::Float,
|
||||
AppPosMode::Anchored => PositionMode::Anchor,
|
||||
AppPosMode::Static => PositionMode::Static,
|
||||
};
|
||||
|
||||
let mut userdata = HashMap::new();
|
||||
|
|
@ -408,20 +462,20 @@ impl View {
|
|||
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 {
|
||||
ResMode::Res1440 => 2560 * 1440,
|
||||
ResMode::Res1080 => 1920 * 1080,
|
||||
ResMode::Res720 => 1280 * 720,
|
||||
ResMode::Res480 => 854 * 480,
|
||||
AppResMode::Res1440 => 2560 * 1440,
|
||||
AppResMode::Res1080 => 1920 * 1080,
|
||||
AppResMode::Res720 => 1280 * 720,
|
||||
AppResMode::Res480 => 854 * 480,
|
||||
};
|
||||
|
||||
let (ratio_w, ratio_h) = match orientation_mode {
|
||||
OrientationMode::Wide => (16, 9),
|
||||
OrientationMode::SemiWide => (3, 2),
|
||||
OrientationMode::Square => (1, 1),
|
||||
OrientationMode::SemiTall => (2, 3),
|
||||
OrientationMode::Tall => (9, 16),
|
||||
AppOrientationMode::Wide => (16, 9),
|
||||
AppOrientationMode::SemiWide => (3, 2),
|
||||
AppOrientationMode::Square => (1, 1),
|
||||
AppOrientationMode::SemiTall => (2, 3),
|
||||
AppOrientationMode::Tall => (9, 16),
|
||||
};
|
||||
|
||||
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
|
||||
.clone()
|
||||
.push(FrontendTask::MountPopupOnce(MountPopupOnceParams::new(
|
||||
Translation::from_raw_text(&entry.app_name),
|
||||
Box::new(move |data| {
|
||||
let on_launched = popup.get_close_callback(data.layout);
|
||||
let on_close_request = popup.get_close_callback(data.layout);
|
||||
let view = View::new(Params {
|
||||
entry: entry.clone(),
|
||||
globals: &globals,
|
||||
|
|
@ -447,7 +508,9 @@ pub fn mount_popup(frontend_tasks: FrontendTasks, globals: WguiGlobals, entry: D
|
|||
parent_id: data.id_content,
|
||||
frontend_tasks: &frontend_tasks,
|
||||
config: data.config,
|
||||
on_launched,
|
||||
on_close_request,
|
||||
on_app_pins_changed,
|
||||
pinned_app,
|
||||
})?;
|
||||
|
||||
popup.set_view(data.handle, view, None);
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ use std::rc::Rc;
|
|||
|
||||
use wgui::{
|
||||
assets::AssetPath,
|
||||
color::{WguiColor, WguiColorName},
|
||||
components::{
|
||||
self,
|
||||
button::{ButtonClickCallback, ComponentButton},
|
||||
|
|
@ -110,11 +111,13 @@ struct MultiSelectorParams<'a> {
|
|||
}
|
||||
|
||||
fn mount_multi_selector(params: MultiSelectorParams) -> anyhow::Result<()> {
|
||||
let accent_color = params.ess.layout.state.theme.accent_color;
|
||||
|
||||
for cell in params.cells {
|
||||
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
|
||||
let (_, button) = components::button::construct(
|
||||
|
|
@ -664,11 +667,11 @@ impl View {
|
|||
|
||||
let mut perform = |btn_num: u8, btn: &Rc<ComponentButton>| {
|
||||
let color = if num == btn_num {
|
||||
com.state.theme.accent_color
|
||||
WguiColorName::Primary
|
||||
} 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);
|
||||
|
|
|
|||
|
|
@ -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(¶ms.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 */
|
||||
)));
|
||||
}
|
||||
|
|
@ -69,13 +69,7 @@ impl ViewTrait for View {
|
|||
if let Some(on_downloaded) = self.on_downloaded.take() {
|
||||
self
|
||||
.executor
|
||||
.spawn(View::download(
|
||||
self.tasks.clone(),
|
||||
self.executor.clone(),
|
||||
url,
|
||||
path,
|
||||
on_downloaded,
|
||||
))
|
||||
.spawn(View::download(self.tasks.clone(), url, path, on_downloaded))
|
||||
.detach();
|
||||
}
|
||||
}
|
||||
|
|
@ -189,37 +183,12 @@ impl View {
|
|||
|
||||
async fn download(
|
||||
tasks: Tasks<Task>,
|
||||
executor: AsyncExecutor,
|
||||
url: String,
|
||||
target_path: PathBuf,
|
||||
on_downloaded: Box<dyn FnOnce()>,
|
||||
) -> Option<()> {
|
||||
tasks.push(Task::SetStatusText(String::from("Connecting to the server...")));
|
||||
|
||||
// start downloading from the server with progress reporting
|
||||
let res = handle_async_result(
|
||||
"Download failed",
|
||||
&tasks,
|
||||
http_client::get(http_client::GetParams {
|
||||
executor: &executor,
|
||||
url: &url,
|
||||
on_progress: Some(Box::new({
|
||||
let tasks = tasks.clone();
|
||||
move |data: ProgressFuncData| {
|
||||
tasks.push(Task::SetStatusText(format!(
|
||||
"{}/{} KiB ({}%)",
|
||||
data.bytes_downloaded / 1024,
|
||||
data.file_size / 1024,
|
||||
(data.bytes_downloaded as f32 / data.file_size as f32 * 100.0).round()
|
||||
)))
|
||||
}
|
||||
})),
|
||||
})
|
||||
.await,
|
||||
)?;
|
||||
|
||||
tasks.push(Task::SetStatusText(String::from("Writing to file...")));
|
||||
|
||||
// create parent directory if it doesn't exist yet
|
||||
if let Some(parent) = target_path.parent() {
|
||||
handle_async_result(
|
||||
|
|
@ -229,10 +198,28 @@ impl View {
|
|||
)?;
|
||||
}
|
||||
|
||||
// start downloading from the server with progress reporting
|
||||
handle_async_result(
|
||||
"File write failed",
|
||||
"Download failed",
|
||||
&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")));
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ use wgui::{
|
|||
globals::WguiGlobals,
|
||||
i18n::Translation,
|
||||
layout::{Layout, WidgetID},
|
||||
renderer_vk::text::{FontWeight, HorizontalAlign, TextShadow, TextStyle, custom_glyph::CustomGlyphData},
|
||||
renderer_vk::text::{FontWeight, HorizontalAlign, TextStyle, WguiTextShadow, custom_glyph::CustomGlyphData},
|
||||
taffy::{
|
||||
self, AlignItems, AlignSelf, JustifyContent, JustifySelf,
|
||||
prelude::{auto, length, percent},
|
||||
|
|
@ -61,12 +61,8 @@ const GAME_COVER_SIZE_X: f32 = 140.0;
|
|||
const GAME_COVER_SIZE_Y: f32 = 210.0;
|
||||
|
||||
impl View {
|
||||
async fn request_cover_image(
|
||||
executor: AsyncExecutor,
|
||||
manifest: steam_utils::AppManifest,
|
||||
on_loaded: Box<dyn FnOnce(CoverArt)>,
|
||||
) {
|
||||
let cover_art = match cached_fetcher::request_image(executor, manifest.app_id.clone()).await {
|
||||
async fn request_cover_image(manifest: steam_utils::AppManifest, on_loaded: Box<dyn FnOnce(CoverArt)>) {
|
||||
let cover_art = match cached_fetcher::request_image(manifest.app_id.clone()).await {
|
||||
Ok(cover_art) => cover_art,
|
||||
Err(e) => {
|
||||
log::error!("request_cover_image failed: {:?}", e);
|
||||
|
|
@ -107,16 +103,17 @@ impl View {
|
|||
content: Translation::from_raw_text(text),
|
||||
style: TextStyle {
|
||||
weight: Some(FontWeight::Bold),
|
||||
color: Some(drawing::Color::new(1.0, 1.0, 1.0, 1.0).into()),
|
||||
wrap: true,
|
||||
size: Some(16.0),
|
||||
align: Some(HorizontalAlign::Center),
|
||||
shadow: Some(TextShadow {
|
||||
color: drawing::Color::new(0.0, 0.0, 0.0, 1.0),
|
||||
x: 2.0,
|
||||
y: 2.0,
|
||||
shadow: Some(WguiTextShadow {
|
||||
color: drawing::Color::new(0.0, 0.0, 0.0, 1.0).into(),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
|
|
@ -125,8 +122,8 @@ impl View {
|
|||
label,
|
||||
taffy::Style {
|
||||
position: taffy::Position::Absolute,
|
||||
align_self: Some(AlignSelf::Baseline),
|
||||
justify_self: Some(JustifySelf::Center),
|
||||
align_self: Some(AlignSelf::BASELINE),
|
||||
justify_self: Some(JustifySelf::CENTER),
|
||||
margin: taffy::Rect {
|
||||
top: length(32.0),
|
||||
bottom: auto(),
|
||||
|
|
@ -173,9 +170,9 @@ impl View {
|
|||
let (widget_button, button) = components::button::construct(
|
||||
params.ess,
|
||||
components::button::Params {
|
||||
color: Some(drawing::Color::new(1.0, 1.0, 1.0, 0.0)),
|
||||
border_color: Some(BORDER_COLOR_DEFAULT),
|
||||
hover_border_color: Some(BORDER_COLOR_HOVERED),
|
||||
color: Some(drawing::Color::new(1.0, 1.0, 1.0, 0.0).into()),
|
||||
border_color: Some(BORDER_COLOR_DEFAULT.into()),
|
||||
hover_border_color: Some(BORDER_COLOR_HOVERED.into()),
|
||||
round: WLength::Units(12.0),
|
||||
border: 2.0,
|
||||
tooltip: Some(TooltipInfo {
|
||||
|
|
@ -184,8 +181,8 @@ impl View {
|
|||
}),
|
||||
style: taffy::Style {
|
||||
position: taffy::Position::Relative,
|
||||
align_items: Some(taffy::AlignItems::Center),
|
||||
justify_content: Some(taffy::JustifyContent::Center),
|
||||
align_items: Some(AlignItems::CENTER),
|
||||
justify_content: Some(JustifyContent::CENTER),
|
||||
size: taffy::Size {
|
||||
width: length(GAME_COVER_SIZE_X * params.scale),
|
||||
height: length(GAME_COVER_SIZE_Y * params.scale),
|
||||
|
|
@ -206,16 +203,16 @@ impl View {
|
|||
height: percent(1.0),
|
||||
},
|
||||
padding: taffy::Rect::length(2.0),
|
||||
align_items: Some(AlignItems::Center),
|
||||
justify_content: Some(JustifyContent::Center),
|
||||
align_items: Some(AlignItems::CENTER),
|
||||
justify_content: Some(JustifyContent::CENTER),
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
let rect_gradient = |color: drawing::Color, color2: drawing::Color| {
|
||||
rectangle::WidgetRectangle::create(rectangle::WidgetRectangleParams {
|
||||
color,
|
||||
color2,
|
||||
color: color.into(),
|
||||
color2: color2.into(),
|
||||
round: WLength::Units(12.0),
|
||||
gradient: GradientMode::Vertical,
|
||||
..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.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
|
||||
|
|
@ -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.0),
|
||||
),
|
||||
rect_gradient_style(taffy::AlignSelf::Baseline, 0.5),
|
||||
rect_gradient_style(taffy::AlignSelf::BASELINE, 0.5),
|
||||
)?;
|
||||
|
||||
// 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.25),
|
||||
),
|
||||
rect_gradient_style(taffy::AlignSelf::End, 0.5),
|
||||
rect_gradient_style(taffy::AlignSelf::END, 0.5),
|
||||
)?;
|
||||
|
||||
// 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.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 {
|
||||
|
|
@ -285,7 +282,6 @@ impl View {
|
|||
params
|
||||
.executor
|
||||
.spawn(View::request_cover_image(
|
||||
params.executor.clone(),
|
||||
params.manifest.clone(),
|
||||
Box::new(params.on_loaded),
|
||||
))
|
||||
|
|
|
|||
|
|
@ -75,8 +75,8 @@ impl ViewTrait for View {
|
|||
}
|
||||
|
||||
impl View {
|
||||
async fn fetch_details(executor: AsyncExecutor, tasks: Tasks<Task>, app_id: AppID) {
|
||||
let Some(details) = cached_fetcher::get_app_details_json(executor, app_id).await else {
|
||||
async fn fetch_details(tasks: Tasks<Task>, app_id: AppID) {
|
||||
let Some(details) = cached_fetcher::get_app_details_json(app_id).await else {
|
||||
return;
|
||||
};
|
||||
|
||||
|
|
@ -103,7 +103,7 @@ impl View {
|
|||
let tasks = Tasks::new();
|
||||
|
||||
// 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();
|
||||
|
||||
let id_cover_art_parent = state.get_widget_id("cover_art_parent")?;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ use wlx_common::{async_executor::AsyncExecutor, config::GeneralConfig, dash_inte
|
|||
pub mod app_launcher;
|
||||
pub mod audio_settings;
|
||||
pub mod bindings;
|
||||
pub mod color_palettes;
|
||||
pub mod dialog_box;
|
||||
pub mod download_file;
|
||||
pub mod game_cover;
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@ use crate::{
|
|||
use anyhow::Context;
|
||||
use wgui::{
|
||||
assets::AssetPath,
|
||||
color::WguiColorName,
|
||||
components::button::ComponentButton,
|
||||
drawing::Color,
|
||||
globals::WguiGlobals,
|
||||
i18n::Translation,
|
||||
layout::{Layout, WidgetID},
|
||||
|
|
@ -86,7 +86,7 @@ fn mount_resolution_button(
|
|||
let button = data.fetch_component_as::<ComponentButton>("button")?;
|
||||
|
||||
if already_downloaded {
|
||||
button.set_color(&mut layout.common(), Color::new(0.0, 0.4, 0.0, 1.0)); // green
|
||||
button.set_color(&mut layout.common(), WguiColorName::Tertiary.into()); // green
|
||||
}
|
||||
tasks.handle_button(&button, Task::ResolutionClicked(res));
|
||||
Ok(())
|
||||
|
|
|
|||
|
|
@ -119,8 +119,8 @@ impl ViewTrait for View {
|
|||
}
|
||||
|
||||
impl View {
|
||||
async fn skymap_catalog_request_wrapper(tasks: Tasks<Task>, executor: AsyncExecutor) {
|
||||
let res = networking::skymap_catalog::request_catalog(&executor).await;
|
||||
async fn skymap_catalog_request_wrapper(tasks: Tasks<Task>) {
|
||||
let res = networking::skymap_catalog::request_catalog().await;
|
||||
tasks.push(Task::SetSkymapCatalog(Rc::new(res)))
|
||||
}
|
||||
|
||||
|
|
@ -131,7 +131,7 @@ impl View {
|
|||
with_text: true,
|
||||
})?;
|
||||
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();
|
||||
Ok(Self {
|
||||
id_parent: par.parent_id,
|
||||
|
|
@ -147,15 +147,10 @@ impl View {
|
|||
})
|
||||
}
|
||||
|
||||
async fn request_skymap_preview(
|
||||
globals: WguiGlobals,
|
||||
executor: AsyncExecutor,
|
||||
entry: SkymapCatalogEntry,
|
||||
tasks: Tasks<Task>,
|
||||
) {
|
||||
async fn request_skymap_preview(globals: WguiGlobals, entry: SkymapCatalogEntry, tasks: Tasks<Task>) {
|
||||
tasks.push(Task::SetSkymapPreview((
|
||||
entry.uuid,
|
||||
networking::image_fetch::fetch_to_glyph_data(&globals, &executor, &entry.files.get_url_preview())
|
||||
networking::image_fetch::fetch_to_glyph_data(&globals, &entry.files.get_url_preview())
|
||||
.await
|
||||
.ok(),
|
||||
)));
|
||||
|
|
@ -192,12 +187,7 @@ impl View {
|
|||
let id_list = parser_state.fetch_widget(&layout.state, "list")?.id;
|
||||
|
||||
for entry in &catalog.entries {
|
||||
let task = View::request_skymap_preview(
|
||||
self.globals.clone(),
|
||||
self.executor.clone(),
|
||||
entry.clone(),
|
||||
self.tasks.clone(),
|
||||
);
|
||||
let task = View::request_skymap_preview(self.globals.clone(), entry.clone(), self.tasks.clone());
|
||||
|
||||
let skymap_uuid = entry.uuid;
|
||||
|
||||
|
|
|
|||
|
|
@ -8,14 +8,16 @@ authors = ["galister", "oo8dev"]
|
|||
repository = "https://github.com/wlx-team/wayvr"
|
||||
|
||||
[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/" }
|
||||
wgui = { path = "../wgui/" }
|
||||
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"
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -10,6 +10,7 @@ use wgui::{
|
|||
font_config::WguiFontConfig,
|
||||
globals::WguiGlobals,
|
||||
layout::{Layout, LayoutParams, LayoutUpdateParams},
|
||||
palette::WguiColorPalette,
|
||||
parser::{ParseDocumentParams, ParserState},
|
||||
};
|
||||
use wlx_common::locale::WayVRLangProvider;
|
||||
|
|
@ -30,12 +31,14 @@ impl TestbedAny {
|
|||
};
|
||||
|
||||
let lang_provider = WayVRLangProvider::default();
|
||||
let palette_name = std::env::var("PALETTE").unwrap_or_else(|_| "Default".to_string());
|
||||
|
||||
let globals = WguiGlobals::new(
|
||||
assets,
|
||||
&lang_provider,
|
||||
&WguiFontConfig::default(),
|
||||
PathBuf::new(), // cwd
|
||||
WguiColorPalette::get_builtin(&palette_name),
|
||||
)?;
|
||||
|
||||
let (layout, state) = wgui::parser::new_layout_from_assets(
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ impl TestbedDashboard {
|
|||
pub fn new() -> anyhow::Result<Self> {
|
||||
let interface = DashInterfaceEmulated::new();
|
||||
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 {
|
||||
interface: Box::new(interface),
|
||||
|
|
@ -20,6 +21,7 @@ impl TestbedDashboard {
|
|||
has_monado: true,
|
||||
lang_provider: &lang_provider,
|
||||
theme: Rc::new(WguiTheme::default()),
|
||||
color_palette: &palette_name,
|
||||
})?;
|
||||
Ok(Self { frontend })
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ use wgui::{
|
|||
globals::WguiGlobals,
|
||||
i18n::Translation,
|
||||
layout::{Layout, LayoutParams, LayoutUpdateParams, Widget},
|
||||
palette::WguiColorPalette,
|
||||
parser::{Fetchable, ParseDocumentExtra, ParseDocumentParams, ParserState},
|
||||
taffy::{self, prelude::length},
|
||||
task::Tasks,
|
||||
|
|
@ -86,12 +87,14 @@ impl TestbedGeneric {
|
|||
|
||||
pub fn new(assets: Box<assets::Asset>) -> anyhow::Result<Self> {
|
||||
let lang_provider = WayVRLangProvider::default();
|
||||
let palette_name = std::env::var("PALETTE").unwrap_or_else(|_| "Default".to_string());
|
||||
|
||||
let globals = WguiGlobals::new(
|
||||
assets,
|
||||
&lang_provider,
|
||||
&WguiFontConfig::default(),
|
||||
PathBuf::new(), // cwd
|
||||
WguiColorPalette::get_builtin(&palette_name),
|
||||
)?;
|
||||
|
||||
let extra = ParseDocumentExtra {
|
||||
|
|
@ -116,9 +119,9 @@ impl TestbedGeneric {
|
|||
color = color.mult_rgb(mult_f32);
|
||||
|
||||
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(
|
||||
|
|
|
|||
|
|
@ -8,18 +8,19 @@ authors = ["galister", "oo8dev"]
|
|||
repository = "https://github.com/wlx-team/wayvr"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.93"
|
||||
anyhow.workspace = true
|
||||
async-channel.workspace = true
|
||||
async-io.workspace = true
|
||||
bytes.workspace = true
|
||||
log = "0.4.22"
|
||||
interprocess.workspace = true
|
||||
log.workspace = true
|
||||
serde.workspace = true
|
||||
smallvec = "1.13.2"
|
||||
smallvec.workspace = true
|
||||
|
||||
# client-only deps
|
||||
interprocess = { version = "2.2.2", features = ["tokio"], optional = true }
|
||||
serde_json.workspace = true
|
||||
tokio = { version = "1.43.1", features = ["macros"], optional = true }
|
||||
tokio-util = { version = "0.7.13", optional = true }
|
||||
smol = { workspace = true, optional = true }
|
||||
|
||||
[features]
|
||||
default = ["client"]
|
||||
client = ["dep:interprocess", "dep:tokio", "dep:tokio-util"]
|
||||
client = ["dep:smol"]
|
||||
|
|
|
|||
|
|
@ -1,16 +1,12 @@
|
|||
use bytes::BufMut;
|
||||
use interprocess::local_socket::{
|
||||
self, GenericNamespaced,
|
||||
tokio::{Stream, prelude::*},
|
||||
};
|
||||
use interprocess::local_socket::{self, ToNsName};
|
||||
use serde::Serialize;
|
||||
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 tokio::{
|
||||
io::{AsyncReadExt, AsyncWriteExt},
|
||||
sync::Mutex,
|
||||
};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
use crate::{
|
||||
gen_id,
|
||||
|
|
@ -43,10 +39,10 @@ type SignalFunc = Box<dyn FnMut(&packet_server::PacketServer) -> bool + Send>;
|
|||
pub struct WayVRClient {
|
||||
receiver: ReceiverMutex,
|
||||
sender: SenderMutex,
|
||||
cancel_token: CancellationToken,
|
||||
cancel_tx: async_channel::Sender<()>,
|
||||
exiting: bool,
|
||||
queued_packets: QueuedPacketVec,
|
||||
pub auth: Option<AuthInfo>, // Some if authenticated
|
||||
pub auth: Option<AuthInfo>,
|
||||
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 WayVRClientWeak = Weak<Mutex<WayVRClient>>;
|
||||
|
||||
type ReceiverMutex = Arc<Mutex<local_socket::tokio::RecvHalf>>;
|
||||
type SenderMutex = Arc<Mutex<local_socket::tokio::SendHalf>>;
|
||||
type ReceiverMutex = Arc<Mutex<smol::net::unix::UnixStream>>;
|
||||
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<()> {
|
||||
loop {
|
||||
|
|
@ -79,7 +93,7 @@ async fn client_runner(client: WayVRClientMutex) -> anyhow::Result<()> {
|
|||
type Payload = SmallVec<[u8; 64]>;
|
||||
|
||||
async fn read_payload(
|
||||
conn: &mut local_socket::tokio::RecvHalf,
|
||||
conn: &mut smol::net::unix::UnixStream,
|
||||
size: u32,
|
||||
) -> anyhow::Result<Payload> {
|
||||
let mut payload = Payload::new();
|
||||
|
|
@ -122,32 +136,32 @@ impl WayVRClient {
|
|||
|
||||
pub async fn new(client_name: &str) -> anyhow::Result<WayVRClientMutex> {
|
||||
let printname = "/tmp/wayvr_ipc.sock";
|
||||
let name = printname.to_ns_name::<GenericNamespaced>()?;
|
||||
|
||||
let stream = match Stream::connect(name).await {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
anyhow::bail!("Failed to connect to the WayVR IPC: {}", e)
|
||||
}
|
||||
};
|
||||
let (receiver, sender) = stream.split();
|
||||
let name = printname
|
||||
.to_ns_name::<local_socket::GenericNamespaced>()
|
||||
.map_err(|e| anyhow::anyhow!("Failed to create socket name: {}", e))?;
|
||||
let opts = local_socket::ConnectOptions::new().name(name);
|
||||
let ip_stream = opts
|
||||
.connect_sync()
|
||||
.map_err(|e| anyhow::anyhow!("Failed to connect to the WayVR IPC: {}", e))?;
|
||||
|
||||
let receiver = Arc::new(Mutex::new(receiver));
|
||||
let sender = Arc::new(Mutex::new(sender));
|
||||
let async_stream = interprocess_stream_to_async(ip_stream)?;
|
||||
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 {
|
||||
receiver,
|
||||
sender: sender.clone(),
|
||||
cancel_tx,
|
||||
exiting: false,
|
||||
cancel_token: cancel_token.clone(),
|
||||
queued_packets: QueuedPacketVec::new(),
|
||||
auth: None,
|
||||
on_signal: None,
|
||||
}));
|
||||
|
||||
WayVRClient::start_runner(client.clone(), cancel_token);
|
||||
WayVRClient::start_runner(client.clone(), cancel_rx);
|
||||
|
||||
// Send handshake to the server
|
||||
send_packet(
|
||||
|
|
@ -163,17 +177,31 @@ impl WayVRClient {
|
|||
Ok(client)
|
||||
}
|
||||
|
||||
fn start_runner(client: WayVRClientMutex, cancel_token: CancellationToken) {
|
||||
tokio::spawn(async move {
|
||||
tokio::select! {
|
||||
_ = cancel_token.cancelled() => {
|
||||
log::info!("Exiting IPC runner gracefully");
|
||||
}
|
||||
e = client_runner(client.clone()) => {
|
||||
log::info!("IPC Runner failed: {:?}", e);
|
||||
fn start_runner(client: WayVRClientMutex, cancel_rx: async_channel::Receiver<()>) {
|
||||
smol::spawn(async move {
|
||||
let cancel_fut = async {
|
||||
cancel_rx
|
||||
.recv()
|
||||
.await
|
||||
.map_err(|_| anyhow::anyhow!("cancelled"))
|
||||
};
|
||||
|
||||
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<()> {
|
||||
|
|
@ -185,7 +213,9 @@ impl WayVRClient {
|
|||
// read packet
|
||||
let packet = {
|
||||
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);
|
||||
if packet_size > 128 * 1024 {
|
||||
anyhow::bail!("packet size too large (> 128 KiB)");
|
||||
|
|
@ -442,6 +472,6 @@ impl WayVRClient {
|
|||
impl Drop for WayVRClient {
|
||||
fn drop(&mut self) {
|
||||
self.exiting = true;
|
||||
self.cancel_token.cancel();
|
||||
let _ = self.cancel_tx.try_send(());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,25 +1,29 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use tokio::sync::Notify;
|
||||
use async_channel::{Receiver, Sender};
|
||||
|
||||
// Copyable wrapped Notify struct for easier usage
|
||||
#[derive(Default, Clone)]
|
||||
#[derive(Clone)]
|
||||
pub struct Notifier {
|
||||
notifier: Arc<Notify>,
|
||||
sender: Sender<()>,
|
||||
receiver: Receiver<()>,
|
||||
}
|
||||
|
||||
impl Notifier {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
notifier: Arc::new(Notify::new()),
|
||||
}
|
||||
let (sender, receiver) = async_channel::bounded(1);
|
||||
Self { sender, receiver }
|
||||
}
|
||||
|
||||
pub fn notify(&self) {
|
||||
self.notifier.notify_waiters();
|
||||
let _ = self.sender.try_send(());
|
||||
}
|
||||
|
||||
pub async fn wait(&self) {
|
||||
self.notifier.notified().await;
|
||||
let _ = self.receiver.recv().await;
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Notifier {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
137
wayvr/Cargo.toml
137
wayvr/Cargo.toml
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "wayvr"
|
||||
version = "26.7.0"
|
||||
version = "26.7.1"
|
||||
edition = "2024"
|
||||
license = "GPL-3.0-only"
|
||||
authors = ["galister", "oo8dev"]
|
||||
|
|
@ -27,10 +27,14 @@ wlx-capture = { path = "../wlx-capture" }
|
|||
wlx-common = { path = "../wlx-common" }
|
||||
|
||||
anyhow.workspace = true
|
||||
chrono = { workspace = true, features = ["unstable-locales"] }
|
||||
clap.workspace = true
|
||||
dbus.workspace = true
|
||||
glam = { workspace = true, features = ["mint", "serde"] }
|
||||
idmap = { workspace = true, features = ["serde"] }
|
||||
idmap-derive.workspace = true
|
||||
interprocess.workspace = true
|
||||
libc.workspace = true
|
||||
log.workspace = true
|
||||
pipewire = { workspace = true, optional = true }
|
||||
regex.workspace = true
|
||||
|
|
@ -39,68 +43,86 @@ serde = { workspace = true, features = ["rc"] }
|
|||
serde_json.workspace = true
|
||||
serde_json5.workspace = true
|
||||
slotmap.workspace = true
|
||||
smallvec.workspace = true
|
||||
smol = { workspace = true }
|
||||
strum.workspace = true
|
||||
tracing-subscriber.workspace = true
|
||||
uuid = { workspace = true }
|
||||
vulkano.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-protocols = { workspace = true }
|
||||
wayland-protocols-misc = { workspace = true }
|
||||
winit = { version = "0.30.12", optional = true }
|
||||
xcb = { version = "1.6.0", features = [
|
||||
"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"
|
||||
winit = { workspace = true, optional = true }
|
||||
xdg.workspace = true
|
||||
|
||||
calloop = { version = "0.14", optional = true }
|
||||
calloop-wayland-source = { version = "0.4", optional = true }
|
||||
ash = "^0.38.0" # must match vulkano
|
||||
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]
|
||||
|
||||
regex.workspace = true
|
||||
|
||||
[features]
|
||||
|
|
@ -113,6 +135,11 @@ osc = ["dep:rosc"]
|
|||
pipewire = ["wlx-capture/pipewire"]
|
||||
uidev = ["dep:winit"]
|
||||
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"]
|
||||
xcb = ["dep:xcb"]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
#!/bin/bash
|
||||
|
||||
|
||||
|
||||
|
||||
cargo run --profile=plain -- --show --replace
|
||||
|
|
@ -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 |
|
|
@ -10,18 +10,18 @@
|
|||
round="8"
|
||||
align_items="center"
|
||||
justify_content="space_between"
|
||||
color="~color_bg"
|
||||
border_color="~color_accent"
|
||||
color="background"
|
||||
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">
|
||||
<Button macro="window_button" _release="::EditToggle"
|
||||
border_color="~color_faded_translucent" color="~color_faded_50" color2="~color_faded_10">
|
||||
<Button macro="window_button" _release="::EditToggle">
|
||||
<sprite macro="window_button_icon" src="watch/edit.svg" />
|
||||
</Button>
|
||||
|
||||
<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" />
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,33 +1,28 @@
|
|||
<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"
|
||||
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" />
|
||||
|
||||
<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}" />
|
||||
</Button>
|
||||
</template>
|
||||
|
||||
<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}" />
|
||||
</Button>
|
||||
</template>
|
||||
|
||||
<template name="TopButtonFaded">
|
||||
<Button macro="button_style" tooltip="${tooltip}" _press="${press}" _release="${release}" border_color="~color_faded_translucent" color="~color_faded_50" color2="~color_faded_10">
|
||||
<template name="TopButtonTertiary">
|
||||
<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}" />
|
||||
</Button>
|
||||
</template>
|
||||
|
||||
<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}" />
|
||||
</Button>
|
||||
</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">
|
||||
<div />
|
||||
<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="row" gap="4">
|
||||
<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" / -->
|
||||
<TopButtonDanger src="edit/delete.svg" tooltip="EDIT_MODE.DELETE" release="::EditModeDelete" />
|
||||
<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 id="tab_none" display="none" height="100">
|
||||
<label align="center" translation="EDIT_MODE.HINT_POINT_WINDOW" />
|
||||
|
|
@ -129,7 +124,7 @@
|
|||
</div>
|
||||
<div width="100%" justify_content="end" flex_direction="row">
|
||||
<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" />
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -6,25 +6,25 @@
|
|||
padding="16"
|
||||
flex_direction="column"
|
||||
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="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" />
|
||||
</div>
|
||||
<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" />
|
||||
</div>
|
||||
<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" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- A simple separator -->
|
||||
<rectangle width="100%" height="2" color="~color_accent" />
|
||||
<rectangle width="100%" height="2" color="outline" />
|
||||
|
||||
<div align_items="center" padding="8" >
|
||||
<label display="none" id="grabbing_watch" wrap="1" size="20" padding_right="16" translation="GRAB.GRABBING_WATCH" />
|
||||
|
|
|
|||
|
|
@ -1,20 +1,24 @@
|
|||
<layout>
|
||||
<macro name="keycap_rect"
|
||||
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" />
|
||||
|
||||
<macro name="tray_rect"
|
||||
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" />
|
||||
|
||||
<macro name="keycap_div"
|
||||
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">
|
||||
<rectangle width="2" height="100%" color="~color_accent" />
|
||||
<rectangle width="2" height="100%" color="primary" />
|
||||
</template>
|
||||
|
||||
<!-- The keyboard is build from the xkb keymap. This file is for customizing the keycaps. -->
|
||||
|
|
@ -24,7 +28,7 @@
|
|||
<template name="KeySpecial">
|
||||
<div macro="keycap_div">
|
||||
<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>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -34,7 +38,7 @@
|
|||
<template name="KeyLetter">
|
||||
<div macro="keycap_div">
|
||||
<rectangle id="${id}" macro="keycap_rect">
|
||||
<label text="${text}" size="18" />
|
||||
<label text="${text}" size="~text_size" />
|
||||
</rectangle>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -44,8 +48,8 @@
|
|||
<template name="KeyLetterAltGr">
|
||||
<div macro="keycap_div">
|
||||
<rectangle id="${id}" macro="keycap_rect" gap="3">
|
||||
<label text="${text}" size="18" />
|
||||
<label color="~color_text_translucent" text="${text_altgr}" size="18" />
|
||||
<label text="${text}" size="~text_size" />
|
||||
<label color="on_background_variant" text="${text_altgr}" size="~text_size" />
|
||||
</rectangle>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -55,8 +59,8 @@
|
|||
<template name="KeySymbol">
|
||||
<div macro="keycap_div">
|
||||
<rectangle id="${id}" macro="keycap_rect" gap="3">
|
||||
<label color="~color_text_translucent" text="${text_shift}" size="18" />
|
||||
<label text="${text}" size="18" />
|
||||
<label color="on_background_variant" text="${text_shift}" size="~text_size" />
|
||||
<label text="${text}" size="~text_size" />
|
||||
</rectangle>
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -67,14 +71,14 @@
|
|||
<div macro="keycap_div">
|
||||
<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">
|
||||
<label color="~color_text_translucent" text="${text_shift}" size="18" />
|
||||
<label color="on_background_variant" text="${text_shift}" size="~text_size" />
|
||||
</div>
|
||||
<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 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>
|
||||
</rectangle>
|
||||
</div>
|
||||
|
|
@ -90,10 +94,10 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<macro name="button_style" border="2" border_color="~color_accent_translucent" color="~color_bg" round="6"
|
||||
align_items="center" justify_content="center" padding="6" width="60" height="60" overflow="visible"/>
|
||||
<macro name="button_style" border="2" round="6"
|
||||
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">
|
||||
<context_menu >
|
||||
|
|
@ -108,7 +112,7 @@
|
|||
<!-- An app with a single icon. -->
|
||||
<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}">
|
||||
<sprite width="38" height="38" color="~color_text" src_ext="${icon}" />
|
||||
<sprite width="38" height="38" src_ext="${icon}" parent_color="ignore" />
|
||||
</Button>
|
||||
</template>
|
||||
|
||||
|
|
@ -123,9 +127,9 @@
|
|||
<!-- A screen with a shortened connector name, e.g. "H1" for HDMI-A-1 or "D2" for DP-2 -->
|
||||
<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}">
|
||||
<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">
|
||||
<label text="${display}" size="18" color="~color_faded_20" weight="bold" />
|
||||
<label text="${display}" size="~text_size" weight="bold" parent_color="background" />
|
||||
</div>
|
||||
</Button>
|
||||
</template>
|
||||
|
|
@ -141,7 +145,7 @@
|
|||
|
||||
<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}">
|
||||
<sprite width="38" height="38" color="~color_text" src="${icon}" />
|
||||
<sprite width="38" height="38" src="${icon}" />
|
||||
</Button>
|
||||
</template>
|
||||
|
||||
|
|
@ -156,9 +160,9 @@
|
|||
|
||||
<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}">
|
||||
<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">
|
||||
<label text="${display}" size="20" color="~color_faded_20" weight="bold" />
|
||||
<label text="${display}" size="20" weight="bold" parent_color="background" />
|
||||
</div>
|
||||
</Button>
|
||||
</template>
|
||||
|
|
@ -185,18 +189,18 @@
|
|||
|
||||
<template name="Set">
|
||||
<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">
|
||||
<label text="${display}" size="20" color="~color_faded_20" weight="bold" />
|
||||
<label text="${display}" size="20" weight="bold" parent_color="background" />
|
||||
</div>
|
||||
</Button>
|
||||
</template>
|
||||
|
||||
<template name="Clock">
|
||||
<div flex_direction="column" gap="3" align_items="center">
|
||||
<label text="23:59" _source="clock" _display="time" size="21" weight="bold" />
|
||||
<label text="Tuesday" _source="clock" _display="dow" size="15" />
|
||||
<label text="22/2/2022" _source="clock" _display="date" size="15" />
|
||||
<label text="23:59" _source="clock" _display="time" size="21" weight="bold" color="primary" />
|
||||
<label text="Tuesday" _source="clock" _display="dow" size="15" color="on_background_variant" />
|
||||
<label text="22/2/2022" _source="clock" _display="date" size="15" color="on_background_variant" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -205,7 +209,7 @@
|
|||
<rectangle macro="bg_rect" padding="10" align_items="center" justify_content="space_between">
|
||||
<div gap="10">
|
||||
<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>
|
||||
<VerticalSeparator />
|
||||
<div id="panels_root" gap="6">
|
||||
|
|
@ -216,7 +220,7 @@
|
|||
</div>
|
||||
<div id="tray_root" flex_direction="row" gap="10">
|
||||
<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>
|
||||
|
||||
<VerticalSeparator />
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -6,22 +6,22 @@
|
|||
padding="16"
|
||||
flex_direction="column"
|
||||
gap="8"
|
||||
color="#000000c0" border_color="~color_accent" border="2" round="8">
|
||||
color="background" border_color="outline" border="2" round="8">
|
||||
|
||||
<!-- Toast title -->
|
||||
<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" />
|
||||
<label wrap="1" id="toast_title" weight="bold" size="25" padding_left="16" padding_right="16" />
|
||||
<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" color="primary" weight="bold" size="25" padding_left="16" padding_right="16" />
|
||||
</div>
|
||||
|
||||
<!-- A simple separator -->
|
||||
<rectangle width="100%" height="2" color="~color_accent" />
|
||||
<rectangle width="100%" height="2" color="outline" />
|
||||
|
||||
<div>
|
||||
<!-- 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>
|
||||
</rectangle>
|
||||
</div>
|
||||
</elements>
|
||||
</layout>
|
||||
</layout>
|
||||
|
|
|
|||
|
|
@ -3,25 +3,20 @@
|
|||
<var key="clock0_size" value="46" />
|
||||
<var key="clock0_date_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_tz_size" value="14" />
|
||||
</theme>
|
||||
|
||||
<macro name="decorative_rect"
|
||||
padding="8" color="~color_bg"
|
||||
border="2" border_color="~color_accent" round="8" />
|
||||
padding="8" color="background"
|
||||
border="2" border_color="outline" round="8" />
|
||||
|
||||
<macro name="button_style"
|
||||
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" />
|
||||
<macro name="button_style" padding="8" border="2" round="8" align_items="center" justify_content="center" />
|
||||
|
||||
<template name="Device">
|
||||
<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" />
|
||||
<label _source="battery" _device="${idx}" size="24" weight="bold" />
|
||||
<sprite width="32" height="32" src_builtin="${src}" color="on_background" />
|
||||
<label _source="battery" _device="${idx}" size="24" weight="bold" _low_color="danger" _normal_color="primary" _charging_color="tertiary" />
|
||||
</rectangle>
|
||||
</template>
|
||||
|
||||
|
|
@ -40,36 +35,36 @@
|
|||
|
||||
<template name="Screen">
|
||||
<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">
|
||||
<label text="${display}" size="18" color="~color_faded_20" weight="bold" />
|
||||
<label text="${display}" size="18" weight="bold" parent_color="background" />
|
||||
</div>
|
||||
</Button>
|
||||
</template>
|
||||
<template name="Panel">
|
||||
<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>
|
||||
</template>
|
||||
<template name="Mirror">
|
||||
<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">
|
||||
<label text="${display}" size="20" color="~color_faded_20" weight="bold" />
|
||||
<label text="${display}" size="20" weight="bold" parent_color="background" />
|
||||
</div>
|
||||
</Button>
|
||||
</template>
|
||||
<template name="App">
|
||||
<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>
|
||||
</template>
|
||||
|
||||
<template name="Set">
|
||||
<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">
|
||||
<label text="${display}" size="24" color="#00050F" weight="bold" />
|
||||
<label text="${display}" size="24" weight="bold" parent_color="background" />
|
||||
</div>
|
||||
</Button>
|
||||
</template>
|
||||
|
|
@ -77,28 +72,28 @@
|
|||
<template name="Clock">
|
||||
<div gap="12" flex_direction="column" padding="4">
|
||||
<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">
|
||||
<label text="Tue" _source="clock" _display="dow_short" color="~color_text" 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="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="on_background_variant" size="~clock0_date_size" weight="bold" />
|
||||
</div>
|
||||
</div>
|
||||
<div flex_direction="row" gap="8" justify_content="space_around">
|
||||
<!-- Timezone names here are only placeholders. Set your timezones via ~/.config/wayvr/conf.d -->
|
||||
<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="23:59" _source="clock" _display="time" _timezone="0" color="~clock_alt1_color" size="~clock_alt_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="secondary" size="~clock_alt_size" weight="bold" align="center" />
|
||||
</div>
|
||||
<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="23:59" _source="clock" _display="time" _timezone="1" color="~clock_alt2_color" size="~clock_alt_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="tertiary" size="~clock_alt_size" weight="bold" align="center" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template name="VerticalSeparator">
|
||||
<rectangle width="2" height="100%" color="~color_accent" />
|
||||
<rectangle width="2" height="100%" color="outline" />
|
||||
</template>
|
||||
|
||||
<elements>
|
||||
|
|
@ -128,18 +123,18 @@
|
|||
<div flex_direction="column" gap="8">
|
||||
<div gap="8">
|
||||
<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 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>
|
||||
</div>
|
||||
<div gap="8">
|
||||
<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 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>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -149,7 +144,7 @@
|
|||
<div flex_direction="row" gap="8">
|
||||
<div gap="4">
|
||||
<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>
|
||||
</div>
|
||||
<VerticalSeparator />
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
<layout>
|
||||
<macro name="button_style" border="2" border_color="~color_accent_translucent" color="~color_bg" round="6"
|
||||
align_items="center" justify_content="center" padding="6" width="60" height="60" overflow="visible"/>
|
||||
<macro name="button_style" border="2" round="6"
|
||||
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">
|
||||
<rectangle width="2" height="100%" color="~color_accent" />
|
||||
<rectangle width="2" height="100%" color="outline" />
|
||||
</template>
|
||||
|
||||
<elements>
|
||||
|
|
@ -13,27 +13,28 @@
|
|||
<rectangle macro="bg_rect" padding="10" align_items="center" justify_content="space_between">
|
||||
<div gap="10">
|
||||
<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>
|
||||
<VerticalSeparator />
|
||||
<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 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>
|
||||
<VerticalSeparator />
|
||||
<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>
|
||||
<VerticalSeparator />
|
||||
<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>
|
||||
</div>
|
||||
<div gap="10">
|
||||
<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" />
|
||||
<Button macro="button_style" _press="::WhisperUnloadAndClose" tooltip="WHISPER.UNLOAD_AND_CLOSE" tooltip_side="left"
|
||||
hover_color="danger" hover_border_color="danger(rgb-mult-0.5)">
|
||||
<sprite width="38" height="38" src_builtin="edit/close.svg" />
|
||||
</Button>
|
||||
</div>
|
||||
</rectangle>
|
||||
|
|
|
|||
|
|
@ -109,11 +109,12 @@
|
|||
"NO_SET_SELECTED": "Kein Set ausgewählt.",
|
||||
"LAST_EXISTING_SET": "Dies ist das letzte vorhandene Set.",
|
||||
"EMPTY_SET": "Leeres Set!",
|
||||
"LETS_ADD_OVERLAYS": "Lass uns ein paar Overlays von der Uhr hinzufügen!",
|
||||
"ONE_CONTROLLER_ON_FLOOR": "Lege einen Controller auf den Boden!",
|
||||
"CANNOT_ADD_SET": "Satz kann nicht hinzugefügt werden!",
|
||||
"MAXIMUM_SETS_REACHED": "Maximale Anzahl an Sets erreicht.",
|
||||
"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",
|
||||
"WHISPER": {
|
||||
|
|
@ -127,5 +128,12 @@
|
|||
"INITIAL_TEXT": "Halten Sie die Mikrofontaste gedrückt, um mit der Transkription zu beginnen...",
|
||||
"INIT_ERROR": "Fehler beim Starten der Whisper-Engine",
|
||||
"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."
|
||||
}
|
||||
}
|
||||
|
|
@ -89,7 +89,15 @@
|
|||
"GRABBING_WATCH": "To swap hands, move the watch in front and grab it with the other hand.",
|
||||
"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": {
|
||||
"TITLE_SCREENCAST_FAIL": "Could not start screen cast source!",
|
||||
"CANNOT_ADD_SET": "Cannot add set!",
|
||||
"CANNOT_REMOVE_SET": "Cannot remove set!",
|
||||
"DEFAULT_TITLE": "Notification",
|
||||
|
|
@ -97,7 +105,7 @@
|
|||
"ERROR": "Error",
|
||||
"FIXING_FLOOR_IN_X_SECS": "Fixing floor in {SECONDS} second(s)...",
|
||||
"LAST_EXISTING_SET": "This is the last existing set.",
|
||||
"LETS_ADD_OVERLAYS": "Let's add some overlays from the watch!",
|
||||
"LETS_ADD_OVERLAYS": "Let's add some overlays from the taskbar!",
|
||||
"MAXIMUM_SETS_REACHED": "Maximum number of sets reached.",
|
||||
"NO_SET_SELECTED": "No set is selected.",
|
||||
"ONE_CONTROLLER_ON_FLOOR": "Place one controller on the floor!"
|
||||
|
|
|
|||
|
|
@ -109,11 +109,12 @@
|
|||
"NO_SET_SELECTED": "No se ha seleccionado ningún conjunto.",
|
||||
"LAST_EXISTING_SET": "Este es el último conjunto existente.",
|
||||
"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!",
|
||||
"CANNOT_ADD_SET": "¡No se puede agregar el conjunto!",
|
||||
"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",
|
||||
"WHISPER": {
|
||||
|
|
@ -127,5 +128,12 @@
|
|||
"INITIAL_TEXT": "Mantén presionado el botón del micrófono para comenzar a transcribir...",
|
||||
"INIT_ERROR": "Error al iniciar el motor de Whisper",
|
||||
"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."
|
||||
}
|
||||
}
|
||||
|
|
@ -97,9 +97,10 @@
|
|||
"NO_SET_SELECTED": "Nessun set è selezionato.",
|
||||
"LAST_EXISTING_SET": "Questo è l'ultimo set esistente.",
|
||||
"EMPTY_SET": "Set vuoto!",
|
||||
"LETS_ADD_OVERLAYS": "Aggiungiamo degli overlay dal Watch!",
|
||||
"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": {
|
||||
"ADD_NEW_SET": "Aggiungi un nuovo set",
|
||||
|
|
@ -125,5 +126,12 @@
|
|||
"INITIAL_TEXT": "Tieni premuto il tasto del microfono per iniziare la trascrizione...",
|
||||
"INIT_ERROR": "Errore durante l'avvio del motore Whisper",
|
||||
"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."
|
||||
}
|
||||
}
|
||||
|
|
@ -107,11 +107,12 @@
|
|||
"NO_SET_SELECTED": "セットが選択されていません。",
|
||||
"LAST_EXISTING_SET": "これが最後の設定です。",
|
||||
"EMPTY_SET": "空のセットです!",
|
||||
"LETS_ADD_OVERLAYS": "ウォッチからオーバーレイを追加しましょう!",
|
||||
"ONE_CONTROLLER_ON_FLOOR": "コントローラーを床に置いてください!",
|
||||
"CANNOT_ADD_SET": "セットを追加できません!",
|
||||
"MAXIMUM_SETS_REACHED": "最大セット数に達しました。",
|
||||
"FIXING_FLOOR_IN_X_SECS": "{SECONDS}秒で床を固定中..."
|
||||
"FIXING_FLOOR_IN_X_SECS": "{SECONDS}秒で床を固定中...",
|
||||
"TITLE_SCREENCAST_FAIL": "スクリーンキャストのソースを開始できませんでした!",
|
||||
"LETS_ADD_OVERLAYS": "タスクバーからオーバーレイを追加しましょう!"
|
||||
},
|
||||
"DONE": "完了",
|
||||
"WHISPER": {
|
||||
|
|
@ -125,5 +126,12 @@
|
|||
"INITIAL_TEXT": "マイクボタンを押し続けて文字起こしを開始してください...",
|
||||
"INIT_ERROR": "Whisperエンジンの起動中にエラーが発生しました",
|
||||
"ENTER_KEY": "Enterキー"
|
||||
},
|
||||
"SCREENCAST": {
|
||||
"PENDING": "少々お待ちください...",
|
||||
"QUEUED": "別のオーバーレイのスクリーンキャスト許可ポップアップの応答を待っています。",
|
||||
"WAITING_FOR_USER_DESC": "デスクトップのスクリーンキャストのポップアップを確認してください。\n以下を選択してください:",
|
||||
"WAITING_FOR_USER": "デスクトップにスクリーンキャストのポップアップが表示されていないか確認してください。",
|
||||
"FAIL": "スクリーンキャストのソースを初期化できませんでした。"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -107,11 +107,12 @@
|
|||
"NO_SET_SELECTED": "Nie wybrano żadnego zestawu.",
|
||||
"LAST_EXISTING_SET": "To jest ostatni istniejący zestaw.",
|
||||
"EMPTY_SET": "Pusty zestaw!",
|
||||
"LETS_ADD_OVERLAYS": "Dodajmy kilka nakładek z zegarka!",
|
||||
"ONE_CONTROLLER_ON_FLOOR": "Umieść jeden kontroler na podłodze!",
|
||||
"CANNOT_ADD_SET": "Nie można dodać zestawu!",
|
||||
"MAXIMUM_SETS_REACHED": "Osiągnięto maksymalną liczbę zestawów.",
|
||||
"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",
|
||||
"WHISPER": {
|
||||
|
|
@ -125,5 +126,12 @@
|
|||
"INITIAL_TEXT": "Naciśnij i przytrzymaj przycisk mikrofonu, aby rozpocząć transkrypcję...",
|
||||
"INIT_ERROR": "Błąd podczas uruchamiania silnika Whisper",
|
||||
"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."
|
||||
}
|
||||
}
|
||||
|
|
@ -97,9 +97,10 @@
|
|||
"NO_SET_SELECTED": "未选择集合。",
|
||||
"LAST_EXISTING_SET": "这是最后一个现有的集合。",
|
||||
"EMPTY_SET": "空集合!",
|
||||
"LETS_ADD_OVERLAYS": "让我们从手表添加一些覆盖层吧!",
|
||||
"ONE_CONTROLLER_ON_FLOOR": "请将一个控制器放在地面上!",
|
||||
"FIXING_FLOOR_IN_X_SECS": "{SECONDS}秒内固定地面..."
|
||||
"FIXING_FLOOR_IN_X_SECS": "{SECONDS}秒内固定地面...",
|
||||
"TITLE_SCREENCAST_FAIL": "无法启动屏幕投影源!",
|
||||
"LETS_ADD_OVERLAYS": "让我们从任务栏添加一些覆盖层吧!"
|
||||
},
|
||||
"WATCH": {
|
||||
"ADD_NEW_SET": "添加新集合",
|
||||
|
|
@ -125,5 +126,12 @@
|
|||
"INITIAL_TEXT": "按住麦克风按钮开始转录...",
|
||||
"INIT_ERROR": "启动 Whisper 引擎时出错",
|
||||
"ENTER_KEY": "回车键"
|
||||
},
|
||||
"SCREENCAST": {
|
||||
"PENDING": "请稍候...",
|
||||
"QUEUED": "等待另一个覆盖层的屏幕投射确认弹窗。",
|
||||
"WAITING_FOR_USER_DESC": "请检查桌面上的屏幕投射弹出窗口。\n选择以下选项:",
|
||||
"WAITING_FOR_USER": "请检查桌面上的屏幕投射弹出窗口。",
|
||||
"FAIL": "无法初始化屏幕镜像源。"
|
||||
}
|
||||
}
|
||||
|
|
@ -888,7 +888,12 @@ where
|
|||
app.anchor.translation =
|
||||
pointer.pose.transform_point3a(grab_data.offset.translation);
|
||||
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 {
|
||||
// single grab resize
|
||||
|
|
@ -912,7 +917,12 @@ where
|
|||
let scale = scalar_scale(&overlay_state.transform);
|
||||
overlay_state.transform.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.dirty = true;
|
||||
|
|
|
|||
|
|
@ -112,7 +112,9 @@ pub fn openvr_run(args: &Args) -> Result<(), BackendError> {
|
|||
|
||||
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)?;
|
||||
|
||||
|
|
|
|||
|
|
@ -4,10 +4,12 @@ use ovr_overlay::{
|
|||
compositor::CompositorManager,
|
||||
sys::{EChaperoneConfigFile, ETrackingUniverseOrigin, HmdMatrix34_t},
|
||||
};
|
||||
use wgui::log::LogErr;
|
||||
use wlx_common::config::GeneralConfig;
|
||||
|
||||
use crate::{
|
||||
backend::{input::InputState, playspace_common, task::PlayspaceTask},
|
||||
state::AppState,
|
||||
state::{AppState, PlayspaceState, load_playspace_state, save_playspace_state},
|
||||
windowing::manager::OverlayWindowManager,
|
||||
};
|
||||
|
||||
|
|
@ -23,14 +25,18 @@ pub(super) struct PlayspaceMover {
|
|||
universe: ETrackingUniverseOrigin,
|
||||
drag: Option<MoverData<Vec3A>>,
|
||||
rotate: Option<MoverData<Quat>>,
|
||||
floor_y: Option<f32>,
|
||||
playspace_state: PlayspaceState,
|
||||
}
|
||||
|
||||
impl PlayspaceMover {
|
||||
pub const fn new() -> Self {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
universe: ETrackingUniverseOrigin::TrackingUniverseRawAndUncalibrated,
|
||||
drag: None,
|
||||
rotate: None,
|
||||
floor_y: None,
|
||||
playspace_state: load_playspace_state().unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -50,6 +56,9 @@ impl PlayspaceMover {
|
|||
PlayspaceTask::Recenter => {
|
||||
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) {
|
||||
let mut height = 1.6;
|
||||
if let Some(mat) = get_working_copy(&self.universe, chaperone_mgr) {
|
||||
height = input.hmd.translation.y - mat.translation.y;
|
||||
if self.universe == ETrackingUniverseOrigin::TrackingUniverseStanding {
|
||||
apply_chaperone_transform(mat, chaperone_mgr);
|
||||
}
|
||||
}
|
||||
let height = match self.floor_y {
|
||||
Some(y) => y,
|
||||
None => input.hmd.translation.y - 1.7,
|
||||
};
|
||||
|
||||
let xform = if self.universe == ETrackingUniverseOrigin::TrackingUniverseSeated {
|
||||
Affine3A::from_translation(Vec3::NEG_Y * height)
|
||||
|
|
@ -213,6 +219,7 @@ impl PlayspaceMover {
|
|||
};
|
||||
let offset = y1.min(y2) - 0.03;
|
||||
mat.translation.y += offset;
|
||||
self.floor_y = Some(mat.translation.y);
|
||||
|
||||
set_working_copy(&self.universe, chaperone_mgr, &mat);
|
||||
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) {
|
||||
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() {
|
||||
log::info!("Space drag interrupted by recenter");
|
||||
self.drag = None;
|
||||
|
|
@ -247,6 +243,45 @@ impl PlayspaceMover {
|
|||
log::info!("Space rotate interrupted by recenter");
|
||||
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(
|
||||
|
|
@ -277,6 +312,26 @@ impl PlayspaceMover {
|
|||
pub fn get_universe(&self) -> ETrackingUniverseOrigin {
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -87,7 +87,13 @@ impl OverlayWindowData<OpenXrOverlayData> {
|
|||
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 (scale_x, scale_y) = if aspect_ratio < 1.0 {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use glam::{Affine3A, Quat, Vec3A, vec3a};
|
||||
use glam::{Affine3A, Quat, Vec3, Vec3A, vec3a};
|
||||
use libmonado::{MndResult, Monado, Pose, ReferenceSpaceType};
|
||||
use wgui::log::LogErr;
|
||||
|
||||
|
|
@ -8,7 +8,7 @@ use crate::{
|
|||
playspace_common::{self, SpaceGravity, SpaceGravityUpdateParams},
|
||||
task::PlayspaceTask,
|
||||
},
|
||||
state::AppState,
|
||||
state::{AppState, PlayspaceState, load_playspace_state},
|
||||
windowing::manager::OverlayWindowManager,
|
||||
};
|
||||
|
||||
|
|
@ -25,6 +25,7 @@ pub(super) struct PlayspaceMover {
|
|||
rotate: Option<MoverData<Quat>>,
|
||||
gravity: SpaceGravity,
|
||||
floor_y: f32,
|
||||
playspace_state: PlayspaceState,
|
||||
}
|
||||
|
||||
impl PlayspaceMover {
|
||||
|
|
@ -42,6 +43,7 @@ impl PlayspaceMover {
|
|||
rotate: None,
|
||||
gravity: SpaceGravity::new(),
|
||||
floor_y: pose.position.y,
|
||||
playspace_state: load_playspace_state().unwrap_or_default(),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -60,6 +62,7 @@ impl PlayspaceMover {
|
|||
PlayspaceTask::Recenter => {
|
||||
self.recenter(&app.input_state, &mut monado.ipc);
|
||||
}
|
||||
PlayspaceTask::SaveCenter => { /* OpenVR only */ }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -244,8 +247,34 @@ impl PlayspaceMover {
|
|||
return;
|
||||
};
|
||||
|
||||
pose.position.x += input.hmd.translation.x;
|
||||
pose.position.z += input.hmd.translation.z;
|
||||
let cur_rot: Quat = pose.orientation.into();
|
||||
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
|
||||
.set_reference_space_offset(ReferenceSpaceType::Stage, pose)
|
||||
|
|
@ -299,6 +328,28 @@ impl PlayspaceMover {
|
|||
.set_reference_space_offset(ReferenceSpaceType::Stage, pose)
|
||||
.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) {
|
||||
|
|
|
|||
|
|
@ -63,6 +63,8 @@ pub enum PlayspaceTask {
|
|||
Recenter,
|
||||
Reset,
|
||||
FixFloor,
|
||||
#[cfg(feature = "openvr")]
|
||||
SaveCenter,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ impl WayVRCompositor {
|
|||
) -> anyhow::Result<Self> {
|
||||
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())
|
||||
.env("WAYLAND_DISPLAY", wayland_env.wayland_display_num_string())
|
||||
.spawn()
|
||||
|
|
@ -392,3 +392,11 @@ fn accumulate_discrete_scroll(acc: &mut f32, delta_v120: i32) -> i32 {
|
|||
|
||||
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
Loading…
Reference in New Issue