egui_charts/ui_kit/toolbar/mod.rs
1//! Horizontal and vertical toolbar layouts with grouped buttons.
2//!
3//! Use [`ResponsiveToolbar`] for a toolbar container, [`ButtonGroup`] for related
4//! actions and [`ToolbarLayout`] to split left and right controls. Toolbars lay out
5//! contents directly without adding scrolling.
6
7mod button_group;
8
9pub use button_group::{ButtonGroup, ToolbarLayout};
10
11use std::hash::Hash;
12
13use egui::{Id, Ui};
14
15use crate::styles::responsive::LayoutContext;
16
17/// Toolbar orientation
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
19pub enum Orientation {
20 /// Horizontal toolbar (most common)
21 #[default]
22 Horizontal,
23 /// Vertical toolbar (e.g., drawing toolbar)
24 Vertical,
25}
26
27/// Horizontal or vertical toolbar container with an egui state scope.
28///
29/// Contents use the available layout directly, without a scroll area.
30///
31/// # Example
32/// ```
33/// # use egui_charts::ui_kit::ResponsiveToolbar;
34/// # egui::__run_test_ui(|ui| {
35/// ResponsiveToolbar::horizontal("my_toolbar")
36/// .height(48.0)
37/// .show(ui, |ui, ctx| {
38/// // Render toolbar contents
39/// });
40/// # });
41/// ```
42pub struct ResponsiveToolbar {
43 /// Unique toolbar ID, scoping the contents' egui state so multiple toolbars
44 /// in one frame do not collide.
45 id: Id,
46 orientation: Orientation,
47 height: Option<f32>,
48}
49
50impl ResponsiveToolbar {
51 /// Create a new horizontal toolbar with the given ID
52 pub fn horizontal(id: impl Hash + std::fmt::Debug) -> Self {
53 Self {
54 id: Id::unique(id),
55 orientation: Orientation::Horizontal,
56 height: None,
57 }
58 }
59
60 /// Create a new vertical toolbar with the given ID
61 pub fn vertical(id: impl Hash + std::fmt::Debug) -> Self {
62 Self {
63 id: Id::unique(id),
64 orientation: Orientation::Vertical,
65 height: None,
66 }
67 }
68
69 /// Set the fixed height for the toolbar
70 #[must_use]
71 pub fn height(mut self, h: f32) -> Self {
72 self.height = Some(h);
73 self
74 }
75
76 /// Show the toolbar contents without a scroll area.
77 ///
78 /// The callback receives:
79 /// - `ui`: The UI context to render into
80 /// - `ctx`: The layout context
81 ///
82 /// Returns the value returned by the callback.
83 pub fn show<R>(self, ui: &mut Ui, content: impl FnOnce(&mut Ui, &LayoutContext) -> R) -> R {
84 let layout_ctx = LayoutContext::from_egui(ui.ctx());
85 let id = self.id;
86
87 ui.push_id(id, |ui| match self.orientation {
88 Orientation::Horizontal => self.show_horizontal(ui, &layout_ctx, content),
89 Orientation::Vertical => self.show_vertical(ui, &layout_ctx, content),
90 })
91 .inner
92 }
93
94 fn show_horizontal<R>(
95 self,
96 ui: &mut Ui,
97 layout_ctx: &LayoutContext,
98 content: impl FnOnce(&mut Ui, &LayoutContext) -> R,
99 ) -> R {
100 // Get the FULL panel rect
101 let full_rect = ui.max_rect();
102 let bg_color = ui.style().visuals.panel_fill;
103
104 // Paint background over the full area
105 ui.painter().rect_filled(full_rect, 0.0, bg_color);
106
107 // Allocate the entire space to prevent gaps
108 let _ = ui.allocate_rect(full_rect, egui::Sense::hover());
109
110 // Create a child UI with centered layout
111 let mut child_ui = ui.new_child(
112 egui::UiBuilder::new()
113 .max_rect(full_rect)
114 .layout(egui::Layout::left_to_right(egui::Align::Center)),
115 );
116
117 // Desktop: render content directly (no scroll)
118 content(&mut child_ui, layout_ctx)
119 }
120
121 fn show_vertical<R>(
122 self,
123 ui: &mut Ui,
124 layout_ctx: &LayoutContext,
125 content: impl FnOnce(&mut Ui, &LayoutContext) -> R,
126 ) -> R {
127 // Desktop: normal vertical layout (no scroll)
128 ui.vertical(|ui| content(ui, layout_ctx)).inner
129 }
130}