dash-frontend: Top panel title

This commit is contained in:
Aleksander
2026-01-27 18:43:34 +01:00
parent 2c60196ead
commit 19083cfd83
12 changed files with 87 additions and 39 deletions

View File

@@ -3,6 +3,7 @@
<theme> <theme>
<var key="side_size" value="48" /> <var key="side_size" value="48" />
<var key="top_size" value="40" />
<var key="side_sprite_size" value="26" /> <var key="side_sprite_size" value="26" />
<var key="side_button_size" value="48" /> <var key="side_button_size" value="48" />
</theme> </theme>
@@ -25,13 +26,6 @@
</template> </template>
<elements> <elements>
<div position="absolute" width="100%" justify_content="space_between">
<div />
<div padding="6">
<Button id="btn_close" sprite_src_builtin="dashboard/close.svg" color="#000000" border="2" border_color="~color_faded" />
</div>
</div>
<!-- left/right separator (menu and rest) --> <!-- left/right separator (menu and rest) -->
<div flex_direction="row" gap="8" width="100%" height="100%" padding="4" interactable="0"> <div flex_direction="row" gap="8" width="100%" height="100%" padding="4" interactable="0">
<!-- LEFT MENU --> <!-- LEFT MENU -->
@@ -60,15 +54,29 @@
</rectangle> </rectangle>
</div> </div>
<!-- REST --> <!-- REST -->
<!-- content/bottom panel separator --> <!-- top/content/bottom panel separator -->
<div <div
flex_direction="column" flex_direction="column"
gap="8" gap="8"
width="100%" width="100%"
height="100%" height="100%"
overflow_x="scroll"> overflow_x="scroll">
<!-- CONTENT --> <!-- TOP PANEL -->
<div position="relative" width="100%" height="~top_size" min_height="~top_size" max_height="~top_size" justify_content="end">
<!-- Title bar -->
<div width="100%" align_items="center" justify_content="center">
<rectangle min_width="300" height="100%" consume_mouse_events="1" round="100%" color="~color_top_panel" align_items="center" justify_content="center" gap="8">
<sprite id="sprite_titlebar_icon" width="28" height="28" />
<label id="label_titlebar_title" weight="bold" size="16" text="Title" />
</rectangle>
</div>
<!-- Close button -->
<Button position="absolute" id="btn_close" color="~color_top_panel" round="100%" width="~top_size" height="~top_size" tooltip="CLOSE_WINDOW" tooltip_side="left">
<sprite src_builtin="dashboard/close.svg" width="32" height="32" />
</Button>
</div>
<!-- CONTENT -->
<!-- color and color2 alpha will be modified at runtime --> <!-- color and color2 alpha will be modified at runtime -->
<rectangle <rectangle
id="rect_content" id="rect_content"

View File

@@ -1,5 +1,4 @@
<layout> <layout>
<include src="t_tab_title.xml" />
<include src="../theme.xml" /> <include src="../theme.xml" />
<template name="AppEntry"> <template name="AppEntry">
@@ -25,7 +24,6 @@
</template> </template>
<elements> <elements>
<TabTitle translation="APPLICATIONS" icon="dashboard/apps.svg" />
<!-- placeholders for now --> <!-- placeholders for now -->
<!-- <!--
<div gap="4" align_items="center"> <div gap="4" align_items="center">

View File

@@ -1,9 +1,7 @@
<layout> <layout>
<include src="t_tab_title.xml" />
<include src="../t_group_box.xml" /> <include src="../t_group_box.xml" />
<elements> <elements>
<TabTitle translation="GAMES" icon="dashboard/games.svg" />
<rectangle macro="group_box" id="running_games_list_parent" align_self="center" /> <rectangle macro="group_box" id="running_games_list_parent" align_self="center" />
<div id="game_list_parent" align_items="center" /> <div id="game_list_parent" align_items="center" />
</elements> </elements>

View File

