Merge remote-tracking branch 'origin/main'

This commit is contained in:
Diamond White 2026-03-12 05:01:22 +00:00
commit fb4e4d59ff
56 changed files with 443 additions and 373 deletions

8
Cargo.lock generated
View File

@ -1033,6 +1033,7 @@ dependencies = [
"iana-time-zone",
"js-sys",
"num-traits",
"pure-rust-locales",
"wasm-bindgen",
"windows-link 0.2.1",
]
@ -4878,6 +4879,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"
@ -7378,6 +7385,7 @@ dependencies = [
"mint",
"openxr",
"ovr_overlay",
"pure-rust-locales",
"regex",
"rosc",
"rust-embed",

View File

@ -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
@ -139,11 +142,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.

View File

@ -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": {

View File

@ -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": "Floor grid opacity",
"GRID_OPACITY_HELP": "Opacity of the floor grid when the skybox is enabled",
"XR_CLICK_SENSITIVITY": "XR click sensitivity",
"XR_CLICK_SENSITIVITY_HELP": "Analog trigger sensitivity",
"XR_CLICK_SENSITIVITY_RELEASE": "XR release sensitivity",

View File

@ -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": {

View File

@ -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",

View File

@ -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": {

View File

@ -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",

View File

@ -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": "应用已启动",

View File

@ -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<T>,
pub lang_provider: &'a WayVRLangProvider,
pub has_monado: bool,
pub theme: Rc<WguiTheme>,
}
#[derive(Clone)]
@ -106,12 +108,11 @@ pub enum FrontendTask {
RecenterPlayspace,
PushToast(Translation),
PlaySound(SoundType),
UpdateWguiDefaultsFromConfig,
HideDashboard,
}
impl<T: 'static> Frontend<T> {
pub fn new(mut params: InitParams<T>, data: &mut T) -> anyhow::Result<Frontend<T>> {
pub fn new(params: InitParams<T>) -> anyhow::Result<Frontend<T>> {
let mut assets = Box::new(assets::Asset {});
let font_binary_bold = assets.load_from_path_gzip("Quicksand-Bold.ttf.gz")?;
@ -121,7 +122,6 @@ impl<T: 'static> Frontend<T> {
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<T: 'static> Frontend<T> {
PathBuf::new(), //FIXME: pass from somewhere else
)?;
Frontend::update_defaults_from_config(&globals, &mut params.interface, data);
let (layout, state) = wgui::parser::new_layout_from_assets(
&ParseDocumentParams {
globals: globals.clone(),
path: AssetPath::BuiltIn("gui/dashboard.xml"),
extra: Default::default(),
},
&LayoutParams { resize_to_parent: true },
LayoutParams {
resize_to_parent: true,
theme: params.theme,
},
)?;
let id_popup_manager = state.get_widget_id("popup_manager")?;
@ -185,7 +186,6 @@ impl<T: 'static> Frontend<T> {
// init some things first
frontend.tasks.push(FrontendTask::RefreshBackground);
frontend.tasks.push(FrontendTask::RefreshClock);
frontend.tasks.push(FrontendTask::UpdateWguiDefaultsFromConfig);
Frontend::register_widgets(&mut frontend)?;
@ -196,16 +196,6 @@ impl<T: 'static> Frontend<T> {
self.sounds_to_play.push(sound_type);
}
fn update_defaults_from_config(globals: &WguiGlobals, interface: &mut BoxDashInterface<T>, data: &mut T) {
let config = interface.general_config(data);
let mut globals = globals.get();
let defaults = &mut globals.defaults;
defaults.animation_mult = 1.0 / config.ui_animation_speed;
defaults.gradient_intensity = config.ui_gradient_intensity;
}
fn play_sound(&mut self, audio_system: &mut audio::AudioSystem, sound_type: SoundType) -> anyhow::Result<()> {
let mut assets = self.globals.assets_builtin();
@ -273,7 +263,7 @@ impl<T: 'static> Frontend<T> {
{
// always 30 times per second
while self.timestep.on_tick() {
self.toast_manager.tick(&self.globals, &mut self.layout)?;
self.toast_manager.tick(&mut self.layout)?;
}
}
@ -374,9 +364,6 @@ impl<T: 'static> Frontend<T> {
FrontendTask::PushToast(content) => self.toast_manager.push(content),
FrontendTask::PlaySound(sound_type) => self.queue_play_sound(sound_type),
FrontendTask::HideDashboard => self.action_hide_dashboard(params.data),
FrontendTask::UpdateWguiDefaultsFromConfig => {
Frontend::update_defaults_from_config(&self.globals, &mut self.interface, params.data)
}
};
Ok(())
}

