Skip to main content

egui_charts/ui/drawing_toolbar/toolbar/
floating.rs

1//! Floating strip of favourite drawing tools.
2
3use super::super::{components::render_svg_at_rect_themed, icons};
4use super::DrawingToolbar;
5use crate::drawings::DrawingToolType;
6use crate::icons::icons as embedded_icons;
7use crate::localization as l10n;
8use crate::styles::icons as icon_sizes;
9use crate::theme::ThemeExt;
10use crate::tokens::DESIGN_TOKENS;
11use egui::{Color32, Id, Pos2, Rect, Sense, Ui, Vec2};
12
13impl DrawingToolbar {
14    /// Render the floating strip of favourite drawing tools.
15    ///
16    /// [`Self::show`] and [`Self::show_with_action`] already draw it beside the toolbar and return
17    /// a tool picked on it; call this only to place the strip elsewhere, before them, as a
18    /// second call in the same pass draws nothing.
19    ///
20    /// The strip lists [`ToolbarState::favorites`][favorites], so starring or unstarring a tool changes it
21    /// at once, and it is hidden while there are none. `position` places it until it has been
22    /// moved; afterwards [`ToolbarState::floating_pos`][floating_pos] holds its position. Its grip drags it, kept
23    /// on screen, and its close button clears [`ToolbarState::floating_visible`][floating_visible], as the
24    /// favourites button of the toolbar does.
25    ///
26    /// [favorites]: super::super::state::ToolbarState::favorites
27    /// [floating_pos]: super::super::state::ToolbarState::floating_pos
28    /// [floating_visible]: super::super::state::ToolbarState::floating_visible
29    pub fn show_floating(&mut self, ui: &mut Ui, position: Pos2) -> Option<DrawingToolType> {
30        let pass = ui.ctx().cumulative_pass_nr();
31        if self.strip_pass == Some(pass) {
32            return None;
33        }
34        let tools: Vec<_> = self
35            .state
36            .favorites
37            .iter()
38            .copied()
39            .filter(|tool| self.registry.availability(*tool).visible)
40            .collect();
41        if !self.state.floating_visible || tools.is_empty() {
42            return None;
43        }
44        self.strip_pass = Some(pass);
45
46        let mut new_selection = None;
47        let mut moved = Vec2::ZERO;
48        let area = egui::Area::new(Id::NULL.with("floating_drawing_toolbar"))
49            .enabled(ui.is_enabled())
50            .current_pos(self.state.floating_pos.unwrap_or(position))
51            .show(ui.ctx(), |ui| {
52                egui::Frame::new()
53                    .fill(ui.visuals().panel_fill)
54                    .stroke(ui.style().visuals.window_stroke)
55                    .corner_radius(ui.active_theme().radius.md)
56                    .show(ui, |ui| {
57                        ui.horizontal(|ui| {
58                            moved = Self::strip_grip(ui);
59                            for tool in tools {
60                                let is_sel = self.state.sel_tool == Some(tool);
61                                let enabled = self.registry.availability(tool).enabled;
62                                let (rect, response) = ui.allocate_exact_size(
63                                    Vec2::splat(DESIGN_TOKENS.sizing.button_md),
64                                    Sense::click(),
65                                );
66                                let translated_label = l10n::text(ui.ctx(), tool.as_str());
67                                response.widget_info(|| {
68                                    egui::WidgetInfo::selected(
69                                        egui::Role::Button,
70                                        enabled,
71                                        is_sel,
72                                        &translated_label,
73                                    )
74                                });
75                                crate::styles::focus::draw_focus_ring(
76                                    ui,
77                                    &response,
78                                    ui.active_theme().radius.sm,
79                                );
80
81                                let bg = if is_sel {
82                                    ui.active_theme().ui.accent
83                                } else if response.hovered() {
84                                    ui.visuals().widgets.hovered.bg_fill
85                                } else {
86                                    Color32::TRANSPARENT
87                                };
88                                ui.painter()
89                                    .rect_filled(rect, ui.active_theme().radius.sm, bg);
90
91                                let icon_rect = Rect::from_center_size(
92                                    rect.center(),
93                                    Vec2::splat(icon_sizes::MD),
94                                );
95                                render_svg_at_rect_themed(
96                                    ui,
97                                    icons::get_icon(tool),
98                                    icon_rect,
99                                    response.hovered(),
100                                    is_sel,
101                                );
102
103                                if response.on_hover_text(translated_label).clicked() && enabled {
104                                    new_selection = Some(tool);
105                                }
106                            }
107
108                            ui.separator();
109
110                            if crate::ui_kit::buttons::Button::icon(&embedded_icons::CLOSE)
111                                .ghost()
112                                .tooltip("Hide favorite tools")
113                                .show(ui)
114                                .clicked()
115                            {
116                                self.state.floating_visible = false;
117                            }
118                        });
119                    });
120            });
121        self.state.floating_pos = Some(area.response.rect.min + moved);
122        if moved != Vec2::ZERO {
123            ui.ctx().request_repaint();
124        }
125
126        if let Some(tool) = new_selection {
127            self.select_tool(tool);
128        }
129
130        new_selection
131    }
132
133    /// The dotted grip at the strip's leading edge. Returns how far the user dragged it.
134    fn strip_grip(ui: &mut Ui) -> Vec2 {
135        let size = Vec2::new(icon_sizes::SM, DESIGN_TOKENS.sizing.button_md);
136        let (rect, response) = ui.allocate_exact_size(size, Sense::drag());
137        let label = l10n::text(ui.ctx(), "Move favorite tools");
138        response.widget_info(|| egui::WidgetInfo::labeled(egui::Role::Button, true, &label));
139        let response = response.on_hover_cursor(if ui.input(|i| i.pointer.any_down()) {
140            egui::CursorIcon::Grabbing
141        } else {
142            egui::CursorIcon::Grab
143        });
144        let color = if response.hovered() || response.dragged() {
145            ui.visuals().text_color()
146        } else {
147            ui.visuals().weak_text_color()
148        };
149        for col in [-2.0, 2.0] {
150            for row in [-5.0, 0.0, 5.0] {
151                ui.painter()
152                    .circle_filled(rect.center() + Vec2::new(col, row), 1.0, color);
153            }
154        }
155        response.drag_delta()
156    }
157}