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#[derive(Debug)]
32pub struct WindowFrameState {
33 pub limit: Rect,
37 pub area: Rect,
41 pub arc_area: Rect,
44 pub widget_area: Rect,
47 pub top: bool,
50
51 pub can_move: bool,
54 pub can_resize: bool,
57 pub can_close: bool,
60 pub can_min: bool,
63 pub can_max: bool,
66
67 pub move_area: Rect,
69 pub resize_area: Rect,
71 pub close_area: Rect,
73 pub min_area: Rect,
74 pub max_area: Rect,
75
76 pub mouse_close: MouseFlags,
78 pub mouse_min: MouseFlags,
79 pub mouse_max: MouseFlags,
80 pub mouse_resize: MouseFlags,
82
83 pub start_move: (Rect, Position),
85 pub mouse_move: MouseFlags,
87
88 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 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 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 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 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 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}