settings tab styles

This commit is contained in:
galister 2026-07-13 01:33:51 +09:00
parent 6a064248dc
commit 031157ea8c
5 changed files with 160 additions and 54 deletions

View File

@ -56,17 +56,19 @@
</div>
</template>
<macro name="tab_style" color="background" sticky_color="primary" sticky_border_color="primary(rgb-mult-0.5)" hover_color="tertiary" hover_border_color="tertiary(rgb-mult-0.5)" />
<elements>
<div gap="4">
<Tabs id="tabs">
<Tab name="look_and_feel" translation="APP_SETTINGS.LOOK_AND_FEEL" sprite_src_builtin="dashboard/palette.svg" />
<Tab name="skybox" translation="APP_SETTINGS.SKYBOX" sprite_src_builtin="dashboard/globe.svg" />
<Tab name="features" translation="APP_SETTINGS.FEATURES" sprite_src_builtin="dashboard/options.svg" />
<Tab name="controls" translation="APP_SETTINGS.CONTROLS" sprite_src_builtin="dashboard/controller.svg" />
<Tab name="space_drag" translation="APP_SETTINGS.SPACE_DRAG" sprite_src_builtin="dashboard/drag.svg" />
<Tab name="misc" translation="APP_SETTINGS.MISC" sprite_src_builtin="dashboard/blocks.svg" />
<Tab name="autostart_apps" translation="APP_SETTINGS.AUTOSTART_APPS" sprite_src_builtin="dashboard/apps.svg" />
<Tab name="troubleshooting" translation="APP_SETTINGS.TROUBLESHOOTING" sprite_src_builtin="dashboard/cpu.svg" />
<Tab macro="tab_style" name="look_and_feel" translation="APP_SETTINGS.LOOK_AND_FEEL" sprite_src_builtin="dashboard/palette.svg" />
<Tab macro="tab_style" name="skybox" translation="APP_SETTINGS.SKYBOX" sprite_src_builtin="dashboard/globe.svg" />
<Tab macro="tab_style" name="features" translation="APP_SETTINGS.FEATURES" sprite_src_builtin="dashboard/options.svg" />
<Tab macro="tab_style" name="controls" translation="APP_SETTINGS.CONTROLS" sprite_src_builtin="dashboard/controller.svg" />
<Tab macro="tab_style" name="space_drag" translation="APP_SETTINGS.SPACE_DRAG" sprite_src_builtin="dashboard/drag.svg" />
<Tab macro="tab_style" name="misc" translation="APP_SETTINGS.MISC" sprite_src_builtin="dashboard/blocks.svg" />
<Tab macro="tab_style" name="autostart_apps" translation="APP_SETTINGS.AUTOSTART_APPS" sprite_src_builtin="dashboard/apps.svg" />
<Tab macro="tab_style" name="troubleshooting" translation="APP_SETTINGS.TROUBLESHOOTING" sprite_src_builtin="dashboard/cpu.svg" />
</Tabs>
<div flex_wrap="wrap" justify_content="stretch" gap="4" id="settings_root" width="100%">
<!-- filled-in at runtime -->

View File

@ -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<str>), // tied to internal wgui AssetProvider. Used internally
BuiltIn(Rc<str>), // tied to user AssetProvider
FileOrBuiltIn(Rc<str>), // attempts to load from a path relative to asset_folder, falls back to BuiltIn
File(Rc<str>), // 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

View File

@ -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<AssetPath<'a>>,
pub struct Entry {
pub sprite_src: Option<AssetPathRc>,
pub text: Translation,
pub name: &'a str,
pub name: Rc<str>,
}
pub struct Params<'a> {
pub style: taffy::Style,
pub entries: Vec<Entry<'a>>,
pub entries: Vec<Entry>,
pub selected_entry_name: &'a str, // default: ""
pub on_select: Option<TabSelectCallback>,
pub round: WLength,
pub color: Option<WguiColor>,
pub border: f32,
pub border_color: Option<WguiColor>,
pub hover_color: Option<WguiColor>,
pub hover_border_color: Option<WguiColor>,
pub sticky_color: Option<WguiColor>,
pub sticky_border_color: Option<WguiColor>,
}
struct MountedEntry {
@ -65,18 +73,10 @@ impl ComponentTrait for ComponentTabs {
}
}
fn set_button_selected(common: &mut CallbackDataCommon, button: &Rc<ComponentButton>, 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<str>) {
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::<MountedEntry>::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,
});
}

View File

@ -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::<tabs::Entry>::new();
let mut border = 2.0;
let mut color: Option<WguiColor> = None;
let mut border_color: Option<WguiColor> = None;
let mut hover_color: Option<WguiColor> = None;
let mut hover_border_color: Option<WguiColor> = None;
let mut sticky_color: Option<WguiColor> = None;
let mut sticky_border_color: Option<WguiColor> = 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<Rc<str>> = None;
let mut text: Option<Translation> = None;
let mut sprite_src: Option<AssetPath> = None;
let mut sprite_src: Option<AssetPathRc> = 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,
},
)?;

View File

@ -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<str>) -> 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}"),
}
}