dash-frontend: tabs, other fixes (desc)

- set rustfmt line width to 120 columns by default for wgui
- dashboard tabs
- wgui: `remove_children`
This commit is contained in:
Aleksander
2025-09-15 20:37:55 +02:00
parent f115d2d2cf
commit 54767d75da
30 changed files with 624 additions and 191 deletions

View File

@@ -7,4 +7,5 @@ edition = "2024"
anyhow.workspace = true
wgui = { path = "../wgui/" }
glam = { workspace = true }
log = { workspace = true }
rust-embed = "8.7.2"

View File

@@ -1,11 +1,25 @@
<layout>
<include src="theme.xml" />
<include src="templates.xml" />
<theme>
<var key="side_size" value="48" />
<var key="side_sprite_size" value="26" />
<var key="side_button_size" value="48" />
</theme>
<template name="SideButton">
<Button
id="${id}"
round="100%"
width="~side_button_size"
height="~side_button_size"
color="#44444400"
hover_color="#333333ff"
hover_border_color="#555555ff">
<sprite src="${src}" width="~side_sprite_size" height="~side_sprite_size" />
</Button>
</template>
<elements>
<!-- background for testing -->
<rectangle position="absolute" color="#333333" width="100%" height="100%" />
@@ -13,7 +27,7 @@
<!-- left/right separator (menu and rest) -->
<div flex_direction="row" gap="8" width="100%" height="100%">
<!-- LEFT MENU -->
<div id="menu" width="48" min_width="48" max_width="48" height="100%" align_items="center" justify_content="center">
<div id="menu" width="~size_size" min_width="~side_size" max_width="~side_size" height="100%" align_items="center" justify_content="center">
<rectangle
width="100%"
round="100%"
@@ -21,17 +35,15 @@
flex_direction="column"
justify_content="center"
align_items="center"
gap="24"
padding_top="16"
padding_bottom="16"
gap="4"
>
<sprite src="dashboard/wayvr_dashboard_mono.svg" width="~side_sprite_size" height="~side_sprite_size" />
<sprite src="dashboard/apps.svg" width="~side_sprite_size" height="~side_sprite_size" />
<sprite src="dashboard/games.svg" width="~side_sprite_size" height="~side_sprite_size" />
<sprite src="dashboard/monado.svg" width="~side_sprite_size" height="~side_sprite_size" />
<sprite src="dashboard/window.svg" width="~side_sprite_size" height="~side_sprite_size" />
<SideButton id="btn_side_home" src="dashboard/wayvr_dashboard_mono.svg" />
<SideButton id="btn_side_apps" src="dashboard/apps.svg" />
<SideButton id="btn_side_games" src="dashboard/games.svg" />
<SideButton id="btn_side_monado" src="dashboard/monado.svg" />
<SideButton id="btn_side_processes" src="dashboard/window.svg" />
<rectangle height="2" color="#FFFFFF33" width="~side_sprite_size" />
<sprite src="dashboard/settings.svg" width="~side_sprite_size" height="~side_sprite_size" />
<SideButton id="btn_side_settings" src="dashboard/settings.svg" />
</rectangle>
</div>
<!-- REST -->
@@ -52,18 +64,7 @@
flex_direction="column"
gap="24"
>
<sprite src="dashboard/wayvr_dashboard.svg" min_width="96" min_height="96" />
<label text="Hello, user!" size="32" weight="bold" color="#FFFFFF" />
<label text="Connected to wlx-overlay-s" size="16" weight="bold" color="#bbffbb" />
<!-- main button list -->
<div flex_direction="row" gap="8" margin_top="32">
<MenuButton icon="dashboard/apps.svg" text="Apps" />
<MenuButton icon="dashboard/games.svg" text="Games" />
<MenuButton icon="dashboard/monado.svg" text="Monado" />
<MenuButton icon="dashboard/window.svg" text="Processes" />
<MenuButton icon="dashboard/settings.svg" text="Settings" />
</div>
<!-- filled-in at runtime -->
</div>
</rectangle>
<!-- BOTTOM PANEL -->

View File

@@ -1,6 +1,9 @@
<layout>
<include src="theme.xml" />
<template name="MenuButton">
<Button
id="${id}"
width="120"
height="82"
color="#00000033"

View File

@@ -0,0 +1,7 @@
<layout>
<elements>
<label text="Apps" size="64" weight="bold" color="#FFFFFF" />
<label text="bottom text" size="16" weight="bold" color="#FFFFFF88" />
</elements>
</layout>

View File

@@ -0,0 +1,5 @@
<layout>
<elements>
<label text="Games" size="32" weight="bold" color="#FFFFFF" />
</elements>
</layout>

