From b517afc3798645ffbbc0f6dd2dc276021b4f267e Mon Sep 17 00:00:00 2001 From: Kresny Date: Wed, 25 Feb 2026 06:58:30 +0100 Subject: [PATCH 01/16] Rebind toggle_dashboard and show_hide (#449) --- wayvr/src/backend/openxr/openxr_actions.json5 | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/wayvr/src/backend/openxr/openxr_actions.json5 b/wayvr/src/backend/openxr/openxr_actions.json5 index 30574e0a..53467d7c 100644 --- a/wayvr/src/backend/openxr/openxr_actions.json5 +++ b/wayvr/src/backend/openxr/openxr_actions.json5 @@ -214,8 +214,11 @@ left: "/user/hand/left/input/trackpad/x", right: "/user/hand/right/input/trackpad/x" }, + toggle_dashboard: { + right: "/user/hand/right/input/system/click", + }, show_hide: { - left: "/user/hand/left/input/menu/click", + left: "/user/hand/left/input/system/click", }, space_drag: { right: "/user/hand/right/input/menu/click", From 39152b8548b4949d9ac3dc11e9f03946caaf8d27 Mon Sep 17 00:00:00 2001 From: Torge Matthies Date: Wed, 25 Feb 2026 20:51:12 +0100 Subject: [PATCH 02/16] add option for grid opacity --- dash-frontend/assets/lang/en.json | 2 ++ dash-frontend/src/tab/settings.rs | 5 +++++ wayvr/src/backend/openxr/skybox.rs | 6 +++++- wayvr/src/config.rs | 2 ++ wayvr/src/res/config.yaml | 3 +++ wayvr/src/shaders/grid.frag | 6 +++++- wlx-common/src/config.rs | 3 +++ 7 files changed, 25 insertions(+), 2 deletions(-) diff --git a/dash-frontend/assets/lang/en.json b/dash-frontend/assets/lang/en.json index b2471032..109723cc 100644 --- a/dash-frontend/assets/lang/en.json +++ b/dash-frontend/assets/lang/en.json @@ -107,6 +107,8 @@ "USE_PASSTHROUGH_HELP": "Allow passthrough if the XR runtime supports it", "USE_SKYBOX": "Enable skybox", "USE_SKYBOX_HELP": "Show a skybox if there's no scene app or passthrough", + "GRID_OPACITY": "Skybox grid opacity", + "GRID_OPACITY_HELP": "Opacity of the floor grid when the skybox is enabled", "XR_CLICK_SENSITIVITY": "XR click sensitivity", "XR_CLICK_SENSITIVITY_HELP": "Analog trigger sensitivity", "XR_CLICK_SENSITIVITY_RELEASE": "XR release sensitivity", diff --git a/dash-frontend/src/tab/settings.rs b/dash-frontend/src/tab/settings.rs index cdb8e97d..67f00070 100644 --- a/dash-frontend/src/tab/settings.rs +++ b/dash-frontend/src/tab/settings.rs @@ -247,6 +247,7 @@ enum SettingType { UprightScreenFix, UsePassthrough, UseSkybox, + GridOpacity, XrClickSensitivity, XrClickSensitivityRelease, XwaylandByDefault, @@ -294,6 +295,7 @@ impl SettingType { Self::XrClickSensitivityRelease => &mut config.xr_click_sensitivity_release, Self::SpaceDragMultiplier => &mut config.space_drag_multiplier, Self::PointerLerpFactor => &mut config.pointer_lerp_factor, + Self::GridOpacity => &mut config.grid_opacity, _ => panic!("Requested f32 for non-f32 SettingType"), } } @@ -368,6 +370,7 @@ impl SettingType { Self::Clock12h => Ok("APP_SETTINGS.CLOCK_12H"), Self::DoubleCursorFix => Ok("APP_SETTINGS.DOUBLE_CURSOR_FIX"), Self::FocusFollowsMouseMode => Ok("APP_SETTINGS.FOCUS_FOLLOWS_MOUSE_MODE"), + Self::GridOpacity => Ok("APP_SETTINGS.GRID_OPACITY"), Self::HandsfreePointer => Ok("APP_SETTINGS.HANDSFREE_POINTER"), Self::HideGrabHelp => Ok("APP_SETTINGS.HIDE_GRAB_HELP"), Self::HideUsername => Ok("APP_SETTINGS.HIDE_USERNAME"), @@ -408,6 +411,7 @@ impl SettingType { Self::BlockPosesOnKbdInteraction => Some("APP_SETTINGS.BLOCK_POSES_ON_KBD_INTERACTION_HELP"), Self::CaptureMethod => Some("APP_SETTINGS.CAPTURE_METHOD_HELP"), Self::DoubleCursorFix => Some("APP_SETTINGS.DOUBLE_CURSOR_FIX_HELP"), + Self::GridOpacity => Some("APP_SETTINGS.GRID_OPACITY_HELP"), Self::HandsfreePointer => Some("APP_SETTINGS.HANDSFREE_POINTER_HELP"), Self::KeyboardMiddleClick => Some("APP_SETTINGS.KEYBOARD_MIDDLE_CLICK_HELP"), Self::LeftHandedMouse => Some("APP_SETTINGS.LEFT_HANDED_MOUSE_HELP"), @@ -795,6 +799,7 @@ impl TabSettings { slider_f32!(mp, c, SettingType::UiRoundMultiplier, 0.5, 5.0, 0.1); checkbox!(mp, c, SettingType::SetsOnWatch); checkbox!(mp, c, SettingType::UseSkybox); + slider_f32!(mp, c, SettingType::GridOpacity, 0.0, 1.0, 0.05); // min, max, step checkbox!(mp, c, SettingType::UsePassthrough); checkbox!(mp, c, SettingType::Clock12h); } diff --git a/wayvr/src/backend/openxr/skybox.rs b/wayvr/src/backend/openxr/skybox.rs index 804a2ede..b7f24131 100644 --- a/wayvr/src/backend/openxr/skybox.rs +++ b/wayvr/src/backend/openxr/skybox.rs @@ -161,13 +161,17 @@ impl Skybox { .into_iter() .next() .unwrap(); + let set0 = pipeline.uniform_buffer_upload( + 0, + vec![app.session.config.grid_opacity * app.session.config.grid_opacity], + )?; let pass = pipeline.create_pass( tgt.extent_f32(), [0.0, 0.0], app.gfx_extras.quad_verts.clone(), 0..4, 0..1, - vec![], + vec![set0], &Default::default(), )?; diff --git a/wayvr/src/config.rs b/wayvr/src/config.rs index 2a797e1c..94b4d2e2 100644 --- a/wayvr/src/config.rs +++ b/wayvr/src/config.rs @@ -130,6 +130,7 @@ pub struct AutoSettings { pub block_poses_on_kbd_interaction: bool, pub space_drag_multiplier: f32, pub use_skybox: bool, + pub grid_opacity: f32, pub use_passthrough: bool, pub screen_render_down: bool, pub pointer_lerp_factor: f32, @@ -180,6 +181,7 @@ pub fn save_settings(config: &GeneralConfig) -> anyhow::Result<()> { block_poses_on_kbd_interaction: config.block_poses_on_kbd_interaction, space_drag_multiplier: config.space_drag_multiplier, use_skybox: config.use_skybox, + grid_opacity: config.grid_opacity, use_passthrough: config.use_passthrough, screen_render_down: config.screen_render_down, pointer_lerp_factor: config.pointer_lerp_factor, diff --git a/wayvr/src/res/config.yaml b/wayvr/src/res/config.yaml index 76064afd..55747a09 100644 --- a/wayvr/src/res/config.yaml +++ b/wayvr/src/res/config.yaml @@ -125,6 +125,9 @@ ## If disabled, there will be no background rendered (solid black). #use_skybox: true +## Monado/WiVRn only. Opacity of the floor grid if use_skybox is enabled. +#grid_opacity: true + ## Hide the help popup that appears on your hand while grabbing an overlay #hide_grab_help: false diff --git a/wayvr/src/shaders/grid.frag b/wayvr/src/shaders/grid.frag index f2250645..9daa2fc0 100644 --- a/wayvr/src/shaders/grid.frag +++ b/wayvr/src/shaders/grid.frag @@ -4,6 +4,10 @@ precision highp float; layout (location = 0) in vec2 in_uv; layout (location = 0) out vec4 out_color; +layout (set = 0, binding = 0) uniform OpacityBlock { + uniform float opacity; +}; + void main() { float fade = max(1.0 - 2.0 * length(in_uv.xy + vec2(-0.5, -0.5)), 0.0); @@ -14,6 +18,6 @@ void main() } else { grid = 0.0; } - out_color = vec4(1.0, 1.0, 1.0, grid * fade); + out_color = vec4(1.0, 1.0, 1.0, grid * fade * opacity); } diff --git a/wlx-common/src/config.rs b/wlx-common/src/config.rs index 60864770..8ab23297 100644 --- a/wlx-common/src/config.rs +++ b/wlx-common/src/config.rs @@ -324,4 +324,7 @@ pub struct GeneralConfig { #[serde(default)] pub handsfree_pointer: HandsfreePointer, + + #[serde(default = "def_one")] + pub grid_opacity: f32, } From ab3f873a387820701c9662c8ed8a4aa68a076503 Mon Sep 17 00:00:00 2001 From: Torge Matthies Date: Wed, 25 Feb 2026 22:31:11 +0100 Subject: [PATCH 03/16] fix grid opacity not updating live --- wayvr/src/backend/openxr/helpers.rs | 12 ++++++++ wayvr/src/backend/openxr/overlay.rs | 16 +++------- wayvr/src/backend/openxr/skybox.rs | 48 +++++++++++++++++++++++------ wayvr/src/shaders/grid.frag | 7 +---- 4 files changed, 55 insertions(+), 28 deletions(-) diff --git a/wayvr/src/backend/openxr/helpers.rs b/wayvr/src/backend/openxr/helpers.rs index 722e25c8..6424fdf0 100644 --- a/wayvr/src/backend/openxr/helpers.rs +++ b/wayvr/src/backend/openxr/helpers.rs @@ -3,6 +3,18 @@ use glam::{Affine3A, Quat, Vec3, Vec3A}; use openxr::{self as xr, SessionCreateFlags, Version, sys::Handle}; use xr::OverlaySessionCreateFlagsEXTX; +macro_rules! next_chain_insert { + ($layer:expr, $payload:expr) => {{ + let payload_ptr = $payload as *mut _ as *mut xr::sys::BaseInStructure; + let new_elem = payload_ptr.as_mut().unwrap(); + let mut raw = $layer.into_raw(); + new_elem.next = raw.next as _; + raw.next = payload_ptr as *const _; + raw + }}; +} +pub(crate) use next_chain_insert; + pub(super) fn init_xr() -> Result<(xr::Instance, xr::SystemId), anyhow::Error> { let entry = xr::Entry::linked(); diff --git a/wayvr/src/backend/openxr/overlay.rs b/wayvr/src/backend/openxr/overlay.rs index ff02dad9..b4404e86 100644 --- a/wayvr/src/backend/openxr/overlay.rs +++ b/wayvr/src/backend/openxr/overlay.rs @@ -6,7 +6,10 @@ use xr::EyeVisibility; use super::{CompositionLayer, XrState, helpers, swapchain::WlxSwapchain}; use crate::{ - backend::openxr::swapchain::{SwapchainOpts, WlxSwapchainImage, create_swapchain}, + backend::openxr::{ + helpers::next_chain_insert, + swapchain::{SwapchainOpts, WlxSwapchainImage, create_swapchain}, + }, state::AppState, windowing::window::OverlayWindowData, }; @@ -20,17 +23,6 @@ pub struct OpenXrOverlayData { color_bias_khr: Option>, } -macro_rules! next_chain_insert { - ($layer:expr, $payload:expr) => {{ - let payload_ptr = $payload.as_mut() as *mut _ as *mut xr::sys::BaseInStructure; - let new_elem = payload_ptr.as_mut().unwrap(); - let mut raw = $layer.into_raw(); - new_elem.next = raw.next as _; - raw.next = payload_ptr as *const _; - raw - }}; -} - impl OverlayWindowData { pub(super) fn ensure_swapchain_acquire<'a>( &'a mut self, diff --git a/wayvr/src/backend/openxr/skybox.rs b/wayvr/src/backend/openxr/skybox.rs index b7f24131..21435cf1 100644 --- a/wayvr/src/backend/openxr/skybox.rs +++ b/wayvr/src/backend/openxr/skybox.rs @@ -14,7 +14,10 @@ use wgui::gfx::{cmd::WGfxClearMode, pipeline::WPipelineCreateInfo}; use wlx_common::config_io; use crate::{ - backend::openxr::{helpers::translation_rotation_to_posef, swapchain::SwapchainOpts}, + backend::openxr::{ + helpers::{next_chain_insert, translation_rotation_to_posef}, + swapchain::SwapchainOpts, + }, graphics::{ExtentExt, GpuFutures, dds::WlxCommandBufferDds}, state::AppState, }; @@ -28,10 +31,11 @@ pub(super) struct Skybox { view: Arc, sky: Option, grid: Option, + grid_color_scale_bias_khr: Option, } impl Skybox { - pub fn new(app: &AppState) -> anyhow::Result { + pub fn new(xr: &XrState, app: &AppState) -> anyhow::Result { let mut command_buffer = app .gfx .create_xfer_command_buffer(CommandBufferUsage::OneTimeSubmit)?; @@ -74,10 +78,27 @@ impl Skybox { let view = ImageView::new_default(maybe_image.unwrap())?; // safe unwrap + let grid_color_scale_bias_khr = xr + .instance + .exts() + .khr_composition_layer_color_scale_bias + .map(|_| xr::sys::CompositionLayerColorScaleBiasKHR { + ty: xr::StructureType::COMPOSITION_LAYER_COLOR_SCALE_BIAS_KHR, + next: std::ptr::null(), + color_bias: Default::default(), + color_scale: xr::Color4f { + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, + }, + }); + Ok(Self { view, sky: None, grid: None, + grid_color_scale_bias_khr: grid_color_scale_bias_khr, }) } @@ -161,17 +182,13 @@ impl Skybox { .into_iter() .next() .unwrap(); - let set0 = pipeline.uniform_buffer_upload( - 0, - vec![app.session.config.grid_opacity * app.session.config.grid_opacity], - )?; let pass = pipeline.create_pass( tgt.extent_f32(), [0.0, 0.0], app.gfx_extras.quad_verts.clone(), 0..4, 0..1, - vec![set0], + vec![], &Default::default(), )?; @@ -236,8 +253,10 @@ impl Skybox { .lower_vertical_angle(LO_VERT_ANGLE); self.grid.as_mut().unwrap().ensure_image_released()?; - let grid = xr::CompositionLayerQuad::new() - .layer_flags(xr::CompositionLayerFlags::BLEND_TEXTURE_SOURCE_ALPHA) + let flags = xr::CompositionLayerFlags::BLEND_TEXTURE_SOURCE_ALPHA + | xr::CompositionLayerFlags::UNPREMULTIPLIED_ALPHA; + let mut grid = xr::CompositionLayerQuad::new() + .layer_flags(flags) .pose(*GRID_POSE) .size(xr::Extent2Df { width: 10.0, @@ -247,6 +266,15 @@ impl Skybox { .eye_visibility(xr::EyeVisibility::BOTH) .space(&xr.stage); + if let Some(grid_color_scale_bias_khr) = self.grid_color_scale_bias_khr.as_mut() { + let grid_opacity = app.session.config.grid_opacity; + grid_color_scale_bias_khr.color_scale.a = grid_opacity * grid_opacity; + unsafe { + let raw = next_chain_insert!(grid, grid_color_scale_bias_khr); + grid = xr::CompositionLayerQuad::from_raw(raw); + } + } + Ok(vec![ CompositionLayer::Equirect2(sky), CompositionLayer::Quad(grid), @@ -258,5 +286,5 @@ pub(super) fn create_skybox(xr: &XrState, app: &AppState) -> Option { xr.instance .exts() .khr_composition_layer_equirect2 - .and_then(|_| Skybox::new(app).ok()) + .and_then(|_| Skybox::new(xr, app).ok()) } diff --git a/wayvr/src/shaders/grid.frag b/wayvr/src/shaders/grid.frag index 9daa2fc0..467aec41 100644 --- a/wayvr/src/shaders/grid.frag +++ b/wayvr/src/shaders/grid.frag @@ -4,10 +4,6 @@ precision highp float; layout (location = 0) in vec2 in_uv; layout (location = 0) out vec4 out_color; -layout (set = 0, binding = 0) uniform OpacityBlock { - uniform float opacity; -}; - void main() { float fade = max(1.0 - 2.0 * length(in_uv.xy + vec2(-0.5, -0.5)), 0.0); @@ -18,6 +14,5 @@ void main() } else { grid = 0.0; } - out_color = vec4(1.0, 1.0, 1.0, grid * fade * opacity); + out_color = vec4(1.0, 1.0, 1.0, grid * fade); } - From f21776b123e1af453197e3750f96f5f8fb846d73 Mon Sep 17 00:00:00 2001 From: galister <22305755+galister@users.noreply.github.com> Date: Sat, 28 Feb 2026 13:10:02 +0900 Subject: [PATCH 04/16] box CompositionLayerColorScaleBiasKHR --- wayvr/src/backend/openxr/skybox.rs | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/wayvr/src/backend/openxr/skybox.rs b/wayvr/src/backend/openxr/skybox.rs index 21435cf1..3dbc04b1 100644 --- a/wayvr/src/backend/openxr/skybox.rs +++ b/wayvr/src/backend/openxr/skybox.rs @@ -31,7 +31,7 @@ pub(super) struct Skybox { view: Arc, sky: Option, grid: Option, - grid_color_scale_bias_khr: Option, + grid_color_scale_bias_khr: Option>, } impl Skybox { @@ -82,16 +82,18 @@ impl Skybox { .instance .exts() .khr_composition_layer_color_scale_bias - .map(|_| xr::sys::CompositionLayerColorScaleBiasKHR { - ty: xr::StructureType::COMPOSITION_LAYER_COLOR_SCALE_BIAS_KHR, - next: std::ptr::null(), - color_bias: Default::default(), - color_scale: xr::Color4f { - r: 1.0, - g: 1.0, - b: 1.0, - a: 1.0, - }, + .map(|_| { + Box::new(xr::sys::CompositionLayerColorScaleBiasKHR { + ty: xr::StructureType::COMPOSITION_LAYER_COLOR_SCALE_BIAS_KHR, + next: std::ptr::null(), + color_bias: Default::default(), + color_scale: xr::Color4f { + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, + }, + }) }); Ok(Self { From ed8ca1e0680d78fbd1597edb282918c7b48e136b Mon Sep 17 00:00:00 2001 From: galister <22305755+galister@users.noreply.github.com> Date: Sat, 28 Feb 2026 13:33:13 +0900 Subject: [PATCH 05/16] fix copy-paste fail causing stuck images --- wayvr/src/backend/openxr/helpers.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wayvr/src/backend/openxr/helpers.rs b/wayvr/src/backend/openxr/helpers.rs index 6424fdf0..48cd2054 100644 --- a/wayvr/src/backend/openxr/helpers.rs +++ b/wayvr/src/backend/openxr/helpers.rs @@ -5,7 +5,7 @@ use xr::OverlaySessionCreateFlagsEXTX; macro_rules! next_chain_insert { ($layer:expr, $payload:expr) => {{ - let payload_ptr = $payload as *mut _ as *mut xr::sys::BaseInStructure; + let payload_ptr = $payload.as_mut() as *mut _ as *mut xr::sys::BaseInStructure; let new_elem = payload_ptr.as_mut().unwrap(); let mut raw = $layer.into_raw(); new_elem.next = raw.next as _; From 5ffa5e59bf8c93978ea3fab674bf2140b021bdf7 Mon Sep 17 00:00:00 2001 From: galister <22305755+galister@users.noreply.github.com> Date: Sat, 28 Feb 2026 13:33:54 +0900 Subject: [PATCH 06/16] skybox cleanups --- wayvr/src/backend/openxr/skybox.rs | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/wayvr/src/backend/openxr/skybox.rs b/wayvr/src/backend/openxr/skybox.rs index 3dbc04b1..de010956 100644 --- a/wayvr/src/backend/openxr/skybox.rs +++ b/wayvr/src/backend/openxr/skybox.rs @@ -1,8 +1,4 @@ -use std::{ - f32::consts::PI, - fs::File, - sync::{Arc, LazyLock}, -}; +use std::{f32::consts::PI, fs::File, sync::Arc}; use glam::{Quat, Vec3A}; use openxr as xr; @@ -31,6 +27,7 @@ pub(super) struct Skybox { view: Arc, sky: Option, grid: Option, + grid_pose: xr::Posef, grid_color_scale_bias_khr: Option>, } @@ -100,6 +97,7 @@ impl Skybox { view, sky: None, grid: None, + grid_pose: translation_rotation_to_posef(Vec3A::ZERO, Quat::from_rotation_x(PI * -0.5)), grid_color_scale_bias_khr: grid_color_scale_bias_khr, }) } @@ -228,10 +226,6 @@ impl Skybox { const HI_VERT_ANGLE: f32 = 0.5 * PI; const LO_VERT_ANGLE: f32 = -0.5 * PI; - static GRID_POSE: LazyLock = LazyLock::new(|| { - translation_rotation_to_posef(Vec3A::ZERO, Quat::from_rotation_x(PI * -0.5)) - }); - let pose = xr::Posef { orientation: xr::Quaternionf::IDENTITY, position: xr::Vector3f { @@ -255,11 +249,12 @@ impl Skybox { .lower_vertical_angle(LO_VERT_ANGLE); self.grid.as_mut().unwrap().ensure_image_released()?; + let flags = xr::CompositionLayerFlags::BLEND_TEXTURE_SOURCE_ALPHA | xr::CompositionLayerFlags::UNPREMULTIPLIED_ALPHA; let mut grid = xr::CompositionLayerQuad::new() .layer_flags(flags) - .pose(*GRID_POSE) + .pose(self.grid_pose) .size(xr::Extent2Df { width: 10.0, height: 10.0, @@ -270,7 +265,7 @@ impl Skybox { if let Some(grid_color_scale_bias_khr) = self.grid_color_scale_bias_khr.as_mut() { let grid_opacity = app.session.config.grid_opacity; - grid_color_scale_bias_khr.color_scale.a = grid_opacity * grid_opacity; + grid_color_scale_bias_khr.color_scale.a = (grid_opacity * grid_opacity).clamp(0.0, 1.0); unsafe { let raw = next_chain_insert!(grid, grid_color_scale_bias_khr); grid = xr::CompositionLayerQuad::from_raw(raw); From 2b4a0b6a0d00d1b4ecca49d426fc7587c5e90a52 Mon Sep 17 00:00:00 2001 From: Earthgames Date: Sat, 28 Feb 2026 23:23:42 +0100 Subject: [PATCH 07/16] Changed old link to the new one --- wayvr-ipc/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wayvr-ipc/README.md b/wayvr-ipc/README.md index 30d33c73..776c16bc 100644 --- a/wayvr-ipc/README.md +++ b/wayvr-ipc/README.md @@ -2,7 +2,7 @@ This repository contains the IPC specification and client implementation for the WayVR protocol. The primary purpose is to enable communication between applications and the WayVR server, offering a range of API functions. -[WayVR Server README](https://github.com/galister/wlx-overlay-s/tree/main/contrib/wayvr) +[WayVR Server README](https://github.com/wlx-team/wayvr/tree/main/wayvr/contrib/wayvr) ## Usage From e8c8a67fc01e4836a86406f373ee06f2932422a9 Mon Sep 17 00:00:00 2001 From: galister <22305755+galister@users.noreply.github.com> Date: Tue, 3 Mar 2026 02:52:51 +0900 Subject: [PATCH 08/16] add wiki links to readme --- README.md | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5629c1a0..9545c9e3 100644 --- a/README.md +++ b/README.md @@ -139,11 +139,23 @@ Typing ![Touch Controller Bindings](https://github.com/wlx-team/wayvr/blob/guide/wlx-oculus.png) -To customize bindings on OpenXR, refer to the [OpenXR Bindings wiki page](https://github.com/wlx-team/wayvr/wiki/OpenXR-Bindings). -If your bindings are not supported, please reach out. \ +### 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 [Wiki: OpenXR Bindings](https://github.com/wlx-team/wayvr/wiki/OpenXR-Bindings) + +If your controllers are not supported, please reach out. \ We would like to work with you and include additional bindings. +## Customization + +See these relevant wiki pages: +- For all available config options, check [Wiki: Configuration](https://github.com/wlx-team/wayvr/wiki/Configuration) +- Looking to customize look & feel, or add functionality? See [Wiki: Customization](https://github.com/wlx-team/wayvr/wiki/Customization) +- Looking to change the OpenXR background? See [Wiki: OpenXR Skybox](https://github.com/wlx-team/wayvr/wiki/OpenXR-Skybox) + ## Troubleshooting When an error is detected, we often print tips for fixing it into the log file. From a83af3d833ce05492f96168bb932d6e864833688 Mon Sep 17 00:00:00 2001 From: Aleksander Date: Thu, 5 Mar 2026 12:42:04 +0100 Subject: [PATCH 09/16] translations --- dash-frontend/assets/lang/de.json | 4 +++- dash-frontend/assets/lang/en.json | 2 +- dash-frontend/assets/lang/es.json | 4 +++- dash-frontend/assets/lang/it.json | 4 +++- dash-frontend/assets/lang/ja.json | 4 +++- dash-frontend/assets/lang/pl.json | 4 +++- dash-frontend/assets/lang/zh_CN.json | 4 +++- 7 files changed, 19 insertions(+), 7 deletions(-) diff --git a/dash-frontend/assets/lang/de.json b/dash-frontend/assets/lang/de.json index 149d849a..6310d031 100644 --- a/dash-frontend/assets/lang/de.json +++ b/dash-frontend/assets/lang/de.json @@ -90,7 +90,9 @@ "BLOCK_POSES_ON_KBD_INTERACTION": "Posen beim Interagieren mit der Tastatur blockieren", "BLOCK_POSES_ON_KBD_INTERACTION_HELP": "Verhindert, dass das Spiel Posen empfängt, wenn die Tastatur angefahren wird und „Spieleingabe blockieren“ aktiviert ist", "LANGUAGE": "Sprache", - "REQUIRES_RESTART": "Erfordert Neustart" + "REQUIRES_RESTART": "Erfordert Neustart", + "GRID_OPACITY": "Bodenraster-Undurchsichtigkeit", + "GRID_OPACITY_HELP": "Undurchsichtigkeit des Bodenrasters, wenn der Skybox aktiviert ist" }, "HELLO": "Hallo!", "AUDIO": { diff --git a/dash-frontend/assets/lang/en.json b/dash-frontend/assets/lang/en.json index 109723cc..c7b4f8ea 100644 --- a/dash-frontend/assets/lang/en.json +++ b/dash-frontend/assets/lang/en.json @@ -107,7 +107,7 @@ "USE_PASSTHROUGH_HELP": "Allow passthrough if the XR runtime supports it", "USE_SKYBOX": "Enable skybox", "USE_SKYBOX_HELP": "Show a skybox if there's no scene app or passthrough", - "GRID_OPACITY": "Skybox grid opacity", + "GRID_OPACITY": "Floor grid opacity", "GRID_OPACITY_HELP": "Opacity of the floor grid when the skybox is enabled", "XR_CLICK_SENSITIVITY": "XR click sensitivity", "XR_CLICK_SENSITIVITY_HELP": "Analog trigger sensitivity", diff --git a/dash-frontend/assets/lang/es.json b/dash-frontend/assets/lang/es.json index b8622030..a42b8b99 100644 --- a/dash-frontend/assets/lang/es.json +++ b/dash-frontend/assets/lang/es.json @@ -90,7 +90,9 @@ "BLOCK_POSES_ON_KBD_INTERACTION": "Bloquear poses al interactuar con el teclado", "BLOCK_POSES_ON_KBD_INTERACTION_HELP": "Bloquea que el juego reciba poses cuando el teclado está sobre él y 'Bloquear entrada del juego' está habilitado", "LANGUAGE": "Idioma", - "REQUIRES_RESTART": "Requiere reinicio" + "REQUIRES_RESTART": "Requiere reinicio", + "GRID_OPACITY": "Opacidad de la cuadrícula del suelo", + "GRID_OPACITY_HELP": "Opacidad de la cuadrícula del suelo cuando el skybox está habilitado" }, "HELLO": "¡Hola!", "AUDIO": { diff --git a/dash-frontend/assets/lang/it.json b/dash-frontend/assets/lang/it.json index 51ab5c4e..7e154553 100644 --- a/dash-frontend/assets/lang/it.json +++ b/dash-frontend/assets/lang/it.json @@ -111,7 +111,9 @@ "BLOCK_POSES_ON_KBD_INTERACTION": "Blocca le pose durante l'interazione con la tastiera", "BLOCK_POSES_ON_KBD_INTERACTION_HELP": "Impedisce al gioco di ricevere pose quando la tastiera è evidenziata e 'Blocca input di gioco' è abilitato", "LANGUAGE": "Lingua", - "REQUIRES_RESTART": "Richiede riavvio" + "REQUIRES_RESTART": "Richiede riavvio", + "GRID_OPACITY": "Opacità della griglia del pavimento", + "GRID_OPACITY_HELP": "Opacità della griglia del pavimento quando lo skybox è abilitato" }, "APPLICATION_LAUNCHER": "Lanciatore applicazioni", "APPLICATION_STARTED": "Applicazione avviata", diff --git a/dash-frontend/assets/lang/ja.json b/dash-frontend/assets/lang/ja.json index 15322fce..4569f6a6 100644 --- a/dash-frontend/assets/lang/ja.json +++ b/dash-frontend/assets/lang/ja.json @@ -90,7 +90,9 @@ "BLOCK_POSES_ON_KBD_INTERACTION": "キーボード操作時のポーズをブロック", "BLOCK_POSES_ON_KBD_INTERACTION_HELP": "キーボードがホバーされ、「ゲーム入力をブロック」が有効になっている場合、ゲームがポーズを受信することをブロックします", "LANGUAGE": "言語", - "REQUIRES_RESTART": "再起動が必要です" + "REQUIRES_RESTART": "再起動が必要です", + "GRID_OPACITY": "フロアグリッドの不透明度", + "GRID_OPACITY_HELP": "スカイボックスが有効なときの床グリッドの不透明度" }, "HELLO": "こんにちは!", "AUDIO": { diff --git a/dash-frontend/assets/lang/pl.json b/dash-frontend/assets/lang/pl.json index 9e86daaf..250c387f 100644 --- a/dash-frontend/assets/lang/pl.json +++ b/dash-frontend/assets/lang/pl.json @@ -85,7 +85,9 @@ "BLOCK_POSES_ON_KBD_INTERACTION_HELP": "Blokuje odbieranie póz przez grę, gdy kursor myszy znajduje się nad klawiaturą i włączona jest opcja 'Blokuj dane wejściowe z gry'", "LANGUAGE": "Język", "REQUIRES_RESTART": "Wymaga restartu", - "MISC": "Różne" + "MISC": "Różne", + "GRID_OPACITY": "Przezroczystość siatki podłogowej", + "GRID_OPACITY_HELP": "Przezroczystość siatki podłogowej, gdy włączony jest skybox" }, "APPLICATION_LAUNCHER": "Uruchamiacz aplikacji", "APPLICATIONS": "Aplikacje", diff --git a/dash-frontend/assets/lang/zh_CN.json b/dash-frontend/assets/lang/zh_CN.json index 81f6b04e..bde60a4e 100644 --- a/dash-frontend/assets/lang/zh_CN.json +++ b/dash-frontend/assets/lang/zh_CN.json @@ -111,7 +111,9 @@ "BLOCK_POSES_ON_KBD_INTERACTION": "与键盘交互时阻止姿势", "BLOCK_POSES_ON_KBD_INTERACTION_HELP": "当键盘悬停且启用“阻止游戏输入”时,阻止游戏接收姿势", "LANGUAGE": "语言", - "REQUIRES_RESTART": "需要重启" + "REQUIRES_RESTART": "需要重启", + "GRID_OPACITY": "地面网格不透明度", + "GRID_OPACITY_HELP": "启用天空盒时地板网格的不透明度" }, "APPLICATION_LAUNCHER": "应用启动器", "APPLICATION_STARTED": "应用已启动", From cb899235b43b1600c1bf0f37528e4ff771951057 Mon Sep 17 00:00:00 2001 From: Aleksander Date: Thu, 5 Mar 2026 19:28:42 +0100 Subject: [PATCH 10/16] wgui: separate globals and themes --- dash-frontend/src/frontend.rs | 29 +++------- dash-frontend/src/tab/settings.rs | 23 ++++---- dash-frontend/src/util/toast_manager.rs | 44 ++++++---------- dash-frontend/src/util/wgui_simple.rs | 2 +- dash-frontend/src/views/audio_settings.rs | 19 +++---- dash-frontend/src/views/game_cover.rs | 12 ++--- dash-frontend/src/views/game_list.rs | 19 +++---- uidev/src/testbed/testbed_any.rs | 3 +- uidev/src/testbed/testbed_dashboard.rs | 18 +++---- uidev/src/testbed/testbed_generic.rs | 5 +- wayvr/src/gui/panel/mod.rs | 3 +- wayvr/src/overlays/dashboard.rs | 14 +++-- wayvr/src/overlays/edit/lock.rs | 18 ++++--- wayvr/src/overlays/edit/mod.rs | 4 +- wayvr/src/overlays/keyboard/builder.rs | 6 +-- wayvr/src/state.rs | 25 +++++---- wgui/src/components/button.rs | 64 +++++++++++------------ wgui/src/components/checkbox.rs | 27 ++++------ wgui/src/components/editbox.rs | 43 +++++++-------- wgui/src/components/slider.rs | 32 +++++------- wgui/src/components/tabs.rs | 4 +- wgui/src/components/tooltip.rs | 27 ++++------ wgui/src/event.rs | 6 +-- wgui/src/globals.rs | 46 +--------------- wgui/src/layout.rs | 6 ++- wgui/src/lib.rs | 1 + wgui/src/parser/component_button.rs | 2 +- wgui/src/parser/mod.rs | 14 ++--- wgui/src/parser/widget_image.rs | 2 +- wgui/src/parser/widget_label.rs | 7 +-- wgui/src/parser/widget_rectangle.rs | 2 +- wgui/src/theme.rs | 36 +++++++++++++ wgui/src/widget/label.rs | 11 ++-- 33 files changed, 257 insertions(+), 317 deletions(-) create mode 100644 wgui/src/theme.rs diff --git a/dash-frontend/src/frontend.rs b/dash-frontend/src/frontend.rs index 3c334356..ac2000f4 100644 --- a/dash-frontend/src/frontend.rs +++ b/dash-frontend/src/frontend.rs @@ -12,6 +12,7 @@ use wgui::{ parser::{Fetchable, ParseDocumentParams, ParserState}, renderer_vk::text::custom_glyph::CustomGlyphData, task::Tasks, + theme::WguiTheme, widget::{label::WidgetLabel, rectangle::WidgetRectangle, sprite::WidgetSprite}, windowing::window::{WguiWindow, WguiWindowParams, WguiWindowParamsExtra, WguiWindowPlacement}, }; @@ -86,6 +87,7 @@ pub struct InitParams<'a, T> { pub interface: BoxDashInterface, pub lang_provider: &'a WayVRLangProvider, pub has_monado: bool, + pub theme: Rc, } #[derive(Clone)] @@ -106,12 +108,11 @@ pub enum FrontendTask { RecenterPlayspace, PushToast(Translation), PlaySound(SoundType), - UpdateWguiDefaultsFromConfig, HideDashboard, } impl Frontend { - pub fn new(mut params: InitParams, data: &mut T) -> anyhow::Result> { + pub fn new(params: InitParams) -> anyhow::Result> { let mut assets = Box::new(assets::Asset {}); let font_binary_bold = assets.load_from_path_gzip("Quicksand-Bold.ttf.gz")?; @@ -121,7 +122,6 @@ impl Frontend { let globals = WguiGlobals::new( assets, params.lang_provider, - wgui::globals::Defaults::default(), &WguiFontConfig { binaries: vec![&font_binary_regular, &font_binary_bold, &font_binary_light], family_name_sans_serif: "Quicksand", @@ -131,15 +131,16 @@ impl Frontend { PathBuf::new(), //FIXME: pass from somewhere else )?; - Frontend::update_defaults_from_config(&globals, &mut params.interface, data); - let (layout, state) = wgui::parser::new_layout_from_assets( &ParseDocumentParams { globals: globals.clone(), path: AssetPath::BuiltIn("gui/dashboard.xml"), extra: Default::default(), }, - &LayoutParams { resize_to_parent: true }, + LayoutParams { + resize_to_parent: true, + theme: params.theme, + }, )?; let id_popup_manager = state.get_widget_id("popup_manager")?; @@ -185,7 +186,6 @@ impl Frontend { // init some things first frontend.tasks.push(FrontendTask::RefreshBackground); frontend.tasks.push(FrontendTask::RefreshClock); - frontend.tasks.push(FrontendTask::UpdateWguiDefaultsFromConfig); Frontend::register_widgets(&mut frontend)?; @@ -196,16 +196,6 @@ impl Frontend { self.sounds_to_play.push(sound_type); } - fn update_defaults_from_config(globals: &WguiGlobals, interface: &mut BoxDashInterface, data: &mut T) { - let config = interface.general_config(data); - - let mut globals = globals.get(); - let defaults = &mut globals.defaults; - - defaults.animation_mult = 1.0 / config.ui_animation_speed; - defaults.gradient_intensity = config.ui_gradient_intensity; - } - fn play_sound(&mut self, audio_system: &mut audio::AudioSystem, sound_type: SoundType) -> anyhow::Result<()> { let mut assets = self.globals.assets_builtin(); @@ -273,7 +263,7 @@ impl Frontend { { // always 30 times per second while self.timestep.on_tick() { - self.toast_manager.tick(&self.globals, &mut self.layout)?; + self.toast_manager.tick(&mut self.layout)?; } } @@ -374,9 +364,6 @@ impl Frontend { FrontendTask::PushToast(content) => self.toast_manager.push(content), FrontendTask::PlaySound(sound_type) => self.queue_play_sound(sound_type), FrontendTask::HideDashboard => self.action_hide_dashboard(params.data), - FrontendTask::UpdateWguiDefaultsFromConfig => { - Frontend::update_defaults_from_config(&self.globals, &mut self.interface, params.data) - } }; Ok(()) } diff --git a/dash-frontend/src/tab/settings.rs b/dash-frontend/src/tab/settings.rs index 67f00070..32a1a2c4 100644 --- a/dash-frontend/src/tab/settings.rs +++ b/dash-frontend/src/tab/settings.rs @@ -163,7 +163,7 @@ impl Tab for TabSettings { } Task::SettingUpdated(setting) => match setting { SettingType::UiAnimationSpeed | SettingType::UiGradientIntensity | SettingType::UiRoundMultiplier => { - frontend.tasks.push(FrontendTask::UpdateWguiDefaultsFromConfig); + // todo: currently, wayvr restart is required to apply these changes (WguiTheme is Rc) } _ => { /* do nothing */ } }, @@ -426,16 +426,17 @@ impl SettingType { } fn requires_restart(self) -> bool { - match self { + matches!( + self, Self::UiAnimationSpeed - | Self::UiRoundMultiplier - | Self::UprightScreenFix - | Self::DoubleCursorFix - | Self::ScreenRenderDown - | Self::Language - | Self::CaptureMethod => true, - _ => false, - } + | Self::UiRoundMultiplier + | Self::UiGradientIntensity + | Self::UprightScreenFix + | Self::DoubleCursorFix + | Self::ScreenRenderDown + | Self::Language + | Self::CaptureMethod + ) } fn get_frontend_task(self) -> Option { @@ -466,7 +467,7 @@ fn horiz_cell(layout: &mut Layout, parent: WidgetID) -> anyhow::Result fn mount_requires_restart(layout: &mut Layout, parent: WidgetID) -> anyhow::Result<()> { let content = Translation::from_translation_key("APP_SETTINGS.REQUIRES_RESTART"); let label = WidgetLabel::create( - &mut layout.state.globals.get(), + &mut layout.state, WidgetLabelParams { content, style: TextStyle { diff --git a/dash-frontend/src/util/toast_manager.rs b/dash-frontend/src/util/toast_manager.rs index b2c0f035..b5128957 100644 --- a/dash-frontend/src/util/toast_manager.rs +++ b/dash-frontend/src/util/toast_manager.rs @@ -5,7 +5,6 @@ use wgui::{ animation::{Animation, AnimationEasing}, components::tooltip::{TOOLTIP_BORDER_COLOR, TOOLTIP_COLOR}, drawing::Color, - globals::WguiGlobals, i18n::Translation, layout::{Layout, LayoutTask, LayoutTasks, WidgetID}, renderer_vk::{ @@ -61,15 +60,7 @@ impl ToastManager { } } - fn mount_toast( - &self, - globals: &WguiGlobals, - layout: &mut Layout, - state: &mut State, - content: Translation, - ) -> anyhow::Result<()> { - let mut globals = globals.get(); - + fn mount_toast(&self, layout: &mut Layout, state: &mut State, content: Translation) -> anyhow::Result<()> { let (root, _) = layout.add_topmost_child( WidgetDiv::create(), taffy::Style { @@ -110,27 +101,24 @@ impl ToastManager { }, )?; - let (label, _) = layout.add_child( - rect.id, - WidgetLabel::create( - &mut globals, - WidgetLabelParams { - content, - style: TextStyle { - weight: Some(FontWeight::Bold), - align: Some(HorizontalAlign::Center), - wrap: true, - ..Default::default() - }, + let label = WidgetLabel::create( + &mut layout.state, + WidgetLabelParams { + content, + style: TextStyle { + weight: Some(FontWeight::Bold), + align: Some(HorizontalAlign::Center), + wrap: true, + ..Default::default() }, - ), - taffy::Style { ..Default::default() }, - )?; + }, + ); + let (label, _) = layout.add_child(rect.id, label, taffy::Style { ..Default::default() })?; // show-up animation layout.animations.add(Animation::new( rect.id, - (TOAST_DURATION_TICKS as f32 * globals.defaults.animation_mult) as u32, + (TOAST_DURATION_TICKS as f32 * layout.state.theme.animation_mult) as u32, AnimationEasing::Linear, Box::new(move |common, data| { let pos_showup = AnimationEasing::OutQuint.interpolate((data.pos * 4.0).min(1.0)); @@ -161,7 +149,7 @@ impl ToastManager { Ok(()) } - pub fn tick(&mut self, globals: &WguiGlobals, layout: &mut Layout) -> anyhow::Result<()> { + pub fn tick(&mut self, layout: &mut Layout) -> anyhow::Result<()> { let mut state = self.state.borrow_mut(); if state.timeout > 0 { state.timeout -= 1; @@ -176,7 +164,7 @@ impl ToastManager { state.timeout = TOAST_DURATION_TICKS; // mount next if let Some(content) = state.queue.pop_front() { - self.mount_toast(globals, layout, &mut state, content)?; + self.mount_toast(layout, &mut state, content)?; } } diff --git a/dash-frontend/src/util/wgui_simple.rs b/dash-frontend/src/util/wgui_simple.rs index b689b194..44dad5ee 100644 --- a/dash-frontend/src/util/wgui_simple.rs +++ b/dash-frontend/src/util/wgui_simple.rs @@ -8,7 +8,7 @@ use wgui::{ #[allow(dead_code)] pub fn create_label(layout: &mut Layout, parent: WidgetID, content: Translation) -> anyhow::Result<()> { let label = WidgetLabel::create( - &mut layout.state.globals.get(), + &mut layout.state, WidgetLabelParams { content, style: TextStyle { diff --git a/dash-frontend/src/views/audio_settings.rs b/dash-frontend/src/views/audio_settings.rs index 5eaa489a..f2c43f45 100644 --- a/dash-frontend/src/views/audio_settings.rs +++ b/dash-frontend/src/views/audio_settings.rs @@ -110,8 +110,7 @@ struct MultiSelectorParams<'a> { } fn mount_multi_selector(params: MultiSelectorParams) -> anyhow::Result<()> { - let globals = params.ess.layout.state.globals.clone(); - let accent_color = globals.defaults().accent_color; + let accent_color = params.ess.layout.state.theme.accent_color; for cell in params.cells { let highlighted = cell.key == params.def_cell; @@ -654,8 +653,6 @@ impl View { } fn update_button_highlights(&self, layout: &mut Layout) -> anyhow::Result<()> { - let defaults = self.globals.defaults(); - let mut c = layout.start_common(); let mut common = c.common(); @@ -667,14 +664,12 @@ impl View { }; let mut perform = |btn_num: u8, btn: &Rc| { - btn.set_color( - &mut common, - if num == btn_num { - defaults.accent_color - } else { - defaults.button_color - }, - ); + let color = if num == btn_num { + common.state.theme.accent_color + } else { + common.state.theme.button_color + }; + btn.set_color(&mut common, color); }; perform(0, &self.btn_sinks); diff --git a/dash-frontend/src/views/game_cover.rs b/dash-frontend/src/views/game_cover.rs index 07fdc478..5221ea1d 100644 --- a/dash-frontend/src/views/game_cover.rs +++ b/dash-frontend/src/views/game_cover.rs @@ -98,15 +98,9 @@ impl View { Ok(()) } - fn mount_placeholder_text( - &self, - globals: &WguiGlobals, - layout: &mut Layout, - parent: WidgetID, - text: &str, - ) -> anyhow::Result<()> { + fn mount_placeholder_text(&self, layout: &mut Layout, parent: WidgetID, text: &str) -> anyhow::Result<()> { let label = WidgetLabel::create( - &mut globals.get(), + &mut layout.state, WidgetLabelParams { content: Translation::from_raw_text(text), style: TextStyle { @@ -153,7 +147,7 @@ impl View { // mount placeholder let img = view_common.get_placeholder_image()?.clone(); self.mount_image(layout, &img)?; - self.mount_placeholder_text(&view_common.globals, layout, self.id_image_parent, &self.app_name)?; + self.mount_placeholder_text(layout, self.id_image_parent, &self.app_name)?; } else { // mount image let path = format!("app:{:?}", self.app_id); diff --git a/dash-frontend/src/views/game_list.rs b/dash-frontend/src/views/game_list.rs index 56dcac36..9946bdf6 100644 --- a/dash-frontend/src/views/game_list.rs +++ b/dash-frontend/src/views/game_list.rs @@ -256,17 +256,14 @@ impl View { )?; if let Some(text) = text.take() { - layout.add_child( - self.id_list_parent, - WidgetLabel::create( - &mut self.globals.get(), - WidgetLabelParams { - content: text, - ..Default::default() - }, - ), - Default::default(), - )?; + let label = WidgetLabel::create( + &mut layout.state, + WidgetLabelParams { + content: text, + ..Default::default() + }, + ); + layout.add_child(self.id_list_parent, label, Default::default())?; } Ok(()) diff --git a/uidev/src/testbed/testbed_any.rs b/uidev/src/testbed/testbed_any.rs index fc3a02ef..04ba5663 100644 --- a/uidev/src/testbed/testbed_any.rs +++ b/uidev/src/testbed/testbed_any.rs @@ -34,7 +34,6 @@ impl TestbedAny { let globals = WguiGlobals::new( assets, &lang_provider, - wgui::globals::Defaults::default(), &WguiFontConfig::default(), PathBuf::new(), // cwd )?; @@ -45,7 +44,7 @@ impl TestbedAny { path, extra: Default::default(), }, - &LayoutParams::default(), + LayoutParams::default(), )?; Ok(Self { layout, state }) } diff --git a/uidev/src/testbed/testbed_dashboard.rs b/uidev/src/testbed/testbed_dashboard.rs index a051ad67..c6aaba5a 100644 --- a/uidev/src/testbed/testbed_dashboard.rs +++ b/uidev/src/testbed/testbed_dashboard.rs @@ -1,6 +1,8 @@ +use std::rc::Rc; + use crate::testbed::{Testbed, TestbedUpdateParams}; use dash_frontend::frontend::{self, FrontendUpdateParams}; -use wgui::layout::Layout; +use wgui::{layout::Layout, theme::WguiTheme}; use wlx_common::{dash_interface_emulated::DashInterfaceEmulated, locale::WayVRLangProvider}; pub struct TestbedDashboard { @@ -12,14 +14,12 @@ impl TestbedDashboard { let interface = DashInterfaceEmulated::new(); let lang_provider = WayVRLangProvider::default(); - let frontend = frontend::Frontend::new( - frontend::InitParams { - interface: Box::new(interface), - has_monado: true, - lang_provider: &lang_provider, - }, - &mut (), - )?; + let frontend = frontend::Frontend::new(frontend::InitParams { + interface: Box::new(interface), + has_monado: true, + lang_provider: &lang_provider, + theme: Rc::new(WguiTheme::default()), + })?; Ok(Self { frontend }) } } diff --git a/uidev/src/testbed/testbed_generic.rs b/uidev/src/testbed/testbed_generic.rs index 6388bf5d..6f8425c6 100644 --- a/uidev/src/testbed/testbed_generic.rs +++ b/uidev/src/testbed/testbed_generic.rs @@ -21,6 +21,7 @@ use wgui::{ parser::{Fetchable, ParseDocumentExtra, ParseDocumentParams, ParserState}, taffy::{self, prelude::length}, task::Tasks, + theme::WguiTheme, widget::{div::WidgetDiv, label::WidgetLabel, rectangle::WidgetRectangle}, windowing::{ context_menu::{self, TickResult}, @@ -91,7 +92,6 @@ impl TestbedGeneric { let globals = WguiGlobals::new( assets, &lang_provider, - wgui::globals::Defaults::default(), &WguiFontConfig::default(), PathBuf::new(), // cwd )?; @@ -125,8 +125,9 @@ impl TestbedGeneric { let (layout, parser_state) = wgui::parser::new_layout_from_assets( &TestbedGeneric::doc_params(&globals, extra), - &LayoutParams { + LayoutParams { resize_to_parent: true, + theme: Rc::new(WguiTheme::default()), }, )?; diff --git a/wayvr/src/gui/panel/mod.rs b/wayvr/src/gui/panel/mod.rs index 3bb15adf..dc170904 100644 --- a/wayvr/src/gui/panel/mod.rs +++ b/wayvr/src/gui/panel/mod.rs @@ -147,8 +147,9 @@ impl GuiPanel { let (mut layout, mut parser_state) = wgui::parser::new_layout_from_assets( &doc_params, - &LayoutParams { + LayoutParams { resize_to_parent: params.resize_to_parent, + theme: app.wgui_theme.clone(), }, )?; diff --git a/wayvr/src/overlays/dashboard.rs b/wayvr/src/overlays/dashboard.rs index 1fc4b4db..48e9ef07 100644 --- a/wayvr/src/overlays/dashboard.rs +++ b/wayvr/src/overlays/dashboard.rs @@ -77,14 +77,12 @@ impl DashFrontend { let _ = interface.process_launch(app, false, p)?; } - let frontend = frontend::Frontend::new( - frontend::InitParams { - interface: Box::new(interface), - lang_provider: &WayVRLangProvider::from_config(&app.session.config), - has_monado: matches!(app.xr_backend, XrBackend::OpenXR), - }, - app, - )?; + let frontend = frontend::Frontend::new(frontend::InitParams { + interface: Box::new(interface), + lang_provider: &WayVRLangProvider::from_config(&app.session.config), + has_monado: matches!(app.xr_backend, XrBackend::OpenXR), + theme: app.wgui_theme.clone(), + })?; frontend .tasks diff --git a/wayvr/src/overlays/edit/lock.rs b/wayvr/src/overlays/edit/lock.rs index 2732cbb0..3edfd528 100644 --- a/wayvr/src/overlays/edit/lock.rs +++ b/wayvr/src/overlays/edit/lock.rs @@ -11,7 +11,7 @@ use wgui::{ widget::rectangle::WidgetRectangle, }; -use crate::{backend::task::ModifyOverlayTask, overlays::edit::EditModeWrapPanel, state::AppState}; +use crate::{backend::task::ModifyOverlayTask, overlays::edit::EditModeWrapPanel}; #[derive(Default)] pub(super) struct InteractLockHandler { @@ -53,21 +53,18 @@ impl InteractLockHandler { button.set_sticky_state(common, !interactable); } - let globals = common.state.globals.get(); if interactable { - set_anim_color(&mut rect, 0.0, self.color, globals.defaults.danger_color); + set_anim_color(&mut rect, 0.0, self.color, common.state.theme.danger_color); } else { - set_anim_color(&mut rect, 0.2, self.color, globals.defaults.danger_color); + set_anim_color(&mut rect, 0.2, self.color, common.state.theme.danger_color); } } pub fn toggle( &mut self, common: &mut CallbackDataCommon, - app: &mut AppState, anim_mult: f32, ) -> Box { - let defaults = app.wgui_globals.defaults().clone(); let rect_color = self.color; self.interactable = !self.interactable; @@ -86,7 +83,7 @@ impl InteractLockHandler { rect, 0.2 - (data.pos * 0.2), rect_color, - defaults.danger_color, + common.state.theme.danger_color, ); common.alterables.mark_redraw(); }), @@ -98,7 +95,12 @@ impl InteractLockHandler { AnimationEasing::OutBack, Box::new(move |common, data| { let rect = data.obj.get_as_mut::().unwrap(); - set_anim_color(rect, data.pos * 0.2, rect_color, defaults.danger_color); + set_anim_color( + rect, + data.pos * 0.2, + rect_color, + common.state.theme.danger_color, + ); common.alterables.mark_redraw(); }), ) diff --git a/wayvr/src/overlays/edit/mod.rs b/wayvr/src/overlays/edit/mod.rs index 78db7089..7eddf592 100644 --- a/wayvr/src/overlays/edit/mod.rs +++ b/wayvr/src/overlays/edit/mod.rs @@ -272,7 +272,7 @@ fn make_edit_panel(app: &mut AppState) -> anyhow::Result { mouse: SpriteTabHandler::default(), }; - let anim_mult = app.wgui_globals.defaults().animation_mult; + let anim_mult = app.wgui_theme.animation_mult; let on_custom_attrib: OnCustomAttribFunc = Box::new(move |layout, parser, attribs, _app| { let Ok(button) = @@ -302,7 +302,7 @@ fn make_edit_panel(app: &mut AppState) -> anyhow::Result { } let sel = OverlaySelector::Id(*state.id.borrow()); - let task = state.lock.toggle(common, app, anim_mult); + let task = state.lock.toggle(common, anim_mult); app.tasks .enqueue(TaskType::Overlay(OverlayTask::Modify(sel, task))); Ok(EventResult::Consumed) diff --git a/wayvr/src/overlays/keyboard/builder.rs b/wayvr/src/overlays/keyboard/builder.rs index 874d8516..b96b0368 100644 --- a/wayvr/src/overlays/keyboard/builder.rs +++ b/wayvr/src/overlays/keyboard/builder.rs @@ -53,11 +53,9 @@ pub(super) fn create_keyboard_panel( let doc_params = new_doc_params(&mut panel); - let globals = app.wgui_globals.clone(); - let (accent_color, anim_mult) = { - let def = globals.defaults(); - (def.accent_color, def.animation_mult) + let theme = &app.wgui_theme; + (theme.accent_color, theme.animation_mult) }; let root = panel diff --git a/wayvr/src/state.rs b/wayvr/src/state.rs index 0547549e..59146f36 100644 --- a/wayvr/src/state.rs +++ b/wayvr/src/state.rs @@ -1,8 +1,10 @@ use glam::Affine3A; use idmap::IdMap; use smallvec::{SmallVec, smallvec}; +use std::rc::Rc; use std::sync::Arc; use wgui::log::LogErr; +use wgui::theme::WguiTheme; use wgui::{ drawing, font_config::WguiFontConfig, gfx::WGfx, globals::WguiGlobals, parser::parse_color_hex, renderer_vk::context::SharedContext as WSharedContext, @@ -48,6 +50,7 @@ pub struct AppState { pub anchor_grabbed: bool, pub wgui_globals: WguiGlobals, + pub wgui_theme: Rc, pub dbus: DbusConnector, @@ -90,7 +93,7 @@ impl AppState { let osc_sender = crate::subsystem::osc::OscSender::new(session.config.osc_out_port).ok(); let wgui_shared = WSharedContext::new(gfx.clone())?; - let theme = session.config.theme_path.clone(); + let theme_path = session.config.theme_path.clone(); let mut audio_sample_player = audio::SamplePlayer::new(); audio_sample_player.register_sample( @@ -112,7 +115,7 @@ impl AppState { let mut assets = Box::new(gui::asset::GuiAsset {}); audio_sample_player.register_wgui_samples(assets.as_mut())?; - let mut defaults = wgui::globals::Defaults::default(); + let mut theme = WguiTheme::default(); { #[allow(clippy::ref_option)] @@ -122,15 +125,15 @@ impl AppState { } } - apply_color(&mut defaults.text_color, &session.config.color_text); - apply_color(&mut defaults.accent_color, &session.config.color_accent); - apply_color(&mut defaults.danger_color, &session.config.color_danger); - apply_color(&mut defaults.faded_color, &session.config.color_faded); - apply_color(&mut defaults.bg_color, &session.config.color_background); + apply_color(&mut theme.text_color, &session.config.color_text); + apply_color(&mut theme.accent_color, &session.config.color_accent); + apply_color(&mut theme.danger_color, &session.config.color_danger); + apply_color(&mut theme.faded_color, &session.config.color_faded); + apply_color(&mut theme.bg_color, &session.config.color_background); } - defaults.animation_mult = 1. / session.config.ui_animation_speed; - defaults.rounding_mult = session.config.ui_round_multiplier; + theme.animation_mult = 1. / session.config.ui_animation_speed; + theme.rounding_mult = session.config.ui_round_multiplier; let dbus = DbusConnector::default(); @@ -157,10 +160,10 @@ impl AppState { wgui_globals: WguiGlobals::new( assets, &lang_provider, - defaults, &WguiFontConfig::default(), - get_config_file_path(&theme), + get_config_file_path(&theme_path), )?, + wgui_theme: Rc::new(theme), dbus, xr_backend, ipc_server, diff --git a/wgui/src/components/button.rs b/wgui/src/components/button.rs index 0243c7e1..b782c280 100644 --- a/wgui/src/components/button.rs +++ b/wgui/src/components/button.rs @@ -157,7 +157,7 @@ impl ComponentButton { } pub fn set_color(&self, common: &mut CallbackDataCommon, color: Color) { - let gradient_intensity = common.defaults().gradient_intensity; + let gradient_intensity = common.state.theme.gradient_intensity; let Some(mut rect) = common.state.widgets.get_as::(self.data.id_rect) else { return; @@ -194,8 +194,8 @@ impl ComponentButton { } let (anim_mult, gradient_intensity) = { - let defaults = common.state.globals.defaults(); - (defaults.animation_mult, defaults.gradient_intensity) + let theme = &common.state.theme; + (theme.animation_mult, theme.gradient_intensity) }; let anim_ticks = if sticky_down { 5. } else { 10. }; @@ -247,7 +247,7 @@ fn anim_hover( let bgcolor = init_color.lerp(&colors.hover_color, mult); - let gradient_intensity = common.globals().defaults.gradient_intensity; + let gradient_intensity = common.state.theme.gradient_intensity; //let t = Mat4::from_scale(Vec3::splat(1.0 + pos * 0.5)) * Mat4::from_rotation_z(pos * 1.0); @@ -416,8 +416,8 @@ fn register_event_mouse_release( } pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Result<(WidgetPair, Rc)> { - let globals = ess.layout.state.globals.clone(); let mut style = params.style; + let theme = &ess.layout.state.theme; // force-override style style.align_items = Some(AlignItems::Center); @@ -431,7 +431,7 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul let color = if let Some(color) = params.color { color } else { - globals.defaults().button_color + theme.button_color }; let border_color = if let Some(border_color) = params.border_color { @@ -452,7 +452,12 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul Color::new(color.r + 0.5, color.g + 0.5, color.g + 0.5, color.a + 0.5) }; - let gradient_intensity = ess.layout.state.globals.defaults().gradient_intensity; + let gradient_intensity = theme.gradient_intensity; + + let light_text = { + let mult = if theme.dark_mode { color.a } else { 1.0 - color.a }; + (color.r + color.g + color.b) * mult < 1.5 + }; let (root, _) = ess.layout.add_child( ess.parent, @@ -469,15 +474,6 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul let id_rect = root.id; - let light_text = { - let mult = if globals.defaults().dark_mode { - color.a - } else { - 1.0 - color.a - }; - (color.r + color.g + color.b) * mult < 1.5 - }; - let default_margin = taffy::Rect { top: length(4.0), bottom: length(4.0), @@ -487,7 +483,7 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul if let Some(sprite_path) = params.sprite_src { let sprite = WidgetSprite::create(WidgetSpriteParams { - glyph_data: Some(CustomGlyphData::from_assets(&globals, sprite_path)?), + glyph_data: Some(CustomGlyphData::from_assets(&ess.layout.state.globals, sprite_path)?), ..Default::default() }); @@ -506,23 +502,25 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul } let id_label = if let Some(content) = params.text { + let widget_label = WidgetLabel::create( + &mut ess.layout.state, + WidgetLabelParams { + content, + style: TextStyle { + weight: Some(FontWeight::Bold), + color: Some(if light_text { + Color::new(1.0, 1.0, 1.0, 1.0) + } else { + Color::new(0.0, 0.0, 0.0, 1.0) + }), + ..params.text_style + }, + }, + ); + let (label, _node_label) = ess.layout.add_child( id_rect, - WidgetLabel::create( - &mut globals.get(), - WidgetLabelParams { - content, - style: TextStyle { - weight: Some(FontWeight::Bold), - color: Some(if light_text { - Color::new(1.0, 1.0, 1.0, 1.0) - } else { - Color::new(0.0, 0.0, 0.0, 1.0) - }), - ..params.text_style - }, - }, - ), + widget_label, taffy::Style { margin: default_margin, ..Default::default() @@ -558,7 +556,7 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul id: root.id, lhandles: { let listeners = &mut root.widget.state().event_listeners; - let anim_mult = ess.layout.state.globals.defaults().animation_mult; + let anim_mult = ess.layout.state.theme.animation_mult; vec![ register_event_mouse_enter(data.clone(), state.clone(), listeners, params.tooltip, anim_mult), register_event_mouse_leave(state.clone(), listeners, anim_mult), diff --git a/wgui/src/components/checkbox.rs b/wgui/src/components/checkbox.rs index 1d48167f..68dff648 100644 --- a/wgui/src/components/checkbox.rs +++ b/wgui/src/components/checkbox.rs @@ -343,8 +343,6 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul (WLength::Units(5.0), WLength::Units(8.0)) }; - let globals = ess.layout.state.globals.clone(); - let (root, _) = ess.layout.add_child( ess.parent, WidgetRectangle::create(WidgetRectangleParams { @@ -397,20 +395,17 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul }, )?; - let (label, _node_label) = ess.layout.add_child( - id_container, - WidgetLabel::create( - &mut globals.get(), - WidgetLabelParams { - content: params.text, - style: TextStyle { - weight: Some(FontWeight::Bold), - ..Default::default() - }, + let widget_label = WidgetLabel::create( + &mut ess.layout.state, + WidgetLabelParams { + content: params.text, + style: TextStyle { + weight: Some(FontWeight::Bold), + ..Default::default() }, - ), - Default::default(), - )?; + }, + ); + let (label, _node_label) = ess.layout.add_child(id_container, widget_label, Default::default())?; let data = Rc::new(Data { id_container, @@ -433,7 +428,7 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul id: root.id, lhandles: { let listeners = &mut root.widget.state().event_listeners; - let anim_mult = ess.layout.state.globals.defaults().animation_mult; + let anim_mult = ess.layout.state.theme.animation_mult; vec![ register_event_mouse_enter(state.clone(), listeners, params.tooltip, anim_mult), register_event_mouse_leave(state.clone(), listeners, anim_mult), diff --git a/wgui/src/components/editbox.rs b/wgui/src/components/editbox.rs index a1e5454e..7dff8cb5 100644 --- a/wgui/src/components/editbox.rs +++ b/wgui/src/components/editbox.rs @@ -99,11 +99,10 @@ fn anim_bottom_rect( } fn refresh_all(common: &mut CallbackDataCommon, data: &Data, state: &mut State) -> Option<()> { - let defaults = common.defaults(); - let editbox_color = defaults.editbox_color; - let anim_mult = defaults.animation_mult; - let accent_color = defaults.accent_color; - drop(defaults); + let theme = &common.state.theme; + let editbox_color = theme.editbox_color; + let anim_mult = theme.animation_mult; + let accent_color = theme.accent_color; let (rect_color, border_color) = if state.focused { (editbox_color.add_rgb(0.15), editbox_color.add_rgb(0.15 + 0.25)) @@ -279,10 +278,7 @@ pub fn construct( ess: &mut ConstructEssentials, mut params: Params, ) -> anyhow::Result<(WidgetPair, Rc)> { - let globals = ess.layout.state.globals.clone(); - let defaults = globals.defaults(); - let text_color = defaults.text_color; - drop(defaults); + let text_color = ess.layout.state.theme.text_color; if params.style.size.width.is_auto() { params.style.size.width = length(128.0); @@ -345,22 +341,23 @@ pub fn construct( }, )?; + let widget_label = WidgetLabel::create( + &mut ess.layout.state, + WidgetLabelParams { + content: Translation::from_raw_text(¶ms.initial_text), + style: TextStyle { + shadow: Some(TextShadow { + x: 1.0, + y: 1.0, + color: Color::new(0.0, 0.0, 0.0, 1.0), + }), + ..Default::default() + }, + }, + ); let (label, _node_label) = ess.layout.add_child( label_parent.id, - WidgetLabel::create( - &mut globals.get(), - WidgetLabelParams { - content: Translation::from_raw_text(¶ms.initial_text), - style: TextStyle { - shadow: Some(TextShadow { - x: 1.0, - y: 1.0, - color: Color::new(0.0, 0.0, 0.0, 1.0), - }), - ..Default::default() - }, - }, - ), + widget_label, taffy::Style { position: taffy::Position::Relative, ..Default::default() diff --git a/wgui/src/components/slider.rs b/wgui/src/components/slider.rs index 28d74e58..af94d3ec 100644 --- a/wgui/src/components/slider.rs +++ b/wgui/src/components/slider.rs @@ -498,26 +498,20 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul active_tooltip: None, }; - let globals = ess.layout.state.globals.clone(); - let slider_text: Option<(WidgetPair, taffy::NodeId)> = if params.show_value { - let pair = ess.layout.add_child( - slider_handle.id, - WidgetLabel::create( - &mut globals.get(), - WidgetLabelParams { - content: Translation::default(), - style: TextStyle { - color: Some(drawing::Color::new(0.0, 0.0, 0.0, 1.0)), // always black - weight: Some(FontWeight::Bold), - align: Some(HorizontalAlign::Center), - ..Default::default() - }, + let label = WidgetLabel::create( + &mut ess.layout.state, + WidgetLabelParams { + content: Translation::default(), + style: TextStyle { + color: Some(drawing::Color::new(0.0, 0.0, 0.0, 1.0)), // always black + weight: Some(FontWeight::Bold), + align: Some(HorizontalAlign::Center), + ..Default::default() }, - ), - Default::default(), - )?; - Some(pair) + }, + ); + Some(ess.layout.add_child(slider_handle.id, label, Default::default())?) } else { None }; @@ -535,7 +529,7 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul id: root.id, lhandles: { let listeners = &mut root.widget.state().event_listeners; - let anim_mult = ess.layout.state.globals.defaults().animation_mult; + let anim_mult = ess.layout.state.theme.animation_mult; vec![ register_event_mouse_enter(data.clone(), state.clone(), listeners, params.tooltip, anim_mult), register_event_mouse_leave(data.clone(), state.clone(), listeners, anim_mult), diff --git a/wgui/src/components/tabs.rs b/wgui/src/components/tabs.rs index b680d491..521fb842 100644 --- a/wgui/src/components/tabs.rs +++ b/wgui/src/components/tabs.rs @@ -67,8 +67,8 @@ impl ComponentTrait for ComponentTabs { impl State { fn select_entry(&mut self, common: &mut CallbackDataCommon, name: &Rc) { let (color_accent, color_button) = { - let def = common.state.globals.defaults(); - (def.accent_color, def.button_color) + let theme = &common.state.theme; + (theme.accent_color, theme.button_color) }; for entry in &self.mounted_entries { diff --git a/wgui/src/components/tooltip.rs b/wgui/src/components/tooltip.rs index 4f9b40f3..05648ae5 100644 --- a/wgui/src/components/tooltip.rs +++ b/wgui/src/components/tooltip.rs @@ -163,8 +163,6 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul ), }; - let globals = ess.layout.state.globals.clone(); - let (div, _) = ess.layout.add_child( ess.parent, WidgetDiv::create(), @@ -211,20 +209,17 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul }, )?; - let (label, _) = ess.layout.add_child( - rect.id, - WidgetLabel::create( - &mut globals.get(), - WidgetLabelParams { - content: params.info.text, - style: TextStyle { - weight: Some(FontWeight::Bold), - ..Default::default() - }, + let label = WidgetLabel::create( + &mut ess.layout.state, + WidgetLabelParams { + content: params.info.text, + style: TextStyle { + weight: Some(FontWeight::Bold), + ..Default::default() }, - ), - Default::default(), - )?; + }, + ); + let (label, _) = ess.layout.add_child(rect.id, label, Default::default())?; let data = Rc::new(Data { id_root: div.id }); @@ -246,7 +241,7 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul TooltipSide::Bottom => Vec2::new(0.0, 1.0), }; - let anim_mult = ess.layout.state.globals.defaults().animation_mult; + let anim_mult = ess.layout.state.theme.animation_mult; ess.layout.animations.add(Animation::new( rect.id, (10.0 * anim_mult) as u32, diff --git a/wgui/src/event.rs b/wgui/src/event.rs index 57417808..e3d93dac 100644 --- a/wgui/src/event.rs +++ b/wgui/src/event.rs @@ -1,6 +1,6 @@ use std::{ any::{Any, TypeId}, - cell::{Ref, RefMut}, + cell::RefMut, collections::HashSet, rc::{Rc, Weak}, }; @@ -181,10 +181,6 @@ impl CallbackDataCommon<'_> { pub fn globals(&self) -> RefMut<'_, globals::Globals> { self.state.globals.get() } - - pub fn defaults(&self) -> Ref<'_, globals::Defaults> { - self.state.globals.defaults() - } } pub struct CallbackData<'a> { diff --git a/wgui/src/globals.rs b/wgui/src/globals.rs index 47cb4325..e1a54333 100644 --- a/wgui/src/globals.rs +++ b/wgui/src/globals.rs @@ -1,5 +1,5 @@ use std::{ - cell::{Ref, RefCell, RefMut}, + cell::{RefCell, RefMut}, io::Read, path::PathBuf, rc::Rc, @@ -11,53 +11,17 @@ use regex::Regex; use crate::{ assets::{AssetPath, AssetProvider, LangProvider}, - assets_internal, drawing, + assets_internal, font_config::{WguiFontConfig, WguiFontSystem}, i18n::I18n, renderer_vk::text::custom_glyph::CustomGlyphCache, }; -#[derive(Clone)] -pub struct Defaults { - pub dark_mode: bool, - pub text_color: drawing::Color, - pub button_color: drawing::Color, - pub accent_color: drawing::Color, - pub danger_color: drawing::Color, - pub faded_color: drawing::Color, - pub bg_color: drawing::Color, - pub editbox_color: drawing::Color, - pub translucent_alpha: f32, - pub animation_mult: f32, - pub rounding_mult: f32, - pub gradient_intensity: f32, // currently used for buttons -} - -impl Default for Defaults { - fn default() -> Self { - Self { - dark_mode: true, - text_color: drawing::Color::new(1.0, 1.0, 1.0, 1.0), - button_color: drawing::Color::new(1.0, 1.0, 1.0, 0.02), - accent_color: drawing::Color::new(0.13, 0.68, 1.0, 1.0), - danger_color: drawing::Color::new(0.9, 0.0, 0.0, 1.0), - faded_color: drawing::Color::new(0.67, 0.74, 0.80, 1.0), - bg_color: drawing::Color::new(0.0, 0.07, 0.1, 0.75), - editbox_color: drawing::Color::new(0.15, 0.25, 0.35, 0.95), - translucent_alpha: 0.5, - animation_mult: 1.0, - rounding_mult: 1.0, - gradient_intensity: 0.3, - } - } -} - pub struct Globals { pub assets_internal: Box, pub assets_builtin: Box, pub asset_folder: PathBuf, pub i18n_builtin: I18n, - pub defaults: Defaults, pub font_system: WguiFontSystem, pub custom_glyph_cache: CustomGlyphCache, } @@ -69,7 +33,6 @@ impl WguiGlobals { pub fn new( mut assets_builtin: Box, lang_provider: &dyn LangProvider, - defaults: Defaults, font_config: &WguiFontConfig, asset_folder: PathBuf, ) -> anyhow::Result { @@ -79,7 +42,6 @@ impl WguiGlobals { Ok(Self(Rc::new(RefCell::new(Globals { assets_internal, assets_builtin, - defaults, asset_folder, font_system: WguiFontSystem::new(font_config, i18n_builtin.get_locale()), i18n_builtin, @@ -128,10 +90,6 @@ impl WguiGlobals { RefMut::map(self.0.borrow_mut(), |x| &mut x.i18n_builtin) } - pub fn defaults(&self) -> Ref<'_, Defaults> { - Ref::map(self.0.borrow(), |x| &x.defaults) - } - pub fn assets_internal(&self) -> RefMut<'_, Box> { RefMut::map(self.0.borrow_mut(), |x| &mut x.assets_internal) } diff --git a/wgui/src/layout.rs b/wgui/src/layout.rs index 8370e653..2efb3398 100644 --- a/wgui/src/layout.rs +++ b/wgui/src/layout.rs @@ -15,6 +15,7 @@ use crate::{ globals::WguiGlobals, sound::WguiSoundType, task::Tasks, + theme::WguiTheme, widget::{self, EventParams, EventResult, WidgetObj, WidgetState, WidgetStateFlags, div::WidgetDiv}, }; @@ -118,6 +119,7 @@ impl WidgetMap { pub struct LayoutState { pub globals: WguiGlobals, + pub theme: Rc, pub widgets: WidgetMap, pub nodes: WidgetNodeMap, pub tree: taffy::tree::TaffyTree, @@ -185,6 +187,7 @@ pub struct Layout { #[derive(Default)] pub struct LayoutParams { pub resize_to_parent: bool, + pub theme: Rc, } fn add_child_internal( @@ -526,12 +529,13 @@ impl Layout { Ok(event_result) } - pub fn new(globals: WguiGlobals, params: &LayoutParams) -> anyhow::Result { + pub fn new(globals: WguiGlobals, params: LayoutParams) -> anyhow::Result { let mut state = LayoutState { tree: TaffyTree::new(), widgets: WidgetMap::new(), nodes: WidgetNodeMap::default(), globals, + theme: params.theme, }; let size = if params.resize_to_parent { diff --git a/wgui/src/lib.rs b/wgui/src/lib.rs index f81e501b..d913f0bf 100644 --- a/wgui/src/lib.rs +++ b/wgui/src/lib.rs @@ -40,6 +40,7 @@ pub mod renderer_vk; pub mod sound; pub mod stack; pub mod task; +pub mod theme; pub mod widget; pub mod windowing; diff --git a/wgui/src/parser/component_button.rs b/wgui/src/parser/component_button.rs index 9a03657c..4e640c1c 100644 --- a/wgui/src/parser/component_button.rs +++ b/wgui/src/parser/component_button.rs @@ -57,7 +57,7 @@ pub fn parse_component_button<'a>( key, value, &mut round, - ctx.doc_params.globals.defaults().rounding_mult, + ctx.layout.state.theme.rounding_mult, ); } "color" => { diff --git a/wgui/src/parser/mod.rs b/wgui/src/parser/mod.rs index a97c80d4..2ed37d82 100644 --- a/wgui/src/parser/mod.rs +++ b/wgui/src/parser/mod.rs @@ -462,7 +462,7 @@ impl ParserContext<'_> { } fn populate_theme_variables(&mut self) { - let def = self.doc_params.globals.defaults(); + let theme = self.layout.state.theme.clone(); macro_rules! insert_color_vars { ($self:expr, $name:literal, $field:expr, $alpha:expr) => { @@ -479,11 +479,11 @@ impl ParserContext<'_> { }; } - insert_color_vars!(self, "text", def.text_color, def.translucent_alpha); - insert_color_vars!(self, "accent", def.accent_color, def.translucent_alpha); - insert_color_vars!(self, "danger", def.danger_color, def.translucent_alpha); - insert_color_vars!(self, "faded", def.faded_color, def.translucent_alpha); - insert_color_vars!(self, "bg", def.bg_color, def.translucent_alpha); + insert_color_vars!(self, "text", theme.text_color, theme.translucent_alpha); + insert_color_vars!(self, "accent", theme.accent_color, theme.translucent_alpha); + insert_color_vars!(self, "danger", theme.danger_color, theme.translucent_alpha); + insert_color_vars!(self, "faded", theme.faded_color, theme.translucent_alpha); + insert_color_vars!(self, "bg", theme.bg_color, theme.translucent_alpha); } fn print_invalid_attrib(&self, tag_name: &str, key: &str, value: &str) { @@ -1232,7 +1232,7 @@ pub fn parse_from_assets( pub fn new_layout_from_assets( doc_params: &ParseDocumentParams, - layout_params: &LayoutParams, + layout_params: LayoutParams, ) -> anyhow::Result<(Layout, ParserState)> { let mut layout = Layout::new(doc_params.globals.clone(), layout_params)?; let widget = layout.content_root_widget; diff --git a/wgui/src/parser/widget_image.rs b/wgui/src/parser/widget_image.rs index 90dc8770..6abdc8e1 100644 --- a/wgui/src/parser/widget_image.rs +++ b/wgui/src/parser/widget_image.rs @@ -42,7 +42,7 @@ pub fn parse_widget_image<'a>( key, value, &mut params.round, - ctx.doc_params.globals.defaults().rounding_mult, + ctx.layout.state.theme.rounding_mult, ); } "border" => { diff --git a/wgui/src/parser/widget_label.rs b/wgui/src/parser/widget_label.rs index 8c69292e..4ad72e19 100644 --- a/wgui/src/parser/widget_label.rs +++ b/wgui/src/parser/widget_label.rs @@ -45,11 +45,8 @@ pub fn parse_widget_label<'a>( } } - let globals = ctx.layout.state.globals.clone(); - - let (widget, _) = ctx - .layout - .add_child(parent_id, WidgetLabel::create(&mut globals.get(), params), style)?; + let label = WidgetLabel::create(&mut ctx.layout.state, params); + let (widget, _) = ctx.layout.add_child(parent_id, label, style)?; parse_widget_universal(ctx, &widget, attribs, tag_name); parse_children(file, ctx, node, widget.id)?; diff --git a/wgui/src/parser/widget_rectangle.rs b/wgui/src/parser/widget_rectangle.rs index 95305668..555c7380 100644 --- a/wgui/src/parser/widget_rectangle.rs +++ b/wgui/src/parser/widget_rectangle.rs @@ -47,7 +47,7 @@ pub fn parse_widget_rectangle<'a>( key, value, &mut params.round, - ctx.doc_params.globals.defaults().rounding_mult, + ctx.layout.state.theme.rounding_mult, ); } "border" => { diff --git a/wgui/src/theme.rs b/wgui/src/theme.rs new file mode 100644 index 00000000..6d9c530a --- /dev/null +++ b/wgui/src/theme.rs @@ -0,0 +1,36 @@ +use crate::drawing; + +#[derive(Clone)] +pub struct WguiTheme { + pub dark_mode: bool, + pub text_color: drawing::Color, + pub button_color: drawing::Color, + pub accent_color: drawing::Color, + pub danger_color: drawing::Color, + pub faded_color: drawing::Color, + pub bg_color: drawing::Color, + pub editbox_color: drawing::Color, + pub translucent_alpha: f32, + pub animation_mult: f32, + pub rounding_mult: f32, + pub gradient_intensity: f32, // currently used for buttons +} + +impl Default for WguiTheme { + fn default() -> Self { + Self { + dark_mode: true, + text_color: drawing::Color::new(1.0, 1.0, 1.0, 1.0), + button_color: drawing::Color::new(1.0, 1.0, 1.0, 0.02), + accent_color: drawing::Color::new(0.13, 0.68, 1.0, 1.0), + danger_color: drawing::Color::new(0.9, 0.0, 0.0, 1.0), + faded_color: drawing::Color::new(0.67, 0.74, 0.80, 1.0), + bg_color: drawing::Color::new(0.0, 0.07, 0.1, 0.75), + editbox_color: drawing::Color::new(0.15, 0.25, 0.35, 0.95), + translucent_alpha: 0.5, + animation_mult: 1.0, + rounding_mult: 1.0, + gradient_intensity: 0.3, + } + } +} diff --git a/wgui/src/widget/label.rs b/wgui/src/widget/label.rs index 89a17c9e..e83bb46f 100644 --- a/wgui/src/widget/label.rs +++ b/wgui/src/widget/label.rs @@ -9,8 +9,9 @@ use crate::{ event::CallbackDataCommon, globals::Globals, i18n::Translation, - layout::WidgetID, + layout::{LayoutState, WidgetID}, renderer_vk::text::TextStyle, + theme::WguiTheme, widget::WidgetStateFlags, }; @@ -31,9 +32,13 @@ pub struct WidgetLabel { } impl WidgetLabel { - pub fn create(globals: &mut Globals, mut params: WidgetLabelParams) -> WidgetState { + pub fn create(state: &mut LayoutState, params: WidgetLabelParams) -> WidgetState { + WidgetLabel::create_ex(&mut state.globals.get(), &state.theme, params) + } + + pub fn create_ex(globals: &mut Globals, theme: &WguiTheme, mut params: WidgetLabelParams) -> WidgetState { if params.style.color.is_none() { - params.style.color = Some(globals.defaults.text_color); + params.style.color = Some(theme.text_color); } let metrics = Metrics::from(¶ms.style); From 6f9ca5b3b7652a466632835007b95ae7c584ffd7 Mon Sep 17 00:00:00 2001 From: Earthgames Date: Thu, 5 Mar 2026 19:52:36 +0100 Subject: [PATCH 11/16] Added documentation for default colors --- wgui/doc/widgets.md | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/wgui/doc/widgets.md b/wgui/doc/widgets.md index 655a047f..4878f2cc 100644 --- a/wgui/doc/widgets.md +++ b/wgui/doc/widgets.md @@ -60,7 +60,7 @@ _They can be used in any widget/component._ `width`, `height`: **float** | **percent** -### Advanced attributes +## Advanced attributes `interactable`: "1" | "0" @@ -74,6 +74,32 @@ _Used in case of overlapping pop-ups or windows, most notably applied to various _Set to 1 if you want to render overlapping pop-ups to properly render your widgets in order. Wgui renders with as few Vulkan drawcalls as possible, so this is your responsibility._ +## Default Colors + +_These colors can be defined by the user to control the color scheme. They always exists and can be used in any place a normal color could be used._ + +`~color_text`: default: white(#FFFFFF) +`~color_accent`: default: light blue(#21ADFF) +`~color_danger`: default: red(#E60000) +`~color_faded`: default: grey(#ABBDCC) +`~color_bg`: default: black(#00121ABF) (color_background in config) + +_Only the default colors can be changed with the following suffixes._ + +`_translucent` + +_Halves the alpha of the color._ + +`_10` +`_20` +`_30` +`_40` +`_50` + +_Darkens the color by multiplying it with a percentage. `~color_accent_50` Would be half of the normal brightness but the same alpha. You can't combine the suffixes_ + +### Color + # Widgets ## div widget From 12ceebec6df846346b172682aecf11e67d41f14c Mon Sep 17 00:00:00 2001 From: Earthgames Date: Sun, 1 Mar 2026 13:01:52 +0100 Subject: [PATCH 12/16] Used locale in chrono --- Cargo.lock | 8 ++++++++ wayvr/Cargo.toml | 3 ++- wayvr/src/gui/panel/label.rs | 19 +++++++++++++++++-- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a2c4ab85..86f41ee9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -932,6 +932,7 @@ dependencies = [ "iana-time-zone", "js-sys", "num-traits", + "pure-rust-locales", "wasm-bindgen", "windows-link 0.2.1", ] @@ -4194,6 +4195,12 @@ dependencies = [ "syn 2.0.113", ] +[[package]] +name = "pure-rust-locales" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "869675ad2d7541aea90c6d88c81f46a7f4ea9af8cd0395d38f11a95126998a0d" + [[package]] name = "pxfm" version = "0.1.27" @@ -6314,6 +6321,7 @@ dependencies = [ "mint", "openxr", "ovr_overlay", + "pure-rust-locales", "regex", "rosc", "rust-embed", diff --git a/wayvr/Cargo.toml b/wayvr/Cargo.toml index 5762f7cb..22c08795 100644 --- a/wayvr/Cargo.toml +++ b/wayvr/Cargo.toml @@ -44,7 +44,8 @@ xdg.workspace = true ash = "^0.38.0" # must match vulkano bytes = { version = "1.11.1" } -chrono = "0.4.42" +chrono = { version = "0.4.42", features = [ "unstable-locales"]} +pure-rust-locales = "0.8.2" chrono-tz = "0.10.4" config = "0.15.19" dbus = { version = "0.9.9" } diff --git a/wayvr/src/gui/panel/label.rs b/wayvr/src/gui/panel/label.rs index c281619e..b4e3e5cd 100644 --- a/wayvr/src/gui/panel/label.rs +++ b/wayvr/src/gui/panel/label.rs @@ -220,9 +220,24 @@ fn clock_on_tick( common: &mut event::CallbackDataCommon, data: &mut event::CallbackData, ) { + let locale = { + let binding = common.i18n(); + let locale = binding.get_locale(); + match pure_rust_locales::Locale::try_from(locale.to_string().as_str()) { + Ok(loc) => loc, + Err(_) => pure_rust_locales::Locale::en_US, + } + }; let date_time = state.timezone.as_ref().map_or_else( - || format!("{}", Local::now().format(&state.format)), - |tz| format!("{}", Local::now().with_timezone(tz).format(&state.format)), + || format!("{}", Local::now().format_localized(&state.format, locale)), + |tz| { + format!( + "{}", + Local::now() + .with_timezone(tz) + .format_localized(&state.format, locale) + ) + }, ); let label = data.obj.get_as_mut::().unwrap(); From ad78fdab244182b1a2454476ed7109504d3bd37a Mon Sep 17 00:00:00 2001 From: Earthgames Date: Sun, 1 Mar 2026 13:43:38 +0100 Subject: [PATCH 13/16] Hack in full lang codes Add British --- wgui/src/i18n.rs | 8 +------- wlx-common/src/locale.rs | 15 +++++++++------ 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/wgui/src/i18n.rs b/wgui/src/i18n.rs index e2384bef..8fcad26b 100644 --- a/wgui/src/i18n.rs +++ b/wgui/src/i18n.rs @@ -123,13 +123,7 @@ impl Locale { // check if forced language is set if let Some(forced_lang) = lang_provider.forced_lang() { - let matched = - Self::match_locale(default_lang, forced_lang, None, lang_provider.langs_list().all_locale()).to_string(); - return Self { - lang: forced_lang.to_string(), - region: None, - matched, - }; + return Self::parse_str(lang_provider.langs_list(), forced_lang); } // fallback to environment variables diff --git a/wlx-common/src/locale.rs b/wlx-common/src/locale.rs index 7679c47b..66e31fb1 100644 --- a/wlx-common/src/locale.rs +++ b/wlx-common/src/locale.rs @@ -9,6 +9,8 @@ use crate::config::GeneralConfig; pub enum Language { #[strum(props(Text = "English"))] English, + #[strum(props(Text = "British"))] + British, #[strum(props(Text = "Polski"))] Polish, #[strum(props(Text = "日本語"))] @@ -26,13 +28,14 @@ pub enum Language { impl Language { pub const fn code(&self) -> &'static str { match self { - Language::English => "en", - Language::Polish => "pl", - Language::Japanese => "ja", - Language::German => "de", - Language::Italian => "it", + Language::English => "en_US", + Language::British => "en_GB", + Language::Polish => "pl_PL", + Language::Japanese => "ja_JP", + Language::German => "de_DE", + Language::Italian => "it_IT", Language::ChineseSimplified => "zh_CN", - Language::Spanish => "es", + Language::Spanish => "es_ES", } } From 6b3f8a7b42814d67e96360cdd694709949991172 Mon Sep 17 00:00:00 2001 From: Earthgames Date: Sat, 7 Mar 2026 12:26:39 +0100 Subject: [PATCH 14/16] Only convert locale one time --- wayvr/src/gui/panel/label.rs | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/wayvr/src/gui/panel/label.rs b/wayvr/src/gui/panel/label.rs index b4e3e5cd..dd785c52 100644 --- a/wayvr/src/gui/panel/label.rs +++ b/wayvr/src/gui/panel/label.rs @@ -130,6 +130,14 @@ pub(super) fn setup_custom_label( .ok() }), format: format.into(), + locale: { + let i18n = app.wgui_globals.i18n(); + let locale = i18n.get_locale(); + match pure_rust_locales::Locale::try_from(locale.to_string().as_str()) { + Ok(loc) => loc, + Err(_) => pure_rust_locales::Locale::en_US, + } + }, }; Box::new(move |common, data, _, _| { @@ -213,6 +221,7 @@ fn battery_on_tick( struct ClockLabelState { timezone: Option, format: Rc, + locale: chrono::Locale, } fn clock_on_tick( @@ -220,22 +229,19 @@ fn clock_on_tick( common: &mut event::CallbackDataCommon, data: &mut event::CallbackData, ) { - let locale = { - let binding = common.i18n(); - let locale = binding.get_locale(); - match pure_rust_locales::Locale::try_from(locale.to_string().as_str()) { - Ok(loc) => loc, - Err(_) => pure_rust_locales::Locale::en_US, - } - }; let date_time = state.timezone.as_ref().map_or_else( - || format!("{}", Local::now().format_localized(&state.format, locale)), + || { + format!( + "{}", + Local::now().format_localized(&state.format, state.locale) + ) + }, |tz| { format!( "{}", Local::now() .with_timezone(tz) - .format_localized(&state.format, locale) + .format_localized(&state.format, state.locale) ) }, ); From 1a74eb150703b134d7e45eb2d91362832e5dd104 Mon Sep 17 00:00:00 2001 From: Earthgames Date: Sat, 7 Mar 2026 12:29:39 +0100 Subject: [PATCH 15/16] Use a more common name --- wlx-common/src/locale.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wlx-common/src/locale.rs b/wlx-common/src/locale.rs index 66e31fb1..78d814db 100644 --- a/wlx-common/src/locale.rs +++ b/wlx-common/src/locale.rs @@ -9,7 +9,7 @@ use crate::config::GeneralConfig; pub enum Language { #[strum(props(Text = "English"))] English, - #[strum(props(Text = "British"))] + #[strum(props(Text = "English(UK)"))] British, #[strum(props(Text = "Polski"))] Polish, From f2c34fba8f96453e05d45c47b7a4e52900f73c3a Mon Sep 17 00:00:00 2001 From: galister <22305755+galister@users.noreply.github.com> Date: Wed, 11 Mar 2026 10:44:01 +0900 Subject: [PATCH 16/16] sticky modifier keys explanation in README --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9545c9e3..811068ff 100644 --- a/README.md +++ b/README.md @@ -131,7 +131,10 @@ Typing - While using the ORANGE laser, all keystrokes will have SHIFT applied. - Purple laser is customizable via the settings, no modifier by default. -**Modifier Keys** are sticky. They will remain pressed until a non-modifier key is pressed, the modifier gets toggled off, or the keyboard gets hidden. +**Modifier Keys are sticky**. They will remain pressed until eiter: +- a non-modifier key is pressed +- the modifier is toggled off by clicking again +- the keyboard is hidden (including via show-hide) ### Default Bindings