From 3dff9c588288385935e5de224c2224525c4334d8 Mon Sep 17 00:00:00 2001 From: Aleksander Date: Sun, 5 Oct 2025 13:48:58 +0200 Subject: [PATCH] wgui: Separate user and wgui assets, topmost widgets (poc) --- Cargo.lock | 1 + dash-frontend/src/lib.rs | 3 +- dash-frontend/src/tab/apps.rs | 3 +- dash-frontend/src/tab/games.rs | 7 +- dash-frontend/src/tab/home.rs | 3 +- dash-frontend/src/tab/monado.rs | 7 +- dash-frontend/src/tab/processes.rs | 7 +- dash-frontend/src/tab/settings.rs | 7 +- dash-frontend/src/util/desktop_finder.rs | 2 + uidev/assets/gui/various_widgets.xml | 5 +- uidev/src/main.rs | 2 +- uidev/src/testbed/testbed_any.rs | 5 +- uidev/src/testbed/testbed_generic.rs | 120 ++++++++++++++++-- wgui/Cargo.toml | 1 + wgui/assets/wgui/close.svg | 3 + wgui/assets/wgui/window_frame.xml | 25 ++++ wgui/doc/widgets.md | 6 +- wgui/src/assets.rs | 72 +++++++++++ wgui/src/assets_internal.rs | 12 ++ wgui/src/drawing.rs | 20 ++- wgui/src/gfx/pipeline.rs | 24 ++-- wgui/src/globals.rs | 51 ++++++-- wgui/src/layout.rs | 67 +++++++--- wgui/src/lib.rs | 1 + wgui/src/parser/mod.rs | 58 ++++----- wgui/src/parser/widget_sprite.rs | 17 ++- wgui/src/renderer_vk/context.rs | 3 + wgui/src/renderer_vk/text/custom_glyph.rs | 30 +---- wgui/src/widget/label.rs | 2 +- wlx-overlay-s/src/gui/panel/mod.rs | 7 +- .../src/overlays/keyboard/builder.rs | 13 +- wlx-overlay-s/src/overlays/toast.rs | 9 +- 32 files changed, 442 insertions(+), 151 deletions(-) create mode 100644 wgui/assets/wgui/close.svg create mode 100644 wgui/assets/wgui/window_frame.xml create mode 100644 wgui/src/assets_internal.rs diff --git a/Cargo.lock b/Cargo.lock index bfba3eb..5f07720 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6285,6 +6285,7 @@ dependencies = [ "regex", "resvg", "roxmltree 0.20.0", + "rust-embed", "rustc-hash 2.1.1", "serde_json", "slotmap", diff --git a/dash-frontend/src/lib.rs b/dash-frontend/src/lib.rs index 16b946a..383e840 100644 --- a/dash-frontend/src/lib.rs +++ b/dash-frontend/src/lib.rs @@ -3,6 +3,7 @@ use std::{cell::RefCell, collections::VecDeque, rc::Rc}; use chrono::Timelike; use glam::Vec2; use wgui::{ + assets::AssetPath, components::button::ComponentButton, event::{CallbackDataCommon, EventAlterables, EventListenerCollection}, globals::WguiGlobals, @@ -56,7 +57,7 @@ impl Frontend { params.listeners, &ParseDocumentParams { globals: globals.clone(), - path: "gui/dashboard.xml", + path: AssetPath::BuiltIn("gui/dashboard.xml"), extra: Default::default(), }, &LayoutParams { resize_to_parent: true }, diff --git a/dash-frontend/src/tab/apps.rs b/dash-frontend/src/tab/apps.rs index 22d1744..67573d7 100644 --- a/dash-frontend/src/tab/apps.rs +++ b/dash-frontend/src/tab/apps.rs @@ -1,6 +1,7 @@ use std::{collections::HashMap, rc::Rc}; use wgui::{ + assets::AssetPath, components::button::ComponentButton, layout::WidgetPair, parser::{Fetchable, ParseDocumentParams, ParserData, ParserState}, @@ -36,7 +37,7 @@ impl TabApps { pub fn new(mut tab_params: TabParams) -> anyhow::Result { let doc_params = &ParseDocumentParams { globals: tab_params.globals.clone(), - path: "gui/tab/apps.xml", + path: AssetPath::BuiltIn("gui/tab/apps.xml"), extra: Default::default(), }; diff --git a/dash-frontend/src/tab/games.rs b/dash-frontend/src/tab/games.rs index a898383..6107717 100644 --- a/dash-frontend/src/tab/games.rs +++ b/dash-frontend/src/tab/games.rs @@ -1,4 +1,7 @@ -use wgui::parser::{ParseDocumentParams, ParserState}; +use wgui::{ + assets::AssetPath, + parser::{ParseDocumentParams, ParserState}, +}; use crate::tab::{Tab, TabParams, TabType}; @@ -18,7 +21,7 @@ impl TabGames { let state = wgui::parser::parse_from_assets( &ParseDocumentParams { globals: params.globals.clone(), - path: "gui/tab/games.xml", + path: AssetPath::BuiltIn("gui/tab/games.xml"), extra: Default::default(), }, params.layout, diff --git a/dash-frontend/src/tab/home.rs b/dash-frontend/src/tab/home.rs index 0d90db5..9d6c6dd 100644 --- a/dash-frontend/src/tab/home.rs +++ b/dash-frontend/src/tab/home.rs @@ -1,4 +1,5 @@ use wgui::{ + assets::AssetPath, components::button::ComponentButton, i18n::Translation, parser::{Fetchable, ParseDocumentParams, ParserState}, @@ -38,7 +39,7 @@ impl TabHome { let state = wgui::parser::parse_from_assets( &ParseDocumentParams { globals: params.globals.clone(), - path: "gui/tab/home.xml", + path: AssetPath::BuiltIn("gui/tab/home.xml"), extra: Default::default(), }, params.layout, diff --git a/dash-frontend/src/tab/monado.rs b/dash-frontend/src/tab/monado.rs index c5ae2ae..d59c47e 100644 --- a/dash-frontend/src/tab/monado.rs +++ b/dash-frontend/src/tab/monado.rs @@ -1,4 +1,7 @@ -use wgui::parser::{ParseDocumentParams, ParserState}; +use wgui::{ + assets::AssetPath, + parser::{ParseDocumentParams, ParserState}, +}; use crate::tab::{Tab, TabParams, TabType}; @@ -18,7 +21,7 @@ impl TabMonado { let state = wgui::parser::parse_from_assets( &ParseDocumentParams { globals: params.globals.clone(), - path: "gui/tab/monado.xml", + path: AssetPath::BuiltIn("gui/tab/monado.xml"), extra: Default::default(), }, params.layout, diff --git a/dash-frontend/src/tab/processes.rs b/dash-frontend/src/tab/processes.rs index 9634365..f1b6580 100644 --- a/dash-frontend/src/tab/processes.rs +++ b/dash-frontend/src/tab/processes.rs @@ -1,4 +1,7 @@ -use wgui::parser::{ParseDocumentParams, ParserState}; +use wgui::{ + assets::AssetPath, + parser::{ParseDocumentParams, ParserState}, +}; use crate::tab::{Tab, TabParams, TabType}; @@ -18,7 +21,7 @@ impl TabProcesses { let state = wgui::parser::parse_from_assets( &ParseDocumentParams { globals: params.globals.clone(), - path: "gui/tab/processes.xml", + path: AssetPath::BuiltIn("gui/tab/processes.xml"), extra: Default::default(), }, params.layout, diff --git a/dash-frontend/src/tab/settings.rs b/dash-frontend/src/tab/settings.rs index dd6b253..f8c7184 100644 --- a/dash-frontend/src/tab/settings.rs +++ b/dash-frontend/src/tab/settings.rs @@ -1,4 +1,7 @@ -use wgui::parser::{ParseDocumentParams, ParserState}; +use wgui::{ + assets::AssetPath, + parser::{ParseDocumentParams, ParserState}, +}; use crate::tab::{Tab, TabParams, TabType}; @@ -18,7 +21,7 @@ impl TabSettings { let state = wgui::parser::parse_from_assets( &ParseDocumentParams { globals: params.globals.clone(), - path: "gui/tab/settings.xml", + path: AssetPath::BuiltIn("gui/tab/settings.xml"), extra: Default::default(), }, params.layout, diff --git a/dash-frontend/src/util/desktop_finder.rs b/dash-frontend/src/util/desktop_finder.rs index 42327a4..bdb6caa 100644 --- a/dash-frontend/src/util/desktop_finder.rs +++ b/dash-frontend/src/util/desktop_finder.rs @@ -2,6 +2,7 @@ use gio::prelude::{AppInfoExt, IconExt}; use gtk::traits::IconThemeExt; #[derive(Debug)] +#[allow(dead_code)] // TODO: remove this pub struct DesktopEntry { pub exec_path: String, pub exec_args: Vec, @@ -10,6 +11,7 @@ pub struct DesktopEntry { pub categories: Vec, } +#[allow(dead_code)] // TODO: remove this pub struct EntrySearchCell { pub exec_path: String, pub exec_args: Vec, diff --git a/uidev/assets/gui/various_widgets.xml b/uidev/assets/gui/various_widgets.xml index b8ba9b8..8501b42 100644 --- a/uidev/assets/gui/various_widgets.xml +++ b/uidev/assets/gui/various_widgets.xml @@ -1,15 +1,16 @@ + + \ No newline at end of file diff --git a/wgui/doc/widgets.md b/wgui/doc/widgets.md index e2c95d8..4f21959 100644 --- a/wgui/doc/widgets.md +++ b/wgui/doc/widgets.md @@ -152,7 +152,11 @@ _Internal (assets) image path_ `src_ext`: **string** -_External image path_ +_External (filesystem) image path_ + +`src_internal`: **string** + +_wgui internal image path. Do not use directly unless it's related to the core wgui assets._ --- diff --git a/wgui/src/assets.rs b/wgui/src/assets.rs index dd2e822..b70d26c 100644 --- a/wgui/src/assets.rs +++ b/wgui/src/assets.rs @@ -1,5 +1,77 @@ use std::path::{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 +} + +#[derive(Clone)] +pub enum AssetPathOwned { + WguiInternal(PathBuf), + BuiltIn(PathBuf), + Filesystem(PathBuf), +} + +impl AssetPath<'_> { + pub const fn get_str(&self) -> &str { + match &self { + AssetPath::WguiInternal(path) => path, + AssetPath::BuiltIn(path) => path, + AssetPath::Filesystem(path) => path, + } + } + + pub fn to_owned(&self) -> AssetPathOwned { + 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)), + } + } +} + +impl AssetPathOwned { + pub fn as_ref(&'_ self) -> AssetPath<'_> { + 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()), + } + } + + pub const fn get_path_buf(&self) -> &PathBuf { + match self { + AssetPathOwned::WguiInternal(buf) => buf, + AssetPathOwned::BuiltIn(buf) => buf, + AssetPathOwned::Filesystem(buf) => buf, + } + } +} + +impl AssetPathOwned { + #[must_use] + pub fn push_include(&self, include: &str) -> AssetPathOwned { + let buf = self.get_path_buf(); + let mut new_path = buf.parent().unwrap_or_else(|| Path::new("/")).to_path_buf(); + new_path.push(include); + let new_path = normalize_path(&new_path); + + match self { + AssetPathOwned::WguiInternal(_) => AssetPathOwned::WguiInternal(new_path), + AssetPathOwned::BuiltIn(_) => AssetPathOwned::BuiltIn(new_path), + AssetPathOwned::Filesystem(_) => AssetPathOwned::Filesystem(new_path), + } + } +} + +impl Default for AssetPathOwned { + fn default() -> Self { + Self::WguiInternal(PathBuf::default()) + } +} + pub trait AssetProvider { fn load_from_path(&mut self, path: &str) -> anyhow::Result>; } diff --git a/wgui/src/assets_internal.rs b/wgui/src/assets_internal.rs new file mode 100644 index 0000000..1b0c848 --- /dev/null +++ b/wgui/src/assets_internal.rs @@ -0,0 +1,12 @@ +#[derive(rust_embed::Embed)] +#[folder = "assets/"] +pub struct AssetInternal; + +impl crate::assets::AssetProvider for AssetInternal { + fn load_from_path(&mut self, path: &str) -> anyhow::Result> { + match AssetInternal::get(path) { + Some(data) => Ok(data.data.to_vec()), + None => anyhow::bail!("internal file {path} not found"), + } + } +} diff --git a/wgui/src/drawing.rs b/wgui/src/drawing.rs index 4f96334..be15d5f 100644 --- a/wgui/src/drawing.rs +++ b/wgui/src/drawing.rs @@ -146,6 +146,7 @@ pub struct PrimitiveExtent { } pub enum RenderPrimitive { + NewPass, Rectangle(PrimitiveExtent, Rectangle), Text(PrimitiveExtent, Rc>, Option), Sprite(PrimitiveExtent, Option), //option because we want as_slice @@ -276,7 +277,7 @@ fn draw_widget( widget_state.draw_all(state, &draw_params); - draw_children(params, state, node_id); + draw_children(params, state, node_id, false); if scissor_pushed { state.scissor_stack.pop(); @@ -296,7 +297,7 @@ fn draw_widget( } } -fn draw_children(params: &DrawParams, state: &mut DrawState, parent_node_id: taffy::NodeId) { +fn draw_children(params: &DrawParams, state: &mut DrawState, parent_node_id: taffy::NodeId, is_topmost: bool) { let layout = ¶ms.layout; for node_id in layout.state.tree.child_ids(parent_node_id) { @@ -316,6 +317,10 @@ fn draw_children(params: &DrawParams, state: &mut DrawState, parent_node_id: taf }; draw_widget(params, state, node_id, style, widget); + + if is_topmost { + state.primitives.push(RenderPrimitive::NewPass); + } } } @@ -324,14 +329,6 @@ pub fn draw(params: &mut DrawParams) -> anyhow::Result> { let mut transform_stack = TransformStack::new(); let mut scissor_stack = ScissorStack::new(); - let Some(root_widget) = params.layout.state.widgets.get(params.layout.root_widget) else { - panic!(); - }; - - let Ok(style) = params.layout.state.tree.style(params.layout.root_node) else { - panic!(); - }; - let mut alterables = EventAlterables::default(); let mut state = DrawState { @@ -342,7 +339,8 @@ pub fn draw(params: &mut DrawParams) -> anyhow::Result> { alterables: &mut alterables, }; - draw_widget(params, &mut state, params.layout.root_node, style, root_widget); + draw_children(params, &mut state, params.layout.tree_root_node, true); + params.layout.process_alterables(alterables)?; Ok(primitives) diff --git a/wgui/src/gfx/pipeline.rs b/wgui/src/gfx/pipeline.rs index 5949954..c22b4c0 100644 --- a/wgui/src/gfx/pipeline.rs +++ b/wgui/src/gfx/pipeline.rs @@ -1,14 +1,14 @@ use std::{marker::PhantomData, ops::Range, sync::Arc}; -use smallvec::{smallvec, SmallVec}; +use smallvec::{SmallVec, smallvec}; use vulkano::{ buffer::{ - allocator::{SubbufferAllocator, SubbufferAllocatorCreateInfo}, BufferContents, BufferUsage, Subbuffer, + allocator::{SubbufferAllocator, SubbufferAllocatorCreateInfo}, }, descriptor_set::{ - layout::{DescriptorBindingFlags, DescriptorSetLayoutCreateFlags}, DescriptorSet, WriteDescriptorSet, + layout::{DescriptorBindingFlags, DescriptorSetLayoutCreateFlags}, }, format::Format, image::{ @@ -17,8 +17,9 @@ use vulkano::{ }, memory::allocator::MemoryTypeFilter, pipeline::{ + DynamicState, GraphicsPipeline, Pipeline, PipelineLayout, graphics::{ - self, + self, GraphicsPipelineCreateInfo, color_blend::{AttachmentBlend, ColorBlendAttachmentState, ColorBlendState}, input_assembly::{InputAssemblyState, PrimitiveTopology}, multisample::MultisampleState, @@ -26,15 +27,13 @@ use vulkano::{ subpass::PipelineRenderingCreateInfo, vertex_input::{Vertex, VertexDefinition, VertexInputState}, viewport::ViewportState, - GraphicsPipelineCreateInfo, }, layout::PipelineDescriptorSetLayoutCreateInfo, - DynamicState, GraphicsPipeline, Pipeline, PipelineLayout, }, shader::{EntryPoint, ShaderModule}, }; -use super::{pass::WGfxPass, WGfx}; +use super::{WGfx, pass::WGfxPass}; pub struct WGfxPipeline { pub graphics: Arc, @@ -47,6 +46,7 @@ impl WGfxPipeline where V: Sized, { + #[allow(clippy::too_many_arguments)] fn new_from_stages( graphics: Arc, format: Format, @@ -207,21 +207,25 @@ impl WPipelineCreateInfo { } } - pub fn use_blend(mut self, blend: AttachmentBlend) -> Self { + #[must_use] + pub const fn use_blend(mut self, blend: AttachmentBlend) -> Self { self.blend = Some(blend); self } - pub fn use_topology(mut self, topology: PrimitiveTopology) -> Self { + #[must_use] + pub const fn use_topology(mut self, topology: PrimitiveTopology) -> Self { self.topology = topology; self } - pub fn use_instanced(mut self) -> Self { + #[must_use] + pub const fn use_instanced(mut self) -> Self { self.instanced = true; self } + #[must_use] pub fn use_updatable_descriptors(mut self, updatable_sets: SmallVec<[usize; 8]>) -> Self { self.updatable_sets = updatable_sets; self diff --git a/wgui/src/globals.rs b/wgui/src/globals.rs index 035fff7..d0219dc 100644 --- a/wgui/src/globals.rs +++ b/wgui/src/globals.rs @@ -1,9 +1,14 @@ use std::{ cell::{RefCell, RefMut}, + io::Read, rc::Rc, }; -use crate::{assets::AssetProvider, drawing, i18n::I18n}; +use crate::{ + assets::{AssetPath, AssetProvider}, + assets_internal, drawing, + i18n::I18n, +}; pub struct Defaults { pub dark_mode: bool, @@ -22,8 +27,9 @@ impl Default for Defaults { } pub struct Globals { - pub assets: Box, - pub i18n: I18n, + pub assets_internal: Box, + pub assets_builtin: Box, + pub i18n_builtin: I18n, pub defaults: Defaults, } @@ -31,10 +37,33 @@ pub struct Globals { pub struct WguiGlobals(Rc>); impl WguiGlobals { - pub fn new(mut assets: Box, defaults: Defaults) -> anyhow::Result { - let i18n = I18n::new(&mut assets)?; + pub fn new(mut assets_builtin: Box, defaults: Defaults) -> anyhow::Result { + let i18n_builtin = I18n::new(&mut assets_builtin)?; + let assets_internal = Box::new(assets_internal::AssetInternal {}); - Ok(Self(Rc::new(RefCell::new(Globals { assets, i18n, defaults })))) + Ok(Self(Rc::new(RefCell::new(Globals { + assets_internal, + assets_builtin, + i18n_builtin, + defaults, + })))) + } + + pub fn get_asset(&self, asset_path: AssetPath) -> anyhow::Result> { + 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 = std::fs::File::open(path)?; + /* 16 MiB safeguard */ + if file.metadata()?.len() > 16 * 1024 * 1024 { + anyhow::bail!("Too large file size"); + } + let mut data = Vec::new(); + file.read_to_end(&mut data)?; + Ok(data) + } + } } pub fn get(&self) -> RefMut<'_, Globals> { @@ -42,10 +71,14 @@ impl WguiGlobals { } pub fn i18n(&self) -> RefMut<'_, I18n> { - RefMut::map(self.0.borrow_mut(), |x| &mut x.i18n) + RefMut::map(self.0.borrow_mut(), |x| &mut x.i18n_builtin) } - pub fn assets(&self) -> RefMut<'_, Box> { - RefMut::map(self.0.borrow_mut(), |x| &mut x.assets) + pub fn assets_internal(&self) -> RefMut<'_, Box> { + RefMut::map(self.0.borrow_mut(), |x| &mut x.assets_internal) + } + + pub fn assets_builtin(&self) -> RefMut<'_, Box> { + RefMut::map(self.0.borrow_mut(), |x| &mut x.assets_builtin) } } diff --git a/wgui/src/layout.rs b/wgui/src/layout.rs index 4b89be9..b0895bb 100644 --- a/wgui/src/layout.rs +++ b/wgui/src/layout.rs @@ -6,14 +6,14 @@ use std::{ use crate::{ animation::Animations, components::{Component, InitData}, - drawing::{push_scissor_stack, push_transform_stack, Boundary}, + drawing::{Boundary, push_scissor_stack, push_transform_stack}, event::{self, CallbackDataCommon, EventAlterables, EventListenerCollection}, globals::WguiGlobals, - widget::{self, div::WidgetDiv, EventParams, WidgetObj, WidgetState}, + widget::{self, EventParams, WidgetObj, WidgetState, div::WidgetDiv}, }; -use glam::{vec2, Vec2}; -use slotmap::{new_key_type, HopSlotMap, SecondaryMap}; +use glam::{Vec2, vec2}; +use slotmap::{HopSlotMap, SecondaryMap, new_key_type}; use taffy::{NodeId, TaffyTree, TraversePartialTree}; new_key_type! { @@ -113,8 +113,15 @@ pub struct Layout { pub components_to_init: Vec, pub widgets_to_tick: Vec, - pub root_widget: WidgetID, - pub root_node: taffy::NodeId, + // *Main root* + // contains content_root_widget and topmost widgets + pub tree_root_widget: WidgetID, + pub tree_root_node: taffy::NodeId, + + // *Main topmost widget* + // main topmost widget, always present, parent of `tree_root_widget` + pub content_root_widget: WidgetID, + pub content_root_node: taffy::NodeId, pub prev_size: Vec2, pub content_size: Vec2, @@ -165,6 +172,22 @@ impl Layout { Rc::new(RefCell::new(self)) } + pub fn add_topmost_child( + &mut self, + widget: WidgetState, + style: taffy::Style, + ) -> anyhow::Result<(WidgetPair, taffy::NodeId)> { + self.mark_redraw(); + add_child_internal( + &mut self.state.tree, + &mut self.state.widgets, + &mut self.state.nodes, + Some(self.tree_root_node), + widget, + style, + ) + } + pub fn add_child( &mut self, parent_widget_id: WidgetID, @@ -217,7 +240,7 @@ impl Layout { self.needs_redraw = true; } - fn process_pending_components(&mut self, alterables: &mut EventAlterables) -> anyhow::Result<()> { + fn process_pending_components(&mut self, alterables: &mut EventAlterables) { for comp in &self.components_to_init { let mut common = CallbackDataCommon { state: &self.state, @@ -227,7 +250,6 @@ impl Layout { comp.0.init(&mut InitData { common: &mut common }); } self.components_to_init.clear(); - Ok(()) } fn process_pending_widget_ticks(&mut self, alterables: &mut EventAlterables) { @@ -358,9 +380,7 @@ impl Layout { mut user_data: (&mut U1, &mut U2), ) -> anyhow::Result<()> { let mut alterables = EventAlterables::default(); - - self.push_event_widget(listeners, self.root_node, event, &mut alterables, &mut user_data)?; - + self.push_event_widget(listeners, self.tree_root_node, event, &mut alterables, &mut user_data)?; self.process_alterables(alterables)?; listeners.gc(); @@ -376,7 +396,7 @@ impl Layout { globals, }; - let (root_widget, root_node) = add_child_internal( + let (tree_root_widget, tree_root_node) = add_child_internal( &mut state.tree, &mut state.widgets, &mut state.nodes, @@ -392,12 +412,23 @@ impl Layout { }, )?; + let (content_root_widget, content_root_node) = add_child_internal( + &mut state.tree, + &mut state.widgets, + &mut state.nodes, + Some(tree_root_node), + WidgetDiv::create(), + taffy::Style::default(), + )?; + Ok(Self { state, prev_size: Vec2::default(), content_size: Vec2::default(), - root_node, - root_widget: root_widget.id, + tree_root_node, + tree_root_widget: tree_root_widget.id, + content_root_node, + content_root_widget: content_root_widget.id, needs_redraw: true, haptics_triggered: false, animations: Animations::default(), @@ -407,7 +438,7 @@ impl Layout { } fn try_recompute_layout(&mut self, size: Vec2) -> anyhow::Result<()> { - if !self.state.tree.dirty(self.root_node)? && self.prev_size == size { + if !self.state.tree.dirty(self.tree_root_node)? && self.prev_size == size { // Nothing to do return Ok(()); } @@ -417,7 +448,7 @@ impl Layout { self.prev_size = size; self.state.tree.compute_layout_with_measure( - self.root_node, + self.tree_root_node, taffy::Size { width: taffy::AvailableSpace::Definite(size.x), height: taffy::AvailableSpace::Definite(size.y), @@ -443,7 +474,7 @@ impl Layout { } }, )?; - let root_size = self.state.tree.layout(self.root_node).unwrap().size; + let root_size = self.state.tree.layout(self.tree_root_node).unwrap().size; if self.content_size.x != root_size.width || self.content_size.y != root_size.height { log::debug!( "content size changed: {:.0}x{:.0} → {:.0}x{:.0}", @@ -470,7 +501,7 @@ impl Layout { pub fn tick(&mut self) -> anyhow::Result<()> { let mut alterables = EventAlterables::default(); self.animations.tick(&self.state, &mut alterables); - self.process_pending_components(&mut alterables)?; + self.process_pending_components(&mut alterables); self.process_pending_widget_ticks(&mut alterables); self.process_alterables(alterables)?; Ok(()) diff --git a/wgui/src/lib.rs b/wgui/src/lib.rs index 2ef7cf4..415d900 100644 --- a/wgui/src/lib.rs +++ b/wgui/src/lib.rs @@ -23,6 +23,7 @@ pub mod animation; pub mod any; pub mod assets; +mod assets_internal; pub mod components; pub mod drawing; pub mod event; diff --git a/wgui/src/parser/mod.rs b/wgui/src/parser/mod.rs index 34dc228..5c71158 100644 --- a/wgui/src/parser/mod.rs +++ b/wgui/src/parser/mod.rs @@ -8,7 +8,7 @@ mod widget_rectangle; mod widget_sprite; use crate::{ - assets::{self, AssetProvider}, + assets::{AssetPath, AssetPathOwned, normalize_path}, components::{Component, ComponentWeak}, drawing::{self}, event::EventListenerCollection, @@ -22,12 +22,7 @@ use crate::{ }; use ouroboros::self_referencing; use smallvec::SmallVec; -use std::{ - cell::RefMut, - collections::HashMap, - path::{Path, PathBuf}, - rc::Rc, -}; +use std::{cell::RefMut, collections::HashMap, path::Path, rc::Rc}; #[self_referencing] struct XmlDocument { @@ -44,7 +39,7 @@ pub struct Template { } struct ParserFile { - path: PathBuf, + path: AssetPathOwned, document: Rc, template_parameters: HashMap, Rc>, } @@ -201,7 +196,7 @@ impl Fetchable for ParserData { #[derive(Default)] pub struct ParserState { pub data: ParserData, - pub path: PathBuf, + pub path: AssetPathOwned, } impl ParserState { @@ -558,11 +553,22 @@ fn parse_tag_include( #[allow(clippy::single_match)] match pair.attrib.as_ref() { "src" => { - let mut new_path = file.path.parent().unwrap_or_else(|| Path::new("/")).to_path_buf(); - new_path.push(pair.value.as_ref()); - let new_path = assets::normalize_path(&new_path); + let new_path = { + let this = &file.path.clone(); + let include: &str = &pair.value; + let buf = this.get_path_buf(); + let mut new_path = buf.parent().unwrap_or_else(|| Path::new("/")).to_path_buf(); + new_path.push(include); + let new_path = normalize_path(&new_path); - let (new_file, node_layout) = get_doc_from_path(ctx, &new_path)?; + match this { + AssetPathOwned::WguiInternal(_) => AssetPathOwned::WguiInternal(new_path), + AssetPathOwned::BuiltIn(_) => AssetPathOwned::BuiltIn(new_path), + AssetPathOwned::Filesystem(_) => AssetPathOwned::Filesystem(new_path), + } + }; + let new_path_ref = new_path.as_ref(); + let (new_file, node_layout) = get_doc_from_asset_path(ctx, new_path_ref)?; parse_document_root(&new_file, ctx, parent_id, node_layout)?; return Ok(()); @@ -992,7 +998,7 @@ pub struct ParseDocumentExtra { // filled-in by you in `new_layout_from_assets` function pub struct ParseDocumentParams<'a> { pub globals: WguiGlobals, // mandatory field - pub path: &'a str, // mandatory field + pub path: AssetPath<'a>, // mandatory field pub extra: ParseDocumentExtra, // optional field, can be Default-ed } @@ -1002,18 +1008,15 @@ pub fn parse_from_assets( listeners: &mut EventListenerCollection, parent_id: WidgetID, ) -> anyhow::Result { - let path = PathBuf::from(doc_params.path); - let parser_data = ParserData::default(); let mut ctx = create_default_context(doc_params, layout, listeners, &parser_data); - - let (file, node_layout) = get_doc_from_path(&ctx, &path)?; + let (file, node_layout) = get_doc_from_asset_path(&ctx, doc_params.path)?; parse_document_root(&file, &mut ctx, parent_id, node_layout)?; // move everything essential to the result let result = ParserState { data: std::mem::take(&mut ctx.data_local), - path, + path: doc_params.path.to_owned(), }; drop(ctx); @@ -1027,21 +1030,18 @@ pub fn new_layout_from_assets( layout_params: &LayoutParams, ) -> anyhow::Result<(Layout, ParserState)> { let mut layout = Layout::new(doc_params.globals.clone(), layout_params)?; - let widget = layout.root_widget; + let widget = layout.content_root_widget; let state = parse_from_assets(doc_params, &mut layout, listeners, widget)?; Ok((layout, state)) } -fn assets_path_to_xml(assets: &mut Box, path: &Path) -> anyhow::Result { - let data = assets.load_from_path(&path.to_string_lossy())?; - Ok(String::from_utf8(data)?) -} - -fn get_doc_from_path( +fn get_doc_from_asset_path( ctx: &ParserContext, - path: &Path, + asset_path: AssetPath, ) -> anyhow::Result<(ParserFile, roxmltree::NodeId)> { - let xml = assets_path_to_xml(&mut ctx.layout.state.globals.assets(), path)?; + let data = ctx.layout.state.globals.get_asset(asset_path)?; + let xml = String::from_utf8(data)?; + let document = Rc::new(XmlDocument::new(xml, |xml| { let opt = roxmltree::ParsingOptions { allow_dtd: true, @@ -1054,7 +1054,7 @@ fn get_doc_from_path( let tag_layout = require_tag_by_name(&root, "layout")?; let file = ParserFile { - path: PathBuf::from(path), + path: asset_path.to_owned(), document: document.clone(), template_parameters: Default::default(), }; diff --git a/wgui/src/parser/widget_sprite.rs b/wgui/src/parser/widget_sprite.rs index e64c781..dd72f7e 100644 --- a/wgui/src/parser/widget_sprite.rs +++ b/wgui/src/parser/widget_sprite.rs @@ -1,4 +1,5 @@ use crate::{ + assets::AssetPath, layout::WidgetID, parser::{AttribPair, ParserContext, ParserFile, parse_children, parse_widget_universal, style::parse_style}, renderer_vk::text::custom_glyph::{CustomGlyphContent, CustomGlyphData}, @@ -21,9 +22,16 @@ pub fn parse_widget_sprite<'a, U1, U2>( for pair in attribs { let (key, value) = (pair.attrib.as_ref(), pair.value.as_ref()); match key { - "src" => { + "src" | "src_ext" | "src_internal" => { + let asset_path = match key { + "src" => AssetPath::BuiltIn(value), + "src_ext" => AssetPath::Filesystem(value), + "src_internal" => AssetPath::WguiInternal(value), + _ => unreachable!(), + }; + if !value.is_empty() { - glyph = match CustomGlyphContent::from_assets(&mut ctx.layout.state.globals.assets(), value) { + glyph = match CustomGlyphContent::from_assets(&mut ctx.layout.state.globals, asset_path) { Ok(glyph) => Some(glyph), Err(e) => { log::warn!("failed to load {value}: {e}"); @@ -32,11 +40,6 @@ pub fn parse_widget_sprite<'a, U1, U2>( } } } - "src_ext" => { - if !value.is_empty() && std::fs::exists(value).unwrap_or(false) { - glyph = CustomGlyphContent::from_file(value).ok(); - } - } "color" => { if let Some(color) = parse_color_hex(value) { params.color = Some(color); diff --git a/wgui/src/renderer_vk/context.rs b/wgui/src/renderer_vk/context.rs index 78bdd8b..2a20aea 100644 --- a/wgui/src/renderer_vk/context.rs +++ b/wgui/src/renderer_vk/context.rs @@ -243,6 +243,9 @@ impl Context { let pass = passes.last_mut().unwrap(); // always safe match &primitive { + drawing::RenderPrimitive::NewPass => { + needs_new_pass = true; + } drawing::RenderPrimitive::Rectangle(extent, rectangle) => { pass .rect_renderer diff --git a/wgui/src/renderer_vk/text/custom_glyph.rs b/wgui/src/renderer_vk/text/custom_glyph.rs index 8241286..600942f 100644 --- a/wgui/src/renderer_vk/text/custom_glyph.rs +++ b/wgui/src/renderer_vk/text/custom_glyph.rs @@ -10,7 +10,7 @@ use cosmic_text::SubpixelBin; use image::RgbaImage; use resvg::usvg::{Options, Tree}; -use crate::assets::AssetProvider; +use crate::{assets::AssetPath, globals::WguiGlobals}; static AUTO_INCREMENT: AtomicUsize = AtomicUsize::new(0); @@ -32,19 +32,10 @@ impl CustomGlyphContent { } #[allow(clippy::case_sensitive_file_extension_comparisons)] - pub fn from_assets(provider: &mut Box, path: &str) -> anyhow::Result { - let data = provider.load_from_path(path)?; - if path.ends_with(".svg") || path.ends_with(".svgz") { - Ok(Self::from_bin_svg(&data)?) - } else { - Ok(Self::from_bin_raster(&data)?) - } - } - - #[allow(clippy::case_sensitive_file_extension_comparisons)] - pub fn from_file(path: &str) -> anyhow::Result { - let data = std::fs::read(path)?; - if path.ends_with(".svg") || path.ends_with(".svgz") { + pub fn from_assets(globals: &mut WguiGlobals, path: AssetPath) -> anyhow::Result { + let path_str = path.get_str(); + let data = globals.get_asset(path)?; + if path_str.ends_with(".svg") || path_str.ends_with(".svgz") { Ok(Self::from_bin_svg(&data)?) } else { Ok(Self::from_bin_raster(&data)?) @@ -165,11 +156,7 @@ impl RasterizedCustomGlyph { } } - pub(super) fn validate( - &self, - input: &RasterizeCustomGlyphRequest, - expected_type: Option, - ) { + pub(super) fn validate(&self, input: &RasterizeCustomGlyphRequest, expected_type: Option) { if let Some(expected_type) = expected_type { assert_eq!( self.content_type, expected_type, @@ -222,10 +209,7 @@ impl ContentType { } } -fn rasterize_svg( - tree: &Tree, - input: &RasterizeCustomGlyphRequest, -) -> Option { +fn rasterize_svg(tree: &Tree, input: &RasterizeCustomGlyphRequest) -> Option { // Calculate the scale based on the "glyph size". let svg_size = tree.size(); let scale_x = f32::from(input.width) / svg_size.width(); diff --git a/wgui/src/widget/label.rs b/wgui/src/widget/label.rs index fc8b4cf..79ca803 100644 --- a/wgui/src/widget/label.rs +++ b/wgui/src/widget/label.rs @@ -46,7 +46,7 @@ impl WidgetLabel { buffer.set_wrap(wrap); buffer.set_rich_text( - [(params.content.generate(&mut globals.i18n).as_ref(), attrs)], + [(params.content.generate(&mut globals.i18n_builtin).as_ref(), attrs)], &Attrs::new(), Shaping::Advanced, params.style.align.map(Into::into), diff --git a/wlx-overlay-s/src/gui/panel/mod.rs b/wlx-overlay-s/src/gui/panel/mod.rs index 8229939..a0597ed 100644 --- a/wlx-overlay-s/src/gui/panel/mod.rs +++ b/wlx-overlay-s/src/gui/panel/mod.rs @@ -1,10 +1,11 @@ use std::{cell::RefCell, rc::Rc, sync::Arc}; use button::setup_custom_button; -use glam::{vec2, Affine2, Vec2}; +use glam::{Affine2, Vec2, vec2}; use label::setup_custom_label; use vulkano::{command_buffer::CommandBufferUsage, image::view::ImageView}; use wgui::{ + assets::AssetPath, drawing, event::{ Event as WguiEvent, EventListenerCollection, InternalStateChangeEvent, ListenerHandleVec, @@ -21,7 +22,7 @@ use crate::{ backend::input::{Haptics, PointerHit, PointerMode}, graphics::{CommandBuffers, ExtentExt}, state::AppState, - windowing::backend::{ui_transform, FrameMeta, OverlayBackend, ShouldRender}, + windowing::backend::{FrameMeta, OverlayBackend, ShouldRender, ui_transform}, }; use super::{timer::GuiTimer, timestep::Timestep}; @@ -72,7 +73,7 @@ impl GuiPanel { let doc_params = wgui::parser::ParseDocumentParams { globals: app.wgui_globals.clone(), - path, + path: AssetPath::BuiltIn(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 f232193..f16479a 100644 --- a/wlx-overlay-s/src/overlays/keyboard/builder.rs +++ b/wlx-overlay-s/src/overlays/keyboard/builder.rs @@ -1,8 +1,9 @@ use std::{collections::HashMap, rc::Rc}; -use glam::{vec2, vec3, Affine3A, Mat4, Quat, Vec2, Vec3}; +use glam::{Affine3A, Mat4, Quat, Vec2, Vec3, vec2, vec3}; use wgui::{ animation::{Animation, AnimationEasing}, + assets::AssetPath, drawing::Color, event::{self, CallbackMetadata, EventListenerKind}, layout::LayoutParams, @@ -19,14 +20,14 @@ use wgui::{ use crate::{ gui::panel::GuiPanel, state::AppState, - subsystem::hid::{XkbKeymap, ALT, CTRL, META, SHIFT, SUPER}, + subsystem::hid::{ALT, CTRL, META, SHIFT, SUPER, XkbKeymap}, windowing::window::{OverlayWindowConfig, OverlayWindowState, Positioning}, }; use super::{ - handle_press, handle_release, + KEYBOARD_NAME, KeyButtonData, KeyState, KeyboardBackend, KeyboardState, handle_press, + handle_release, layout::{self, AltModifier, KeyCapType}, - KeyButtonData, KeyState, KeyboardBackend, KeyboardState, KEYBOARD_NAME, }; const BACKGROUND_PADDING: f32 = 4.; @@ -54,7 +55,7 @@ pub fn create_keyboard( let mut panel = GuiPanel::new_blank(app, state)?; let (background, _) = panel.layout.add_child( - panel.layout.root_widget, + panel.layout.content_root_widget, WidgetRectangle::create(WidgetRectangleParams { color: wgui::drawing::Color::new(0., 0., 0., 0.6), round: WLength::Units(4.0), @@ -75,7 +76,7 @@ pub fn create_keyboard( let parse_doc_params = wgui::parser::ParseDocumentParams { globals: app.wgui_globals.clone(), - path: "gui/keyboard.xml", + path: AssetPath::BuiltIn("gui/keyboard.xml"), extra: Default::default(), }; diff --git a/wlx-overlay-s/src/overlays/toast.rs b/wlx-overlay-s/src/overlays/toast.rs index 00011c8..20fe917 100644 --- a/wlx-overlay-s/src/overlays/toast.rs +++ b/wlx-overlay-s/src/overlays/toast.rs @@ -5,7 +5,7 @@ use std::{ time::Instant, }; -use glam::{vec3, Affine3A, Quat, Vec3}; +use glam::{Affine3A, Quat, Vec3, vec3}; use idmap_derive::IntegerId; use serde::{Deserialize, Serialize}; use wgui::{ @@ -28,8 +28,8 @@ use crate::{ gui::panel::GuiPanel, state::{AppState, LeftRight}, windowing::{ - window::{OverlayWindowConfig, OverlayWindowState, Positioning}, OverlaySelector, Z_ORDER_TOAST, + window::{OverlayWindowConfig, OverlayWindowState, Positioning}, }, }; @@ -144,8 +144,7 @@ fn new_toast(toast: Toast, app: &mut AppState) -> Option { Positioning::FollowHead { lerp: 0.1 }, ), DisplayMethod::Watch => { - let mut watch_pos = - Vec3::from(app.session.config.watch_pos) + vec3(-0.005, -0.05, 0.02); + let mut watch_pos = app.session.config.watch_pos + vec3(-0.005, -0.05, 0.02); let mut watch_rot = app.session.config.watch_rot; let relative_to = match app.session.config.watch_hand { LeftRight::Left => Positioning::FollowHand { hand: 0, lerp: 1.0 }, @@ -172,7 +171,7 @@ fn new_toast(toast: Toast, app: &mut AppState) -> Option { let (rect, _) = panel .layout .add_child( - panel.layout.root_widget, + panel.layout.content_root_widget, WidgetRectangle::create(WidgetRectangleParams { color: parse_color_hex("#1e2030").unwrap(), border_color: parse_color_hex("#5e7090").unwrap(),