View File

@@ -0,0 +1,18 @@
<layout>
<include src="../t_menu_button.xml" />
<elements>
<sprite src="dashboard/wayvr_dashboard.svg" min_width="96" min_height="96" />
<label text="Hello, user!" size="32" weight="bold" color="#FFFFFF" />
<label text="Connected to wlx-overlay-s" size="16" weight="bold" color="#bbffbb" />
<!-- main button list -->
<div flex_direction="row" gap="8" margin_top="32">
<MenuButton id="btn_apps" icon="dashboard/apps.svg" text="Apps" />
<MenuButton id="btn_games" icon="dashboard/games.svg" text="Games" />
<MenuButton id="btn_monado" icon="dashboard/monado.svg" text="Monado" />
<MenuButton id="btn_processes" icon="dashboard/window.svg" text="Processes" />
<MenuButton id="btn_settings" icon="dashboard/settings.svg" text="Settings" />
</div>
</elements>
</layout>

View File

@@ -0,0 +1,5 @@
<layout>
<elements>
<label text="Monado" size="32" weight="bold" color="#FFFFFF" />
</elements>
</layout>

View File

@@ -0,0 +1,5 @@
<layout>
<elements>
<label text="Processes" size="32" weight="bold" color="#FFFFFF" />
</elements>
</layout>

View File

@@ -0,0 +1,5 @@
<layout>
<elements>
<label text="Settings" size="32" weight="bold" color="#FFFFFF" />
</elements>
</layout>

View File

@@ -1,2 +1,3 @@
tab_spaces = 2
hard_tabs = true
hard_tabs = true
max_width = 120

View File

