diff --git a/Cargo.lock b/Cargo.lock index 18302e7a..8be646bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -910,6 +910,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "chacha20" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.0", +] + [[package]] name = "chrono" version = "0.4.42" @@ -1231,6 +1242,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + [[package]] name = "crc32fast" version = "1.5.0" @@ -1417,6 +1437,7 @@ dependencies = [ "hyper", "keyvalues-parser", "log", + "rand 0.10.0", "rust-embed", "serde", "serde_json", @@ -2106,6 +2127,20 @@ dependencies = [ "wasip2", ] +[[package]] +name = "getrandom" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "rand_core 0.10.0", + "wasip2", + "wasip3", +] + [[package]] name = "gif" version = "0.14.1" @@ -2440,6 +2475,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + [[package]] name = "ident_case" version = "1.0.1" @@ -2575,6 +2616,8 @@ checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" dependencies = [ "equivalent", "hashbrown 0.16.1", + "serde", + "serde_core", ] [[package]] @@ -2795,6 +2838,18 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + [[package]] name = "lebe" version = "0.5.3" @@ -4330,6 +4385,17 @@ dependencies = [ "rand_core 0.9.3", ] +[[package]] +name = "rand" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8" +dependencies = [ + "chacha20", + "getrandom 0.4.1", + "rand_core 0.10.0", +] + [[package]] name = "rand_chacha" version = "0.2.2" @@ -4387,6 +4453,12 @@ dependencies = [ "getrandom 0.3.4", ] +[[package]] +name = "rand_core" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" + [[package]] name = "rand_hc" version = "0.2.0" @@ -4604,6 +4676,12 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "ringbuffer" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57b0b88a509053cbfd535726dcaaceee631313cef981266119527a1d110f6d2b" + [[package]] name = "rodio" version = "0.21.1" @@ -4983,7 +5061,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest", ] @@ -5925,6 +6003,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "unsafe-libyaml" version = "0.2.11" @@ -6126,7 +6210,16 @@ version = "1.0.1+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.46.0", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen 0.51.0", ] [[package]] @@ -6187,6 +6280,40 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap 2.12.1", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.10.0", + "hashbrown 0.15.5", + "indexmap 2.12.1", + "semver", +] + [[package]] name = "wayland-backend" version = "0.3.12" @@ -6472,6 +6599,7 @@ dependencies = [ "parking_lot", "regex", "resvg", + "ringbuffer", "roxmltree 0.21.1", "rust-embed", "rustc-hash 2.1.1", @@ -6995,6 +7123,94 @@ version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck 0.5.0", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck 0.5.0", + "indexmap 2.12.1", + "prettyplease", + "syn 2.0.113", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.113", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.10.0", + "indexmap 2.12.1", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap 2.12.1", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + [[package]] name = "wlx-capture" version = "0.6.0" diff --git a/dash-frontend/Cargo.toml b/dash-frontend/Cargo.toml index 06f1b010..96848392 100644 --- a/dash-frontend/Cargo.toml +++ b/dash-frontend/Cargo.toml @@ -28,6 +28,7 @@ hyper = { version = "1.8.1", features = ["client", "http1", "http2"] } http-body-util = "0.1.3" async-native-tls = "0.5.0" smol-hyper = "0.1.1" +rand = "0.10.0" [features] default = ["monado"] diff --git a/dash-frontend/assets/gui/tab/monado_tab_debug_timings.xml b/dash-frontend/assets/gui/tab/monado_tab_debug_timings.xml index fed22b53..d74db3d1 100644 --- a/dash-frontend/assets/gui/tab/monado_tab_debug_timings.xml +++ b/dash-frontend/assets/gui/tab/monado_tab_debug_timings.xml @@ -3,7 +3,10 @@ - + + + + \ No newline at end of file diff --git a/dash-frontend/src/tab/monado.rs b/dash-frontend/src/tab/monado.rs index 5a59242d..f1758a5b 100644 --- a/dash-frontend/src/tab/monado.rs +++ b/dash-frontend/src/tab/monado.rs @@ -2,7 +2,13 @@ use std::{collections::HashMap, marker::PhantomData, rc::Rc}; use wgui::{ assets::AssetPath, - components::{checkbox::ComponentCheckbox, slider::ComponentSlider, tabs::ComponentTabs}, + components::{ + bar_graph::{ComponentBarGraph, ValueCell}, + checkbox::ComponentCheckbox, + slider::ComponentSlider, + tabs::ComponentTabs, + }, + drawing, globals::WguiGlobals, layout::{Layout, WidgetID}, parser::{self, Fetchable, ParseDocumentParams, ParserState}, @@ -56,6 +62,9 @@ struct SubtabGeneralSettings { struct SubtabDebugTimings { #[allow(dead_code)] state: ParserState, + + graph_first: Rc, + graph_second: Rc, } #[allow(dead_code)] @@ -121,11 +130,20 @@ impl Tab for TabMonado { } } - // every few seconds - if let Subtab::ProcessList(_) = &self.subtab - && self.ticks.is_multiple_of(500) - { - self.tasks.push(Task::ProcessListRefresh); + match &mut self.subtab { + Subtab::Empty => {} + Subtab::GeneralSettings(_) => {} + Subtab::ProcessList(_) => { + // every few seconds + if let Subtab::ProcessList(_) = &self.subtab + && self.ticks.is_multiple_of(500) + { + self.tasks.push(Task::ProcessListRefresh); + } + } + Subtab::DebugTimings(timings) => { + timings.update(&mut frontend.layout); + } } self.ticks += 1; @@ -214,7 +232,28 @@ impl SubtabDebugTimings { parent_id, )?; - Ok(Self { state }) + let graph_first = state.fetch_component_as::("graph_first")?; + let graph_second = state.fetch_component_as::("graph_second")?; + + Ok(Self { + state, + graph_first, + graph_second, + }) + } + + fn update(&mut self, layout: &mut Layout) { + self.graph_first.push_value(ValueCell { + value: rand::random_range(0.0..50.0), + color: drawing::Color::new(rand::random_range(0.0..1.0), rand::random_range(0.0..1.0), 0.0, 1.0), + }); + + self.graph_second.push_value(ValueCell { + value: rand::random_range(0.0..30.0), + color: drawing::Color::new(0.0, rand::random_range(0.0..1.0), rand::random_range(0.0..1.0), 1.0), + }); + + layout.mark_redraw(); } } diff --git a/wayvr/src/backend/openxr/monado_state.rs b/wayvr/src/backend/openxr/monado_state.rs index 293cb3d7..fd679f74 100644 --- a/wayvr/src/backend/openxr/monado_state.rs +++ b/wayvr/src/backend/openxr/monado_state.rs @@ -7,14 +7,10 @@ pub struct MonadoState { impl MonadoState { pub fn new() -> anyhow::Result { - let mut ipc = libmonado::Monado::auto_connect().map_err(|s| anyhow::anyhow!("{s}"))?; - - let metrics = monado_metrics::metrics_fd::MonadoMetricsFd::new(&mut ipc)?; - - Ok(Self { - ipc, - metrics: Some(metrics), - }) + let ipc = libmonado::Monado::auto_connect().map_err(|s| anyhow::anyhow!("{s}"))?; + let mut res = Self { ipc, metrics: None }; + res.set_metrics_enabled(true)?; + Ok(res) } pub fn update(&mut self) { @@ -22,4 +18,16 @@ impl MonadoState { metrics.update(); } } + + pub fn set_metrics_enabled(&mut self, enabled: bool) -> anyhow::Result<()> { + if enabled && self.metrics.is_none() { + self.metrics = Some(monado_metrics::metrics_fd::MonadoMetricsFd::new( + &mut self.ipc, + )?); + } else { + self.metrics = None; + } + + Ok(()) + } } diff --git a/wayvr/src/subsystem/monado_metrics/metrics_fd.rs b/wayvr/src/subsystem/monado_metrics/metrics_fd.rs index a5c4ca8d..e158e93c 100644 --- a/wayvr/src/subsystem/monado_metrics/metrics_fd.rs +++ b/wayvr/src/subsystem/monado_metrics/metrics_fd.rs @@ -1,4 +1,5 @@ use std::{ + collections::VecDeque, io::Read, os::{fd::AsFd, unix::net::UnixStream}, }; @@ -8,6 +9,8 @@ use crate::subsystem::monado_metrics::proto; pub struct MonadoMetricsFd { stream_reader: UnixStream, stream_writer: UnixStream, + + records: VecDeque, } impl MonadoMetricsFd { @@ -21,11 +24,18 @@ impl MonadoMetricsFd { Ok(Self { stream_reader, stream_writer, + records: VecDeque::new(), }) } - fn parse_message(&self, record: proto::Record) { + fn parse_message(&mut self, record: proto::Record) { log::debug!("metrics message: {record:?}"); + + if self.records.len() < 500 { + self.records.push_back(record); + } else { + log::warn!("record queue full, discarding"); + } } // called every frame diff --git a/wgui/Cargo.toml b/wgui/Cargo.toml index 3b6ab1f8..4a9a8e32 100644 --- a/wgui/Cargo.toml +++ b/wgui/Cargo.toml @@ -35,3 +35,4 @@ vulkano.workspace = true vulkano-shaders.workspace = true rust-embed.workspace = true flate2 = "1.1.5" +ringbuffer = "0.16.0" diff --git a/wgui/doc/widgets.md b/wgui/doc/widgets.md index 24817b15..2c094160 100644 --- a/wgui/doc/widgets.md +++ b/wgui/doc/widgets.md @@ -6,7 +6,7 @@ ## [Built-in components](#components) -[Button](#button-component), [Slider](#slider-component), [CheckBox](#checkbox-component), [Tabs](#tabs-component) ([Tab](#tab-component)), [EditBox](#editbox-component) +[Button](#button-component), [Slider](#slider-component), [CheckBox](#checkbox-component), [Tabs](#tabs-component) ([Tab](#tab-component)), [EditBox](#editbox-component), [BarGraph](#bargraph-component) ## [Examples](#examples) @@ -448,6 +448,30 @@ _Image path (see [sprite](#sprite-widget)) for src descriptions_ _Initial text content_ +## BarGraph component + +### `` + +### A bar graph widget + +#### Parameters + +`limit_min`: **float** + +_Minimum limit value_ + +`limit_max`: **float** + +_Maximum limit value_ + +`unit`: **string** (default: "") + +_Unit type, for example "%" or "ms"_ + +`capacity`: **int** (default: 50) + +_Value count_ + --- # Examples diff --git a/wgui/src/components/bar_graph.rs b/wgui/src/components/bar_graph.rs new file mode 100644 index 00000000..bc56b49b --- /dev/null +++ b/wgui/src/components/bar_graph.rs @@ -0,0 +1,241 @@ +use std::{cell::RefCell, rc::Rc}; + +use glam::Vec2; +use ringbuffer::{AllocRingBuffer, RingBuffer}; +use taffy::{ + FlexDirection, JustifyContent, + prelude::{auto, length, percent}, +}; + +use crate::{ + components::{Component, ComponentBase, ComponentTrait, RefreshData}, + drawing::{self, GradientMode, PrimitiveExtent, RenderPrimitive}, + event::CallbackDataCommon, + i18n::Translation, + layout::{WidgetID, WidgetPair}, + renderer_vk::text::{FontWeight, HorizontalAlign, TextStyle}, + widget::{ + ConstructEssentials, + custom_draw::{WidgetCustomDraw, WidgetCustomDrawParams}, + div::WidgetDiv, + label::{WidgetLabel, WidgetLabelParams}, + rectangle::{WidgetRectangle, WidgetRectangleParams}, + util::WLength, + }, +}; + +#[derive(Default)] +pub struct Params { + pub style: taffy::Style, + pub limits: (f32, f32), + pub unit: String, + pub capacity: u32, +} + +pub struct ValueCell { + pub value: f32, + pub color: drawing::Color, +} + +struct State { + limits: (f32, f32), /* min - max */ + values: AllocRingBuffer, +} + +#[allow(clippy::struct_field_names)] +struct Data { + #[allow(dead_code)] + id_root: WidgetID, + + id_label_val_min: WidgetID, + id_label_val_max: WidgetID, + + unit: String, +} + +pub struct ComponentBarGraph { + base: ComponentBase, + data: Rc, + state: Rc>, +} + +impl ComponentTrait for ComponentBarGraph { + fn base(&self) -> &ComponentBase { + &self.base + } + + fn base_mut(&mut self) -> &mut ComponentBase { + &mut self.base + } + + fn refresh(&self, data: &mut RefreshData) { + let state = self.state.borrow(); + self.update_limits_text(&state, data.common); + } +} + +impl ComponentBarGraph { + fn update_limits_text(&self, state: &State, c: &mut CallbackDataCommon) -> Option<()> { + let mut label_val_min = c.state.widgets.get_as::(self.data.id_label_val_min)?; + let mut label_val_max = c.state.widgets.get_as::(self.data.id_label_val_max)?; + + label_val_min.set_text( + c, + Translation::from_raw_text_string(format!("{}{}", state.limits.0, self.data.unit)), + ); + label_val_max.set_text( + c, + Translation::from_raw_text_string(format!("{}{}", state.limits.1, self.data.unit)), + ); + + Some(()) + } + + pub fn set_limits(&self, c: &mut CallbackDataCommon, limits: (f32, f32)) { + let mut state = self.state.borrow_mut(); + state.limits = limits; + self.update_limits_text(&state, c); + } + + pub fn push_value(&self, cell: ValueCell) { + let mut state = self.state.borrow_mut(); + state.values.enqueue(cell); + } +} + +pub fn construct( + ess: &mut ConstructEssentials, + mut params: Params, +) -> anyhow::Result<(WidgetPair, Rc)> { + let globals = ess.layout.state.globals.clone(); + + params.style.flex_direction = FlexDirection::Row; + params.style.gap = length(4.0); + + // override style + let (root, _) = ess.layout.add_child(ess.parent, WidgetDiv::create(), params.style)?; + + let (vertical_texts, _) = ess.layout.add_child( + root.id, + WidgetDiv::create(), + taffy::Style { + justify_content: Some(JustifyContent::SpaceBetween), + flex_direction: FlexDirection::Column, + size: taffy::Size { + width: auto(), + height: percent(1.0), + }, + ..Default::default() + }, + )?; + + let (rect, _) = ess.layout.add_child( + root.id, + WidgetRectangle::create(WidgetRectangleParams { + border: 2.0, + border_color: drawing::Color::new(1.0, 1.0, 1.0, 0.5), + round: WLength::Units(3.0), + gradient: GradientMode::Vertical, + color: drawing::Color::new(0.0, 0.0, 0.0, 0.6), + ..Default::default() + }), + taffy::Style { + position: taffy::Position::Relative, + size: taffy::Size { + width: percent(1.0), + height: percent(1.0), + }, + ..Default::default() + }, + )?; + + let state = Rc::new(RefCell::new(State { + limits: params.limits, + values: AllocRingBuffer::new(params.capacity.clamp(1, 1000) as usize), + })); + + let (_, _) = ess.layout.add_child( + rect.id, + WidgetCustomDraw::create(WidgetCustomDrawParams { + func: { + let state = state.clone(); + Box::new(move |info| { + let state = state.borrow(); + let (limit_min, limit_max) = state.limits; + + let box_width = info.boundary.width(); + let box_height = info.boundary.height(); + + let bar_width = box_width / state.values.len() as f32; + + for (idx, cell) in state.values.iter().enumerate() { + let norm_value = ((cell.value - limit_min) / (limit_max - limit_min)).clamp(0.0, 1.0); + let bar_height = norm_value * box_height; + let bar_x = bar_width * idx as f32; + let bar_y = box_height - bar_height; + + info.primitives.push(RenderPrimitive::Rectangle( + PrimitiveExtent { + boundary: drawing::Boundary { + pos: Vec2::new(bar_x, bar_y), + size: Vec2::new(bar_width, bar_height), + }, + transform: info.transform.transform, + }, + drawing::Rectangle { + color: cell.color, + ..Default::default() + }, + )); + } + }) + }, + }), + taffy::Style { + size: taffy::Size { + width: percent(1.0), + height: percent(1.0), + }, + ..Default::default() + }, + )?; + + let label_params = WidgetLabelParams { + style: TextStyle { + align: Some(HorizontalAlign::Right), + weight: Some(FontWeight::Bold), + size: Some(11.0), + ..Default::default() + }, + ..Default::default() + }; + + let (label_val_max, _) = ess.layout.add_child( + vertical_texts.id, + WidgetLabel::create(&mut globals.get(), label_params.clone()), + Default::default(), + )?; + + let (label_val_min, _) = ess.layout.add_child( + vertical_texts.id, + WidgetLabel::create(&mut globals.get(), label_params), + Default::default(), + )?; + + let data = Rc::new(Data { + id_root: root.id, + id_label_val_min: label_val_min.id, + id_label_val_max: label_val_max.id, + unit: params.unit, + }); + + let base = ComponentBase { + id: root.id, + lhandles: Vec::new(), + }; + + let bar_graph = Rc::new(ComponentBarGraph { base, data, state }); + + ess.layout.defer_component_refresh(Component(bar_graph.clone())); + Ok((root, bar_graph)) +} diff --git a/wgui/src/components/mod.rs b/wgui/src/components/mod.rs index 57ef7398..b5916f18 100644 --- a/wgui/src/components/mod.rs +++ b/wgui/src/components/mod.rs @@ -7,6 +7,7 @@ use crate::{ layout::WidgetID, }; +pub mod bar_graph; pub mod button; pub mod checkbox; pub mod editbox; diff --git a/wgui/src/drawing.rs b/wgui/src/drawing.rs index 6546fe5a..71a66e43 100644 --- a/wgui/src/drawing.rs +++ b/wgui/src/drawing.rs @@ -225,11 +225,13 @@ pub struct ImagePrimitive { pub round_units: u8, } +#[derive(Clone)] pub struct PrimitiveExtent { pub(super) boundary: Boundary, pub(super) transform: Mat4, } +#[derive(Clone)] pub enum RenderPrimitive { NewPass, Rectangle(PrimitiveExtent, Rectangle), diff --git a/wgui/src/layout.rs b/wgui/src/layout.rs index 2efb3398..9c9a786b 100644 --- a/wgui/src/layout.rs +++ b/wgui/src/layout.rs @@ -883,6 +883,7 @@ impl Layout { widget::WidgetType::Label => drawing::Color::new(0.4, 1.0, 0.0, 1.0), widget::WidgetType::Sprite => drawing::Color::new(0.0, 0.8, 1.0, 1.0), widget::WidgetType::Rectangle => drawing::Color::new(1.0, 0.5, 0.2, 1.0), + widget::WidgetType::CustomDraw => drawing::Color::new(1.0, 1.0, 1.0, 1.0), }; let line = format!( diff --git a/wgui/src/parser/component_bar_graph.rs b/wgui/src/parser/component_bar_graph.rs new file mode 100644 index 00000000..0ae4385b --- /dev/null +++ b/wgui/src/parser/component_bar_graph.rs @@ -0,0 +1,56 @@ +use crate::{ + components::{Component, bar_graph}, + layout::WidgetID, + parser::{AttribPair, ParserContext, process_component, style::parse_style}, + widget::ConstructEssentials, +}; + +pub fn parse_component_bar_graph( + ctx: &mut ParserContext, + parent_id: WidgetID, + attribs: &[AttribPair], + tag_name: &str, +) -> anyhow::Result { + let style = parse_style(ctx, attribs, tag_name); + let mut limit_min = 0.0; + let mut capacity = 50; + let mut limit_max = 100.0; + let mut unit = String::new(); + + for pair in attribs { + let (key, value) = (pair.attrib.as_ref(), pair.value.as_ref()); + #[allow(clippy::single_match)] + match key { + "capacity" => { + ctx.parse_check_i32(tag_name, key, value, &mut capacity); + } + "limit_min" => { + ctx.parse_check_f32(tag_name, key, value, &mut limit_min); + } + "limit_max" => { + ctx.parse_check_f32(tag_name, key, value, &mut limit_max); + } + "unit" => { + unit = value.to_string(); + } + _ => {} + } + } + + let (widget, component) = bar_graph::construct( + &mut ConstructEssentials { + layout: ctx.layout, + parent: parent_id, + }, + bar_graph::Params { + style, + limits: (limit_min, limit_max), + unit, + capacity: capacity.try_into().unwrap_or(50), + }, + )?; + + process_component(ctx, Component(component), widget.id, attribs); + + Ok(widget.id) +} diff --git a/wgui/src/parser/mod.rs b/wgui/src/parser/mod.rs index b5f5fa89..bd716e7e 100644 --- a/wgui/src/parser/mod.rs +++ b/wgui/src/parser/mod.rs @@ -1,3 +1,4 @@ +mod component_bar_graph; mod component_button; mod component_checkbox; mod component_editbox; @@ -21,6 +22,7 @@ use crate::{ layout::{Layout, LayoutParams, LayoutState, Widget, WidgetID, WidgetMap, WidgetPair}, log::LogErr, parser::{ + component_bar_graph::parse_component_bar_graph, component_button::parse_component_button, component_checkbox::{CheckboxKind, parse_component_checkbox}, component_editbox::parse_component_editbox, @@ -1056,6 +1058,7 @@ 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)?); } diff --git a/wgui/src/renderer_vk/text/text_atlas.rs b/wgui/src/renderer_vk/text/text_atlas.rs index 2b91d9ed..dc40d02e 100644 --- a/wgui/src/renderer_vk/text/text_atlas.rs +++ b/wgui/src/renderer_vk/text/text_atlas.rs @@ -187,7 +187,7 @@ impl InnerAtlas { // Grow each dimension by a factor of 2. The growth factor was chosen to match the growth // factor of `Vec`.` let new_size = (self.size * GROWTH_FACTOR).min(self.max_texture_dimension_2d); - log::info!("Grow {:?} atlas {} → {new_size}", self.kind, self.size); + log::debug!("Grow {:?} atlas {} → {new_size}", self.kind, self.size); self.packer.grow(size2(new_size as i32, new_size as i32)); diff --git a/wgui/src/widget/custom_draw.rs b/wgui/src/widget/custom_draw.rs new file mode 100644 index 00000000..e262bca4 --- /dev/null +++ b/wgui/src/widget/custom_draw.rs @@ -0,0 +1,65 @@ +use slotmap::Key; + +use crate::{ + drawing::{self}, + layout::WidgetID, + stack, + widget::WidgetStateFlags, +}; + +use super::{WidgetObj, WidgetState}; + +pub struct CustomDrawArgs<'a> { + pub boundary: &'a drawing::Boundary, + pub transform: &'a stack::Transform, + pub primitives: &'a mut Vec, +} + +pub struct WidgetCustomDrawParams { + pub func: Box, +} + +// FIXME: bring up a better name for this +pub struct WidgetCustomDraw { + pub params: WidgetCustomDrawParams, + id: WidgetID, +} + +impl WidgetCustomDraw { + pub fn create(params: WidgetCustomDrawParams) -> WidgetState { + WidgetState::new( + WidgetStateFlags::default(), + Box::new(Self { + params, + id: WidgetID::null(), + }), + ) + } +} + +impl WidgetObj for WidgetCustomDraw { + fn draw(&mut self, state: &mut super::DrawState, _params: &super::DrawParams) { + let boundary = drawing::Boundary::construct_relative(state.transform_stack); + (*self.params.func)(CustomDrawArgs { + primitives: state.primitives, + boundary: &boundary, + transform: state.transform_stack.get(), + }); + } + + fn get_id(&self) -> WidgetID { + self.id + } + + fn set_id(&mut self, id: WidgetID) { + self.id = id; + } + + fn get_type(&self) -> super::WidgetType { + super::WidgetType::Rectangle + } + + fn debug_print(&self) -> String { + String::default() + } +} diff --git a/wgui/src/widget/label.rs b/wgui/src/widget/label.rs index e83bb46f..8e4cf0b2 100644 --- a/wgui/src/widget/label.rs +++ b/wgui/src/widget/label.rs @@ -17,7 +17,7 @@ use crate::{ use super::{WidgetObj, WidgetState}; -#[derive(Debug, Default)] +#[derive(Debug, Clone, Default)] pub struct WidgetLabelParams { pub content: Translation, pub style: TextStyle, diff --git a/wgui/src/widget/mod.rs b/wgui/src/widget/mod.rs index d07a34eb..08f7aeda 100644 --- a/wgui/src/widget/mod.rs +++ b/wgui/src/widget/mod.rs @@ -17,6 +17,7 @@ use crate::{ stack::{ScissorStack, TransformStack}, }; +pub mod custom_draw; pub mod div; pub mod image; pub mod label; @@ -152,6 +153,7 @@ pub enum WidgetType { Label, Sprite, Rectangle, + CustomDraw, } impl WidgetType { @@ -161,6 +163,7 @@ impl WidgetType { WidgetType::Label => "label", WidgetType::Sprite => "sprite", WidgetType::Rectangle => "rectangle", + WidgetType::CustomDraw => "custom_draw", } } }