From 1724d3969767ad3eed887321189fbc2e53361ff1 Mon Sep 17 00:00:00 2001 From: galister <22305755+galister@users.noreply.github.com> Date: Thu, 11 Dec 2025 01:09:49 +0900 Subject: [PATCH] FileOrBuiltIn asset paths, theming support --- dash-frontend/src/frontend.rs | 7 +-- uidev/src/testbed/testbed_any.rs | 5 +- uidev/src/testbed/testbed_generic.rs | 5 +- wgui/doc/widgets.md | 10 ++-- wgui/src/assets.rs | 18 +++---- wgui/src/globals.rs | 47 ++++++++++++------- wgui/src/parser/component_button.rs | 12 ++--- wgui/src/parser/mod.rs | 8 ++-- wgui/src/parser/widget_sprite.rs | 8 ++-- wlx-common/src/config.rs | 9 +++- wlx-overlay-s/src/gui/panel/mod.rs | 8 ++-- .../src/overlays/keyboard/builder.rs | 12 ++--- wlx-overlay-s/src/state.rs | 4 +- 13 files changed, 90 insertions(+), 63 deletions(-) diff --git a/dash-frontend/src/frontend.rs b/dash-frontend/src/frontend.rs index eba4446..ba7347f 100644 --- a/dash-frontend/src/frontend.rs +++ b/dash-frontend/src/frontend.rs @@ -1,4 +1,4 @@ -use std::{cell::RefCell, rc::Rc}; +use std::{cell::RefCell, path::PathBuf, rc::Rc}; use chrono::Timelike; use glam::Vec2; @@ -18,8 +18,8 @@ use wlx_common::timestep::Timestep; use crate::{ assets, settings, tab::{ - Tab, TabParams, TabType, apps::TabApps, games::TabGames, home::TabHome, monado::TabMonado, processes::TabProcesses, - settings::TabSettings, + apps::TabApps, games::TabGames, home::TabHome, monado::TabMonado, processes::TabProcesses, settings::TabSettings, + Tab, TabParams, TabType, }, task::Tasks, util::{ @@ -96,6 +96,7 @@ impl Frontend { family_name_serif: "Quicksand", family_name_monospace: "", }, + PathBuf::new(), //FIXME: pass from somewhere else )?; let (layout, state) = wgui::parser::new_layout_from_assets( diff --git a/uidev/src/testbed/testbed_any.rs b/uidev/src/testbed/testbed_any.rs index 68cd708..9c8ccae 100644 --- a/uidev/src/testbed/testbed_any.rs +++ b/uidev/src/testbed/testbed_any.rs @@ -1,3 +1,5 @@ +use std::path::PathBuf; + use crate::{ assets, testbed::{Testbed, TestbedUpdateParams}, @@ -21,7 +23,7 @@ pub struct TestbedAny { impl TestbedAny { pub fn new(name: &str) -> anyhow::Result { let path = if name.ends_with(".xml") { - AssetPath::Filesystem(name) + AssetPath::FileOrBuiltIn(name) } else { AssetPath::BuiltIn(&format!("gui/{name}.xml")) }; @@ -30,6 +32,7 @@ impl TestbedAny { Box::new(assets::Asset {}), wgui::globals::Defaults::default(), &WguiFontConfig::default(), + PathBuf::new(), // cwd )?; let (layout, state) = wgui::parser::new_layout_from_assets( diff --git a/uidev/src/testbed/testbed_generic.rs b/uidev/src/testbed/testbed_generic.rs index b2ab604..0356784 100644 --- a/uidev/src/testbed/testbed_generic.rs +++ b/uidev/src/testbed/testbed_generic.rs @@ -1,4 +1,4 @@ -use std::{cell::RefCell, collections::VecDeque, rc::Rc}; +use std::{cell::RefCell, collections::VecDeque, path::PathBuf, rc::Rc}; use crate::{ assets, @@ -8,9 +8,9 @@ use glam::Vec2; use wgui::{ assets::AssetPath, components::{ - Component, button::{ButtonClickCallback, ComponentButton}, checkbox::ComponentCheckbox, + Component, }, drawing::Color, event::StyleSetRequest, @@ -80,6 +80,7 @@ impl TestbedGeneric { Box::new(assets::Asset {}), wgui::globals::Defaults::default(), &WguiFontConfig::default(), + PathBuf::new(), // cwd )?; let extra = ParseDocumentExtra { diff --git a/wgui/doc/widgets.md b/wgui/doc/widgets.md index af2db70..1cde488 100644 --- a/wgui/doc/widgets.md +++ b/wgui/doc/widgets.md @@ -158,12 +158,12 @@ _2nd gradient color_ `src`: **string** +_External (filesystem) image path. Falls back to Internal (assets) if not found._ + +`src_builtin`: **string** + _Internal (assets) image path_ -`src_ext`: **string** - -_External (filesystem) image path_ - `src_internal`: **string** _wgui internal image path. Do not use directly unless it's related to the core wgui assets._ @@ -210,7 +210,7 @@ _Tooltip text on hover, translated by key_ _make button act as a toggle (visual only)_ -`sprite_src` | `sprite_src_ext` | `sprite_src_internal` +`sprite_src` | `sprite_src_builtin` | `sprite_src_internal` _Image path (see [sprite](#sprite-widget)) for src descriptions_ diff --git a/wgui/src/assets.rs b/wgui/src/assets.rs index 289155f..17beec8 100644 --- a/wgui/src/assets.rs +++ b/wgui/src/assets.rs @@ -5,16 +5,16 @@ use std::path::{Component, Path, PathBuf}; #[derive(Clone, Copy)] pub enum AssetPath<'a> { - WguiInternal(&'a str), // tied to internal wgui AssetProvider. Used internally - BuiltIn(&'a str), // tied to user AssetProvider - Filesystem(&'a str), // tied to filesystem path + WguiInternal(&'a str), // tied to internal wgui AssetProvider. Used internally + BuiltIn(&'a str), // tied to user AssetProvider + FileOrBuiltIn(&'a str), // attempts to load from a path relative to asset_folder, falls back to BuiltIn } #[derive(Clone)] pub enum AssetPathOwned { WguiInternal(PathBuf), BuiltIn(PathBuf), - Filesystem(PathBuf), + FileOrBuiltIn(PathBuf), } impl AssetPath<'_> { @@ -22,7 +22,7 @@ impl AssetPath<'_> { match &self { AssetPath::WguiInternal(path) => path, AssetPath::BuiltIn(path) => path, - AssetPath::Filesystem(path) => path, + AssetPath::FileOrBuiltIn(path) => path, } } @@ -30,7 +30,7 @@ impl AssetPath<'_> { match self { AssetPath::WguiInternal(path) => AssetPathOwned::WguiInternal(PathBuf::from(path)), AssetPath::BuiltIn(path) => AssetPathOwned::BuiltIn(PathBuf::from(path)), - AssetPath::Filesystem(path) => AssetPathOwned::Filesystem(PathBuf::from(path)), + AssetPath::FileOrBuiltIn(path) => AssetPathOwned::FileOrBuiltIn(PathBuf::from(path)), } } } @@ -40,7 +40,7 @@ impl AssetPathOwned { match self { AssetPathOwned::WguiInternal(buf) => AssetPath::WguiInternal(buf.to_str().unwrap()), AssetPathOwned::BuiltIn(buf) => AssetPath::BuiltIn(buf.to_str().unwrap()), - AssetPathOwned::Filesystem(buf) => AssetPath::Filesystem(buf.to_str().unwrap()), + AssetPathOwned::FileOrBuiltIn(buf) => AssetPath::FileOrBuiltIn(buf.to_str().unwrap()), } } @@ -48,7 +48,7 @@ impl AssetPathOwned { match self { AssetPathOwned::WguiInternal(buf) => buf, AssetPathOwned::BuiltIn(buf) => buf, - AssetPathOwned::Filesystem(buf) => buf, + AssetPathOwned::FileOrBuiltIn(buf) => buf, } } } @@ -64,7 +64,7 @@ impl AssetPathOwned { match self { AssetPathOwned::WguiInternal(_) => AssetPathOwned::WguiInternal(new_path), AssetPathOwned::BuiltIn(_) => AssetPathOwned::BuiltIn(new_path), - AssetPathOwned::Filesystem(_) => AssetPathOwned::Filesystem(new_path), + AssetPathOwned::FileOrBuiltIn(_) => AssetPathOwned::FileOrBuiltIn(new_path), } } } diff --git a/wgui/src/globals.rs b/wgui/src/globals.rs index 91bc384..37d0a75 100644 --- a/wgui/src/globals.rs +++ b/wgui/src/globals.rs @@ -1,9 +1,12 @@ use std::{ cell::{Ref, RefCell, RefMut}, io::Read, + path::PathBuf, rc::Rc, }; +use anyhow::Context; + use crate::{ assets::{AssetPath, AssetProvider}, assets_internal, drawing, @@ -35,6 +38,7 @@ impl Default for Defaults { 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, @@ -48,6 +52,7 @@ impl WguiGlobals { mut assets_builtin: Box, defaults: Defaults, font_config: &WguiFontConfig, + asset_folder: PathBuf, ) -> anyhow::Result { let i18n_builtin = I18n::new(&mut assets_builtin)?; let assets_internal = Box::new(assets_internal::AssetInternal {}); @@ -57,6 +62,7 @@ impl WguiGlobals { assets_builtin, i18n_builtin, defaults, + asset_folder, font_system: WguiFontSystem::new(font_config), })))) } @@ -65,26 +71,33 @@ impl WguiGlobals { match asset_path { AssetPath::WguiInternal(path) => self.assets_internal().load_from_path(path), AssetPath::BuiltIn(path) => self.assets_builtin().load_from_path(path), - AssetPath::Filesystem(path) => { - let mut file = match std::fs::File::open(path) { - Ok(f) => f, - Err(e) => { - anyhow::bail!("Could not open asset from {path}: {e}"); - } - }; - /* 16 MiB safeguard */ - if file.metadata()?.len() > 16 * 1024 * 1024 { - anyhow::bail!("Could not open asset from {path}: Over size limit (16MiB)"); - } - let mut data = Vec::new(); - if let Err(e) = file.read_to_end(&mut data) { - anyhow::bail!("Could not read asset from {path}: {e}"); - } - Ok(data) - } + AssetPath::FileOrBuiltIn(path) => self + .load_asset_from_fs(path) + .inspect_err(|e| log::debug!("{e:?}")) + .or_else(|_| self.assets_builtin().load_from_path(path)), } } + fn load_asset_from_fs(&self, path: &str) -> anyhow::Result> { + let path = self.0.borrow().asset_folder.join(path); + let mut file = + std::fs::File::open(path.as_path()).with_context(|| format!("Could not open asset from {}", path.display()))?; + + /* 16 MiB safeguard */ + let metadata = file + .metadata() + .with_context(|| format!("Could not get file metadata for {}", path.display()))?; + + if metadata.len() > 16 * 1024 * 1024 { + anyhow::bail!("Could not open asset from {}: Over size limit (16MiB)", path.display()); + } + let mut data = Vec::new(); + file + .read_to_end(&mut data) + .with_context(|| format!("Could not read asset from {}", path.display()))?; + Ok(data) + } + pub fn get(&self) -> RefMut<'_, Globals> { self.0.borrow_mut() } diff --git a/wgui/src/parser/component_button.rs b/wgui/src/parser/component_button.rs index 7eb5f52..db9cf1b 100644 --- a/wgui/src/parser/component_button.rs +++ b/wgui/src/parser/component_button.rs @@ -1,13 +1,13 @@ use crate::{ assets::AssetPath, - components::{Component, button, tooltip}, + components::{button, tooltip, Component}, drawing::Color, i18n::Translation, layout::WidgetID, parser::{ - AttribPair, ParserContext, ParserFile, parse_check_f32, parse_check_i32, parse_children, print_invalid_attrib, - process_component, + parse_check_f32, parse_check_i32, parse_children, print_invalid_attrib, process_component, style::{parse_color_opt, parse_round, parse_style, parse_text_style}, + AttribPair, ParserContext, ParserFile, }, widget::util::WLength, }; @@ -62,10 +62,10 @@ pub fn parse_component_button<'a>( "hover_border_color" => { parse_color_opt(value, &mut hover_border_color); } - "sprite_src" | "sprite_src_ext" | "sprite_src_internal" => { + "sprite_src" | "sprite_src_builtin" | "sprite_src_internal" => { let asset_path = match key { - "sprite_src" => AssetPath::BuiltIn(value), - "sprite_src_ext" => AssetPath::Filesystem(value), + "sprite_src" => AssetPath::FileOrBuiltIn(value), + "sprite_src_builtin" => AssetPath::BuiltIn(value), "sprite_src_internal" => AssetPath::WguiInternal(value), _ => unreachable!(), }; diff --git a/wgui/src/parser/mod.rs b/wgui/src/parser/mod.rs index cc81c1d..f9bebe8 100644 --- a/wgui/src/parser/mod.rs +++ b/wgui/src/parser/mod.rs @@ -551,7 +551,7 @@ fn parse_tag_include( for pair in attribs { #[allow(clippy::single_match)] match pair.attrib.as_ref() { - "src" | "src_ext" | "src_internal" => { + "src" | "src_builtin" | "src_internal" => { path = Some({ let this = &file.path.clone(); let include: &str = &pair.value; @@ -564,9 +564,9 @@ fn parse_tag_include( "src" => match this { AssetPathOwned::WguiInternal(_) => AssetPathOwned::WguiInternal(new_path), AssetPathOwned::BuiltIn(_) => AssetPathOwned::BuiltIn(new_path), - AssetPathOwned::Filesystem(_) => AssetPathOwned::Filesystem(new_path), + AssetPathOwned::FileOrBuiltIn(_) => AssetPathOwned::FileOrBuiltIn(new_path), }, - "src_ext" => AssetPathOwned::Filesystem(new_path), + "src_builtin" => AssetPathOwned::BuiltIn(new_path), "src_internal" => AssetPathOwned::WguiInternal(new_path), _ => unreachable!(), } @@ -583,7 +583,7 @@ fn parse_tag_include( } let Some(path) = path else { - log::warn!("include tag with no source! specify either: src, src_ext, src_internal"); + log::warn!("include tag with no source! specify either: src, src_builtin, src_internal"); return Ok(()); }; let path_ref = path.as_ref(); diff --git a/wgui/src/parser/widget_sprite.rs b/wgui/src/parser/widget_sprite.rs index 9181037..b919a61 100644 --- a/wgui/src/parser/widget_sprite.rs +++ b/wgui/src/parser/widget_sprite.rs @@ -1,7 +1,7 @@ use crate::{ assets::AssetPath, layout::WidgetID, - parser::{AttribPair, ParserContext, ParserFile, parse_children, parse_widget_universal, style::parse_style}, + parser::{parse_children, parse_widget_universal, style::parse_style, AttribPair, ParserContext, ParserFile}, renderer_vk::text::custom_glyph::{CustomGlyphContent, CustomGlyphData}, widget::sprite::{WidgetSprite, WidgetSpriteParams}, }; @@ -22,10 +22,10 @@ pub fn parse_widget_sprite<'a>( for pair in attribs { let (key, value) = (pair.attrib.as_ref(), pair.value.as_ref()); match key { - "src" | "src_ext" | "src_internal" => { + "src" | "src_builtin" | "src_internal" => { let asset_path = match key { - "src" => AssetPath::BuiltIn(value), - "src_ext" => AssetPath::Filesystem(value), + "src" => AssetPath::FileOrBuiltIn(value), + "src_builtin" => AssetPath::BuiltIn(value), "src_internal" => AssetPath::WguiInternal(value), _ => unreachable!(), }; diff --git a/wlx-common/src/config.rs b/wlx-common/src/config.rs index fb5c87b..780a23f 100644 --- a/wlx-common/src/config.rs +++ b/wlx-common/src/config.rs @@ -1,7 +1,7 @@ use std::{collections::HashMap, sync::Arc}; use chrono::Offset; -use glam::{Affine3A, Quat, Vec3, vec3}; +use glam::{vec3, Affine3A, Quat, Vec3}; use idmap::IdMap; use serde::{Deserialize, Serialize}; @@ -116,6 +116,10 @@ fn def_empty() -> Arc { "".into() } +fn def_theme_path() -> Arc { + "theme".into() +} + fn def_toast_topics() -> IdMap { IdMap::new() } @@ -130,6 +134,9 @@ const fn def_max_height() -> u16 { #[derive(Deserialize, Serialize)] pub struct GeneralConfig { + #[serde(default = "def_theme_path")] + pub theme_path: Arc, + #[serde(default = "def_watch_pos")] pub watch_pos: Vec3, diff --git a/wlx-overlay-s/src/gui/panel/mod.rs b/wlx-overlay-s/src/gui/panel/mod.rs index 9d79db3..9edaaba 100644 --- a/wlx-overlay-s/src/gui/panel/mod.rs +++ b/wlx-overlay-s/src/gui/panel/mod.rs @@ -1,7 +1,7 @@ use std::{cell::RefCell, rc::Rc}; use button::setup_custom_button; -use glam::{Affine2, Vec2, vec2}; +use glam::{vec2, Affine2, Vec2}; use label::setup_custom_label; use wgui::{ assets::AssetPath, @@ -15,7 +15,7 @@ use wgui::{ layout::{Layout, LayoutParams, WidgetID}, parser::{CustomAttribsInfoOwned, ParserState}, renderer_vk::context::Context as WguiContext, - widget::{EventResult, label::WidgetLabel, rectangle::WidgetRectangle}, + widget::{label::WidgetLabel, rectangle::WidgetRectangle, EventResult}, }; use wlx_common::timestep::Timestep; @@ -24,7 +24,7 @@ use crate::{ state::AppState, subsystem::hid::WheelDelta, windowing::backend::{ - FrameMeta, OverlayBackend, OverlayEventData, RenderResources, ShouldRender, ui_transform, + ui_transform, FrameMeta, OverlayBackend, OverlayEventData, RenderResources, ShouldRender, }, }; @@ -96,7 +96,7 @@ impl GuiPanel { let doc_params = wgui::parser::ParseDocumentParams { globals: app.wgui_globals.clone(), - path: AssetPath::BuiltIn(path), + path: AssetPath::FileOrBuiltIn(path), extra: wgui::parser::ParseDocumentExtra { on_custom_attribs: Some(Box::new({ let custom_elems = custom_elems.clone(); diff --git a/wlx-overlay-s/src/overlays/keyboard/builder.rs b/wlx-overlay-s/src/overlays/keyboard/builder.rs index 3c4bdb1..e24233c 100644 --- a/wlx-overlay-s/src/overlays/keyboard/builder.rs +++ b/wlx-overlay-s/src/overlays/keyboard/builder.rs @@ -1,6 +1,6 @@ use std::{collections::HashMap, rc::Rc}; -use glam::{Affine3A, FloatExt, Mat4, Quat, Vec2, Vec3, vec2, vec3}; +use glam::{vec2, vec3, Affine3A, FloatExt, Mat4, Quat, Vec2, Vec3}; use wgui::{ animation::{Animation, AnimationEasing}, assets::AssetPath, @@ -11,10 +11,10 @@ use wgui::{ renderer_vk::util, taffy::{self, prelude::length}, widget::{ - EventResult, div::WidgetDiv, rectangle::{WidgetRectangle, WidgetRectangleParams}, util::WLength, + EventResult, }, }; use wlx_common::windowing::{OverlayWindowState, Positioning}; @@ -22,14 +22,14 @@ use wlx_common::windowing::{OverlayWindowState, Positioning}; use crate::{ gui::panel::GuiPanel, state::AppState, - subsystem::hid::{ALT, CTRL, META, SHIFT, SUPER, XkbKeymap}, + subsystem::hid::{XkbKeymap, ALT, CTRL, META, SHIFT, SUPER}, windowing::window::OverlayWindowConfig, }; use super::{ - KEYBOARD_NAME, KeyButtonData, KeyState, KeyboardBackend, KeyboardState, handle_press, - handle_release, + handle_press, handle_release, layout::{self, AltModifier, KeyCapType}, + KeyButtonData, KeyState, KeyboardBackend, KeyboardState, KEYBOARD_NAME, }; const BACKGROUND_PADDING: f32 = 16.0; @@ -83,7 +83,7 @@ pub fn create_keyboard( let parse_doc_params = wgui::parser::ParseDocumentParams { globals, - path: AssetPath::BuiltIn("gui/keyboard.xml"), + path: AssetPath::FileOrBuiltIn("gui/keyboard.xml"), extra: Default::default(), }; diff --git a/wlx-overlay-s/src/state.rs b/wlx-overlay-s/src/state.rs index 846527f..5e691f6 100644 --- a/wlx-overlay-s/src/state.rs +++ b/wlx-overlay-s/src/state.rs @@ -24,7 +24,7 @@ use crate::subsystem::osc::OscSender; use crate::{ backend::{input::InputState, task::TaskContainer}, config::load_general_config, - config_io, + config_io::{self, get_config_file_path}, graphics::WGfxExtras, gui, subsystem::{audio::AudioOutput, input::HidWrapper}, @@ -85,6 +85,7 @@ impl AppState { ); let wgui_shared = WSharedContext::new(gfx.clone())?; + let theme = session.config.theme_path.clone(); Ok(Self { session, @@ -103,6 +104,7 @@ impl AppState { Box::new(gui::asset::GuiAsset {}), wgui::globals::Defaults::default(), &WguiFontConfig::default(), + get_config_file_path(&*theme), )?, #[cfg(feature = "osc")]