@@ -1,8 +1,6 @@
<layout> <layout>
<include src="t_tab_title.xml" />
<include src="../t_group_box.xml" /> <include src="../t_group_box.xml" />
<!-- key: str, value: str --> <!-- key: str, value: str -->
<template name="BoolFlag"> <template name="BoolFlag">
<div flex_direction="row" gap="4"> <div flex_direction="row" gap="4">
@@ -27,7 +25,6 @@
</template> </template>
<elements> <elements>
<TabTitle translation="MONADO_RUNTIME" icon="dashboard/monado.svg" />
<label translation="DISPLAY_BRIGHTNESS" /> <label translation="DISPLAY_BRIGHTNESS" />
<Slider id="slider_brightness" width="300" height="24" min_value="0" max_value="140" /> <Slider id="slider_brightness" width="300" height="24" min_value="0" max_value="140" />

View File

@@ -1,5 +1,4 @@
<layout> <layout>
<include src="t_tab_title.xml" />
<include src="../t_group_box.xml" /> <include src="../t_group_box.xml" />
<include src="../t_dropdown_button.xml" /> <include src="../t_dropdown_button.xml" />
@@ -46,7 +45,6 @@
</template> </template>
<elements> <elements>
<TabTitle translation="SETTINGS" icon="dashboard/settings.svg" />
<div gap="4"> <div gap="4">
<Tabs id="tabs"> <Tabs id="tabs">
<Tab name="look_and_feel" translation="APP_SETTINGS.LOOK_AND_FEEL" sprite_src_builtin="dashboard/palette.svg" /> <Tab name="look_and_feel" translation="APP_SETTINGS.LOOK_AND_FEEL" sprite_src_builtin="dashboard/palette.svg" />

View File

@@ -1,9 +0,0 @@
<layout>
<!-- translation, icon -->
<template name="TabTitle">
<div gap="8" align_items="center">
<sprite src_builtin="${icon}" width="24" height="24" />
<label translation="${translation}" size="18" weight="bold" />
</div>
</template>
</layout>

View File

@@ -2,6 +2,7 @@
<theme> <theme>
<var key="color_menu_dark" value="#0A0A0ACC" /> <var key="color_menu_dark" value="#0A0A0ACC" />
<var key="color_accent" value="#008cff" /> <var key="color_accent" value="#008cff" />
<var key="color_top_panel" value="#00000099" />
<var key="color_bottom_panel" value="#141e28" /> <var key="color_bottom_panel" value="#141e28" />
</theme> </theme>
</layout> </layout>

View File

