Skip to main content

egui_charts/ui/drawing_toolbar/components/
svg_helpers.rs

1//! SVG rendering helpers with theme-aware colors.
2//!
3//! Provides utilities for rendering SVG icons with proper
4//! theme colors based on hover/active state.
5
6use crate::icons::{Icon, icons};
7use crate::theme::ThemeExt;
8use egui::{Rect, Ui, Vec2};
9
10/// Render SVG icon centered in a rect with theme-aware colors.
11///
12/// This enforces square aspect ratio and proper theme colors.
13/// The icon color automatically adjusts based on:
14/// - Active state (brightest)
15/// - Hover state (medium brightness)
16/// - Normal state (default icon color)
17///
18/// # Arguments
19///
20/// * `ui` - The egui UI context
21/// * `icon` - The embedded Icon to render
22/// * `rect` - Target rect for the icon
23/// * `is_hovered` - Whether the icon is being hovered
24/// * `is_active` - Whether the icon is in active/selected state
25pub fn render_svg_at_rect_themed(
26    ui: &mut Ui,
27    icon: &Icon,
28    rect: Rect,
29    is_hovered: bool,
30    is_active: bool,
31) {
32    // Get theme-aware color
33    let color = if is_active || is_hovered {
34        ui.visuals().widgets.hovered.fg_stroke.color
35    } else {
36        ui.visuals().widgets.noninteractive.fg_stroke.color
37    };
38
39    // `paint_at` leaves the layout cursor alone; `put` would move it back to the icon's bottom edge.
40    icon.as_image_tinted(rect.size(), color).paint_at(ui, rect);
41}
42
43/// Render SVG icon at a centered position with specified size.
44///
45/// Convenience wrapper that creates a centered rect from a position.
46///
47/// # Arguments
48///
49/// * `ui` - The egui UI context
50/// * `icon` - The embedded Icon to render
51/// * `center` - Center position for the icon
52/// * `size` - Icon size (square)
53/// * `is_hovered` - Whether the icon is being hovered
54/// * `is_active` - Whether the icon is in active/selected state
55pub fn render_svg_centered(
56    ui: &mut Ui,
57    icon: &Icon,
58    center: egui::Pos2,
59    size: f32,
60    is_hovered: bool,
61    is_active: bool,
62) {
63    let rect = Rect::from_center_size(center, Vec2::splat(size));
64    render_svg_at_rect_themed(ui, icon, rect, is_hovered, is_active);
65}
66
67/// Match visible rail ink rather than the transparent padding of each SVG viewport.
68pub(super) fn rail_icon_size(icon: &Icon) -> Vec2 {
69    if [icons::CROSSHAIR, icons::TREND_LINE].contains(icon) {
70        Vec2::splat(29.0)
71    } else if icon == &icons::FIB_RETRACEMENT {
72        Vec2::splat(27.0)
73    } else if [
74        icons::ARROW,
75        icons::XABCD_PATTERN,
76        icons::LONG_POSITION,
77        icons::MEASURE,
78        icons::ZOOM_IN,
79        icons::ZOOM_OUT,
80        icons::DRAWING_KEEP,
81        icons::DRAWING_MAGNET,
82    ]
83    .contains(icon)
84    {
85        Vec2::splat(28.0)
86    } else if icon == &icons::BRUSH {
87        Vec2::splat(31.0)
88    } else if icon == &icons::EMOJI_ICON {
89        Vec2::splat(25.0)
90    } else if [
91        icons::LOCK,
92        icons::UNLOCK,
93        icons::EYE,
94        icons::STAR_EMPTY,
95        icons::TRASH,
96    ]
97    .contains(icon)
98    {
99        Vec2::splat(26.0)
100    } else {
101        Vec2::splat(crate::tokens::DESIGN_TOKENS.sizing.toolbar.icon_size)
102    }
103}
104
105/// Default light rail ink is black; custom theme colours and disabled contrast stay intact.
106pub(super) fn rail_icon_color(ui: &Ui) -> egui::Color32 {
107    let theme = ui.active_theme();
108    if !ui.is_enabled() {
109        theme.ui.text_disabled
110    } else if !theme.dark && theme.ui.text == egui::Color32::from_gray(26) {
111        egui::Color32::BLACK
112    } else {
113        theme.ui.text
114    }
115}