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#[derive(Debug)]
33pub struct WindowFrameState {
34 pub limit: Rect,
38 pub area: Rect,
42 pub arc_area: Rect,
45 pub widget_area: Rect,
48 pub top: bool,
51
52 pub can_move: bool,
55 pub can_resize: bool,
58 pub can_close: bool,
61 pub can_min: bool,
64 pub can_max: bool,
67
68 pub move_area: Rect,
70 pub resize_area: Rect,
72 pub close_area: Rect,
74 pub min_area: Rect,
75 pub max_area: Rect,
76
77 pub mouse_close: MouseFlags,
79 pub mouse_min: MouseFlags,
80 pub mouse_max: MouseFlags,
81 pub mouse_resize: MouseFlags,
83
84 pub start_move: (Rect, Position),
86 pub mouse_move: MouseFlags,
88
89 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 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 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 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 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 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}