Skip to main content

egui_macview/controls/segmented/
item.rs

1//! Authentic macOS `NSSegmentedControl` segment descriptors and menu trigger events.
2
3use crate::symbols::MacSymbol;
4use egui::{Color32, Painter, Rect};
5use std::borrow::Cow;
6use std::sync::Arc;
7
8/// Shared callback signature for stateful template-tinted custom vector segment icons.
9pub type SegmentIconPainter<'a> = Arc<dyn Fn(&Painter, Rect, Color32) + 'a>;
10
11/// Zero-allocation function pointer signature for stateless template-tinted vector segment icons.
12pub type SegmentIconFn = fn(&Painter, Rect, Color32);
13
14/// Visual payload rendered inside an individual segment of [`super::MacSegmentedControl`].
15#[derive(Clone)]
16pub enum SegmentContent<'a> {
17    /// Borrowed or owned text label laid out with Apple HIG optical tracking.
18    Text(Cow<'a, str>),
19    /// Built-in monochrome [`MacSymbol`] glyph.
20    Symbol(MacSymbol),
21    /// Stateless function pointer icon (`fn(&Painter, Rect, Color32)`) with zero heap allocation.
22    IconFn(SegmentIconFn),
23    /// Stateful closure icon (`Arc<dyn Fn(&Painter, Rect, Color32) + 'a>`).
24    Custom(SegmentIconPainter<'a>),
25}
26
27/// Per-segment configuration matching Apple AppKit `NSSegmentedControl` segment slots.
28#[derive(Clone)]
29pub struct SegmentItem<'a> {
30    pub(crate) content: SegmentContent<'a>,
31    pub(crate) width: Option<f32>,
32    pub(crate) enabled: bool,
33    pub(crate) has_menu: bool,
34    pub(crate) shows_menu_indicator: bool,
35    pub(crate) tooltip: Option<Cow<'a, str>>,
36}
37
38impl<'a> SegmentItem<'a> {
39    const fn with_content(content: SegmentContent<'a>) -> Self {
40        Self {
41            content,
42            width: None,
43            enabled: true,
44            has_menu: false,
45            shows_menu_indicator: false,
46            tooltip: None,
47        }
48    }
49
50    pub(crate) fn accessible_cow(&self) -> Option<Cow<'a, str>> {
51        match &self.content {
52            SegmentContent::Text(s) if !s.is_empty() => Some(s.clone()),
53            _ => self.tooltip.clone(),
54        }
55    }
56
57    /// Creates an enabled segment displaying a borrowed or owned text label.
58    pub fn new(label: impl Into<Cow<'a, str>>) -> Self {
59        Self::with_content(SegmentContent::Text(label.into()))
60    }
61
62    /// Creates an enabled segment displaying a built-in [`MacSymbol`].
63    pub const fn from_symbol(symbol: MacSymbol) -> Self {
64        Self::with_content(SegmentContent::Symbol(symbol))
65    }
66
67    /// Creates an enabled segment rendered by a zero-allocation stateless icon function.
68    pub const fn from_icon_fn(painter: SegmentIconFn) -> Self {
69        Self::with_content(SegmentContent::IconFn(painter))
70    }
71
72    /// Creates an enabled segment rendered by a custom template-tinted vector closure.
73    pub fn from_icon(painter: impl Fn(&Painter, Rect, Color32) + 'a) -> Self {
74        Self::with_content(SegmentContent::Custom(Arc::new(painter)))
75    }
76
77    /// Assigns an explicit width in points (`setWidth(_:forSegment:)`).
78    pub const fn with_width(mut self, width: f32) -> Self {
79        self.width = Some(width);
80        self
81    }
82
83    /// Enables or disables interaction and dims tint for this segment (`setEnabled(_:forSegment:)`).
84    pub const fn enabled(mut self, enabled: bool) -> Self {
85        self.enabled = enabled;
86        self
87    }
88
89    /// Attaches or detaches menu trigger behavior (`setMenu(_:forSegment:)`) without altering indicator visibility.
90    pub const fn with_menu(mut self, has_menu: bool) -> Self {
91        self.has_menu = has_menu;
92        self
93    }
94
95    /// Controls whether the trailing downward chevron indicator is drawn (`setShowsMenuIndicator(_:forSegment:)`).
96    pub const fn with_shows_menu_indicator(mut self, shows: bool) -> Self {
97        self.shows_menu_indicator = shows;
98        self
99    }
100
101    /// Convenience builder setting both [`Self::with_menu`] and [`Self::with_shows_menu_indicator`].
102    pub const fn with_submenu(mut self, enabled: bool) -> Self {
103        self.has_menu = enabled;
104        self.shows_menu_indicator = enabled;
105        self
106    }
107
108    /// Attaches a hover tooltip (`setToolTip(_:forSegment:)`), borrowing `&'a str` without allocation.
109    pub fn with_tooltip(mut self, tooltip: impl Into<Cow<'a, str>>) -> Self {
110        self.tooltip = Some(tooltip.into());
111        self
112    }
113}
114
115impl<'a> From<&'a str> for SegmentItem<'a> {
116    fn from(s: &'a str) -> Self {
117        Self::new(s)
118    }
119}
120
121impl<'a> From<MacSymbol> for SegmentItem<'a> {
122    fn from(sym: MacSymbol) -> Self {
123        Self::from_symbol(sym)
124    }
125}
126
127/// Gesture that opened a segment's attached menu (`NSSegmentedCell` menu activation).
128#[derive(Debug, Clone, Copy, PartialEq, Eq)]
129pub enum SegmentMenuTrigger {
130    /// Primary mouse button held past the `menu_press_hold_delay` token (`0.25s`).
131    PressAndHold,
132    /// Pointer dragged downward past the `menu_drag_threshold` token (`4.0pt`).
133    PullDown,
134    /// Right-click or `Control` + primary-click on a menu-bearing segment.
135    SecondaryClick,
136    /// Keyboard activation (`ArrowDown` or `Shift+F10`) while focused on a menu-bearing segment.
137    Keyboard,
138}
139
140/// Interaction event emitted by [`super::MacSegmentedControl::show_with_events`].
141#[derive(Debug, Clone, Copy, PartialEq)]
142pub struct SegmentEvent {
143    /// Zero-based index of the interacted segment.
144    pub index: usize,
145    /// `None` when selecting the segment; `Some(trigger)` when opening its attached menu.
146    pub menu_trigger: Option<SegmentMenuTrigger>,
147    /// Screen-space bounding rectangle of the target segment for anchoring a menu or popover.
148    pub rect: Rect,
149}
150
151impl SegmentEvent {
152    /// Returns `true` when this event requests opening the segment's attached menu rather than selecting it.
153    #[inline]
154    pub const fn opens_menu(&self) -> bool {
155        self.menu_trigger.is_some()
156    }
157}
158
159/// Type alias for [`SegmentItem`].
160pub type MacSegmentItem<'a> = SegmentItem<'a>;
161
162/// Type alias for [`SegmentEvent`].
163pub type MacSegmentEvent = SegmentEvent;