1use iced::widget::{button, checkbox, column, container, pick_list, row, scrollable, text};
2use iced::{Color, Element, Length, Size, Subscription, Task, application, window};
3
4use iced_window_chrome::{
5 ChromeSettings, MacosTitlebarSeparatorStyle, WindowCornerPreference, WindowsBackdrop,
6 WindowsCapabilities,
7};
8
9const WINDOW_CORNER_CHOICES: [WindowCornerChoice; 4] = [
10 WindowCornerChoice::SystemDefault,
11 WindowCornerChoice::Square,
12 WindowCornerChoice::Round,
13 WindowCornerChoice::RoundSmall,
14];
15
16const WINDOWS_COLOR_CHOICES: [WindowsColorChoice; 6] = [
17 WindowsColorChoice::SystemDefault,
18 WindowsColorChoice::Crimson,
19 WindowsColorChoice::Emerald,
20 WindowsColorChoice::Indigo,
21 WindowsColorChoice::Amber,
22 WindowsColorChoice::White,
23];
24
25const WINDOWS_BACKDROP_CHOICES: [WindowsBackdropChoice; 5] = [
26 WindowsBackdropChoice::SystemDefault,
27 WindowsBackdropChoice::Off,
28 WindowsBackdropChoice::Mica,
29 WindowsBackdropChoice::Acrylic,
30 WindowsBackdropChoice::MicaAlt,
31];
32
33const MACOS_TITLEBAR_HEIGHT_CHOICES: [MacosTitlebarHeightChoice; 6] = [
34 MacosTitlebarHeightChoice::SystemDefault,
35 MacosTitlebarHeightChoice::Compact,
36 MacosTitlebarHeightChoice::Regular,
37 MacosTitlebarHeightChoice::Tall,
38 MacosTitlebarHeightChoice::Hero,
39 MacosTitlebarHeightChoice::Huge,
40];
41
42const MACOS_TRAFFIC_LIGHT_OFFSET_CHOICES: [MacosTrafficLightOffsetChoice; 7] = [
43 MacosTrafficLightOffsetChoice::SystemDefault,
44 MacosTrafficLightOffsetChoice::LiftLarge,
45 MacosTrafficLightOffsetChoice::LiftSmall,
46 MacosTrafficLightOffsetChoice::Aligned,
47 MacosTrafficLightOffsetChoice::DropSmall,
48 MacosTrafficLightOffsetChoice::DropMedium,
49 MacosTrafficLightOffsetChoice::DropLarge,
50];
51
52const MACOS_SEPARATOR_STYLE_CHOICES: [MacosSeparatorStyleChoice; 4] = [
53 MacosSeparatorStyleChoice::SystemDefault,
54 MacosSeparatorStyleChoice::Hidden,
55 MacosSeparatorStyleChoice::Line,
56 MacosSeparatorStyleChoice::Shadow,
57];
58
59fn main() -> iced::Result {
60 application(ChromeLab::boot, update, view)
61 .title(title)
62 .window(window::Settings {
63 size: Size::new(900.0, 760.0),
64 ..window::Settings::default()
65 })
66 .subscription(subscription)
67 .run()
68}
69
70fn title(_: &ChromeLab) -> String {
71 String::from("chrome-lab")
72}
73
74#[derive(Debug, Clone)]
75enum Message {
76 Chrome(iced_window_chrome::Event),
77 ApplyNow,
78 OpenWindow,
79 Ignore,
80 WindowsCaption(bool),
81 WindowsBorder(bool),
82 WindowsClose(bool),
83 WindowsMinimize(bool),
84 WindowsMaximize(bool),
85 WindowsCorner(WindowCornerChoice),
86 WindowsBorderColor(WindowsColorChoice),
87 WindowsTitleBackgroundColor(WindowsColorChoice),
88 WindowsTitleTextColor(WindowsColorChoice),
89 WindowsBackdrop(WindowsBackdropChoice),
90 MacosTitlebar(bool),
91 MacosTitle(bool),
92 MacosTrafficLights(bool),
93 MacosTransparent(bool),
94 MacosFullsize(bool),
95 MacosTitlebarHeight(MacosTitlebarHeightChoice),
96 MacosTrafficLightOffset(MacosTrafficLightOffsetChoice),
97 MacosSeparatorStyle(MacosSeparatorStyleChoice),
98 LinuxDecorations(bool),
99 LinuxClose(bool),
100 LinuxMinimize(bool),
101 LinuxMaximize(bool),
102}
103
104#[derive(Debug, Clone)]
105struct ChromeLab {
106 chrome: ChromeSettings,
107 windows_capabilities: Option<WindowsCapabilities>,
108}
109
110impl ChromeLab {
111 fn boot() -> (Self, Task<Message>) {
112 let state = Self {
113 chrome: ChromeSettings::default(),
114 windows_capabilities: iced_window_chrome::current_windows_capabilities(),
115 };
116
117 (
118 state.clone(),
119 iced_window_chrome::apply_to_latest(state.chrome.clone()),
120 )
121 }
122}
123
124fn update(state: &mut ChromeLab, message: Message) -> Task<Message> {
125 match message {
126 Message::Chrome(event) => iced_window_chrome::handle(event),
127 Message::ApplyNow => reapply(state),
128 Message::OpenWindow => {
129 let (_, task) = window::open(window::Settings {
130 size: Size::new(640.0, 420.0),
131 ..window::Settings::default()
132 });
133
134 task.map(|_| Message::Ignore)
135 }
136 Message::Ignore => Task::none(),
137 Message::WindowsCaption(value) => {
138 state.chrome.windows.caption = value;
139 reapply(state)
140 }
141 Message::WindowsBorder(value) => {
142 state.chrome.windows.border = value;
143 reapply(state)
144 }
145 Message::WindowsClose(value) => {
146 state.chrome.windows.buttons.close = value;
147 reapply(state)
148 }
149 Message::WindowsMinimize(value) => {
150 state.chrome.windows.buttons.minimize = value;
151 reapply(state)
152 }
153 Message::WindowsMaximize(value) => {
154 state.chrome.windows.buttons.maximize = value;
155 reapply(state)
156 }
157 Message::WindowsCorner(value) => {
158 state.chrome.windows.corner_preference = value.into_setting();
159 reapply(state)
160 }
161 Message::WindowsBorderColor(value) => {
162 state.chrome.windows.border_color = value.into_setting();
163 reapply(state)
164 }
165 Message::WindowsTitleBackgroundColor(value) => {
166 state.chrome.windows.title_background_color = value.into_setting();
167 reapply(state)
168 }
169 Message::WindowsTitleTextColor(value) => {
170 state.chrome.windows.title_text_color = value.into_setting();
171 reapply(state)
172 }
173 Message::WindowsBackdrop(value) => {
174 state.chrome.windows.backdrop = value.into_setting();
175 reapply(state)
176 }
177 Message::MacosTitlebar(value) => {
178 state.chrome.macos.titlebar = value;
179 reapply(state)
180 }
181 Message::MacosTitle(value) => {
182 state.chrome.macos.title = value;
183 reapply(state)
184 }
185 Message::MacosTrafficLights(value) => {
186 state.chrome.macos.traffic_lights = value;
187 reapply(state)
188 }
189 Message::MacosTransparent(value) => {
190 state.chrome.macos.titlebar_transparent = value;
191 reapply(state)
192 }
193 Message::MacosFullsize(value) => {
194 state.chrome.macos.fullsize_content_view = value;
195 reapply(state)
196 }
197 Message::MacosTitlebarHeight(value) => {
198 state.chrome.macos.titlebar_height = value.into_setting();
199 reapply(state)
200 }
201 Message::MacosTrafficLightOffset(value) => {
202 state.chrome.macos.traffic_light_offset_y = value.into_setting();
203 reapply(state)
204 }
205 Message::MacosSeparatorStyle(value) => {
206 state.chrome.macos.titlebar_separator_style = value.into_setting();
207 reapply(state)
208 }
209 Message::LinuxDecorations(value) => {
210 state.chrome.linux.decorations = value;
211 reapply(state)
212 }
213 Message::LinuxClose(value) => {
214 state.chrome.linux.buttons.close = value;
215 reapply(state)
216 }
217 Message::LinuxMinimize(value) => {
218 state.chrome.linux.buttons.minimize = value;
219 reapply(state)
220 }
221 Message::LinuxMaximize(value) => {
222 state.chrome.linux.buttons.maximize = value;
223 reapply(state)
224 }
225 }
226}
227
228fn subscription(state: &ChromeLab) -> Subscription<Message> {
229 iced_window_chrome::subscription(state.chrome.clone()).map(Message::Chrome)
230}
231
232fn view(state: &ChromeLab) -> Element<'_, Message> {
233 let windows_visuals_supported = state
234 .windows_capabilities
235 .map(WindowsCapabilities::supports_dwm_visuals)
236 .unwrap_or(false);
237 let windows_backdrop_supported = state
238 .windows_capabilities
239 .map(WindowsCapabilities::supports_system_backdrop)
240 .unwrap_or(false);
241
242 let corner_row: Element<'_, Message> = if windows_visuals_supported {
243 picker_row(
244 "Corner rounding",
245 pick_list(
246 WINDOW_CORNER_CHOICES,
247 Some(WindowCornerChoice::from_setting(
248 state.chrome.windows.corner_preference,
249 )),
250 Message::WindowsCorner,
251 )
252 .width(180)
253 .into(),
254 )
255 } else {
256 picker_row(
257 "Corner rounding (Win11)",
258 locked_picker(WindowCornerChoice::from_setting(
259 state.chrome.windows.corner_preference,
260 )),
261 )
262 };
263
264 let border_color_row: Element<'_, Message> = if windows_visuals_supported {
265 picker_row(
266 "Border color",
267 pick_list(
268 WINDOWS_COLOR_CHOICES,
269 Some(WindowsColorChoice::from_setting(
270 state.chrome.windows.border_color,
271 )),
272 Message::WindowsBorderColor,
273 )
274 .width(180)
275 .into(),
276 )
277 } else {
278 picker_row(
279 "Border color (Win11)",
280 locked_picker(WindowsColorChoice::from_setting(
281 state.chrome.windows.border_color,
282 )),
283 )
284 };
285
286 let title_background_row: Element<'_, Message> = if windows_visuals_supported {
287 picker_row(
288 "Title background",
289 pick_list(
290 WINDOWS_COLOR_CHOICES,
291 Some(WindowsColorChoice::from_setting(
292 state.chrome.windows.title_background_color,
293 )),
294 Message::WindowsTitleBackgroundColor,
295 )
296 .width(180)
297 .into(),
298 )
299 } else {
300 picker_row(
301 "Title background (Win11)",
302 locked_picker(WindowsColorChoice::from_setting(
303 state.chrome.windows.title_background_color,
304 )),
305 )
306 };
307
308 let title_text_row: Element<'_, Message> = if windows_visuals_supported {
309 picker_row(
310 "Title text",
311 pick_list(
312 WINDOWS_COLOR_CHOICES,
313 Some(WindowsColorChoice::from_setting(
314 state.chrome.windows.title_text_color,
315 )),
316 Message::WindowsTitleTextColor,
317 )
318 .width(180)
319 .into(),
320 )
321 } else {
322 picker_row(
323 "Title text (Win11)",
324 locked_picker(WindowsColorChoice::from_setting(
325 state.chrome.windows.title_text_color,
326 )),
327 )
328 };
329
330 let backdrop_row: Element<'_, Message> = if windows_backdrop_supported {
331 picker_row(
332 "Backdrop material",
333 pick_list(
334 WINDOWS_BACKDROP_CHOICES,
335 Some(WindowsBackdropChoice::from_setting(
336 state.chrome.windows.backdrop,
337 )),
338 Message::WindowsBackdrop,
339 )
340 .width(180)
341 .into(),
342 )
343 } else {
344 picker_row(
345 "Backdrop material (22621+)",
346 locked_picker(WindowsBackdropChoice::from_setting(
347 state.chrome.windows.backdrop,
348 )),
349 )
350 };
351
352 let windows = column![
353 text("Windows").size(24),
354 text(windows_version_label(state.windows_capabilities)),
355 checkbox(state.chrome.windows.caption)
356 .label("Caption")
357 .on_toggle(Message::WindowsCaption),
358 checkbox(state.chrome.windows.border)
359 .label("Border")
360 .on_toggle(Message::WindowsBorder),
361 checkbox(state.chrome.windows.buttons.close)
362 .label("Close button")
363 .on_toggle(Message::WindowsClose),
364 checkbox(state.chrome.windows.buttons.minimize)
365 .label("Minimize button")
366 .on_toggle(Message::WindowsMinimize),
367 checkbox(state.chrome.windows.buttons.maximize)
368 .label("Maximize button")
369 .on_toggle(Message::WindowsMaximize),
370 corner_row,
371 border_color_row,
372 title_background_row,
373 title_text_row,
374 backdrop_row,
375 ]
376 .spacing(12);
377
378 let macos = column![
379 text("macOS").size(24),
380 checkbox(state.chrome.macos.titlebar)
381 .label("Titlebar")
382 .on_toggle(Message::MacosTitlebar),
383 checkbox(state.chrome.macos.title)
384 .label("Title text")
385 .on_toggle(Message::MacosTitle),
386 checkbox(state.chrome.macos.traffic_lights)
387 .label("Traffic lights")
388 .on_toggle(Message::MacosTrafficLights),
389 checkbox(state.chrome.macos.titlebar_transparent)
390 .label("Transparent titlebar")
391 .on_toggle(Message::MacosTransparent),
392 checkbox(state.chrome.macos.fullsize_content_view)
393 .label("Full-size content view")
394 .on_toggle(Message::MacosFullsize),
395 picker_row(
396 "Separator",
397 pick_list(
398 MACOS_SEPARATOR_STYLE_CHOICES,
399 Some(MacosSeparatorStyleChoice::from_setting(
400 state.chrome.macos.titlebar_separator_style
401 )),
402 Message::MacosSeparatorStyle,
403 )
404 .width(180)
405 .into(),
406 ),
407 if state.chrome.macos.titlebar || state.chrome.macos.traffic_lights {
408 picker_row(
409 "Titlebar height",
410 pick_list(
411 MACOS_TITLEBAR_HEIGHT_CHOICES,
412 Some(MacosTitlebarHeightChoice::from_setting(
413 state.chrome.macos.titlebar_height,
414 )),
415 Message::MacosTitlebarHeight,
416 )
417 .width(180)
418 .into(),
419 )
420 } else {
421 picker_row(
422 "Titlebar height",
423 locked_picker(MacosTitlebarHeightChoice::from_setting(
424 state.chrome.macos.titlebar_height,
425 )),
426 )
427 },
428 if state.chrome.macos.titlebar || state.chrome.macos.traffic_lights {
429 picker_row(
430 "Traffic light offset",
431 pick_list(
432 MACOS_TRAFFIC_LIGHT_OFFSET_CHOICES,
433 Some(MacosTrafficLightOffsetChoice::from_setting(
434 state.chrome.macos.traffic_light_offset_y,
435 )),
436 Message::MacosTrafficLightOffset,
437 )
438 .width(180)
439 .into(),
440 )
441 } else {
442 picker_row(
443 "Traffic light offset",
444 locked_picker(MacosTrafficLightOffsetChoice::from_setting(
445 state.chrome.macos.traffic_light_offset_y,
446 )),
447 )
448 },
449 ]
450 .spacing(12);
451
452 let linux = column![
453 text("Linux").size(24),
454 text("X11 only"),
455 checkbox(state.chrome.linux.decorations)
456 .label("Decorations")
457 .on_toggle(Message::LinuxDecorations),
458 checkbox(state.chrome.linux.buttons.close)
459 .label("Close button")
460 .on_toggle(Message::LinuxClose),
461 checkbox(state.chrome.linux.buttons.minimize)
462 .label("Minimize button")
463 .on_toggle(Message::LinuxMinimize),
464 checkbox(state.chrome.linux.buttons.maximize)
465 .label("Maximize button")
466 .on_toggle(Message::LinuxMaximize),
467 ]
468 .spacing(12);
469
470 let controls = row![
471 button("Apply to latest window").on_press(Message::ApplyNow),
472 button("Open extra window").on_press(Message::OpenWindow),
473 ]
474 .spacing(12);
475
476 let platform_section: Element<'_, Message> = if cfg!(target_os = "windows") {
477 windows.width(Length::Fill).into()
478 } else if cfg!(target_os = "macos") {
479 macos.width(Length::Fill).into()
480 } else if cfg!(target_os = "linux") {
481 linux.width(Length::Fill).into()
482 } else {
483 column![
484 text("Unsupported platform").size(24),
485 text("This example currently targets Windows, macOS, and Linux."),
486 ]
487 .spacing(12)
488 .width(Length::Fill)
489 .into()
490 };
491
492 let content = column![
493 text("iced-window-chrome").size(34),
494 controls,
495 platform_section,
496 ]
497 .spacing(24)
498 .padding(24);
499
500 container(scrollable(content).width(Length::Fill).height(Length::Fill))
501 .width(Length::Fill)
502 .height(Length::Fill)
503 .into()
504}
505
506fn reapply(state: &ChromeLab) -> Task<Message> {
507 iced_window_chrome::apply_to_latest(state.chrome.clone())
508}
509
510fn picker_row<'a, Message: 'a>(
511 label: &'a str,
512 control: Element<'a, Message>,
513) -> Element<'a, Message> {
514 row![text(label).width(Length::Fill), control]
515 .spacing(12)
516 .into()
517}
518
519fn locked_picker<'a, Message: Clone + 'a>(value: impl ToString) -> Element<'a, Message> {
520 button(
521 row![text(value.to_string()).width(Length::Fill), text("v")]
522 .spacing(8)
523 .width(Length::Fill),
524 )
525 .width(180)
526 .on_press_maybe(None)
527 .into()
528}
529
530fn windows_version_label(capabilities: Option<WindowsCapabilities>) -> String {
531 capabilities
532 .map(|capabilities| format!("Windows {}", capabilities.version))
533 .unwrap_or_else(|| String::from("Windows version unavailable"))
534}
535
536#[derive(Debug, Clone, Copy, PartialEq, Eq)]
537enum WindowCornerChoice {
538 SystemDefault,
539 Square,
540 Round,
541 RoundSmall,
542}
543
544impl WindowCornerChoice {
545 fn from_setting(value: Option<WindowCornerPreference>) -> Self {
546 match value {
547 Some(WindowCornerPreference::DoNotRound) => Self::Square,
548 Some(WindowCornerPreference::Round) => Self::Round,
549 Some(WindowCornerPreference::RoundSmall) => Self::RoundSmall,
550 Some(WindowCornerPreference::Default) | None => Self::SystemDefault,
551 }
552 }
553
554 fn into_setting(self) -> Option<WindowCornerPreference> {
555 match self {
556 Self::SystemDefault => None,
557 Self::Square => Some(WindowCornerPreference::DoNotRound),
558 Self::Round => Some(WindowCornerPreference::Round),
559 Self::RoundSmall => Some(WindowCornerPreference::RoundSmall),
560 }
561 }
562}
563
564impl std::fmt::Display for WindowCornerChoice {
565 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
566 f.write_str(match self {
567 Self::SystemDefault => "System default",
568 Self::Square => "Square",
569 Self::Round => "Round",
570 Self::RoundSmall => "Round small",
571 })
572 }
573}
574
575#[derive(Debug, Clone, Copy, PartialEq, Eq)]
576enum WindowsColorChoice {
577 SystemDefault,
578 Crimson,
579 Emerald,
580 Indigo,
581 Amber,
582 White,
583}
584
585impl WindowsColorChoice {
586 fn from_setting(value: Option<Color>) -> Self {
587 match value.map(Color::into_rgba8) {
588 Some([184, 50, 88, 255]) => Self::Crimson,
589 Some([20, 184, 166, 255]) => Self::Emerald,
590 Some([99, 102, 241, 255]) => Self::Indigo,
591 Some([245, 158, 11, 255]) => Self::Amber,
592 Some([255, 255, 255, 255]) => Self::White,
593 _ => Self::SystemDefault,
594 }
595 }
596
597 fn into_setting(self) -> Option<Color> {
598 match self {
599 Self::SystemDefault => None,
600 Self::Crimson => Some(Color::from_rgb8(184, 50, 88)),
601 Self::Emerald => Some(Color::from_rgb8(20, 184, 166)),
602 Self::Indigo => Some(Color::from_rgb8(99, 102, 241)),
603 Self::Amber => Some(Color::from_rgb8(245, 158, 11)),
604 Self::White => Some(Color::from_rgb8(255, 255, 255)),
605 }
606 }
607}
608
609impl std::fmt::Display for WindowsColorChoice {
610 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
611 f.write_str(match self {
612 Self::SystemDefault => "System default",
613 Self::Crimson => "Crimson",
614 Self::Emerald => "Emerald",
615 Self::Indigo => "Indigo",
616 Self::Amber => "Amber",
617 Self::White => "White",
618 })
619 }
620}
621
622#[derive(Debug, Clone, Copy, PartialEq, Eq)]
623enum WindowsBackdropChoice {
624 SystemDefault,
625 Off,
626 Mica,
627 Acrylic,
628 MicaAlt,
629}
630
631impl WindowsBackdropChoice {
632 fn from_setting(value: Option<WindowsBackdrop>) -> Self {
633 match value {
634 Some(WindowsBackdrop::None) => Self::Off,
635 Some(WindowsBackdrop::Mica) => Self::Mica,
636 Some(WindowsBackdrop::Acrylic) => Self::Acrylic,
637 Some(WindowsBackdrop::MicaAlt) => Self::MicaAlt,
638 None => Self::SystemDefault,
639 }
640 }
641
642 fn into_setting(self) -> Option<WindowsBackdrop> {
643 match self {
644 Self::SystemDefault => None,
645 Self::Off => Some(WindowsBackdrop::None),
646 Self::Mica => Some(WindowsBackdrop::Mica),
647 Self::Acrylic => Some(WindowsBackdrop::Acrylic),
648 Self::MicaAlt => Some(WindowsBackdrop::MicaAlt),
649 }
650 }
651}
652
653impl std::fmt::Display for WindowsBackdropChoice {
654 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
655 f.write_str(match self {
656 Self::SystemDefault => "System default",
657 Self::Off => "Off",
658 Self::Mica => "Mica",
659 Self::Acrylic => "Acrylic",
660 Self::MicaAlt => "Mica Alt",
661 })
662 }
663}
664
665#[derive(Debug, Clone, Copy, PartialEq, Eq)]
666enum MacosTitlebarHeightChoice {
667 SystemDefault,
668 Compact,
669 Regular,
670 Tall,
671 Hero,
672 Huge,
673}
674
675impl MacosTitlebarHeightChoice {
676 fn from_setting(value: Option<f64>) -> Self {
677 match value {
678 Some(value) if approx_eq(value, 28.0) => Self::Compact,
679 Some(value) if approx_eq(value, 36.0) => Self::Regular,
680 Some(value) if approx_eq(value, 48.0) => Self::Tall,
681 Some(value) if approx_eq(value, 60.0) => Self::Hero,
682 Some(value) if approx_eq(value, 72.0) => Self::Huge,
683 _ => Self::SystemDefault,
684 }
685 }
686
687 fn into_setting(self) -> Option<f64> {
688 match self {
689 Self::SystemDefault => None,
690 Self::Compact => Some(28.0),
691 Self::Regular => Some(36.0),
692 Self::Tall => Some(48.0),
693 Self::Hero => Some(60.0),
694 Self::Huge => Some(72.0),
695 }
696 }
697}
698
699impl std::fmt::Display for MacosTitlebarHeightChoice {
700 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
701 f.write_str(match self {
702 Self::SystemDefault => "System default",
703 Self::Compact => "28 pt",
704 Self::Regular => "36 pt",
705 Self::Tall => "48 pt",
706 Self::Hero => "60 pt",
707 Self::Huge => "72 pt",
708 })
709 }
710}
711
712#[derive(Debug, Clone, Copy, PartialEq, Eq)]
713enum MacosTrafficLightOffsetChoice {
714 SystemDefault,
715 LiftLarge,
716 LiftSmall,
717 Aligned,
718 DropSmall,
719 DropMedium,
720 DropLarge,
721}
722
723impl MacosTrafficLightOffsetChoice {
724 fn from_setting(value: Option<f64>) -> Self {
725 match value {
726 Some(value) if approx_eq(value, -12.0) => Self::LiftLarge,
727 Some(value) if approx_eq(value, -6.0) => Self::LiftSmall,
728 Some(value) if approx_eq(value, 0.0) => Self::Aligned,
729 Some(value) if approx_eq(value, 6.0) => Self::DropSmall,
730 Some(value) if approx_eq(value, 12.0) => Self::DropMedium,
731 Some(value) if approx_eq(value, 18.0) => Self::DropLarge,
732 _ => Self::SystemDefault,
733 }
734 }
735
736 fn into_setting(self) -> Option<f64> {
737 match self {
738 Self::SystemDefault => None,
739 Self::LiftLarge => Some(-12.0),
740 Self::LiftSmall => Some(-6.0),
741 Self::Aligned => Some(0.0),
742 Self::DropSmall => Some(6.0),
743 Self::DropMedium => Some(12.0),
744 Self::DropLarge => Some(18.0),
745 }
746 }
747}
748
749impl std::fmt::Display for MacosTrafficLightOffsetChoice {
750 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
751 f.write_str(match self {
752 Self::SystemDefault => "System default",
753 Self::LiftLarge => "-12 pt",
754 Self::LiftSmall => "-6 pt",
755 Self::Aligned => "0 pt",
756 Self::DropSmall => "+6 pt",
757 Self::DropMedium => "+12 pt",
758 Self::DropLarge => "+18 pt",
759 })
760 }
761}
762
763#[derive(Debug, Clone, Copy, PartialEq, Eq)]
764enum MacosSeparatorStyleChoice {
765 SystemDefault,
766 Hidden,
767 Line,
768 Shadow,
769}
770
771impl MacosSeparatorStyleChoice {
772 fn from_setting(value: Option<MacosTitlebarSeparatorStyle>) -> Self {
773 match value {
774 Some(MacosTitlebarSeparatorStyle::None) => Self::Hidden,
775 Some(MacosTitlebarSeparatorStyle::Line) => Self::Line,
776 Some(MacosTitlebarSeparatorStyle::Shadow) => Self::Shadow,
777 Some(MacosTitlebarSeparatorStyle::Automatic) | None => Self::SystemDefault,
778 }
779 }
780
781 fn into_setting(self) -> Option<MacosTitlebarSeparatorStyle> {
782 match self {
783 Self::SystemDefault => None,
784 Self::Hidden => Some(MacosTitlebarSeparatorStyle::None),
785 Self::Line => Some(MacosTitlebarSeparatorStyle::Line),
786 Self::Shadow => Some(MacosTitlebarSeparatorStyle::Shadow),
787 }
788 }
789}
790
791impl std::fmt::Display for MacosSeparatorStyleChoice {
792 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
793 f.write_str(match self {
794 Self::SystemDefault => "System default",
795 Self::Hidden => "Hidden",
796 Self::Line => "Line",
797 Self::Shadow => "Shadow",
798 })
799 }
800}
801
802fn approx_eq(left: f64, right: f64) -> bool {
803 (left - right).abs() < 0.001
804}