View File

@ -163,7 +163,7 @@ impl<T> Tab<T> for TabSettings<T> {
}
Task::SettingUpdated(setting) => match setting {
SettingType::UiAnimationSpeed | SettingType::UiGradientIntensity | SettingType::UiRoundMultiplier => {
frontend.tasks.push(FrontendTask::UpdateWguiDefaultsFromConfig);
// todo: currently, wayvr restart is required to apply these changes (WguiTheme is Rc)
}
_ => { /* do nothing */ }
},
@ -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"),
@ -422,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<FrontendTask> {
@ -462,7 +467,7 @@ fn horiz_cell(layout: &mut Layout, parent: WidgetID) -> anyhow::Result<WidgetID>
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 {
@ -795,6 +800,7 @@ impl<T> TabSettings<T> {
slider_f32!(mp, c, SettingType::UiRoundMultiplier, 0.5, 5.0, 0.1);
checkbox!(mp, c, SettingType::SetsOnWatch);
checkbox!(mp, c, SettingType::UseSkybox);
slider_f32!(mp, c, SettingType::GridOpacity, 0.0, 1.0, 0.05); // min, max, step
checkbox!(mp, c, SettingType::UsePassthrough);
checkbox!(mp, c, SettingType::Clock12h);
}

View File

@ -5,7 +5,6 @@ use wgui::{
animation::{Animation, AnimationEasing},
components::tooltip::{TOOLTIP_BORDER_COLOR, TOOLTIP_COLOR},
drawing::Color,
globals::WguiGlobals,
i18n::Translation,
layout::{Layout, LayoutTask, LayoutTasks, WidgetID},
renderer_vk::{
@ -61,15 +60,7 @@ impl ToastManager {
}
}
fn mount_toast(
&self,
globals: &WguiGlobals,
layout: &mut Layout,
state: &mut State,
content: Translation,
) -> anyhow::Result<()> {
let mut globals = globals.get();
fn mount_toast(&self, layout: &mut Layout, state: &mut State, content: Translation) -> anyhow::Result<()> {
let (root, _) = layout.add_topmost_child(
WidgetDiv::create(),
taffy::Style {
@ -110,27 +101,24 @@ impl ToastManager {
},
)?;
let (label, _) = layout.add_child(
rect.id,
WidgetLabel::create(
&mut globals,
WidgetLabelParams {
content,
style: TextStyle {
weight: Some(FontWeight::Bold),
align: Some(HorizontalAlign::Center),
wrap: true,
..Default::default()
},
let label = WidgetLabel::create(
&mut layout.state,
WidgetLabelParams {
content,
style: TextStyle {
weight: Some(FontWeight::Bold),
align: Some(HorizontalAlign::Center),
wrap: true,
..Default::default()
},
),
taffy::Style { ..Default::default() },
)?;
},
);
let (label, _) = layout.add_child(rect.id, label, taffy::Style { ..Default::default() })?;
// show-up animation
layout.animations.add(Animation::new(
rect.id,
(TOAST_DURATION_TICKS as f32 * globals.defaults.animation_mult) as u32,
(TOAST_DURATION_TICKS as f32 * layout.state.theme.animation_mult) as u32,
AnimationEasing::Linear,
Box::new(move |common, data| {
let pos_showup = AnimationEasing::OutQuint.interpolate((data.pos * 4.0).min(1.0));
@ -161,7 +149,7 @@ impl ToastManager {
Ok(())
}
pub fn tick(&mut self, globals: &WguiGlobals, layout: &mut Layout) -> anyhow::Result<()> {
pub fn tick(&mut self, layout: &mut Layout) -> anyhow::Result<()> {
let mut state = self.state.borrow_mut();
if state.timeout > 0 {
state.timeout -= 1;
@ -176,7 +164,7 @@ impl ToastManager {
state.timeout = TOAST_DURATION_TICKS;
// mount next
if let Some(content) = state.queue.pop_front() {
self.mount_toast(globals, layout, &mut state, content)?;
self.mount_toast(layout, &mut state, content)?;
}
}

View File

@ -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 {

View File

@ -110,8 +110,7 @@ struct MultiSelectorParams<'a> {
}
fn mount_multi_selector(params: MultiSelectorParams) -> anyhow::Result<()> {
let globals = params.ess.layout.state.globals.clone();
let accent_color = globals.defaults().accent_color;
let accent_color = params.ess.layout.state.theme.accent_color;
for cell in params.cells {
let highlighted = cell.key == params.def_cell;
@ -654,8 +653,6 @@ impl View {
}
fn update_button_highlights(&self, layout: &mut Layout) -> anyhow::Result<()> {
let defaults = self.globals.defaults();
let mut c = layout.start_common();
let mut common = c.common();
@ -667,14 +664,12 @@ impl View {
};
let mut perform = |btn_num: u8, btn: &Rc<ComponentButton>| {
btn.set_color(
&mut common,
if num == btn_num {
defaults.accent_color
} else {
defaults.button_color
},
);
let color = if num == btn_num {
common.state.theme.accent_color
} else {
common.state.theme.button_color
};
btn.set_color(&mut common, color);
};
perform(0, &self.btn_sinks);

View File

@ -98,15 +98,9 @@ impl View {
Ok(())
}
fn mount_placeholder_text(
&self,
globals: &WguiGlobals,
layout: &mut Layout,
parent: WidgetID,
text: &str,
) -> anyhow::Result<()> {
fn mount_placeholder_text(&self, layout: &mut Layout, parent: WidgetID, text: &str) -> anyhow::Result<()> {
let label = WidgetLabel::create(
&mut globals.get(),
&mut layout.state,
WidgetLabelParams {
content: Translation::from_raw_text(text),
style: TextStyle {
@ -153,7 +147,7 @@ impl View {
// mount placeholder
let img = view_common.get_placeholder_image()?.clone();
self.mount_image(layout, &img)?;
self.mount_placeholder_text(&view_common.globals, layout, self.id_image_parent, &self.app_name)?;
self.mount_placeholder_text(layout, self.id_image_parent, &self.app_name)?;
} else {
// mount image
let path = format!("app:{:?}", self.app_id);

View File

@ -256,17 +256,14 @@ impl View {
)?;
if let Some(text) = text.take() {
layout.add_child(
self.id_list_parent,
WidgetLabel::create(
&mut self.globals.get(),
WidgetLabelParams {
content: text,
..Default::default()
},
),
Default::default(),
)?;
let label = WidgetLabel::create(
&mut layout.state,
WidgetLabelParams {
content: text,
..Default::default()
},
);
layout.add_child(self.id_list_parent, label, Default::default())?;
}
Ok(())

View File

@ -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 })
}

View File

@ -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 })
}
}

View File

@ -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()),
},
)?;

View File

@ -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

View File

@ -44,7 +44,8 @@ xdg.workspace = true
ash = "^0.38.0" # must match vulkano
bytes = { version = "1.11.1" }
chrono = "0.4.42"
chrono = { version = "0.4.42", features = [ "unstable-locales"]}
pure-rust-locales = "0.8.2"
chrono-tz = "0.10.4"
config = "0.15.19"
dbus = { version = "0.9.9" }

View File

@ -3,6 +3,18 @@ use glam::{Affine3A, Quat, Vec3, Vec3A};
use openxr::{self as xr, SessionCreateFlags, Version, sys::Handle};
use xr::OverlaySessionCreateFlagsEXTX;
macro_rules! next_chain_insert {
($layer:expr, $payload:expr) => {{
let payload_ptr = $payload.as_mut() as *mut _ as *mut xr::sys::BaseInStructure;
let new_elem = payload_ptr.as_mut().unwrap();
let mut raw = $layer.into_raw();
new_elem.next = raw.next as _;
raw.next = payload_ptr as *const _;
raw
}};
}
pub(crate) use next_chain_insert;
pub(super) fn init_xr() -> Result<(xr::Instance, xr::SystemId), anyhow::Error> {
let entry = xr::Entry::linked();

View File

@ -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",

View File

@ -6,7 +6,10 @@ use xr::EyeVisibility;
use super::{CompositionLayer, XrState, helpers, swapchain::WlxSwapchain};
use crate::{
backend::openxr::swapchain::{SwapchainOpts, WlxSwapchainImage, create_swapchain},
backend::openxr::{
helpers::next_chain_insert,
swapchain::{SwapchainOpts, WlxSwapchainImage, create_swapchain},
},
state::AppState,
windowing::window::OverlayWindowData,
};
@ -20,17 +23,6 @@ pub struct OpenXrOverlayData {
color_bias_khr: Option<Box<xr::sys::CompositionLayerColorScaleBiasKHR>>,
}
macro_rules! next_chain_insert {
($layer:expr, $payload:expr) => {{
let payload_ptr = $payload.as_mut() as *mut _ as *mut xr::sys::BaseInStructure;
let new_elem = payload_ptr.as_mut().unwrap();
let mut raw = $layer.into_raw();
new_elem.next = raw.next as _;
raw.next = payload_ptr as *const _;
raw
}};
}
impl OverlayWindowData<OpenXrOverlayData> {
pub(super) fn ensure_swapchain_acquire<'a>(
&'a mut self,

View File

@ -1,8 +1,4 @@
use std::{
f32::consts::PI,
fs::File,
sync::{Arc, LazyLock},
};
use std::{f32::consts::PI, fs::File, sync::Arc};
use glam::{Quat, Vec3A};
use openxr as xr;
@ -14,7 +10,10 @@ use wgui::gfx::{cmd::WGfxClearMode, pipeline::WPipelineCreateInfo};
use wlx_common::config_io;
use crate::{
backend::openxr::{helpers::translation_rotation_to_posef, swapchain::SwapchainOpts},
backend::openxr::{
helpers::{next_chain_insert, translation_rotation_to_posef},
swapchain::SwapchainOpts,
},
graphics::{ExtentExt, GpuFutures, dds::WlxCommandBufferDds},
state::AppState,
};
@ -28,10 +27,12 @@ pub(super) struct Skybox {
view: Arc<ImageView>,
sky: Option<WlxSwapchain>,
grid: Option<WlxSwapchain>,
grid_pose: xr::Posef,
grid_color_scale_bias_khr: Option<Box<xr::sys::CompositionLayerColorScaleBiasKHR>>,
}
impl Skybox {
pub fn new(app: &AppState) -> anyhow::Result<Self> {
pub fn new(xr: &XrState, app: &AppState) -> anyhow::Result<Self> {
let mut command_buffer = app
.gfx
.create_xfer_command_buffer(CommandBufferUsage::OneTimeSubmit)?;
@ -74,10 +75,30 @@ impl Skybox {
let view = ImageView::new_default(maybe_image.unwrap())?; // safe unwrap
let grid_color_scale_bias_khr = xr
.instance
.exts()
.khr_composition_layer_color_scale_bias
.map(|_| {
Box::new(xr::sys::CompositionLayerColorScaleBiasKHR {
ty: xr::StructureType::COMPOSITION_LAYER_COLOR_SCALE_BIAS_KHR,
next: std::ptr::null(),
color_bias: Default::default(),
color_scale: xr::Color4f {
r: 1.0,
g: 1.0,
b: 1.0,
a: 1.0,
},
})
});
Ok(Self {
view,
sky: None,
grid: None,
grid_pose: translation_rotation_to_posef(Vec3A::ZERO, Quat::from_rotation_x(PI * -0.5)),
grid_color_scale_bias_khr: grid_color_scale_bias_khr,
})
}
@ -205,10 +226,6 @@ impl Skybox {
const HI_VERT_ANGLE: f32 = 0.5 * PI;
const LO_VERT_ANGLE: f32 = -0.5 * PI;
static GRID_POSE: LazyLock<xr::Posef> = LazyLock::new(|| {
translation_rotation_to_posef(Vec3A::ZERO, Quat::from_rotation_x(PI * -0.5))
});
let pose = xr::Posef {
orientation: xr::Quaternionf::IDENTITY,
position: xr::Vector3f {
@ -232,9 +249,12 @@ impl Skybox {
.lower_vertical_angle(LO_VERT_ANGLE);
self.grid.as_mut().unwrap().ensure_image_released()?;
let grid = xr::CompositionLayerQuad::new()
.layer_flags(xr::CompositionLayerFlags::BLEND_TEXTURE_SOURCE_ALPHA)
.pose(*GRID_POSE)
let flags = xr::CompositionLayerFlags::BLEND_TEXTURE_SOURCE_ALPHA
| xr::CompositionLayerFlags::UNPREMULTIPLIED_ALPHA;
let mut grid = xr::CompositionLayerQuad::new()
.layer_flags(flags)
.pose(self.grid_pose)
.size(xr::Extent2Df {
width: 10.0,
height: 10.0,
@ -243,6 +263,15 @@ impl Skybox {
.eye_visibility(xr::EyeVisibility::BOTH)
.space(&xr.stage);
if let Some(grid_color_scale_bias_khr) = self.grid_color_scale_bias_khr.as_mut() {
let grid_opacity = app.session.config.grid_opacity;
grid_color_scale_bias_khr.color_scale.a = (grid_opacity * grid_opacity).clamp(0.0, 1.0);
unsafe {
let raw = next_chain_insert!(grid, grid_color_scale_bias_khr);
grid = xr::CompositionLayerQuad::from_raw(raw);
}
}
Ok(vec![
CompositionLayer::Equirect2(sky),
CompositionLayer::Quad(grid),
@ -254,5 +283,5 @@ pub(super) fn create_skybox(xr: &XrState, app: &AppState) -> Option<Skybox> {
xr.instance
.exts()
.khr_composition_layer_equirect2
.and_then(|_| Skybox::new(app).ok())
.and_then(|_| Skybox::new(xr, app).ok())
}

View File

@ -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,

View File

@ -130,6 +130,14 @@ pub(super) fn setup_custom_label<S: 'static>(
.ok()
}),
format: format.into(),
locale: {
let i18n = app.wgui_globals.i18n();
let locale = i18n.get_locale();
match pure_rust_locales::Locale::try_from(locale.to_string().as_str()) {
Ok(loc) => loc,
Err(_) => pure_rust_locales::Locale::en_US,
}
},
};
Box::new(move |common, data, _, _| {
@ -213,6 +221,7 @@ fn battery_on_tick(
struct ClockLabelState {
timezone: Option<Tz>,
format: Rc<str>,
locale: chrono::Locale,
}
fn clock_on_tick(
@ -221,8 +230,20 @@ fn clock_on_tick(
data: &mut event::CallbackData,
) {
let date_time = state.timezone.as_ref().map_or_else(
|| format!("{}", Local::now().format(&state.format)),
|tz| format!("{}", Local::now().with_timezone(tz).format(&state.format)),
|| {
format!(
"{}",
Local::now().format_localized(&state.format, state.locale)
)
},
|tz| {
format!(
"{}",
Local::now()
.with_timezone(tz)
.format_localized(&state.format, state.locale)
)
},
);
let label = data.obj.get_as_mut::<WidgetLabel>().unwrap();

View File

@ -147,8 +147,9 @@ impl<S: 'static> GuiPanel<S> {
let (mut layout, mut parser_state) = wgui::parser::new_layout_from_assets(
&doc_params,
&LayoutParams {
LayoutParams {
resize_to_parent: params.resize_to_parent,
theme: app.wgui_theme.clone(),
},
)?;

View File

@ -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

View File

@ -11,7 +11,7 @@ use wgui::{
widget::rectangle::WidgetRectangle,
};
use crate::{backend::task::ModifyOverlayTask, overlays::edit::EditModeWrapPanel, state::AppState};
use crate::{backend::task::ModifyOverlayTask, overlays::edit::EditModeWrapPanel};
#[derive(Default)]
pub(super) struct InteractLockHandler {
@ -53,21 +53,18 @@ impl InteractLockHandler {
button.set_sticky_state(common, !interactable);
}
let globals = common.state.globals.get();
if interactable {
set_anim_color(&mut rect, 0.0, self.color, globals.defaults.danger_color);
set_anim_color(&mut rect, 0.0, self.color, common.state.theme.danger_color);
} else {
set_anim_color(&mut rect, 0.2, self.color, globals.defaults.danger_color);
set_anim_color(&mut rect, 0.2, self.color, common.state.theme.danger_color);
}
}
pub fn toggle(
&mut self,
common: &mut CallbackDataCommon,
app: &mut AppState,
anim_mult: f32,
) -> Box<ModifyOverlayTask> {
let defaults = app.wgui_globals.defaults().clone();
let rect_color = self.color;
self.interactable = !self.interactable;
@ -86,7 +83,7 @@ impl InteractLockHandler {
rect,
0.2 - (data.pos * 0.2),
rect_color,
defaults.danger_color,
common.state.theme.danger_color,
);
common.alterables.mark_redraw();
}),
@ -98,7 +95,12 @@ impl InteractLockHandler {
AnimationEasing::OutBack,
Box::new(move |common, data| {
let rect = data.obj.get_as_mut::<WidgetRectangle>().unwrap();
set_anim_color(rect, data.pos * 0.2, rect_color, defaults.danger_color);
set_anim_color(
rect,
data.pos * 0.2,
rect_color,
common.state.theme.danger_color,
);
common.alterables.mark_redraw();
}),
)

View File

@ -272,7 +272,7 @@ fn make_edit_panel(app: &mut AppState) -> anyhow::Result<EditModeWrapPanel> {
mouse: SpriteTabHandler::default(),
};
let anim_mult = app.wgui_globals.defaults().animation_mult;
let anim_mult = app.wgui_theme.animation_mult;
let on_custom_attrib: OnCustomAttribFunc = Box::new(move |layout, parser, attribs, _app| {
let Ok(button) =
@ -302,7 +302,7 @@ fn make_edit_panel(app: &mut AppState) -> anyhow::Result<EditModeWrapPanel> {
}
let sel = OverlaySelector::Id(*state.id.borrow());
let task = state.lock.toggle(common, app, anim_mult);
let task = state.lock.toggle(common, anim_mult);
app.tasks
.enqueue(TaskType::Overlay(OverlayTask::Modify(sel, task)));
Ok(EventResult::Consumed)

View File

@ -51,11 +51,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

View File

@ -125,6 +125,9 @@
## If disabled, there will be no background rendered (solid black).
#use_skybox: true
## Monado/WiVRn only. Opacity of the floor grid if use_skybox is enabled.
#grid_opacity: true
## Hide the help popup that appears on your hand while grabbing an overlay
#hide_grab_help: false

View File

@ -16,4 +16,3 @@ void main()
}
out_color = vec4(1.0, 1.0, 1.0, grid * fade);
}

View File

@ -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<WguiTheme>,
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,

View File

@ -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

View File

@ -157,7 +157,7 @@ impl ComponentButton {
}
pub fn set_color(&self, common: &mut CallbackDataCommon, color: Color) {
let gradient_intensity = common.defaults().gradient_intensity;
let gradient_intensity = common.state.theme.gradient_intensity;
let Some(mut rect) = common.state.widgets.get_as::<WidgetRectangle>(self.data.id_rect) else {
return;
@ -194,8 +194,8 @@ impl ComponentButton {
}
let (anim_mult, gradient_intensity) = {
let defaults = common.state.globals.defaults();
(defaults.animation_mult, defaults.gradient_intensity)
let theme = &common.state.theme;
(theme.animation_mult, theme.gradient_intensity)
};
let anim_ticks = if sticky_down { 5. } else { 10. };
@ -247,7 +247,7 @@ fn anim_hover(
let bgcolor = init_color.lerp(&colors.hover_color, mult);
let gradient_intensity = common.globals().defaults.gradient_intensity;
let gradient_intensity = common.state.theme.gradient_intensity;
//let t = Mat4::from_scale(Vec3::splat(1.0 + pos * 0.5)) * Mat4::from_rotation_z(pos * 1.0);
@ -416,8 +416,8 @@ fn register_event_mouse_release(
}
pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Result<(WidgetPair, Rc<ComponentButton>)> {
let globals = ess.layout.state.globals.clone();
let mut style = params.style;
let theme = &ess.layout.state.theme;
// force-override style
style.align_items = Some(AlignItems::Center);
@ -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),

View File

@ -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),

View File

@ -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<ComponentEditBox>)> {
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(&params.initial_text),
style: TextStyle {
shadow: Some(TextShadow {
x: 1.0,
y: 1.0,
color: Color::new(0.0, 0.0, 0.0, 1.0),
}),
..Default::default()
},
},
);
let (label, _node_label) = ess.layout.add_child(
label_parent.id,
WidgetLabel::create(
&mut globals.get(),
WidgetLabelParams {
content: Translation::from_raw_text(&params.initial_text),
style: TextStyle {
shadow: Some(TextShadow {
x: 1.0,
y: 1.0,
color: Color::new(0.0, 0.0, 0.0, 1.0),
}),
..Default::default()
},
},
),
widget_label,
taffy::Style {
position: taffy::Position::Relative,
..Default::default()

View File

@ -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),

View File

@ -67,8 +67,8 @@ impl ComponentTrait for ComponentTabs {
impl State {
fn select_entry(&mut self, common: &mut CallbackDataCommon, name: &Rc<str>) {
let (color_accent, color_button) = {
let def = common.state.globals.defaults();
(def.accent_color, def.button_color)
let theme = &common.state.theme;
(theme.accent_color, theme.button_color)
};
for entry in &self.mounted_entries {

View File

@ -163,8 +163,6 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul
),
};
let globals = ess.layout.state.globals.clone();
let (div, _) = ess.layout.add_child(
ess.parent,
WidgetDiv::create(),
@ -211,20 +209,17 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul
},
)?;
let (label, _) = ess.layout.add_child(
rect.id,
WidgetLabel::create(
&mut globals.get(),
WidgetLabelParams {
content: params.info.text,
style: TextStyle {
weight: Some(FontWeight::Bold),
..Default::default()
},
let label = WidgetLabel::create(
&mut ess.layout.state,
WidgetLabelParams {
content: params.info.text,
style: TextStyle {
weight: Some(FontWeight::Bold),
..Default::default()
},
),
Default::default(),
)?;
},
);
let (label, _) = ess.layout.add_child(rect.id, label, Default::default())?;
let data = Rc::new(Data { id_root: div.id });
@ -246,7 +241,7 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul
TooltipSide::Bottom => Vec2::new(0.0, 1.0),
};
let anim_mult = ess.layout.state.globals.defaults().animation_mult;
let anim_mult = ess.layout.state.theme.animation_mult;
ess.layout.animations.add(Animation::new(
rect.id,
(10.0 * anim_mult) as u32,

View File

@ -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> {

View File

@ -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<dyn AssetProvider>,
pub assets_builtin: Box<dyn AssetProvider>,
pub asset_folder: PathBuf,
pub i18n_builtin: I18n,
pub defaults: Defaults,
pub font_system: WguiFontSystem,
pub custom_glyph_cache: CustomGlyphCache,
}
@ -69,7 +33,6 @@ impl WguiGlobals {
pub fn new(
mut assets_builtin: Box<dyn AssetProvider>,
lang_provider: &dyn LangProvider,
defaults: Defaults,
font_config: &WguiFontConfig,
asset_folder: PathBuf,
) -> anyhow::Result<Self> {
@ -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<dyn AssetProvider>> {
RefMut::map(self.0.borrow_mut(), |x| &mut x.assets_internal)
}

View File

@ -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

View File

@ -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<WguiTheme>,
pub widgets: WidgetMap,
pub nodes: WidgetNodeMap,
pub tree: taffy::tree::TaffyTree<WidgetID>,
@ -185,6 +187,7 @@ pub struct Layout {
#[derive(Default)]
pub struct LayoutParams {
pub resize_to_parent: bool,
pub theme: Rc<WguiTheme>,
}
fn add_child_internal(
@ -526,12 +529,13 @@ impl Layout {
Ok(event_result)
}
pub fn new(globals: WguiGlobals, params: &LayoutParams) -> anyhow::Result<Self> {
pub fn new(globals: WguiGlobals, params: LayoutParams) -> anyhow::Result<Self> {
let mut state = LayoutState {
tree: TaffyTree::new(),
widgets: WidgetMap::new(),
nodes: WidgetNodeMap::default(),
globals,
theme: params.theme,
};
let size = if params.resize_to_parent {

View File

@ -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;

View File

@ -57,7 +57,7 @@ pub fn parse_component_button<'a>(
key,
value,
&mut round,
ctx.doc_params.globals.defaults().rounding_mult,
ctx.layout.state.theme.rounding_mult,
);
}
"color" => {

View File

@ -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;

View File

@ -42,7 +42,7 @@ pub fn parse_widget_image<'a>(
key,
value,
&mut params.round,
ctx.doc_params.globals.defaults().rounding_mult,
ctx.layout.state.theme.rounding_mult,
);
}
"border" => {

View File

@ -45,11 +45,8 @@ pub fn parse_widget_label<'a>(
}
}
let globals = ctx.layout.state.globals.clone();
let (widget, _) = ctx
.layout
.add_child(parent_id, WidgetLabel::create(&mut globals.get(), params), style)?;
let label = WidgetLabel::create(&mut ctx.layout.state, params);
let (widget, _) = ctx.layout.add_child(parent_id, label, style)?;
parse_widget_universal(ctx, &widget, attribs, tag_name);
parse_children(file, ctx, node, widget.id)?;

View File

@ -47,7 +47,7 @@ pub fn parse_widget_rectangle<'a>(
key,
value,
&mut params.round,
ctx.doc_params.globals.defaults().rounding_mult,
ctx.layout.state.theme.rounding_mult,
);
}
"border" => {

36
wgui/src/theme.rs Normal file
View File

@ -0,0 +1,36 @@
use crate::drawing;
#[derive(Clone)]
pub struct WguiTheme {
pub dark_mode: bool,
pub text_color: drawing::Color,
pub button_color: drawing::Color,
pub accent_color: drawing::Color,
pub danger_color: drawing::Color,
pub faded_color: drawing::Color,
pub bg_color: drawing::Color,
pub editbox_color: drawing::Color,
pub translucent_alpha: f32,
pub animation_mult: f32,
pub rounding_mult: f32,
pub gradient_intensity: f32, // currently used for buttons
}
impl Default for WguiTheme {
fn default() -> Self {
Self {
dark_mode: true,
text_color: drawing::Color::new(1.0, 1.0, 1.0, 1.0),
button_color: drawing::Color::new(1.0, 1.0, 1.0, 0.02),
accent_color: drawing::Color::new(0.13, 0.68, 1.0, 1.0),
danger_color: drawing::Color::new(0.9, 0.0, 0.0, 1.0),
faded_color: drawing::Color::new(0.67, 0.74, 0.80, 1.0),
bg_color: drawing::Color::new(0.0, 0.07, 0.1, 0.75),
editbox_color: drawing::Color::new(0.15, 0.25, 0.35, 0.95),
translucent_alpha: 0.5,
animation_mult: 1.0,
rounding_mult: 1.0,
gradient_intensity: 0.3,
}
}
}

View File

@ -9,8 +9,9 @@ use crate::{
event::CallbackDataCommon,
globals::Globals,
i18n::Translation,
layout::WidgetID,
layout::{LayoutState, WidgetID},
renderer_vk::text::TextStyle,
theme::WguiTheme,
widget::WidgetStateFlags,
};
@ -31,9 +32,13 @@ pub struct WidgetLabel {
}
impl WidgetLabel {
pub fn create(globals: &mut Globals, mut params: WidgetLabelParams) -> WidgetState {
pub fn create(state: &mut LayoutState, params: WidgetLabelParams) -> WidgetState {
WidgetLabel::create_ex(&mut state.globals.get(), &state.theme, params)
}
pub fn create_ex(globals: &mut Globals, theme: &WguiTheme, mut params: WidgetLabelParams) -> WidgetState {
if params.style.color.is_none() {
params.style.color = Some(globals.defaults.text_color);
params.style.color = Some(theme.text_color);
}
let metrics = Metrics::from(&params.style);

View File

@ -324,4 +324,7 @@ pub struct GeneralConfig {
#[serde(default)]
pub handsfree_pointer: HandsfreePointer,
#[serde(default = "def_one")]
pub grid_opacity: f32,
}

View File

@ -9,6 +9,8 @@ use crate::config::GeneralConfig;
pub enum Language {
#[strum(props(Text = "English"))]
English,
#[strum(props(Text = "English(UK)"))]
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",
}
}