1use crate::_private::NonExhaustive;
5use crate::WindowFrameOutcome;
6use rat_event::util::MouseFlags;
7use rat_event::{ConsumedEvent, Dialog, HandleEvent, ct_event};
8use rat_focus::{FocusBuilder, FocusFlag, HasFocus, Navigation};
9use rat_widget::util::revert_style;
10use ratatui::buffer::Buffer;
11use ratatui::layout::{Position, Rect};
12use ratatui::style::{Style, Stylize};
13use ratatui::text::Span;
14use ratatui::widgets::{Block, StatefulWidget, Widget};
15use std::cmp::max;
16
17#[derive(Debug, Default)]
29pub struct MacFrame<'a> {
30 block: Block<'a>,
31
32 style: Style,
33 top_style: Option<Style>,
34 focus_style: Option<Style>,
35 hover_style: Style,
36 drag_style: Style,
37 close_style: Option<Style>,
38 min_style: Option<Style>,
39 max_style: Option<Style>,
40
41 limit: Option<Rect>,
42
43 can_move: Option<bool>,
44 can_resize: Option<bool>,
45 can_close: Option<bool>,
46}
47
48#[derive(Debug)]
49pub struct MacFrameStyle {
50 pub style: Style,
51 pub top: Option<Style>,
52 pub focus: Option<Style>,
53 pub block: Block<'static>,
54 pub hover: Option<Style>,
55 pub drag: Option<Style>,
56 pub close: Option<Style>,
57 pub min: Option<Style>,
58 pub max: Option<Style>,
59 pub can_move: Option<bool>,
60 pub can_resize: Option<bool>,
61 pub can_close: Option<bool>,
62 pub non_exhaustive: NonExhaustive,
63}
64
65#[derive(Debug)]
67pub struct MacFrameState {
68 pub limit: Rect,
72 pub area: Rect,
76 pub arc_area: Rect,
79 pub widget_area: Rect,
82 pub top: bool,
85
86 pub can_move: bool,
89 pub can_resize: bool,
92 pub can_close: bool,
95
96 pub move_area: Rect,
98 pub resize_area: Rect,
100 pub close_area: Rect,
102 pub min_area: Rect,
103 pub max_area: Rect,
104
105 pub mouse_close: MouseFlags,
107 pub mouse_min: MouseFlags,
108 pub mouse_max: MouseFlags,
109 pub mouse_resize: MouseFlags,
111
112 pub start_move: (Rect, Position),
114 pub mouse_move: MouseFlags,
116
117 pub focus: FocusFlag,
119
120 pub non_exhaustive: NonExhaustive,
121}
122
123impl Default for MacFrameStyle {
124 fn default() -> Self {
125 Self {
126 style: Default::default(),
127 top: Default::default(),
128 focus: Default::default(),
129 block: Block::bordered(),
130 hover: Default::default(),
131 drag: Default::default(),
132 close: Default::default(),
133 min: Default::default(),
134 max: Default::default(),
135 can_move: Default::default(),
136 can_resize: Default::default(),
137 can_close: Default::default(),
138 non_exhaustive: NonExhaustive,
139 }
140 }
141}
142
143impl<'a> MacFrame<'a> {
144 pub fn new() -> Self {
145 Self {
146 block: Default::default(),
147 style: Default::default(),
148 top_style: Default::default(),
149 focus_style: Default::default(),
150 hover_style: Default::default(),
151 drag_style: Default::default(),
152 close_style: Default::default(),
153 min_style: Default::default(),
154 max_style: Default::default(),
155 limit: Default::default(),
156 can_move: Default::default(),
157 can_resize: Default::default(),
158 can_close: Default::default(),
159 }
160 }
161
162 pub fn limit(mut self, area: Rect) -> Self {
166 self.limit = Some(area);
167 self
168 }
169
170 pub fn can_move(mut self, v: bool) -> Self {
172 self.can_move = Some(v);
173 self
174 }
175
176 pub fn can_resize(mut self, v: bool) -> Self {
178 self.can_resize = Some(v);
179 self
180 }
181
182 pub fn can_close(mut self, v: bool) -> Self {
184 self.can_close = Some(v);
185 self
186 }
187
188 pub fn block(mut self, block: Block<'a>) -> Self {
190 self.block = block.style(self.style);
191 self
192 }
193
194 pub fn styles(mut self, styles: MacFrameStyle) -> Self {
195 self.style = styles.style;
196 self.block = styles.block;
197 if styles.top.is_some() {
198 self.top_style = styles.top;
199 }
200 if styles.focus.is_some() {
201 self.focus_style = styles.focus;
202 }
203 if let Some(hover) = styles.hover {
204 self.hover_style = hover;
205 }
206 if let Some(drag) = styles.drag {
207 self.drag_style = drag;
208 }
209 if let Some(drag) = styles.drag {
210 self.drag_style = drag;
211 }
212 if let Some(close) = styles.close {
213 self.close_style = Some(close);
214 }
215 if let Some(min) = styles.min {
216 self.min_style = Some(min);
217 }
218 if let Some(max) = styles.max {
219 self.max_style = Some(max);
220 }
221 if let Some(can_move) = styles.can_move {
222 self.can_move = Some(can_move);
223 }
224 if let Some(can_resize) = styles.can_resize {
225 self.can_resize = Some(can_resize);
226 }
227 if let Some(can_close) = styles.can_close {
228 self.can_move = Some(can_close);
229 }
230 self
231 }
232
233 pub fn style(mut self, style: Style) -> Self {
235 self.style = style;
236 self.block = self.block.style(style);
237 self
238 }
239
240 pub fn title_style(mut self, style: Style) -> Self {
242 self.top_style = Some(style);
243 self
244 }
245
246 pub fn focus_style(mut self, style: Style) -> Self {
248 self.top_style = Some(style);
249 self
250 }
251
252 pub fn hover_style(mut self, hover: Style) -> Self {
254 self.hover_style = hover;
255 self
256 }
257
258 pub fn drag_style(mut self, drag: Style) -> Self {
260 self.drag_style = drag;
261 self
262 }
263}
264
265impl<'a> StatefulWidget for MacFrame<'a> {
266 type State = MacFrameState;
267
268 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
269 if let Some(limit) = self.limit {
270 state.limit = limit;
271 } else {
272 state.limit = area;
273 }
274 state.area = state.area.intersection(state.limit);
275 state.widget_area = self.block.inner(state.area);
276
277 if let Some(v) = self.can_move {
278 state.can_move = v;
279 }
280 if let Some(v) = self.can_resize {
281 state.can_resize = v;
282 }
283 if let Some(v) = self.can_close {
284 state.can_close = v;
285 }
286
287 if state.can_resize {
288 state.resize_area = Rect::new(
289 state.area.right().saturating_sub(2),
290 state.area.bottom().saturating_sub(1),
291 2,
292 1,
293 );
294 } else {
295 state.resize_area = Default::default();
296 }
297 if state.can_close {
300 state.close_area = Rect::new(state.area.x + 2, state.area.y, 3, 1);
301 } else {
302 state.close_area = Default::default();
303 }
304 if state.can_close {
305 state.min_area = Rect::new(state.area.x + 5, state.area.y, 3, 1);
306 } else {
307 state.min_area = Default::default();
308 }
309 if state.can_close {
310 state.max_area = Rect::new(state.area.x + 8, state.area.y, 3, 1);
311 } else {
312 state.max_area = Default::default();
313 }
314
315 if state.can_move {
316 if state.can_close {
317 state.move_area = Rect::new(
318 state.area.x + 11, state.area.y,
320 state.area.width.saturating_sub(13),
321 1,
322 );
323 } else {
324 state.move_area = Rect::new(
325 state.area.x + 1,
326 state.area.y,
327 state.area.width.saturating_sub(2),
328 1,
329 );
330 }
331 } else {
332 state.move_area = Default::default();
333 }
334
335 for y in state.area.top()..state.area.bottom() {
336 for x in state.area.left()..state.area.right() {
337 if let Some(cell) = buf.cell_mut((x, y)) {
338 cell.reset();
339 }
340 }
341 }
342
343 let block = if state.top {
344 if state.is_focused() {
345 if let Some(top_style) = self.focus_style.or(self.top_style) {
346 self.block.title_style(top_style)
347 } else {
348 self.block
349 }
350 } else {
351 if let Some(top_style) = self.top_style {
352 self.block.title_style(top_style)
353 } else {
354 self.block
355 }
356 }
357 } else {
358 self.block
359 };
360
361 block.render(state.area, buf);
362
363 if state.can_close {
364 Span::from(" ⬤ ")
365 .style(self.close_style.unwrap_or(self.style.red()))
366 .render(state.close_area, buf);
367 Span::from(" ⬤ ")
368 .style(self.min_style.unwrap_or(self.style.yellow()))
369 .render(state.min_area, buf);
370 Span::from(" ⬤ ")
371 .style(self.max_style.unwrap_or(self.style.green()))
372 .render(state.max_area, buf);
373 }
374
375 if state.mouse_close.hover.get() {
376 buf.set_style(
377 state.close_area,
378 revert_style(self.close_style.unwrap_or(self.style.red())),
379 );
380 }
381 if state.mouse_min.hover.get() {
382 buf.set_style(
383 state.min_area,
384 revert_style(self.min_style.unwrap_or(self.style.yellow())),
385 );
386 }
387 if state.mouse_max.hover.get() {
388 buf.set_style(
389 state.max_area,
390 revert_style(self.max_style.unwrap_or(self.style.green())),
391 );
392 }
393
394 if state.mouse_move.drag.get() {
395 buf.set_style(state.move_area, self.drag_style);
396 } else if state.mouse_move.hover.get() {
397 buf.set_style(state.move_area, self.hover_style);
398 }
399
400 if state.mouse_resize.drag.get() {
401 buf.set_style(state.resize_area, self.drag_style);
402 } else if state.mouse_resize.hover.get() {
403 buf.set_style(state.resize_area, self.hover_style);
404 }
405 }
406}
407
408impl Default for MacFrameState {
409 fn default() -> Self {
410 Self {
411 limit: Default::default(),
412 area: Default::default(),
413 arc_area: Default::default(),
414 widget_area: Default::default(),
415 top: Default::default(),
416 can_move: true,
417 can_resize: true,
418 can_close: true,
419 move_area: Default::default(),
420 resize_area: Default::default(),
421 close_area: Default::default(),
422 min_area: Default::default(),
423 max_area: Default::default(),
424 mouse_close: Default::default(),
425 mouse_min: Default::default(),
426 mouse_max: Default::default(),
427 mouse_resize: Default::default(),
428 start_move: Default::default(),
429 mouse_move: Default::default(),
430 focus: Default::default(),
431 non_exhaustive: NonExhaustive,
432 }
433 }
434}
435
436impl HasFocus for MacFrameState {
437 fn build(&self, builder: &mut FocusBuilder) {
438 builder.leaf_widget(self);
439 }
440
441 fn focus(&self) -> FocusFlag {
442 self.focus.clone()
443 }
444
445 fn area(&self) -> Rect {
446 Rect::default()
447 }
448
449 fn navigable(&self) -> Navigation {
450 Navigation::Leave
451 }
452}
453
454impl MacFrameState {
455 pub fn new() -> Self {
456 Self::default()
457 }
458
459 pub fn flip_maximize(&mut self) {
461 if self.area == self.limit && !self.arc_area.is_empty() {
462 self.area = self.arc_area;
463 } else {
464 self.area = self.limit;
465 }
466 }
467
468 pub fn flip_minimize(&mut self) {
470 if self.area == Rect::default() && !self.arc_area.is_empty() {
471 self.area = self.arc_area;
472 } else {
473 self.area = Rect::default();
474 }
475 }
476
477 pub fn set_resized_area(&mut self, mut new_area: Rect, arc: bool) -> WindowFrameOutcome {
485 if new_area.x < self.limit.x {
486 new_area.width -= self.limit.x - new_area.x;
487 new_area.x = self.limit.x;
488 }
489 if new_area.y < self.limit.y {
490 new_area.height -= self.limit.y - new_area.y;
491 new_area.y = self.limit.y;
492 }
493 if new_area.right() > self.limit.right() {
494 new_area.width -= new_area.right() - self.limit.right();
495 }
496 if new_area.bottom() > self.limit.bottom() {
497 new_area.height -= new_area.bottom() - self.limit.bottom();
498 }
499
500 if new_area != self.area {
501 if arc {
502 self.arc_area = new_area;
503 }
504 self.area = new_area;
505 WindowFrameOutcome::Resized
506 } else {
507 WindowFrameOutcome::Continue
508 }
509 }
510
511 pub fn set_moved_area(&mut self, mut new_area: Rect, arc: bool) -> WindowFrameOutcome {
520 if new_area.x < self.limit.x {
521 new_area.x = self.limit.x;
522 }
523 if new_area.y < self.limit.y {
524 new_area.y = self.limit.y;
525 }
526 if new_area.right() > self.limit.right() {
527 let delta = new_area.right() - self.limit.right();
528 new_area.x -= delta;
529 }
530 if new_area.bottom() > self.limit.bottom() {
531 let delta = new_area.bottom() - self.limit.bottom();
532 new_area.y -= delta;
533 }
534
535 if new_area.x < self.limit.x {
537 new_area.x = self.limit.x;
538 new_area.width = self.limit.width;
539 }
540 if new_area.y < self.limit.y {
541 new_area.y = self.limit.y;
542 new_area.height = self.limit.height;
543 }
544
545 if new_area != self.area {
546 if arc {
547 self.arc_area = new_area;
548 }
549 self.area = new_area;
550 WindowFrameOutcome::Moved
551 } else {
552 WindowFrameOutcome::Continue
553 }
554 }
555}
556
557impl HandleEvent<crossterm::event::Event, Dialog, WindowFrameOutcome> for MacFrameState {
558 fn handle(
559 &mut self,
560 event: &crossterm::event::Event,
561 _qualifier: Dialog,
562 ) -> WindowFrameOutcome {
563 let r = if self.is_focused() {
564 match event {
565 ct_event!(keycode press Up) => {
566 let mut new_area = self.area;
567 if new_area.y > 0 {
568 new_area.y -= 1;
569 }
570 self.set_moved_area(new_area, true)
571 }
572 ct_event!(keycode press Down) => {
573 let mut new_area = self.area;
574 new_area.y += 1;
575 self.set_moved_area(new_area, true)
576 }
577 ct_event!(keycode press Left) => {
578 let mut new_area = self.area;
579 if new_area.x > 0 {
580 new_area.x -= 1;
581 }
582 self.set_moved_area(new_area, true)
583 }
584 ct_event!(keycode press Right) => {
585 let mut new_area = self.area;
586 new_area.x += 1;
587 self.set_moved_area(new_area, true)
588 }
589
590 ct_event!(keycode press Home) => {
591 let mut new_area = self.area;
592 new_area.x = self.limit.left();
593 self.set_moved_area(new_area, true)
594 }
595 ct_event!(keycode press End) => {
596 let mut new_area = self.area;
597 new_area.x = self.limit.right().saturating_sub(new_area.width);
598 self.set_moved_area(new_area, true)
599 }
600 ct_event!(keycode press CONTROL-Home) => {
601 let mut new_area = self.area;
602 new_area.y = self.limit.top();
603 self.set_moved_area(new_area, true)
604 }
605 ct_event!(keycode press CONTROL-End) => {
606 let mut new_area = self.area;
607 new_area.y = self.limit.bottom().saturating_sub(new_area.height);
608 self.set_moved_area(new_area, true)
609 }
610
611 ct_event!(keycode press ALT-Up) => {
612 let mut new_area = self.area;
613 if new_area.height > 1 {
614 new_area.height -= 1;
615 }
616 self.set_resized_area(new_area, true)
617 }
618 ct_event!(keycode press ALT-Down) => {
619 let mut new_area = self.area;
620 new_area.height += 1;
621 self.set_resized_area(new_area, true)
622 }
623 ct_event!(keycode press ALT-Left) => {
624 let mut new_area = self.area;
625 if new_area.width > 1 {
626 new_area.width -= 1;
627 }
628 self.set_resized_area(new_area, true)
629 }
630 ct_event!(keycode press ALT-Right) => {
631 let mut new_area = self.area;
632 new_area.width += 1;
633 self.set_resized_area(new_area, true)
634 }
635
636 ct_event!(keycode press CONTROL_ALT-Down) => {
637 let mut new_area = self.area;
638 if new_area.height > 1 {
639 new_area.y += 1;
640 new_area.height -= 1;
641 }
642 self.set_resized_area(new_area, true)
643 }
644 ct_event!(keycode press CONTROL_ALT-Up) => {
645 let mut new_area = self.area;
646 if new_area.y > 0 {
647 new_area.y -= 1;
648 new_area.height += 1;
649 }
650 self.set_resized_area(new_area, true)
651 }
652 ct_event!(keycode press CONTROL_ALT-Right) => {
653 let mut new_area = self.area;
654 if new_area.width > 1 {
655 new_area.x += 1;
656 new_area.width -= 1;
657 }
658 self.set_resized_area(new_area, true)
659 }
660 ct_event!(keycode press CONTROL_ALT-Left) => {
661 let mut new_area = self.area;
662 if new_area.x > 0 {
663 new_area.x -= 1;
664 new_area.width += 1;
665 }
666 self.set_resized_area(new_area, true)
667 }
668
669 ct_event!(keycode press CONTROL-Up) => {
670 let mut new_area = self.area;
671 if self.area.y != self.limit.y || self.area.height != self.limit.height {
672 new_area.y = self.limit.y;
673 new_area.height = self.limit.height;
674 }
675 self.set_resized_area(new_area, false)
676 }
677 ct_event!(keycode press CONTROL-Down) => {
678 let mut new_area = self.area;
679 if !self.arc_area.is_empty() {
680 new_area.y = self.arc_area.y;
681 new_area.height = self.arc_area.height;
682 }
683 self.set_resized_area(new_area, false)
684 }
685 ct_event!(keycode press CONTROL-Right) => {
686 let mut new_area = self.area;
687 if self.area.x != self.limit.x || self.area.width != self.limit.width {
688 new_area.x = self.limit.x;
689 new_area.width = self.limit.width;
690 }
691 self.set_resized_area(new_area, false)
692 }
693 ct_event!(keycode press CONTROL-Left) => {
694 let mut new_area = self.area;
695 if !self.arc_area.is_empty() {
696 new_area.x = self.arc_area.x;
697 new_area.width = self.arc_area.width;
698 }
699 self.set_resized_area(new_area, false)
700 }
701
702 _ => WindowFrameOutcome::Continue,
703 }
704 } else {
705 WindowFrameOutcome::Continue
706 };
707
708 r.or_else(|| match event {
709 ct_event!(mouse any for m) if self.mouse_close.hover(self.close_area, m) => {
710 WindowFrameOutcome::Changed
711 }
712 ct_event!(mouse down Left for x,y) if self.close_area.contains((*x, *y).into()) => {
713 WindowFrameOutcome::ShouldClose
714 }
715 ct_event!(mouse any for m) if self.mouse_min.hover(self.min_area, m) => {
716 WindowFrameOutcome::Changed
717 }
718 ct_event!(mouse down Left for x,y) if self.min_area.contains((*x, *y).into()) => {
719 self.flip_minimize();
720 WindowFrameOutcome::Changed
721 }
722 ct_event!(mouse any for m) if self.mouse_max.hover(self.max_area, m) => {
723 WindowFrameOutcome::Changed
724 }
725 ct_event!(mouse down Left for x,y) if self.max_area.contains((*x, *y).into()) => {
726 self.flip_maximize();
727 WindowFrameOutcome::Changed
728 }
729
730 ct_event!(mouse any for m) if self.mouse_resize.hover(self.resize_area, m) => {
731 WindowFrameOutcome::Changed
732 }
733 ct_event!(mouse any for m) if self.mouse_resize.drag(self.resize_area, m) => {
734 let mut new_area = self.area;
735 new_area.width = max(10, m.column.saturating_sub(self.area.x));
736 new_area.height = max(3, m.row.saturating_sub(self.area.y));
737 self.set_resized_area(new_area, true)
738 }
739
740 ct_event!(mouse any for m) if self.mouse_move.hover(self.move_area, m) => {
741 WindowFrameOutcome::Changed
742 }
743 ct_event!(mouse any for m) if self.mouse_move.doubleclick(self.move_area, m) => {
744 self.flip_maximize();
745 WindowFrameOutcome::Resized
746 }
747 ct_event!(mouse any for m) if self.mouse_move.drag(self.move_area, m) => {
748 let delta_x = m.column as i16 - self.start_move.1.x as i16;
749 let delta_y = m.row as i16 - self.start_move.1.y as i16;
750 self.set_moved_area(
751 Rect::new(
752 self.start_move.0.x.saturating_add_signed(delta_x),
753 self.start_move.0.y.saturating_add_signed(delta_y),
754 self.start_move.0.width,
755 self.start_move.0.height,
756 ),
757 true,
758 )
759 }
760 ct_event!(mouse down Left for x,y) if self.move_area.contains((*x, *y).into()) => {
761 self.start_move = (self.area, Position::new(*x, *y));
762 WindowFrameOutcome::Changed
763 }
764 _ => WindowFrameOutcome::Continue,
765 })
766 }
767}