Skip to main content

native_theme/model/
dialog_order.rs

1// Dialog button ordering convention
2
3use serde::{Deserialize, Serialize};
4
5/// Specifies the order of primary/cancel buttons in dialogs.
6///
7/// This is a **platform convention**, not visual styling. Different desktop
8/// environments place the confirmation button at different ends of the
9/// button row (Windows/KDE: leading/left, GNOME/macOS/iOS: trailing/right).
10/// It is part of the theme model because "native feel" includes layout
11/// conventions that vary by platform, and it is overridable in theme presets.
12///
13/// Windows uses primary-leftmost per the Microsoft Common Buttons guideline
14/// (Win7) and modern WinUI 3 ContentDialog ("PrimaryButton ... Appears as
15/// the leftmost button"). See `docs/platform-facts.md:1494, 1513-1520,
16/// 1835-1836` for the authoritative citations.
17#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
18pub enum DialogButtonOrder {
19    /// Primary button at the trailing (right) end -- GNOME, macOS, iOS style.
20    #[default]
21    #[serde(rename = "primary_right")]
22    PrimaryRight,
23    /// Primary button at the leading (left) end -- Windows, KDE style.
24    #[serde(rename = "primary_left")]
25    PrimaryLeft,
26}
27
28#[cfg(test)]
29#[allow(clippy::unwrap_used, clippy::expect_used)]
30mod tests {
31    use super::*;
32
33    // TOML cannot serialize a bare enum as a top-level value; use a wrapper struct.
34    #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
35    struct Wrapper {
36        order: DialogButtonOrder,
37    }
38
39    #[test]
40    fn serde_round_trip_both_variants() {
41        for (variant, expected_str) in [
42            (DialogButtonOrder::PrimaryRight, "primary_right"),
43            (DialogButtonOrder::PrimaryLeft, "primary_left"),
44        ] {
45            let original = Wrapper { order: variant };
46            let serialized = toml::to_string(&original).unwrap();
47            assert!(serialized.contains(expected_str), "got: {serialized}");
48            let deserialized: Wrapper = toml::from_str(&serialized).unwrap();
49            assert_eq!(deserialized, original);
50        }
51    }
52
53    #[test]
54    fn deserializes_from_toml_string_values() {
55        for (toml_str, expected) in [
56            (
57                r#"order = "primary_right""#,
58                DialogButtonOrder::PrimaryRight,
59            ),
60            (r#"order = "primary_left""#, DialogButtonOrder::PrimaryLeft),
61        ] {
62            let w: Wrapper = toml::from_str(toml_str).unwrap();
63            assert_eq!(w.order, expected);
64        }
65    }
66
67    #[test]
68    fn debug_output_both_variants() {
69        assert_eq!(
70            format!("{:?}", DialogButtonOrder::PrimaryRight),
71            "PrimaryRight"
72        );
73        assert_eq!(
74            format!("{:?}", DialogButtonOrder::PrimaryLeft),
75            "PrimaryLeft"
76        );
77    }
78
79    #[test]
80    fn default_is_primary_right() {
81        assert_eq!(
82            DialogButtonOrder::default(),
83            DialogButtonOrder::PrimaryRight
84        );
85    }
86}