Skip to main content

nifty_macos/
switch.rs

1//! A switch with its title, laid out as System Settings does: the title at the start of
2//! the row, and the switch at its end.
3
4use objc2::MainThreadMarker;
5use objc2::rc::Retained;
6use objc2_app_kit::{NSAccessibility, NSLineBreakMode, NSSwitch, NSTextField, NSView};
7use objc2_foundation::NSString;
8
9use nifty_runtime::{Frame, Size, TextStyle, ToggleHandler};
10
11use crate::classes::{FlippedView, ToggleTarget, control_state};
12use crate::convert::{centered, to_ns_rect, to_size};
13use crate::widgets::{release_after_event, system_font};
14
15/// The space between the title and the switch, in points.
16const GAP: f32 = 12.0;
17
18#[derive(Debug)]
19pub(crate) struct SwitchRow {
20    row: Retained<FlippedView>,
21    title: Retained<NSTextField>,
22    switch: Retained<NSSwitch>,
23    target: Retained<ToggleTarget>,
24}
25
26impl SwitchRow {
27    pub(crate) fn new(
28        mtm: MainThreadMarker,
29        title: &str,
30        on: bool,
31        on_toggle: ToggleHandler,
32    ) -> Self {
33        let row = FlippedView::new(mtm);
34        let title_label = NSTextField::labelWithString(&NSString::from_str(title), mtm);
35        title_label.setFont(Some(&system_font(TextStyle::Body)));
36        title_label.setLineBreakMode(NSLineBreakMode::ByTruncatingTail);
37        let target = ToggleTarget::new(mtm, on_toggle);
38        let switch = NSSwitch::new(mtm);
39        // SAFETY: `ToggleTarget` implements the action selector, and the row that owns the
40        // switch keeps its target alive.
41        unsafe {
42            switch.setTarget(Some(&target));
43            switch.setAction(Some(ToggleTarget::action()));
44        }
45        row.addSubview(&title_label);
46        row.addSubview(&switch);
47        let item = Self {
48            row,
49            title: title_label,
50            switch,
51            target,
52        };
53        item.set_title(title);
54        item.set_on(on);
55        item
56    }
57
58    pub(crate) fn view(&self) -> &NSView {
59        &self.row
60    }
61
62    /// The switch's screen reader label is its title, which sits beside it as a label.
63    pub(crate) fn set_title(&self, title: &str) {
64        let title = NSString::from_str(title);
65        self.title.setStringValue(&title);
66        self.title.setHidden(title.length() == 0);
67        self.switch.setAccessibilityLabel(Some(&title));
68    }
69
70    pub(crate) fn set_on(&self, on: bool) {
71        self.switch.setState(control_state(on));
72    }
73
74    pub(crate) fn measure(&self) -> Size {
75        let switch = to_size(self.switch.fittingSize());
76        let title = self.title_size();
77        let gap = if title.width > 0.0 { GAP } else { 0.0 };
78        Size::new(
79            title.width + gap + switch.width,
80            title.height.max(switch.height),
81        )
82    }
83
84    /// Places the row, with its title at the start and its switch at the end, each
85    /// centered vertically.
86    pub(crate) fn set_frame(&self, frame: Frame) {
87        self.row.setFrame(to_ns_rect(frame));
88        let height = frame.size.height;
89        let switch = to_size(self.switch.fittingSize());
90        let switch_left = frame.size.width - switch.width;
91        self.switch
92            .setFrame(centered(switch_left, switch.width, switch, height));
93        let title_width = (switch_left - GAP).max(0.0);
94        self.title
95            .setFrame(centered(0.0, title_width, self.title_size(), height));
96    }
97
98    /// Released at the end of the current event, since the toggle may be running now.
99    pub(crate) fn remove(self) {
100        self.row.removeFromSuperview();
101        release_after_event(self.row);
102        release_after_event(self.title);
103        release_after_event(self.switch);
104        release_after_event(self.target);
105    }
106
107    fn title_size(&self) -> Size {
108        if self.title.isHidden() {
109            Size::ZERO
110        } else {
111            to_size(self.title.fittingSize())
112        }
113    }
114}