ParserState: add `realize_template`, fix sliders not being updated

This commit is contained in:
Aleksander 2026-03-27 22:11:23 +01:00
parent 0a4fc34fac
commit 80277e0c12
8 changed files with 56 additions and 34 deletions

View File

@ -1,16 +1,16 @@
<layout> <layout>
<include src="../t_group_box.xml" /> <include src_builtin="../t_group_box.xml" />
<!-- device_name, device_icon --> <!-- device_name, device_icon -->
<template name="DeviceSlider"> <template name="DeviceSlider">
<rectangle macro="group_box"> <rectangle macro="group_box">
<div width="100%" align_items="center" justify_content="center" gap="8"> <div width="100%" align_items="center" justify_content="center" gap="8">
<sprite src="${device_icon}" width="16" height="16" /> <sprite src_builtin="${device_icon}" width="16" height="16" />
<label text="${device_name}" margin_right="8" size="12" weight="bold" /> <label text="${device_name}" margin_right="8" size="12" weight="bold" />
</div> </div>
<div width="100%" align_items="center"> <div width="100%" align_items="center">
<CheckBox id="checkbox" /> <CheckBox id="checkbox" />
<Button sprite_src="${volume_icon}" id="btn_mute" width="32" /> <Button sprite_src_builtin="${volume_icon}" id="btn_mute" width="32" />
<Slider id="slider" flex_grow="1" height="16" min_value="0" max_value="150" margin_left="8" /> <Slider id="slider" flex_grow="1" height="16" min_value="0" max_value="150" margin_left="8" />
</div> </div>
</rectangle> </rectangle>
@ -41,7 +41,7 @@
flex_grow="1" flex_grow="1"
id="${id}" id="${id}"
translation="${translation}" translation="${translation}"
sprite_src="${src}"> sprite_src_builtin="${src}">
</Button> </Button>
</template> </template>
@ -67,4 +67,4 @@
</div> </div>
</div> </div>
</elements> </elements>
</layout> </layout>

View File

