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",
}
}
}