Skip to main content

gpui_base/
component_traits.rs

1/// An element or component that exposes controlled selected state.
2///
3/// `selected` is the application's own meaning of selection: the current
4/// view, the active item, the chosen option. `open` is a separate state a
5/// popover, menu or dropdown puts on its trigger for as long as it is open.
6/// The two coincide on a plain button, which paints both the same way, so
7/// `open` falls back to `selected` by default and a trigger that only
8/// implements `selected` keeps working unchanged. A trigger whose selection
9/// means something else, such as a sidebar row that is selected when it is
10/// the current view, overrides `open` and `is_open` to keep the two apart.
11#[allow(patterns_in_fns_without_body)]
12pub trait Selectable: Sized {
13    fn selected(mut self, selected: bool) -> Self;
14    fn is_selected(&self) -> bool;
15
16    fn secondary_selected(self, _: bool) -> Self {
17        self
18    }
19
20    /// Sets the open state a popover, menu or dropdown holds on its trigger
21    /// while it is open.
22    ///
23    /// Defaults to `selected`, so a trigger only overrides this when its
24    /// selected state means something other than "my popup is open".
25    fn open(self, open: bool) -> Self {
26        self.selected(open)
27    }
28
29    /// Whether the trigger is currently marked open.
30    ///
31    /// Defaults to `is_selected`, matching the default of [`Self::open`].
32    fn is_open(&self) -> bool {
33        self.is_selected()
34    }
35}
36
37/// An element or component that can be disabled.
38#[allow(patterns_in_fns_without_body)]
39pub trait Disableable {
40    fn disabled(mut self, disabled: bool) -> Self;
41}
42
43/// A component that exposes whether its UI layer should draw a focus ring.
44///
45/// This trait carries state only. Focus-ring geometry and presentation belong
46/// to the component's visual layer.
47pub trait FocusableExt: Sized {
48    fn focus_ring(self, enabled: bool) -> Self;
49    fn is_focus_ring_enabled(&self) -> bool;
50}
51
52/// An element or component that exposes collapsed state.
53pub trait Collapsible {
54    fn collapsed(self, collapsed: bool) -> Self;
55    fn is_collapsed(&self) -> bool;
56}
57
58#[cfg(test)]
59mod tests {
60    use super::{FocusableExt, Selectable};
61
62    struct CustomControl {
63        focus_ring_enabled: bool,
64    }
65
66    /// A trigger that only knows about selection, the way every trigger did
67    /// before `open` existed.
68    struct SelectedOnlyTrigger {
69        selected: bool,
70    }
71
72    impl Selectable for SelectedOnlyTrigger {
73        fn selected(mut self, selected: bool) -> Self {
74            self.selected = selected;
75            self
76        }
77
78        fn is_selected(&self) -> bool {
79            self.selected
80        }
81    }
82
83    #[test]
84    fn open_falls_back_to_selected_unless_overridden() {
85        let trigger = SelectedOnlyTrigger { selected: false }.open(true);
86        assert!(trigger.is_selected());
87        assert!(trigger.is_open());
88
89        let trigger = SelectedOnlyTrigger { selected: true }.open(false);
90        assert!(!trigger.is_selected());
91        assert!(!trigger.is_open());
92    }
93
94    impl FocusableExt for CustomControl {
95        fn focus_ring(mut self, enabled: bool) -> Self {
96            self.focus_ring_enabled = enabled;
97            self
98        }
99
100        fn is_focus_ring_enabled(&self) -> bool {
101            self.focus_ring_enabled
102        }
103    }
104
105    #[test]
106    fn focus_ring_api_carries_state_without_visuals() {
107        let control = CustomControl {
108            focus_ring_enabled: true,
109        }
110        .focus_ring(false);
111
112        assert!(!control.is_focus_ring_enabled());
113    }
114}