1use 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
15const 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 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 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 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 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}