rat_dialog/decorations/
mac_frame.rs

1//!
2//! Widget for a moveable window.
3//!
4use crate::decorations::frame_state::{WindowFrameState, WindowFrameStyle};
5use rat_focus::HasFocus;
6use rat_widget::util::revert_style;
7use ratatui::buffer::Buffer;
8use ratatui::layout::Rect;
9use ratatui::prelude::BlockExt;
10use ratatui::style::{Style, Stylize};
11use ratatui::text::Span;
12use ratatui::widgets::{Block, StatefulWidget, Widget};
13
14/// Widget for a moveable window.
15///
16/// This widget ignores the area given to render,
17/// and uses the area stored in the state instead.
18/// The area given to render is used as the outer limit for
19/// the window instead.
20///
21/// Render this widget and then use WindowState::widget_area to
22/// render your content.
23///
24/// It can handle events for move/resize/close.
25#[derive(Debug, Default)]
26pub struct MacFrame<'a> {
27    no_fill: bool,
28    block: Option<Block<'a>>,
29
30    style: Style,
31    top_style: Option<Style>,
32    focus_style: Option<Style>,
33    hover_style: Style,
34    drag_style: Style,
35    close_style: Option<Style>,
36    min_style: Option<Style>,
37    max_style: Option<Style>,
38
39    limit: Option<Rect>,
40
41    can_move: Option<bool>,
42    can_resize: Option<bool>,
43    can_close: Option<bool>,
44    can_min: Option<bool>,
45    can_max: Option<bool>,
46}
47
48impl<'a> MacFrame<'a> {
49    pub fn new() -> Self {
50        Self {
51            no_fill: Default::default(),
52            block: Default::default(),
53            style: Default::default(),
54            top_style: Default::default(),
55            focus_style: Default::default(),
56            hover_style: Default::default(),
57            drag_style: Default::default(),
58            close_style: Default::default(),
59            min_style: Default::default(),
60            max_style: Default::default(),
61            limit: Default::default(),
62            can_move: Default::default(),
63            can_resize: Default::default(),
64            can_close: Default::default(),
65            can_min: Default::default(),
66            can_max: Default::default(),
67        }
68    }
69
70    /// Don't fill the area.
71    pub fn no_fill(mut self) -> Self {
72        self.no_fill = true;
73        self
74    }
75
76    /// Limits for the window.
77    ///
78    /// If this is not set, the area given to render will be used.
79    pub fn limit(mut self, area: Rect) -> Self {
80        self.limit = Some(area);
81        self
82    }
83
84    /// Window can be moved?
85    pub fn can_move(mut self, v: bool) -> Self {
86        self.can_move = Some(v);
87        self
88    }
89
90    /// Window can be resized?
91    pub fn can_resize(mut self, v: bool) -> Self {
92        self.can_resize = Some(v);
93        self
94    }
95
96    /// Window can be closed?
97    pub fn can_close(mut self, v: bool) -> Self {
98        self.can_close = Some(v);
99        self
100    }
101
102    /// Window can be minimized?
103    pub fn can_min(mut self, v: bool) -> Self {
104        self.can_min = Some(v);
105        self
106    }
107
108    /// Window can be maximized?
109    pub fn can_max(mut self, v: bool) -> Self {
110        self.can_max = Some(v);
111        self
112    }
113
114    /// Window block
115    pub fn block(mut self, block: Block<'a>) -> Self {
116        self.block = Some(block.style(self.style));
117        self
118    }
119
120    pub fn styles(mut self, styles: WindowFrameStyle) -> Self {
121        self.style = styles.style;
122        self.block = styles.block;
123        if styles.top.is_some() {
124            self.top_style = styles.top;
125        }
126        if styles.focus.is_some() {
127            self.focus_style = styles.focus;
128        }
129        if let Some(hover) = styles.hover {
130            self.hover_style = hover;
131        }
132        if let Some(drag) = styles.drag {
133            self.drag_style = drag;
134        }
135        if let Some(drag) = styles.drag {
136            self.drag_style = drag;
137        }
138        if let Some(close) = styles.close {
139            self.close_style = Some(close);
140        }
141        if let Some(min) = styles.min {
142            self.min_style = Some(min);
143        }
144        if let Some(max) = styles.max {
145            self.max_style = Some(max);
146        }
147        if let Some(can_move) = styles.can_move {
148            self.can_move = Some(can_move);
149        }
150        if let Some(can_resize) = styles.can_resize {
151            self.can_resize = Some(can_resize);
152        }
153        if let Some(can_close) = styles.can_close {
154            self.can_move = Some(can_close);
155        }
156        if let Some(can_min) = styles.can_min {
157            self.can_min = Some(can_min);
158        }
159        if let Some(can_max) = styles.can_max {
160            self.can_max = Some(can_max);
161        }
162        self
163    }
164
165    /// Window base style
166    pub fn style(mut self, style: Style) -> Self {
167        self.style = style;
168        self.block = self.block.map(|v| v.style(style));
169        self
170    }
171
172    /// Window title style
173    pub fn title_style(mut self, style: Style) -> Self {
174        self.top_style = Some(style);
175        self
176    }
177
178    /// Window focus style
179    pub fn focus_style(mut self, style: Style) -> Self {
180        self.top_style = Some(style);
181        self
182    }
183
184    /// Hover style
185    pub fn hover_style(mut self, hover: Style) -> Self {
186        self.hover_style = hover;
187        self
188    }
189
190    /// Drag style
191    pub fn drag_style(mut self, drag: Style) -> Self {
192        self.drag_style = drag;
193        self
194    }
195}
196
197impl<'a> StatefulWidget for MacFrame<'a> {
198    type State = WindowFrameState;
199
200    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
201        if let Some(limit) = self.limit {
202            state.limit = limit;
203        } else {
204            state.limit = area;
205        }
206        state.area = state.area.intersection(state.limit);
207        state.widget_area = self.block.inner_if_some(state.area);
208
209        if let Some(v) = self.can_move {
210            state.can_move = v;
211        }
212        if let Some(v) = self.can_resize {
213            state.can_resize = v;
214        }
215        if let Some(v) = self.can_close {
216            state.can_close = v;
217        }
218
219        if state.can_resize {
220            state.resize_area = Rect::new(
221                state.area.right().saturating_sub(2),
222                state.area.bottom().saturating_sub(1),
223                2,
224                1,
225            );
226        } else {
227            state.resize_area = Default::default();
228        }
229        if state.can_close && !state.area.is_empty() {
230            state.close_area = Rect::new(state.area.x + 2, state.area.y, 3, 1);
231        } else {
232            state.close_area = Default::default();
233        }
234        if state.can_min && !state.area.is_empty() {
235            state.min_area = Rect::new(state.area.x + 5, state.area.y, 3, 1);
236        } else {
237            state.min_area = Default::default();
238        }
239        if state.can_max && !state.area.is_empty() {
240            state.max_area = Rect::new(state.area.x + 8, state.area.y, 3, 1);
241        } else {
242            state.max_area = Default::default();
243        }
244
245        if state.can_move {
246            if state.can_close || state.can_min || state.can_max {
247                state.move_area = Rect::new(
248                    state.area.x + 11, //
249                    state.area.y,
250                    state.area.width.saturating_sub(13),
251                    1,
252                );
253            } else {
254                state.move_area = Rect::new(
255                    state.area.x + 1,
256                    state.area.y,
257                    state.area.width.saturating_sub(2),
258                    1,
259                );
260            }
261        } else {
262            state.move_area = Default::default();
263        }
264
265        if !self.no_fill {
266            for y in state.area.top()..state.area.bottom() {
267                for x in state.area.left()..state.area.right() {
268                    if let Some(cell) = buf.cell_mut((x, y)) {
269                        cell.reset();
270                    }
271                }
272            }
273        }
274
275        let block = if state.top {
276            if state.is_focused() {
277                if let Some(top_style) = self.focus_style.or(self.top_style) {
278                    self.block.map(|v| v.title_style(top_style))
279                } else {
280                    self.block
281                }
282            } else {
283                if let Some(top_style) = self.top_style {
284                    self.block.map(|v| v.title_style(top_style))
285                } else {
286                    self.block
287                }
288            }
289        } else {
290            self.block
291        };
292        let block = if self.no_fill {
293            block.map(|v| v.style(Style::new()))
294        } else {
295            block
296        };
297
298        block.render(state.area, buf);
299
300        if state.can_close && !state.area.is_empty() {
301            Span::from(" ⬤ ")
302                .style(self.close_style.unwrap_or(self.style.red()))
303                .render(state.close_area, buf);
304        }
305        if state.can_min && !state.area.is_empty() {
306            Span::from(" ⬤ ")
307                .style(self.min_style.unwrap_or(self.style.yellow()))
308                .render(state.min_area, buf);
309        }
310        if state.can_max && !state.area.is_empty() {
311            Span::from(" ⬤ ")
312                .style(self.max_style.unwrap_or(self.style.green()))
313                .render(state.max_area, buf);
314        }
315
316        if state.mouse_close.hover.get() {
317            buf.set_style(
318                state.close_area,
319                revert_style(self.close_style.unwrap_or(self.style.red())),
320            );
321        }
322        if state.mouse_min.hover.get() {
323            buf.set_style(
324                state.min_area,
325                revert_style(self.min_style.unwrap_or(self.style.yellow())),
326            );
327        }
328        if state.mouse_max.hover.get() {
329            buf.set_style(
330                state.max_area,
331                revert_style(self.max_style.unwrap_or(self.style.green())),
332            );
333        }
334
335        if state.mouse_move.drag.get() {
336            buf.set_style(state.move_area, self.drag_style);
337        } else if state.mouse_move.hover.get() {
338            buf.set_style(state.move_area, self.hover_style);
339        }
340
341        if state.mouse_resize.drag.get() {
342            buf.set_style(state.resize_area, self.drag_style);
343        } else if state.mouse_resize.hover.get() {
344            buf.set_style(state.resize_area, self.hover_style);
345        }
346    }
347}