Skip to main content

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