@@ -1,5 +1,6 @@
use std::{path::PathBuf, rc::Rc}; use std::{path::PathBuf, rc::Rc};
use anyhow::Context;
use chrono::Timelike; use chrono::Timelike;
use glam::Vec2; use glam::Vec2;
use wgui::{ use wgui::{
@@ -10,8 +11,9 @@ use wgui::{
i18n::Translation, i18n::Translation,
layout::{Layout, LayoutParams, LayoutUpdateParams, LayoutUpdateResult, WidgetID}, layout::{Layout, LayoutParams, LayoutUpdateParams, LayoutUpdateResult, WidgetID},
parser::{Fetchable, ParseDocumentParams, ParserState}, parser::{Fetchable, ParseDocumentParams, ParserState},
renderer_vk::text::custom_glyph::CustomGlyphData,
task::Tasks, task::Tasks,
widget::{label::WidgetLabel, rectangle::WidgetRectangle}, widget::{label::WidgetLabel, rectangle::WidgetRectangle, sprite::WidgetSprite},
windowing::window::{WguiWindow, WguiWindowParams, WguiWindowParamsExtra, WguiWindowPlacement}, windowing::window::{WguiWindow, WguiWindowParams, WguiWindowParamsExtra, WguiWindowPlacement},
}; };
use wlx_common::{ use wlx_common::{
@@ -22,7 +24,7 @@ use wlx_common::{
use crate::{ use crate::{
assets, assets,
tab::{apps::TabApps, games::TabGames, home::TabHome, monado::TabMonado, settings::TabSettings, Tab, TabType}, tab::{Tab, TabType, apps::TabApps, games::TabGames, home::TabHome, monado::TabMonado, settings::TabSettings},
util::{ util::{
popup_manager::{MountPopupParams, PopupManager, PopupManagerParams}, popup_manager::{MountPopupParams, PopupManager, PopupManagerParams},
toast_manager::ToastManager, toast_manager::ToastManager,
@@ -32,8 +34,10 @@ use crate::{
}; };
pub struct FrontendWidgets { pub struct FrontendWidgets {
pub id_label_time: WidgetID, id_label_time: WidgetID,
pub id_rect_content: WidgetID, id_rect_content: WidgetID,
id_sprite_titlebar_icon: WidgetID,
id_label_titlebar_title: WidgetID,
} }
pub type FrontendTasks = Tasks<FrontendTask>; pub type FrontendTasks = Tasks<FrontendTask>;
@@ -148,6 +152,8 @@ impl<T: 'static> Frontend<T> {
let id_label_time = state.get_widget_id("label_time")?; let id_label_time = state.get_widget_id("label_time")?;
let id_rect_content = state.get_widget_id("rect_content")?; let id_rect_content = state.get_widget_id("rect_content")?;
let id_sprite_titlebar_icon = state.get_widget_id("sprite_titlebar_icon")?;
let id_label_titlebar_title = state.get_widget_id("label_titlebar_title")?;
let timestep = Timestep::new(60.0); let timestep = Timestep::new(60.0);
@@ -161,6 +167,8 @@ impl<T: 'static> Frontend<T> {
widgets: FrontendWidgets { widgets: FrontendWidgets {
id_label_time, id_label_time,
id_rect_content, id_rect_content,
id_sprite_titlebar_icon,
id_label_titlebar_title,
}, },
timestep, timestep,
interface: params.interface, interface: params.interface,
@@ -283,9 +291,10 @@ impl<T: 'static> Frontend<T> {
let mut common = c.common(); let mut common = c.common();
{ {
let Some(mut label) = common.state.widgets.get_as::<WidgetLabel>(self.widgets.id_label_time) else { let mut label = common
anyhow::bail!(""); .state
}; .widgets
.cast_as::<WidgetLabel>(self.widgets.id_label_time)?;
let now = chrono::Local::now(); let now = chrono::Local::now();
let hours = now.hour(); let hours = now.hour();
@@ -370,11 +379,48 @@ impl<T: 'static> Frontend<T> {
Ok(()) Ok(())
} }
fn set_tab_title(&mut self, translation: &str, icon: &str) -> anyhow::Result<()> {
let mut c = self.layout.start_common();
let mut common = c.common();
{
let mut label = common
.state
.widgets
.cast_as::<WidgetLabel>(self.widgets.id_label_titlebar_title)?;
label.set_text(&mut common, Translation::from_translation_key(translation));
}
{
let mut sprite = common
.state
.widgets
.cast_as::<WidgetSprite>(self.widgets.id_sprite_titlebar_icon)?;
sprite.set_content(
&mut common,
Some(CustomGlyphData::from_assets(&self.globals, AssetPath::BuiltIn(icon))?),
);
}
c.finish()?;
Ok(())
}
fn set_tab(&mut self, data: &mut T, tab_type: TabType) -> anyhow::Result<()> { fn set_tab(&mut self, data: &mut T, tab_type: TabType) -> anyhow::Result<()> {
log::info!("Setting tab to {tab_type:?}"); log::info!("Setting tab to {tab_type:?}");
let widget_content = self.state.fetch_widget(&self.layout.state, "content")?; let widget_content = self.state.fetch_widget(&self.layout.state, "content")?;
self.layout.remove_children(widget_content.id); self.layout.remove_children(widget_content.id);
let (tab_translation, icon_path) = match tab_type {
TabType::Home => ("HOME_SCREEN", "dashboard/home.svg"),
TabType::Apps => ("APPLICATIONS", "dashboard/apps.svg"),
TabType::Games => ("GAMES", "dashboard/games.svg"),
TabType::Monado => ("MONADO_RUNTIME", "dashboard/monado.svg"),
TabType::Settings => ("SETTINGS", "dashboard/settings.svg"),
};
self.set_tab_title(tab_translation, icon_path)?;
let tab: Box<dyn Tab<T>> = match tab_type { let tab: Box<dyn Tab<T>> = match tab_type {
TabType::Home => Box::new(TabHome::new(self, widget_content.id, data)?), TabType::Home => Box::new(TabHome::new(self, widget_content.id, data)?),
TabType::Apps => Box::new(TabApps::new(self, widget_content.id, data)?), TabType::Apps => Box::new(TabApps::new(self, widget_content.id, data)?),

View File

@@ -55,8 +55,7 @@ fn button_click_callback(
) -> ButtonClickCallback { ) -> ButtonClickCallback {
Rc::new(move |common, _e| { Rc::new(move |common, _e| {
label label
.get_as::<WidgetLabel>() .cast::<WidgetLabel>()?
.unwrap()
.set_text(common, Translation::from_raw_text(text)); .set_text(common, Translation::from_raw_text(text));
button.try_cast::<ComponentButton>()?.set_text( button.try_cast::<ComponentButton>()?.set_text(
@@ -169,7 +168,7 @@ impl TestbedGeneric {
let cb_first = parser_state.fetch_component_as::<ComponentCheckbox>("cb_first")?; let cb_first = parser_state.fetch_component_as::<ComponentCheckbox>("cb_first")?;
let label = label_cur_option.widget.clone(); let label = label_cur_option.widget.clone();
cb_first.on_toggle(Box::new(move |common, e| { cb_first.on_toggle(Box::new(move |common, e| {
let mut widget = label.get_as::<WidgetLabel>().unwrap(); let mut widget = label.cast::<WidgetLabel>()?;
let text = format!("checkbox toggle: {}", e.checked); let text = format!("checkbox toggle: {}", e.checked);
widget.set_text(common, Translation::from_raw_text(&text)); widget.set_text(common, Translation::from_raw_text(&text));
Ok(()) Ok(())

View File

@@ -103,8 +103,7 @@ pub(super) fn setup_custom_label<S: 'static>(
layout layout
.state .state
.widgets .widgets
.get_as::<WidgetLabel>(attribs.widget_id) .cast_as::<WidgetLabel>(attribs.widget_id)?
.unwrap()
.set_text_simple(&mut globals, Translation::from_raw_text(pretty_tz)); .set_text_simple(&mut globals, Translation::from_raw_text(pretty_tz));
// does not need to be dynamic // does not need to be dynamic

View File

@@ -75,6 +75,10 @@ impl WidgetMap {
self.0.get(handle)?.get_as::<T>() self.0.get(handle)?.get_as::<T>()
} }
pub fn cast_as<T: 'static>(&self, handle: WidgetID) -> anyhow::Result<RefMut<'_, T>> {
self.get_as(handle).context("Widget cast failed")
}
pub fn get(&self, handle: WidgetID) -> Option<&Widget> { pub fn get(&self, handle: WidgetID) -> Option<&Widget> {
self.0.get(handle) self.0.get(handle)
} }

View File

@@ -1,3 +1,4 @@
use anyhow::Context;
use glam::Vec2; use glam::Vec2;
use taffy::{NodeId, TaffyTree}; use taffy::{NodeId, TaffyTree};
@@ -248,10 +249,18 @@ impl dyn WidgetObj {
any.downcast_ref::<T>() any.downcast_ref::<T>()
} }
pub fn cast<T: 'static>(&self) -> anyhow::Result<&T> {
self.get_as().context("cast failed")
}
pub fn get_as_mut<T: 'static>(&mut self) -> Option<&mut T> { pub fn get_as_mut<T: 'static>(&mut self) -> Option<&mut T> {
let any = self.as_any_mut(); let any = self.as_any_mut();
any.downcast_mut::<T>() any.downcast_mut::<T>()
} }
pub fn cast_mut<T: 'static>(&mut self) -> anyhow::Result<&mut T> {
self.get_as_mut().context("cast failed")
}
} }
struct InvokeData<'a, 'b, U1: 'static, U2: 'static> { struct InvokeData<'a, 'b, U1: 'static, U2: 'static> {