rat_dialog/decorations/
frame_state.rs

1use crate::_private::NonExhaustive;
2use crate::WindowFrameOutcome;
3use rat_event::util::MouseFlags;
4use rat_event::{ConsumedEvent, Dialog, HandleEvent, ct_event};
5use rat_focus::{FocusBuilder, FocusFlag, HasFocus, Navigation};
6use ratatui::layout::{Position, Rect};
7use ratatui::style::Style;
8use ratatui::widgets::Block;
9use std::cmp::max;
10
11#[derive(Debug)]
12pub struct WindowFrameStyle {
13    pub style: Style,
14    pub top: Option<Style>,
15    pub focus: Option<Style>,
16    pub block: Option<Block<'static>>,
17    pub hover: Option<Style>,
18    pub drag: Option<Style>,
19    pub close: Option<Style>,
20    pub min: Option<Style>,
21    pub max: Option<Style>,
22    pub can_move: Option<bool>,
23    pub can_resize: Option<bool>,
24    pub can_close: Option<bool>,
25    pub can_min: Option<bool>,
26    pub can_max: Option<bool>,
27    pub non_exhaustive: NonExhaustive,
28}
29
30/// Window state.
31#[derive(Debug)]
32pub struct WindowFrameState {
33    /// Outer limit for the window.
34    /// This will be set by the widget during render.
35    /// __read only__
36    pub limit: Rect,
37    /// the rendered window-area.
38    /// change this area to move the window.
39    /// __read+write__
40    pub area: Rect,
41    /// archived area. used when switching between
42    /// maximized and normal size.
43    pub arc_area: Rect,
44    /// area for window content.
45    /// __read only__ renewed with each render.
46    pub widget_area: Rect,
47    /// is this the top window?
48    /// __read+write__
49    pub top: bool,
50
51    /// Window can be moved.
52    /// __read+write__ May be overwritten by the widget.
53    pub can_move: bool,
54    /// Window can be resized.
55    /// __read+write__ May be overwritten by the widget.
56    pub can_resize: bool,
57    /// Window can be closed.
58    /// __read+write__ May be overwritten by the widget.
59    pub can_close: bool,
60    /// Window can be closed.
61    /// __read+write__ May be overwritten by the widget.
62    pub can_min: bool,
63    /// Window can be closed.
64    /// __read+write__ May be overwritten by the widget.
65    pub can_max: bool,
66
67    /// move area
68    pub move_area: Rect,
69    /// resize area
70    pub resize_area: Rect,
71    /// close area
72    pub close_area: Rect,
73    pub min_area: Rect,
74    pub max_area: Rect,
75
76    /// mouse flags for close area
77    pub mouse_close: MouseFlags,
78    pub mouse_min: MouseFlags,
79    pub mouse_max: MouseFlags,
80    /// mouse flags for resize area
81    pub mouse_resize: MouseFlags,
82
83    /// window and mouse position at the start of move
84    pub start_move: (Rect, Position),
85    /// mouse flags for move area
86    pub mouse_move: MouseFlags,
87
88    /// Focus for move/resize
89    pub focus: FocusFlag,
90
91    pub non_exhaustive: NonExhaustive,
92}
93
94impl Default for WindowFrameStyle {
95    fn default() -> Self {
96        Self {
97            style: Default::default(),
98            top: Default::default(),
99            focus: Default::default(),
100            block: Default::default(),
101            hover: Default::default(),
102            drag: Default::default(),
103            close: Default::default(),
104            min: Default::default(),
105            max: Default::default(),
106            can_move: Default::default(),
107            can_resize: Default::default(),
108            can_close: Default::default(),
109            can_min: Default::default(),
110            can_max: Default::default(),
111            non_exhaustive: NonExhaustive,
112        }
113    }
114}
115
116impl Default for WindowFrameState {
117    fn default() -> Self {
118        Self {
119            limit: Default::default(),
120            area: Default::default(),
121            arc_area: Default::default(),
122            widget_area: Default::default(),
123            top: Default::default(),
124            can_move: true,
125            can_resize: true,
126            can_close: true,
127            can_min: true,
128            can_max: true,
129            move_area: Default::default(),
130            resize_area: Default::default(),
131            close_area: Default::default(),
132            min_area: Default::default(),
133            max_area: Default::default(),
134            mouse_close: Default::default(),
135            mouse_min: Default::default(),
136            mouse_max: Default::default(),
137            mouse_resize: Default::default(),
138            start_move: Default::default(),
139            mouse_move: Default::default(),
140            focus: Default::default(),
141            non_exhaustive: NonExhaustive,
142        }
143    }
144}
145
146impl HasFocus for WindowFrameState {
147    fn build(&self, builder: &mut FocusBuilder) {
148        builder.leaf_widget(self);
149    }
150
151    fn focus(&self) -> FocusFlag {
152        self.focus.clone()
153    }
154
155    fn area(&self) -> Rect {
156        Rect::default()
157    }
158
159    fn navigable(&self) -> Navigation {
160        Navigation::Leave
161    }
162}
163
164impl WindowFrameState {
165    pub fn new() -> Self {
166        Self::default()
167    }
168
169    /// Switch between maximized and normal size.
170    pub fn flip_maximize(&mut self) {
171        if self.area == self.limit && !self.arc_area.is_empty() {
172            self.area = self.arc_area;
173        } else {
174            self.arc_area = self.area;
175            self.area = self.limit;
176        }
177    }
178
179    /// Switch between minimized and normal state.
180    pub fn flip_minimize(&mut self) {
181        if self.area == Rect::default() && !self.arc_area.is_empty() {
182            self.area = self.arc_area;
183        } else {
184            self.arc_area = self.area;
185            self.area = Rect::default();
186        }
187    }
188
189    /// Set the window area and check the limits.
190    ///
191    /// It always resizes the area to keep it within the limits.
192    ///
193    /// Return
194    ///
195    /// Returns WindowFrameOutcome::Resized if the area is changed.
196    pub fn set_resized_area(&mut self, mut new_area: Rect) -> WindowFrameOutcome {
197        if new_area.x < self.limit.x {
198            new_area.width -= self.limit.x - new_area.x;
199            new_area.x = self.limit.x;
200        }
201        if new_area.y < self.limit.y {
202            new_area.height -= self.limit.y - new_area.y;
203            new_area.y = self.limit.y;
204        }
205        if new_area.right() > self.limit.right() {
206            new_area.width -= new_area.right() - self.limit.right();
207        }
208        if new_area.bottom() > self.limit.bottom() {
209            new_area.height -= new_area.bottom() - self.limit.bottom();
210        }
211
212        if new_area != self.area {
213            self.area = new_area;
214            WindowFrameOutcome::Resized
215        } else {
216            WindowFrameOutcome::Continue
217        }
218    }
219
220    /// Set the window area and check the limits.
221    ///
222    /// If possible it moves the area to stay within the limits.
223    /// If the given area is bigger than the limit it is clipped.
224    ///
225    /// Return
226    ///
227    /// Returns WindowFrameOutcome::Moved if the area is changed.
228    pub fn set_moved_area(&mut self, mut new_area: Rect) -> WindowFrameOutcome {
229        if new_area.x < self.limit.x {
230            new_area.x = self.limit.x;
231        }
232        if new_area.y < self.limit.y {
233            new_area.y = self.limit.y;
234        }
235        if new_area.right() > self.limit.right() {
236            let delta = new_area.right() - self.limit.right();
237            new_area.x -= delta;
238        }
239        if new_area.bottom() > self.limit.bottom() {
240            let delta = new_area.bottom() - self.limit.bottom();
241            new_area.y -= delta;
242        }
243
244        // need clip
245        if new_area.x < self.limit.x {
246            new_area.x = self.limit.x;
247            new_area.width = self.limit.width;
248        }
249        if new_area.y < self.limit.y {
250            new_area.y = self.limit.y;
251            new_area.height = self.limit.height;
252        }
253
254        if new_area != self.area {
255            self.area = new_area;
256            WindowFrameOutcome::Moved
257        } else {
258            WindowFrameOutcome::Continue
259        }
260    }
261}
262
263impl HandleEvent<crossterm::event::Event, Dialog, WindowFrameOutcome> for WindowFrameState {
264    fn handle(
265        &mut self,
266        event: &crossterm::event::Event,
267        _qualifier: Dialog,
268    ) -> WindowFrameOutcome {
269        let r = if self.is_focused() {
270            match event {
271                ct_event!(keycode press Up) => {
272                    let mut new_area = self.area;
273                    if new_area.y > 0 {
274                        new_area.y -= 1;
275                    }
276                    self.set_moved_area(new_area)
277                }
278                ct_event!(keycode press Down) => {
279                    let mut new_area = self.area;
280                    new_area.y += 1;
281                    self.set_moved_area(new_area)
282                }
283                ct_event!(keycode press Left) => {
284                    let mut new_area = self.area;
285                    if new_area.x > 0 {
286                        new_area.x -= 1;
287                    }
288                    self.set_moved_area(new_area)
289                }
290                ct_event!(keycode press Right) => {
291                    let mut new_area = self.area;
292                    new_area.x += 1;
293                    self.set_moved_area(new_area)
294                }
295
296                ct_event!(keycode press Home) => {
297                    let mut new_area = self.area;
298                    new_area.x = self.limit.left();
299                    self.set_moved_area(new_area)
300                }
301                ct_event!(keycode press End) => {
302                    let mut new_area = self.area;
303                    new_area.x = self.limit.right().saturating_sub(new_area.width);
304                    self.set_moved_area(new_area)
305                }
306                ct_event!(keycode press CONTROL-Home) => {
307                    let mut new_area = self.area;
308                    new_area.y = self.limit.top();
309                    self.set_moved_area(new_area)
310                }
311                ct_event!(keycode press CONTROL-End) => {
312                    let mut new_area = self.area;
313                    new_area.y = self.limit.bottom().saturating_sub(new_area.height);
314                    self.set_moved_area(new_area)
315                }
316
317                ct_event!(keycode press ALT-Up) => {
318                    let mut new_area = self.area;
319                    if new_area.height > 1 {
320                        new_area.height -= 1;
321                    }
322                    self.set_resized_area(new_area)
323                }
324                ct_event!(keycode press ALT-Down) => {
325                    let mut new_area = self.area;
326                    new_area.height += 1;
327                    self.set_resized_area(new_area)
328                }
329                ct_event!(keycode press ALT-Left) => {
330                    let mut new_area = self.area;
331                    if new_area.width > 1 {
332                        new_area.width -= 1;
333                    }
334                    self.set_resized_area(new_area)
335                }
336                ct_event!(keycode press ALT-Right) => {
337                    let mut new_area = self.area;
338                    new_area.width += 1;
339                    self.set_resized_area(new_area)
340                }
341
342                ct_event!(keycode press CONTROL_ALT-Down) => {
343                    let mut new_area = self.area;
344                    if new_area.height > 1 {
345                        new_area.y += 1;
346                        new_area.height -= 1;
347                    }
348                    self.set_resized_area(new_area)
349                }
350                ct_event!(keycode press CONTROL_ALT-Up) => {
351                    let mut new_area = self.area;
352                    if new_area.y > 0 {
353                        new_area.y -= 1;
354                        new_area.height += 1;
355                    }
356                    self.set_resized_area(new_area)
357                }
358                ct_event!(keycode press CONTROL_ALT-Right) => {
359                    let mut new_area = self.area;
360                    if new_area.width > 1 {
361                        new_area.x += 1;
362                        new_area.width -= 1;
363                    }
364                    self.set_resized_area(new_area)
365                }
366                ct_event!(keycode press CONTROL_ALT-Left) => {
367                    let mut new_area = self.area;
368                    if new_area.x > 0 {
369                        new_area.x -= 1;
370                        new_area.width += 1;
371                    }
372                    self.set_resized_area(new_area)
373                }
374
375                ct_event!(keycode press CONTROL-Up) => {
376                    let mut new_area = self.area;
377                    if self.area.y != self.limit.y || self.area.height != self.limit.height {
378                        new_area.y = self.limit.y;
379                        new_area.height = self.limit.height;
380                        self.arc_area.y = self.area.y;
381                        self.arc_area.height = self.area.height;
382                        self.set_resized_area(new_area)
383                    } else {
384                        WindowFrameOutcome::Unchanged
385                    }
386                }
387                ct_event!(keycode press CONTROL-Down) => {
388                    let mut new_area = self.area;
389                    if !self.arc_area.is_empty() {
390                        new_area.y = self.arc_area.y;
391                        new_area.height = self.arc_area.height;
392                        self.set_resized_area(new_area)
393                    } else {
394                        WindowFrameOutcome::Unchanged
395                    }
396                }
397                ct_event!(keycode press CONTROL-Right) => {
398                    let mut new_area = self.area;
399                    if self.area.x != self.limit.x || self.area.width != self.limit.width {
400                        new_area.x = self.limit.x;
401                        new_area.width = self.limit.width;
402                        self.arc_area.x = self.area.x;
403                        self.arc_area.width = self.area.width;
404                        self.set_resized_area(new_area)
405                    } else {
406                        WindowFrameOutcome::Unchanged
407                    }
408                }
409                ct_event!(keycode press CONTROL-Left) => {
410                    let mut new_area = self.area;
411                    if !self.arc_area.is_empty() {
412                        new_area.x = self.arc_area.x;
413                        new_area.width = self.arc_area.width;
414                        self.set_resized_area(new_area)
415                    } else {
416                        WindowFrameOutcome::Unchanged
417                    }
418                }
419
420                _ => WindowFrameOutcome::Continue,
421            }
422        } else {
423            WindowFrameOutcome::Continue
424        };
425
426        r.or_else(|| match event {
427            ct_event!(mouse any for m) if self.mouse_close.hover(self.close_area, m) => {
428                WindowFrameOutcome::Changed
429            }
430            ct_event!(mouse down Left for x,y) if self.close_area.contains((*x, *y).into()) => {
431                WindowFrameOutcome::ShouldClose
432            }
433            ct_event!(mouse any for m) if self.mouse_min.hover(self.min_area, m) => {
434                WindowFrameOutcome::Changed
435            }
436            ct_event!(mouse down Left for x,y) if self.min_area.contains((*x, *y).into()) => {
437                self.flip_minimize();
438                WindowFrameOutcome::Changed
439            }
440            ct_event!(mouse any for m) if self.mouse_max.hover(self.max_area, m) => {
441                WindowFrameOutcome::Changed
442            }
443            ct_event!(mouse down Left for x,y) if self.max_area.contains((*x, *y).into()) => {
444                self.flip_maximize();
445                WindowFrameOutcome::Changed
446            }
447
448            ct_event!(mouse any for m) if self.mouse_resize.hover(self.resize_area, m) => {
449                WindowFrameOutcome::Changed
450            }
451            ct_event!(mouse any for m) if self.mouse_resize.drag(self.resize_area, m) => {
452                let mut new_area = self.area;
453                new_area.width = max(10, m.column.saturating_sub(self.area.x));
454                new_area.height = max(3, m.row.saturating_sub(self.area.y));
455                self.set_resized_area(new_area)
456            }
457
458            ct_event!(mouse any for m) if self.mouse_move.hover(self.move_area, m) => {
459                WindowFrameOutcome::Changed
460            }
461            ct_event!(mouse any for m) if self.mouse_move.doubleclick(self.move_area, m) => {
462                self.flip_maximize();
463                WindowFrameOutcome::Resized
464            }
465            ct_event!(mouse any for m) if self.mouse_move.drag(self.move_area, m) => {
466                let delta_x = m.column as i16 - self.start_move.1.x as i16;
467                let delta_y = m.row as i16 - self.start_move.1.y as i16;
468                self.set_moved_area(Rect::new(
469                    self.start_move.0.x.saturating_add_signed(delta_x),
470                    self.start_move.0.y.saturating_add_signed(delta_y),
471                    self.start_move.0.width,
472                    self.start_move.0.height,
473                ))
474            }
475            ct_event!(mouse down Left for x,y) if self.move_area.contains((*x, *y).into()) => {
476                self.start_move = (self.area, Position::new(*x, *y));
477                WindowFrameOutcome::Changed
478            }
479            _ => WindowFrameOutcome::Continue,
480        })
481    }
482}