@ -286,7 +286,7 @@ impl AppList {
let mut params = HashMap::<Rc<str>, Rc<str>>::new(); let mut params = HashMap::<Rc<str>, Rc<str>>::new();
params.insert("text".into(), category_name.into()); params.insert("text".into(), category_name.into());
parser_state.parse_template( parser_state.realize_template(
doc_params, doc_params,
"CategoryText", "CategoryText",
&mut frontend.layout, &mut frontend.layout,
@ -318,7 +318,7 @@ impl AppList {
); );
params.insert("name".into(), entry.app_name.clone()); params.insert("name".into(), entry.app_name.clone());
let data = parser_state.parse_template( let data = parser_state.realize_template(
doc_params, doc_params,
"AppEntry", "AppEntry",
&mut frontend.layout, &mut frontend.layout,

View File

@ -12,7 +12,7 @@ use wgui::{
drawing::Color, drawing::Color,
globals::WguiGlobals, globals::WguiGlobals,
layout::{Layout, WidgetID}, layout::{Layout, WidgetID},
parser::{self, Fetchable, ParseDocumentParams, ParserData, ParserState}, parser::{self, Fetchable, ParseDocumentParams, ParserState},
task::Tasks, task::Tasks,
}; };
use wlx_common::dash_interface::{self, MonadoDumpSessionFrame}; use wlx_common::dash_interface::{self, MonadoDumpSessionFrame};
@ -66,15 +66,11 @@ struct SubtabGeneralSettings {
struct DebugGraph { struct DebugGraph {
graph: Rc<ComponentBarGraph>, graph: Rc<ComponentBarGraph>,
#[allow(dead_code)]
data: ParserData,
} }
struct DebugSessionList { struct DebugSessionList {
#[allow(dead_code)] #[allow(dead_code)]
buttons: Vec<Rc<ComponentButton>>, buttons: Vec<Rc<ComponentButton>>,
#[allow(dead_code)]
data_vec: Vec<ParserData>,
} }
struct TimingsSession { struct TimingsSession {
@ -288,7 +284,6 @@ fn mount_sessions_list(
sessions: &SessionsMap, sessions: &SessionsMap,
) -> anyhow::Result<DebugSessionList> { ) -> anyhow::Result<DebugSessionList> {
let mut buttons = Vec::new(); let mut buttons = Vec::new();
let mut data_vec = Vec::new();
let globals = layout.state.globals.clone(); let globals = layout.state.globals.clone();
layout.remove_children(id_parent); layout.remove_children(id_parent);
@ -304,7 +299,7 @@ fn mount_sessions_list(
)), )),
); );
let data = state.parse_template( let data = state.realize_template(
&doc_params_tab_debug_timings(&globals), &doc_params_tab_debug_timings(&globals),
"SessionButton", "SessionButton",
layout, layout,
@ -324,10 +319,9 @@ fn mount_sessions_list(
}); });
buttons.push(button); buttons.push(button);
data_vec.push(data);
} }
Ok(DebugSessionList { buttons, data_vec }) Ok(DebugSessionList { buttons })
} }
fn mount_graph( fn mount_graph(
@ -343,7 +337,7 @@ fn mount_graph(
params.insert(Rc::from("limit_min"), Rc::from(limits.0.to_string())); params.insert(Rc::from("limit_min"), Rc::from(limits.0.to_string()));
params.insert(Rc::from("limit_max"), Rc::from(limits.1.to_string())); params.insert(Rc::from("limit_max"), Rc::from(limits.1.to_string()));
let data = state.parse_template( let data = state.realize_template(
&doc_params_tab_debug_timings(&globals), &doc_params_tab_debug_timings(&globals),
"DebugGraph", "DebugGraph",
layout, layout,
@ -352,7 +346,7 @@ fn mount_graph(
)?; )?;
let graph = data.fetch_component_as::<ComponentBarGraph>("graph")?; let graph = data.fetch_component_as::<ComponentBarGraph>("graph")?;
Ok(DebugGraph { graph, data }) Ok(DebugGraph { graph })
} }
fn ns_to_ms(ns: i64) -> f32 { fn ns_to_ms(ns: i64) -> f32 {
@ -585,7 +579,7 @@ impl SubtabProcessList {
let globals = layout.state.globals.clone(); let globals = layout.state.globals.clone();
let state_cell = self.state.parse_template( let state_cell = self.state.realize_template(
&doc_params_tab_process_list(&globals), &doc_params_tab_process_list(&globals),
"Cell", "Cell",
layout, layout,

View File

@ -750,7 +750,7 @@ impl View {
let data = self let data = self
.state .state
.parse_template(&doc_params(&self.globals), "Card", params.layout, self.id_devices, par)?; .realize_template(&doc_params(&self.globals), "Card", params.layout, self.id_devices, par)?;
let btn_card = data.fetch_component_as::<ComponentButton>("btn_card")?; let btn_card = data.fetch_component_as::<ComponentButton>("btn_card")?;
btn_card.on_click({ btn_card.on_click({
@ -764,7 +764,6 @@ impl View {
}) })
}); });
log::info!("mount card TODO: {}", params.card.name);
Ok(()) Ok(())
} }
@ -794,7 +793,7 @@ impl View {
}, },
); );
let data = self.state.parse_template( let data = self.state.realize_template(
&doc_params(&self.globals), &doc_params(&self.globals),
"DeviceSlider", "DeviceSlider",
params.layout, params.layout,
@ -941,7 +940,7 @@ impl View {
layout.remove_children(self.id_devices); layout.remove_children(self.id_devices);
{ {
let data = self.state.parse_template( let data = self.state.realize_template(
&doc_params(&self.globals), &doc_params(&self.globals),
"SelectAudioProfileText", "SelectAudioProfileText",
layout, layout,

View File

@ -127,7 +127,7 @@ impl View {
for game in games { for game in games {
let game_name = View::extract_name_from_appid(&game.app_id, &self.installed_games); let game_name = View::extract_name_from_appid(&game.app_id, &self.installed_games);
let t = self.state.parse_template( let t = self.state.realize_template(
&doc_params(layout.state.globals.clone()), &doc_params(layout.state.globals.clone()),
"RunningGameCell", "RunningGameCell",
layout, layout,

View File

@ -377,7 +377,7 @@ impl Layout {
self.registered_components_to_refresh.insert(*node_id, component.weak()); self.registered_components_to_refresh.insert(*node_id, component.weak());
} }
/// Convenience function to avoid repeated `WidgetID` → `WidgetState` lookups. /// Convenience function to avoid repeated `WidgetID` → `WidgetState` look-ups.
pub fn add_event_listener<U1: 'static, U2: 'static>( pub fn add_event_listener<U1: 'static, U2: 'static>(
&self, &self,
widget_id: WidgetID, widget_id: WidgetID,

View File

@ -203,7 +203,6 @@ impl Fetchable for ParserData {
let casted = widget let casted = widget
.get_as::<T>() .get_as::<T>()
.ok_or_else(|| anyhow::anyhow!("fetch_widget_as({id}): failed to cast"))?; .ok_or_else(|| anyhow::anyhow!("fetch_widget_as({id}): failed to cast"))?;
Ok(casted) Ok(casted)
} }
} }
@ -219,16 +218,40 @@ pub struct ParserState {
} }
impl ParserState { impl ParserState {
/// This function is suitable in cases if you don't want to pollute main parser state with dynamic IDs /// Parse named <template> tag and process it.
/// Use `instantiate_template` instead unless you want to handle `components` results yourself. /// Preferred method of parsing templates. Same as `parse_template_only`,
/// Make sure not to drop them if you want to have your listener handles valid /// but it keeps components data in this `ParserState` object for you.
pub fn parse_template( /// The result can be safely dropped, all required event listeners and components
/// will be kept intact in this `ParserState`.
/// Resulting ParserData::components Vec will be left empty (they are moved into this `ParserState::data`)
pub fn realize_template(
&mut self, &mut self,
doc_params: &ParseDocumentParams, doc_params: &ParseDocumentParams,
template_name: &str, template_name: &str,
layout: &mut Layout, layout: &mut Layout,
widget_id: WidgetID, widget_id: WidgetID,
template_parameters: HashMap<Rc<str>, Rc<str>>, template_parameters: HashMap<Rc<str>, Rc<str>>,
) -> anyhow::Result<ParserData> {
let mut parser_data =
self.parse_template_only(doc_params, template_name, layout, widget_id, template_parameters)?;
// Collect components contained in this freshly-parsed template
self.data.components.append(&mut parser_data.components);
Ok(parser_data)
}
/// Parse named <template> tag and process it.
/// Semi-internal - This function is suitable in cases if you don't want to pollute
/// the main parser state state with dynamic IDs (this won't propagate components!)
/// Use `realize_template` (or in some rare cases: `instantiate_template`) instead unless you want to handle `components` results yourself.
/// Make sure not to drop resulting ParserData if you want to have your listener handles valid
/// (they are contained in components). Use `realize_template` instead if you don't want to think about it.
pub fn parse_template_only(
&self,
doc_params: &ParseDocumentParams,
template_name: &str,
layout: &mut Layout,
widget_id: WidgetID,
template_parameters: HashMap<Rc<str>, Rc<str>>,
) -> anyhow::Result<ParserData> { ) -> anyhow::Result<ParserData> {
let Some(template) = self.data.templates.get(template_name) else { let Some(template) = self.data.templates.get(template_name) else {
anyhow::bail!( anyhow::bail!(
@ -254,7 +277,13 @@ impl ParserState {
Ok(ctx.data_local) Ok(ctx.data_local)
} }
/// Instantiate template by saving all the results into the main `ParserState` /// Parse named <template> tag and process it.
/// Instantiate template by saving all the results into the main `ParserState`.
/// Be aware you this function will save ALL parsed IDs and other metadata
/// into your main ParserState context (deep move).
/// You shouldn't instantiate the same template twice, to prevent ID name clash.
/// Consider using `parse_template_only` or `realize_template` instead if you want
/// to instantiate more than a single template of the same type.
pub fn instantiate_template( pub fn instantiate_template(
&mut self, &mut self,
doc_params: &ParseDocumentParams, doc_params: &ParseDocumentParams,
@ -263,7 +292,7 @@ impl ParserState {
widget_id: WidgetID, widget_id: WidgetID,
template_parameters: HashMap<Rc<str>, Rc<str>>, template_parameters: HashMap<Rc<str>, Rc<str>>,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let mut data_local = self.parse_template(doc_params, template_name, layout, widget_id, template_parameters)?; let mut data_local = self.parse_template_only(doc_params, template_name, layout, widget_id, template_parameters)?;
self.data.take_results_from(&mut data_local); self.data.take_results_from(&mut data_local);
Ok(()) Ok(())

View File

@ -117,7 +117,7 @@ impl ContextMenu {
par.insert(Rc::from("tooltip_str"), tooltip.generate(&mut globals.i18n())); par.insert(Rc::from("tooltip_str"), tooltip.generate(&mut globals.i18n()));
} }
let mut data_cell = inner_parser.parse_template(&doc_params, "Cell", layout, id_buttons, par)?; let mut data_cell = inner_parser.realize_template(&doc_params, "Cell", layout, id_buttons, par)?;
let button = data_cell.fetch_component_as::<ComponentButton>("button")?; let button = data_cell.fetch_component_as::<ComponentButton>("button")?;
let button_id = button.base().get_id(); let button_id = button.base().get_id();
@ -136,7 +136,7 @@ impl ContextMenu {
} }
if idx < cells.len() - 1 { if idx < cells.len() - 1 {
inner_parser.parse_template(&doc_params, "Separator", layout, id_buttons, Default::default())?; inner_parser.realize_template(&doc_params, "Separator", layout, id_buttons, Default::default())?;
} }
} }
Ok(()) Ok(())