miracle_plugin/
container.rs

1use super::bindings;
2use super::host::*;
3use super::window::*;
4
5/// Describes how a parent container lays out its children.
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
7#[repr(u32)]
8pub enum LayoutScheme {
9    /// No layout applied.
10    #[default]
11    None = 0,
12    /// Children arranged side by side.
13    Horizontal = 1,
14    /// Children stacked top to bottom.
15    Vertical = 2,
16    /// Children shown as tabs; only the focused child is visible.
17    Tabbed = 3,
18    /// Children stacked visually with title bars visible.
19    Stacking = 4,
20}
21
22impl From<LayoutScheme> for bindings::miracle_layout_scheme {
23    fn from(value: LayoutScheme) -> Self {
24        value as bindings::miracle_layout_scheme
25    }
26}
27
28impl TryFrom<bindings::miracle_layout_scheme> for LayoutScheme {
29    type Error = ();
30
31    fn try_from(value: bindings::miracle_layout_scheme) -> Result<Self, Self::Error> {
32        match value {
33            0 => Ok(Self::None),
34            1 => Ok(Self::Horizontal),
35            2 => Ok(Self::Vertical),
36            3 => Ok(Self::Tabbed),
37            4 => Ok(Self::Stacking),
38            _ => Err(()),
39        }
40    }
41}
42
43/// A leaf container that holds a single window.
44#[derive(Debug, Clone, Copy)]
45pub struct WindowContainer {
46    /// Whether the container is floating within its workspace.
47    pub is_floating: bool,
48    internal: u64,
49}
50
51impl WindowContainer {
52    /// Get the window info from this container.
53    pub fn window(&self) -> Option<WindowInfo> {
54        const NAME_BUF_LEN: usize = 256;
55        let mut window = std::mem::MaybeUninit::<crate::bindings::miracle_window_info_t>::uninit();
56        let mut name_buf: [u8; NAME_BUF_LEN] = [0; NAME_BUF_LEN];
57
58        unsafe {
59            let result = miracle_container_get_window(
60                self.internal as i64,
61                window.as_mut_ptr() as i32,
62                name_buf.as_mut_ptr() as i32,
63                NAME_BUF_LEN as i32,
64            );
65
66            if result != 0 {
67                return None;
68            }
69
70            let window = window.assume_init();
71
72            let name_len = name_buf
73                .iter()
74                .position(|&c| c == 0)
75                .unwrap_or(NAME_BUF_LEN);
76            let name = String::from_utf8_lossy(&name_buf[..name_len]).into_owned();
77
78            Some(WindowInfo::from_c_with_name(&window, name))
79        }
80    }
81}
82
83/// A parent container that holds multiple child containers.
84#[derive(Debug, Clone, Copy)]
85pub struct ParentContainer {
86    /// Whether the container is floating within its workspace.
87    pub is_floating: bool,
88    /// How the container is laying out its children.
89    pub layout_scheme: LayoutScheme,
90    /// The number of child containers.
91    pub num_children: u32,
92    internal: u64,
93}
94
95impl ParentContainer {
96    /// Get a child container by index.
97    ///
98    /// Returns `None` if the index is out of bounds.
99    pub fn child_at(&self, index: u32) -> Option<Container> {
100        if index >= self.num_children {
101            return None;
102        }
103
104        let mut child_container =
105            std::mem::MaybeUninit::<crate::bindings::miracle_container_t>::uninit();
106        unsafe {
107            let result = miracle_container_get_child_at(
108                self.internal as i64,
109                index,
110                child_container.as_mut_ptr() as i32,
111            );
112
113            if result != 0 {
114                return None;
115            }
116
117            let child_container = child_container.assume_init();
118            Some(Container::from(child_container))
119        }
120    }
121
122    /// Get all child containers.
123    pub fn get_children(&self) -> Vec<Container> {
124        (0..self.num_children)
125            .filter_map(|i| self.child_at(i))
126            .collect()
127    }
128}
129
130/// A node in the workspace container tree.
131///
132/// Match on this enum to distinguish between leaf window nodes ([`Container::Window`])
133/// and parent split nodes ([`Container::Parent`]). Shared operations such as [`Container::id`],
134/// [`Container::parent`], and [`Container::is_floating`] are available directly on the enum.
135#[derive(Debug, Clone, Copy)]
136pub enum Container {
137    /// A leaf node holding a single window.
138    Window(WindowContainer),
139    /// A split node holding child containers.
140    Parent(ParentContainer),
141}
142
143impl Container {
144    /// Returns the opaque internal ID used to refer to this container across API calls.
145    pub fn id(&self) -> u64 {
146        match self {
147            Container::Window(w) => w.internal,
148            Container::Parent(p) => p.internal,
149        }
150    }
151
152    /// Returns whether the container is floating within its workspace.
153    pub fn is_floating(&self) -> bool {
154        match self {
155            Container::Window(w) => w.is_floating,
156            Container::Parent(p) => p.is_floating,
157        }
158    }
159
160    /// Get the parent container of this container.
161    ///
162    /// Returns `None` if the container is a root (has no parent).
163    pub fn parent(&self) -> Option<Container> {
164        let mut parent =
165            std::mem::MaybeUninit::<crate::bindings::miracle_container_t>::uninit();
166
167        unsafe {
168            let result = miracle_container_get_parent(
169                self.id() as i64,
170                parent.as_mut_ptr() as i32,
171            );
172
173            if result != 0 {
174                return None;
175            }
176
177            Some(Container::from(parent.assume_init()))
178        }
179    }
180}
181
182impl From<bindings::miracle_container_t> for Container {
183    fn from(value: bindings::miracle_container_t) -> Self {
184        match value.type_ {
185            1 => Container::Parent(ParentContainer {
186                is_floating: value.is_floating != 0,
187                layout_scheme: LayoutScheme::try_from(value.layout_scheme).unwrap_or_default(),
188                num_children: value.num_child_containers,
189                internal: value.internal,
190            }),
191            _ => Container::Window(WindowContainer {
192                is_floating: value.is_floating != 0,
193                internal: value.internal,
194            }),
195        }
196    }
197}