@@ -1,18 +1,38 @@
use std::{cell::RefCell, collections::VecDeque, rc::Rc};
use glam::Vec2;
use wgui::{
components::button::ComponentButton,
event::EventListenerCollection,
globals::WguiGlobals,
layout::{Layout, LayoutParams},
layout::{LayoutParams, RcLayout},
parser::{ParseDocumentParams, ParserState},
};
use crate::tab::{
Tab, TabParams, TabType, apps::TabApps, games::TabGames, home::TabHome, monado::TabMonado, processes::TabProcesses,
settings::TabSettings,
};
mod assets;
mod tab;
pub struct Frontend {
pub layout: Layout,
pub layout: RcLayout,
globals: WguiGlobals,
#[allow(dead_code)]
state: ParserState,
current_tab: Option<Box<dyn Tab>>,
tasks: VecDeque<FrontendTask>,
}
pub type RcFrontend = Rc<RefCell<Frontend>>;
pub enum FrontendTask {
SetTab(TabType),
}
pub struct FrontendParams<'a> {
@@ -20,32 +40,126 @@ pub struct FrontendParams<'a> {
}
impl Frontend {
pub fn new(params: FrontendParams) -> anyhow::Result<Self> {
pub fn new(params: FrontendParams) -> anyhow::Result<(RcFrontend, RcLayout)> {
let globals = WguiGlobals::new(Box::new(assets::Asset {}))?;
let (layout, state) = wgui::parser::new_layout_from_assets(
params.listeners,
&ParseDocumentParams {
globals,
globals: globals.clone(),
path: "gui/dashboard.xml",
extra: Default::default(),
},
&LayoutParams {
resize_to_parent: true,
},
&LayoutParams { resize_to_parent: true },
)?;
Ok(Self { layout, state })
let rc_layout = layout.as_rc();
let mut tasks = VecDeque::<FrontendTask>::new();
tasks.push_back(FrontendTask::SetTab(TabType::Home));
let res = Rc::new(RefCell::new(Self {
layout: rc_layout.clone(),
state,
current_tab: None,
globals,
tasks,
}));
Frontend::register_buttons(&res)?;
Ok((res, rc_layout))
}
pub fn update(&mut self, width: f32, height: f32, timeste_alpha: f32) -> anyhow::Result<()> {
self
pub fn update(
rc_this: &RcFrontend,
listeners: &mut EventListenerCollection<(), ()>,
width: f32,
height: f32,
timestep_alpha: f32,
) -> anyhow::Result<()> {
let mut this = rc_this.borrow_mut();
while let Some(task) = this.tasks.pop_front() {
this.process_task(rc_this, task, listeners)?;
}
this
.layout
.update(Vec2::new(width, height), timeste_alpha)?;
.borrow_mut()
.update(Vec2::new(width, height), timestep_alpha)?;
Ok(())
}
pub fn get_layout(&mut self) -> &mut Layout {
&mut self.layout
pub fn get_layout(&self) -> &RcLayout {
&self.layout
}
pub fn push_task(&mut self, task: FrontendTask) {
self.tasks.push_back(task);
}
fn process_task(
&mut self,
rc_this: &RcFrontend,
task: FrontendTask,
listeners: &mut EventListenerCollection<(), ()>,
) -> anyhow::Result<()> {
match task {
FrontendTask::SetTab(tab_type) => self.set_tab(tab_type, rc_this, listeners)?,
}
Ok(())
}
fn set_tab(
&mut self,
tab_type: TabType,
rc_this: &RcFrontend,
listeners: &mut EventListenerCollection<(), ()>,
) -> anyhow::Result<()> {
log::info!("Setting tab to {tab_type:?}");
let mut layout = self.layout.borrow_mut();
let widget_content = self.state.fetch_widget(&layout.state, "content")?;
layout.remove_children(widget_content.id);
let tab_params = TabParams {
globals: &self.globals,
layout: &mut layout,
parent_id: widget_content.id,
listeners,
frontend: rc_this,
};
let tab: Box<dyn Tab> = match tab_type {
TabType::Home => Box::new(TabHome::new(tab_params)?),
TabType::Apps => Box::new(TabApps::new(tab_params)?),
TabType::Games => Box::new(TabGames::new(tab_params)?),
TabType::Monado => Box::new(TabMonado::new(tab_params)?),
TabType::Processes => Box::new(TabProcesses::new(tab_params)?),
TabType::Settings => Box::new(TabSettings::new(tab_params)?),
};
self.current_tab = Some(tab);
Ok(())
}
fn register_buttons(rc_this: &RcFrontend) -> anyhow::Result<()> {
let this = rc_this.borrow_mut();
let btn_home = this.state.fetch_component_as::<ComponentButton>("btn_side_home")?;
let btn_apps = this.state.fetch_component_as::<ComponentButton>("btn_side_apps")?;
let btn_games = this.state.fetch_component_as::<ComponentButton>("btn_side_games")?;
let btn_monado = this.state.fetch_component_as::<ComponentButton>("btn_side_monado")?;
let btn_processes = this.state.fetch_component_as::<ComponentButton>("btn_side_processes")?;
let btn_settings = this.state.fetch_component_as::<ComponentButton>("btn_side_settings")?;
TabType::register_button(rc_this.clone(), &btn_home, TabType::Home);
TabType::register_button(rc_this.clone(), &btn_apps, TabType::Apps);
TabType::register_button(rc_this.clone(), &btn_games, TabType::Games);
TabType::register_button(rc_this.clone(), &btn_monado, TabType::Monado);
TabType::register_button(rc_this.clone(), &btn_processes, TabType::Processes);
TabType::register_button(rc_this.clone(), &btn_settings, TabType::Settings);
Ok(())
}
}

View File

@@ -0,0 +1,31 @@
use wgui::parser::{ParseDocumentParams, ParserState};
use crate::tab::{Tab, TabParams, TabType};
pub struct TabApps {
#[allow(dead_code)]
pub state: ParserState,
}
impl Tab for TabApps {
fn get_type(&self) -> TabType {
TabType::Apps
}
}
impl TabApps {
pub fn new(params: TabParams) -> anyhow::Result<Self> {
let state = wgui::parser::parse_from_assets(
&ParseDocumentParams {
globals: params.globals.clone(),
path: "gui/tab/apps.xml",
extra: Default::default(),
},
params.layout,
params.listeners,
params.parent_id,
)?;
Ok(Self { state })
}
}

View File

@@ -0,0 +1,31 @@
use wgui::parser::{ParseDocumentParams, ParserState};
use crate::tab::{Tab, TabParams, TabType};
pub struct TabGames {
#[allow(dead_code)]
pub state: ParserState,
}
impl Tab for TabGames {
fn get_type(&self) -> TabType {
TabType::Games
}
}
impl TabGames {
pub fn new(params: TabParams) -> anyhow::Result<Self> {
let state = wgui::parser::parse_from_assets(
&ParseDocumentParams {
globals: params.globals.clone(),
path: "gui/tab/games.xml",
extra: Default::default(),
},
params.layout,
params.listeners,
params.parent_id,
)?;
Ok(Self { state })
}
}

View File

@@ -0,0 +1,47 @@
use wgui::{
components::button::ComponentButton,
parser::{ParseDocumentParams, ParserState},
};
use crate::tab::{Tab, TabParams, TabType};
pub struct TabHome {
#[allow(dead_code)]
pub state: ParserState,
}
impl Tab for TabHome {
fn get_type(&self) -> TabType {
TabType::Home
}
}
impl TabHome {
pub fn new(params: TabParams) -> anyhow::Result<Self> {
let state = wgui::parser::parse_from_assets(
&ParseDocumentParams {
globals: params.globals.clone(),
path: "gui/tab/home.xml",
extra: Default::default(),
},
params.layout,
params.listeners,
params.parent_id,
)?;
let btn_apps = state.fetch_component_as::<ComponentButton>("btn_apps")?;
let btn_games = state.fetch_component_as::<ComponentButton>("btn_games")?;
let btn_monado = state.fetch_component_as::<ComponentButton>("btn_monado")?;
let btn_processes = state.fetch_component_as::<ComponentButton>("btn_processes")?;
let btn_settings = state.fetch_component_as::<ComponentButton>("btn_settings")?;
let frontend = params.frontend;
TabType::register_button(frontend.clone(), &btn_apps, TabType::Apps);
TabType::register_button(frontend.clone(), &btn_games, TabType::Games);
TabType::register_button(frontend.clone(), &btn_monado, TabType::Monado);
TabType::register_button(frontend.clone(), &btn_processes, TabType::Processes);
TabType::register_button(frontend.clone(), &btn_settings, TabType::Settings);
Ok(Self { state })
}
}

View File

@@ -0,0 +1,51 @@
use std::rc::Rc;
use wgui::{
components::button::ComponentButton,
event::EventListenerCollection,
globals::WguiGlobals,
layout::{Layout, WidgetID},
};
use crate::{FrontendTask, RcFrontend};
pub mod apps;
pub mod games;
pub mod home;
pub mod monado;
pub mod processes;
pub mod settings;
#[derive(Clone, Copy, Debug)]
pub enum TabType {
Home,
Apps,
Games,
Monado,
Processes,
Settings,
}
pub struct TabParams<'a> {
pub globals: &'a WguiGlobals,
pub layout: &'a mut Layout,
pub parent_id: WidgetID,
pub frontend: &'a RcFrontend,
pub listeners: &'a mut EventListenerCollection<(), ()>,
}
pub trait Tab {
#[allow(dead_code)]
fn get_type(&self) -> TabType;
}
impl TabType {
pub fn register_button(this_rc: RcFrontend, btn: &Rc<ComponentButton>, tab: TabType) {
btn.on_click({
Box::new(move |_evt| {
this_rc.borrow_mut().push_task(FrontendTask::SetTab(tab));
Ok(())
})
});
}
}

View File

@@ -0,0 +1,31 @@
use wgui::parser::{ParseDocumentParams, ParserState};
use crate::tab::{Tab, TabParams, TabType};
pub struct TabMonado {
#[allow(dead_code)]
pub state: ParserState,
}
impl Tab for TabMonado {
fn get_type(&self) -> TabType {
TabType::Games
}
}
impl TabMonado {
pub fn new(params: TabParams) -> anyhow::Result<Self> {
let state = wgui::parser::parse_from_assets(
&ParseDocumentParams {
globals: params.globals.clone(),
path: "gui/tab/monado.xml",
extra: Default::default(),
},
params.layout,
params.listeners,
params.parent_id,
)?;
Ok(Self { state })
}
}

View File

@@ -0,0 +1,31 @@
use wgui::parser::{ParseDocumentParams, ParserState};
use crate::tab::{Tab, TabParams, TabType};
pub struct TabProcesses {
#[allow(dead_code)]
pub state: ParserState,
}
impl Tab for TabProcesses {
fn get_type(&self) -> TabType {
TabType::Games
}
}
impl TabProcesses {
pub fn new(params: TabParams) -> anyhow::Result<Self> {
let state = wgui::parser::parse_from_assets(
&ParseDocumentParams {
globals: params.globals.clone(),
path: "gui/tab/processes.xml",
extra: Default::default(),
},
params.layout,
params.listeners,
params.parent_id,
)?;
Ok(Self { state })
}
}

View File

@@ -0,0 +1,31 @@
use wgui::parser::{ParseDocumentParams, ParserState};
use crate::tab::{Tab, TabParams, TabType};
pub struct TabSettings {
#[allow(dead_code)]
pub state: ParserState,
}
impl Tab for TabSettings {
fn get_type(&self) -> TabType {
TabType::Settings
}
}
impl TabSettings {
pub fn new(params: TabParams) -> anyhow::Result<Self> {
let state = wgui::parser::parse_from_assets(
&ParseDocumentParams {
globals: params.globals.clone(),
path: "gui/tab/settings.xml",
extra: Default::default(),
},
params.layout,
params.listeners,
params.parent_id,
)?;
Ok(Self { state })
}
}