diff --git a/dash-frontend/assets/gui/tab/settings.xml b/dash-frontend/assets/gui/tab/settings.xml index 67d7cadd..3afa2c3d 100644 --- a/dash-frontend/assets/gui/tab/settings.xml +++ b/dash-frontend/assets/gui/tab/settings.xml @@ -56,17 +56,19 @@ + +
- - - - - - - - + + + + + + + +
diff --git a/wgui/src/assets.rs b/wgui/src/assets.rs index e1fb4511..7733b77f 100644 --- a/wgui/src/assets.rs +++ b/wgui/src/assets.rs @@ -2,9 +2,34 @@ use flate2::read::GzDecoder; use std::ffi::OsStr; use std::io::Read; use std::path::{Component, Path, PathBuf}; +use std::rc::Rc; use crate::i18n::LangsList; +pub enum AssetPathRc { + WguiInternal(Rc), // tied to internal wgui AssetProvider. Used internally + BuiltIn(Rc), // tied to user AssetProvider + FileOrBuiltIn(Rc), // attempts to load from a path relative to asset_folder, falls back to BuiltIn + File(Rc), // load from filesystem +} + +impl AssetPathRc { + pub fn as_borrowed(&self) -> AssetPath<'_> { + match self { + Self::WguiInternal(path) => AssetPath::WguiInternal(path.as_ref()), + Self::BuiltIn(path) => AssetPath::BuiltIn(path.as_ref()), + Self::FileOrBuiltIn(path) => AssetPath::FileOrBuiltIn(path.as_ref()), + Self::File(path) => AssetPath::File(path.as_ref()), + } + } +} + +impl<'a> From<&'a AssetPathRc> for AssetPath<'a> { + fn from(path: &'a AssetPathRc) -> Self { + path.as_borrowed() + } +} + #[derive(Debug, Clone, Copy)] pub enum AssetPath<'a> { WguiInternal(&'a str), // tied to internal wgui AssetProvider. Used internally diff --git a/wgui/src/components/tabs.rs b/wgui/src/components/tabs.rs index 81e619ae..da26cfb0 100644 --- a/wgui/src/components/tabs.rs +++ b/wgui/src/components/tabs.rs @@ -1,6 +1,6 @@ use crate::{ - assets::AssetPath, - color::WguiColorName, + assets::AssetPathRc, + color::WguiColor, components::{ Component, ComponentBase, ComponentTrait, RefreshData, button::{self, ComponentButton}, @@ -8,7 +8,7 @@ use crate::{ event::CallbackDataCommon, i18n::Translation, layout::WidgetPair, - widget::{ConstructEssentials, div::WidgetDiv}, + widget::{ConstructEssentials, div::WidgetDiv, util::WLength}, }; use std::{cell::RefCell, rc::Rc}; use taffy::{ @@ -16,17 +16,25 @@ use taffy::{ prelude::{length, percent}, }; -pub struct Entry<'a> { - pub sprite_src: Option>, +pub struct Entry { + pub sprite_src: Option, pub text: Translation, - pub name: &'a str, + pub name: Rc, } pub struct Params<'a> { pub style: taffy::Style, - pub entries: Vec>, + pub entries: Vec, pub selected_entry_name: &'a str, // default: "" pub on_select: Option, + pub round: WLength, + pub color: Option, + pub border: f32, + pub border_color: Option, + pub hover_color: Option, + pub hover_border_color: Option, + pub sticky_color: Option, + pub sticky_border_color: Option, } struct MountedEntry { @@ -65,18 +73,10 @@ impl ComponentTrait for ComponentTabs { } } -fn set_button_selected(common: &mut CallbackDataCommon, button: &Rc, selected: bool) { - if selected { - button.set_color(common, WguiColorName::Primary.into()); - } else { - button.set_color(common, WguiColorName::Background.into()); - } -} - impl State { fn select_entry(&mut self, common: &mut CallbackDataCommon, name: &Rc) { for entry in &self.mounted_entries { - set_button_selected(common, &entry.button, *entry.name == **name); + entry.button.set_sticky_state(common, *entry.name == **name); } self.selected_entry_name = name.clone(); @@ -121,7 +121,9 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul let mut mounted_entries = Vec::::new(); // Mount entries - for entry in params.entries { + for (idx, entry) in params.entries.into_iter().enumerate() { + let sprite_src = entry.sprite_src.as_ref().map(AssetPathRc::as_borrowed); + let (_, button) = button::construct( &mut ConstructEssentials { layout: ess.layout, @@ -129,7 +131,7 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul }, button::Params { text: Some(entry.text), - sprite_src: entry.sprite_src, + sprite_src, style: taffy::Style { min_size: taffy::Size { width: percent(1.0), @@ -138,15 +140,23 @@ pub fn construct(ess: &mut ConstructEssentials, params: Params) -> anyhow::Resul justify_content: Some(taffy::JustifyContent::START), ..Default::default() }, + round: params.round, + color: params.color, + border: params.border, + border_color: params.border_color, + hover_color: params.hover_color, + hover_border_color: params.hover_border_color, + sticky_color: params.sticky_color, + sticky_border_color: params.sticky_border_color, ..Default::default() }, )?; // init colors - set_button_selected(&mut ess.layout.common(), &button, false); + button.set_sticky_state(&mut ess.layout.common(), idx == 0); mounted_entries.push(MountedEntry { - name: Rc::from(entry.name), + name: entry.name, button, }); } diff --git a/wgui/src/parser/component_tabs.rs b/wgui/src/parser/component_tabs.rs index c195de7b..e3319a90 100644 --- a/wgui/src/parser/component_tabs.rs +++ b/wgui/src/parser/component_tabs.rs @@ -1,12 +1,20 @@ +use std::rc::Rc; + use crate::{ - assets::AssetPath, + assets::AssetPathRc, + color::WguiColor, components::{Component, tabs}, i18n::Translation, layout::WidgetID, - parser::{AttribPair, ParserContext, get_asset_path_from_kv, process_component, style::parse_style}, + parser::{ + AttribPair, ParserContext, ParserFile, get_asset_path_rc_from_kv, process_attribs, process_component, + style::{parse_color_opt, parse_round, parse_style}, + }, + widget::util::WLength, }; pub fn parse_component_tabs<'a>( + file: &'a ParserFile, ctx: &mut ParserContext, node: roxmltree::Node<'a, 'a>, parent_id: WidgetID, @@ -17,25 +25,71 @@ pub fn parse_component_tabs<'a>( let mut entries = Vec::::new(); + let mut border = 2.0; + let mut color: Option = None; + let mut border_color: Option = None; + let mut hover_color: Option = None; + let mut hover_border_color: Option = None; + let mut sticky_color: Option = None; + let mut sticky_border_color: Option = None; + let mut round = WLength::Units(4.0); + for child in node.children() { match child.tag_name().name() { "" => { /* ignore */ } "Tab" => { - let mut name: Option<&str> = None; + let mut name: Option> = None; let mut text: Option = None; - let mut sprite_src: Option = None; + let mut sprite_src: Option = None; - for attrib in child.attributes() { - let (key, value) = (attrib.name(), attrib.value()); - match key { - "name" => name = Some(value), - "text" => text = Some(Translation::from_raw_text(value)), - "translation" => text = Some(Translation::from_translation_key(value)), + let attribs = process_attribs(file, ctx, &child, false); + + for attrib in attribs.iter() { + match &*attrib.attrib { + "name" => name = Some(attrib.value.clone()), + "text" => text = Some(Translation::from_raw_text(&*attrib.value)), + "translation" => text = Some(Translation::from_translation_key(&*attrib.value)), "sprite_src" | "sprite_src_ext" | "sprite_src_builtin" | "sprite_src_internal" => { - sprite_src = Some(get_asset_path_from_kv("sprite_", key, value)); + sprite_src = Some(get_asset_path_rc_from_kv( + "sprite_", + &*attrib.attrib, + attrib.value.clone(), + )); + continue; + } + "round" => { + parse_round( + ctx, + tag_name, + &*attrib.attrib, + &*attrib.value, + &mut round, + ctx.layout.state.theme.rounding_mult, + ); + } + "color" => { + parse_color_opt(ctx, tag_name, &*attrib.attrib, &*attrib.value, &mut color); + } + "border" => { + ctx.parse_check_f32(tag_name, &*attrib.attrib, &*attrib.value, &mut border); + } + "border_color" => { + parse_color_opt(ctx, tag_name, &*attrib.attrib, &*attrib.value, &mut border_color); + } + "hover_color" => { + parse_color_opt(ctx, tag_name, &*attrib.attrib, &*attrib.value, &mut hover_color); + } + "hover_border_color" => { + parse_color_opt(ctx, tag_name, &*attrib.attrib, &*attrib.value, &mut hover_border_color); + } + "sticky_color" => { + parse_color_opt(ctx, tag_name, &*attrib.attrib, &*attrib.value, &mut sticky_color); + } + "sticky_border_color" => { + parse_color_opt(ctx, tag_name, &*attrib.attrib, &*attrib.value, &mut sticky_border_color); } other_key => { - ctx.print_invalid_attrib("Tab", other_key, value); + ctx.print_invalid_attrib("Tab", other_key, &*attrib.value); } } } @@ -59,6 +113,14 @@ pub fn parse_component_tabs<'a>( selected_entry_name: "first", entries, on_select: None, + border, + color, + border_color, + hover_color, + hover_border_color, + sticky_color, + sticky_border_color, + round, }, )?; diff --git a/wgui/src/parser/mod.rs b/wgui/src/parser/mod.rs index a50a60aa..0630a0e9 100644 --- a/wgui/src/parser/mod.rs +++ b/wgui/src/parser/mod.rs @@ -19,7 +19,7 @@ mod widget_rectangle; mod widget_sprite; use crate::{ - assets::{AssetPath, AssetPathOwned, normalize_path}, + assets::{AssetPath, AssetPathOwned, AssetPathRc, normalize_path}, components::{Component, ComponentWeak}, globals::WguiGlobals, i18n::Translation, @@ -1082,7 +1082,9 @@ fn parse_child<'a>( "EditBox" => new_widget_id = Some(parse_component_editbox(ctx, parent_id, &attribs, tag_name)?), "BarGraph" => new_widget_id = Some(parse_component_bar_graph(ctx, parent_id, &attribs, tag_name)?), "Tabs" => { - new_widget_id = Some(parse_component_tabs(ctx, child_node, parent_id, &attribs, tag_name)?); + new_widget_id = Some(parse_component_tabs( + file, ctx, child_node, parent_id, &attribs, tag_name, + )?); } "" => { /* ignore */ } other_tag_name => { @@ -1327,21 +1329,26 @@ fn parse_document_root( Ok(()) } -fn get_asset_path_from_kv<'a>(prefix: &'static str, key: &'a str, value: &'a str) -> AssetPath<'a> { - let key_split = match key.find(prefix) { - Some(pos) => { - assert!(pos == 0, "invalid split"); - key.get(prefix.len()..).unwrap() - } - None => key, - }; - match key_split { +fn get_asset_path_from_kv<'a>(prefix: &str, key: &'a str, value: &'a str) -> AssetPath<'a> { + let key = key.strip_prefix(prefix).unwrap_or(key); + + match key { "src" => AssetPath::FileOrBuiltIn(value), "src_ext" => AssetPath::File(value), "src_builtin" => AssetPath::BuiltIn(value), "src_internal" => AssetPath::WguiInternal(value), - other => { - panic!("unexpected attrib {other}"); - } + other => panic!("unexpected attrib {other}"), + } +} + +fn get_asset_path_rc_from_kv(prefix: &'static str, key: &str, value: Rc) -> AssetPathRc { + let key = key.strip_prefix(prefix).unwrap_or(key); + + match key { + "src" => AssetPathRc::FileOrBuiltIn(value), + "src_ext" => AssetPathRc::File(value), + "src_builtin" => AssetPathRc::BuiltIn(value), + "src_internal" => AssetPathRc::WguiInternal(value), + other => panic!("unexpected attrib {other}"), } }