Skip to main content

egui_macview/controls/
thumbnail_cell.rs

1//! Authentic macOS AppKit Image/Thumbnail Preview Cell (NSImageCell equivalent).
2//!
3//! Grounded in macOS HIG and W3C DTCG tokens via `MacTheme`.
4//! Fully data-driven: zero hardcoded colors or dimensions.
5
6use crate::theme::MacTheme;
7use egui::{CornerRadius, Painter, Rect, Response, Stroke, StrokeKind, Ui, Vec2, Widget};
8
9/// Type alias for the custom closure drawing into the thumbnail cell.
10pub type ThumbnailDrawer<'a> = Box<dyn FnOnce(&Painter, Rect) + 'a>;
11
12/// Authentic macOS preview canvas cell (`NSImageCell` equivalent).
13///
14/// Provides a bounded canvas rectangle for rendering preview thumbnails, icons, or document previews
15/// with authentic macOS corner rounding, borders, and selection highlight styling.
16///
17/// # Example
18///
19/// ```rust
20/// use egui_macview::{MacTheme, MacThumbnailCell};
21///
22/// # let ctx = egui::Context::default();
23/// # let mut output = ctx.run_ui(egui::RawInput::default(), |ui| {
24/// let theme = MacTheme::default();
25///
26/// MacThumbnailCell::new()
27///     .theme(&theme)
28///     .square(32.0)
29///     .custom_draw(|painter, rect| {
30///         painter.circle_filled(rect.center(), 8.0, egui::Color32::BLUE);
31///     })
32///     .show(ui);
33/// # });
34/// # output.textures_delta.clear();
35/// ```
36#[derive(Default)]
37pub struct MacThumbnailCell<'a> {
38    selected: bool,
39    theme: Option<&'a MacTheme>,
40    size: Option<Vec2>,
41    corner_radius: Option<f32>,
42    border_width: Option<f32>,
43    drawer: Option<ThumbnailDrawer<'a>>,
44}
45
46impl<'a> MacThumbnailCell<'a> {
47    /// Creates a new thumbnail preview cell.
48    pub fn new() -> Self {
49        Self::default()
50    }
51
52    /// Sets whether the thumbnail cell is in a selected state.
53    pub fn selected(mut self, selected: bool) -> Self {
54        self.selected = selected;
55        self
56    }
57
58    /// Injects dynamic theme to eliminate hardcoded metrics and colors.
59    pub fn theme(mut self, theme: &'a MacTheme) -> Self {
60        self.theme = Some(theme);
61        self
62    }
63
64    /// Sets explicit width and height dimensions.
65    pub fn size(mut self, size: Vec2) -> Self {
66        self.size = Some(size);
67        self
68    }
69
70    /// Sets explicit square dimensions (e.g. 24.0 for 24x24 pt).
71    pub fn square(mut self, side: f32) -> Self {
72        self.size = Some(Vec2::splat(side));
73        self
74    }
75
76    /// Sets custom corner radius.
77    pub fn corner_radius(mut self, radius: f32) -> Self {
78        self.corner_radius = Some(radius);
79        self
80    }
81
82    /// Sets custom border width.
83    pub fn border_width(mut self, width: f32) -> Self {
84        self.border_width = Some(width);
85        self
86    }
87
88    /// Custom drawing callback inside the cell canvas rect.
89    pub fn custom_draw(mut self, drawer: impl FnOnce(&Painter, Rect) + 'a) -> Self {
90        self.drawer = Some(Box::new(drawer));
91        self
92    }
93
94    /// Shows the thumbnail cell in the UI and returns its interaction response.
95    pub fn show(self, ui: &mut Ui) -> Response {
96        self.ui(ui)
97    }
98
99    /// Paints the thumbnail cell inside a specific rect using an existing painter.
100    pub fn paint_in_rect(self, painter: &Painter, rect: Rect, hovered: bool) {
101        let theme_cow = MacTheme::resolve(self.theme, painter.ctx());
102        let theme: &MacTheme = &theme_cow;
103
104        let metrics = theme.thumbnail();
105        let corner_radius = self
106            .corner_radius
107            .unwrap_or_else(|| metrics.corner_radius.points());
108        let border_width = self
109            .border_width
110            .unwrap_or_else(|| metrics.border_width.points());
111
112        let bg_color = if self.selected {
113            theme.selected_control()
114        } else if hovered {
115            theme.control()
116        } else {
117            theme.control_background()
118        };
119
120        let stroke_color = if self.selected {
121            theme.control_accent()
122        } else {
123            theme.separator()
124        };
125
126        let border_stroke = Stroke::new(border_width, stroke_color);
127
128        painter.rect(
129            rect,
130            CornerRadius::same(corner_radius as u8),
131            bg_color,
132            border_stroke,
133            StrokeKind::Inside,
134        );
135
136        if let Some(drawer) = self.drawer {
137            let inner_rect = rect.shrink(border_width);
138            drawer(painter, inner_rect);
139        }
140    }
141}
142
143impl<'a> Widget for MacThumbnailCell<'a> {
144    fn ui(self, ui: &mut Ui) -> Response {
145        let theme_cow = MacTheme::resolve(self.theme, ui.ctx());
146        let theme: &MacTheme = &theme_cow;
147
148        let metrics = theme.thumbnail();
149        let default_side = metrics.size.points();
150        let size = self.size.unwrap_or_else(|| Vec2::splat(default_side));
151
152        let (rect, response) = ui.allocate_exact_size(size, egui::Sense::click());
153        let painter = ui.painter_at(rect);
154        let hovered = response.hovered();
155
156        self.paint_in_rect(&painter, rect, hovered);
157
158        response
159    }
160}