1use crate::_private::NonExhaustive;
47use crate::event::TabbedOutcome;
48use crate::tabbed::attached::AttachedTabs;
49use crate::tabbed::glued::GluedTabs;
50use rat_event::util::MouseFlagsN;
51use rat_event::{HandleEvent, MouseOnly, Regular, ct_event, event_flow};
52use rat_focus::{FocusBuilder, FocusFlag, HasFocus, Navigation};
53use rat_reloc::{RelocatableState, relocate_area, relocate_areas};
54use ratatui::buffer::Buffer;
55use ratatui::layout::Rect;
56use ratatui::style::Style;
57use ratatui::text::Line;
58use ratatui::widgets::{Block, StatefulWidget};
59use std::cmp::min;
60use std::fmt::Debug;
61use std::rc::Rc;
62
63mod attached;
64pub(crate) mod event;
65mod glued;
66
67#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
72pub enum TabPlacement {
73 #[default]
76 Top,
77 Left,
80 Right,
83 Bottom,
86}
87
88#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
90#[non_exhaustive]
91pub enum TabType {
92 Glued,
94
95 #[default]
103 Attached,
104}
105
106#[derive(Debug, Default, Clone)]
114pub struct Tabbed<'a> {
115 tab_type: TabType,
116 placement: TabPlacement,
117 closeable: bool,
118 tabs: Vec<Line<'a>>,
119
120 style: Style,
121 block: Option<Block<'a>>,
122 tab_style: Option<Style>,
123 hover_style: Option<Style>,
124 select_style: Option<Style>,
125 focus_style: Option<Style>,
126}
127
128#[derive(Debug, Clone)]
130pub struct LayoutWidget<'a> {
131 tab: Rc<Tabbed<'a>>,
132}
133
134#[derive(Debug, Clone)]
136pub struct TabbedWidget<'a> {
137 tab: Rc<Tabbed<'a>>,
138}
139
140#[derive(Debug, Clone)]
142pub struct TabbedStyle {
143 pub style: Style,
144 pub block: Option<Block<'static>>,
145 pub border_style: Option<Style>,
146 pub title_style: Option<Style>,
147 pub tab: Option<Style>,
148 pub hover: Option<Style>,
149 pub select: Option<Style>,
150 pub focus: Option<Style>,
151
152 pub tab_type: Option<TabType>,
153 pub placement: Option<TabPlacement>,
154
155 pub non_exhaustive: NonExhaustive,
156}
157
158#[derive(Debug)]
160pub struct TabbedState {
161 pub area: Rect,
164 pub block_area: Rect,
167 pub widget_area: Rect,
171
172 pub tab_title_area: Rect,
175 pub tab_title_areas: Vec<Rect>,
178 pub tab_title_close_areas: Vec<Rect>,
181
182 pub selected: Option<usize>,
186
187 pub focus: FocusFlag,
190 pub mouse: MouseFlagsN,
193
194 relocate_popup: bool,
197
198 pub non_exhaustive: NonExhaustive,
199}
200
201impl<'a> Tabbed<'a> {
202 pub fn new() -> Self {
203 Self::default()
204 }
205
206 pub fn tab_type(mut self, tab_type: TabType) -> Self {
208 self.tab_type = tab_type;
209 self
210 }
211
212 pub fn placement(mut self, placement: TabPlacement) -> Self {
214 self.placement = placement;
215 self
216 }
217
218 pub fn tabs(mut self, tabs: impl IntoIterator<Item = impl Into<Line<'a>>>) -> Self {
220 self.tabs = tabs.into_iter().map(|v| v.into()).collect::<Vec<_>>();
221 self
222 }
223
224 pub fn closeable(mut self, closeable: bool) -> Self {
228 self.closeable = closeable;
229 self
230 }
231
232 pub fn block(mut self, block: Block<'a>) -> Self {
234 self.block = Some(block);
235 self
236 }
237
238 pub fn styles(mut self, styles: TabbedStyle) -> Self {
240 self.style = styles.style;
241 if styles.block.is_some() {
242 self.block = styles.block;
243 }
244 if let Some(border_style) = styles.border_style {
245 self.block = self.block.map(|v| v.border_style(border_style));
246 }
247 if let Some(title_style) = styles.title_style {
248 self.block = self.block.map(|v| v.title_style(title_style));
249 }
250 self.block = self.block.map(|v| v.style(self.style));
251
252 if styles.tab.is_some() {
253 self.tab_style = styles.tab;
254 }
255 if styles.select.is_some() {
256 self.select_style = styles.select;
257 }
258 if styles.hover.is_some() {
259 self.hover_style = styles.hover;
260 }
261 if styles.focus.is_some() {
262 self.focus_style = styles.focus;
263 }
264 if let Some(tab_type) = styles.tab_type {
265 self.tab_type = tab_type;
266 }
267 if let Some(placement) = styles.placement {
268 self.placement = placement
269 }
270 self
271 }
272
273 pub fn style(mut self, style: Style) -> Self {
275 self.style = style;
276 self.block = self.block.map(|v| v.style(style));
277 self
278 }
279
280 pub fn tab_style(mut self, style: Style) -> Self {
282 self.tab_style = Some(style);
283 self
284 }
285
286 pub fn hover_style(mut self, style: Style) -> Self {
288 self.hover_style = Some(style);
289 self
290 }
291
292 pub fn select_style(mut self, style: Style) -> Self {
294 self.select_style = Some(style);
295 self
296 }
297
298 pub fn focus_style(mut self, style: Style) -> Self {
300 self.focus_style = Some(style);
301 self
302 }
303
304 pub fn into_widgets(self) -> (LayoutWidget<'a>, TabbedWidget<'a>) {
315 let rc = Rc::new(self);
316 (
317 LayoutWidget {
318 tab: rc.clone(), },
320 TabbedWidget {
321 tab:rc, },
323 )
324 }
325}
326
327impl Default for TabbedStyle {
328 fn default() -> Self {
329 Self {
330 style: Default::default(),
331 tab: None,
332 hover: None,
333 select: None,
334 focus: None,
335 tab_type: None,
336 placement: None,
337 block: None,
338 border_style: None,
339 title_style: None,
340 non_exhaustive: NonExhaustive,
341 }
342 }
343}
344
345impl StatefulWidget for &Tabbed<'_> {
346 type State = TabbedState;
347
348 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
349 layout(self, area, state);
350 render(self, buf, state);
351 state.relocate_popup = false;
352 }
353}
354
355impl StatefulWidget for Tabbed<'_> {
356 type State = TabbedState;
357
358 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
359 layout(&self, area, state);
360 render(&self, buf, state);
361 state.relocate_popup = false;
362 }
363}
364
365impl<'a> StatefulWidget for &LayoutWidget<'a> {
366 type State = TabbedState;
367
368 fn render(self, area: Rect, _buf: &mut Buffer, state: &mut Self::State) {
369 layout(self.tab.as_ref(), area, state);
370 }
371}
372
373impl<'a> StatefulWidget for LayoutWidget<'a> {
374 type State = TabbedState;
375
376 fn render(self, area: Rect, _buf: &mut Buffer, state: &mut Self::State) {
377 layout(self.tab.as_ref(), area, state);
378 }
379}
380
381fn layout(tabbed: &Tabbed<'_>, area: Rect, state: &mut TabbedState) {
382 state.relocate_popup = true;
383 if tabbed.tabs.is_empty() {
384 state.selected = None;
385 } else {
386 if state.selected.is_none() {
387 state.selected = Some(0);
388 }
389 }
390
391 match tabbed.tab_type {
392 TabType::Glued => {
393 GluedTabs.layout(area, tabbed, state);
394 }
395 TabType::Attached => {
396 AttachedTabs.layout(area, tabbed, state);
397 }
398 }
399}
400
401impl<'a> StatefulWidget for &TabbedWidget<'a> {
402 type State = TabbedState;
403
404 fn render(self, _area: Rect, buf: &mut Buffer, state: &mut Self::State) {
405 render(self.tab.as_ref(), buf, state);
406 }
407}
408
409impl<'a> StatefulWidget for TabbedWidget<'a> {
410 type State = TabbedState;
411
412 fn render(self, _area: Rect, buf: &mut Buffer, state: &mut Self::State) {
413 render(self.tab.as_ref(), buf, state);
414 }
415}
416
417fn render(tabbed: &Tabbed<'_>, buf: &mut Buffer, state: &mut TabbedState) {
418 if tabbed.tabs.is_empty() {
419 state.selected = None;
420 } else {
421 if state.selected.is_none() {
422 state.selected = Some(0);
423 }
424 }
425
426 match tabbed.tab_type {
427 TabType::Glued => {
428 GluedTabs.render(buf, tabbed, state);
429 }
430 TabType::Attached => {
431 AttachedTabs.render(buf, tabbed, state);
432 }
433 }
434}
435
436impl Default for TabbedState {
437 fn default() -> Self {
438 Self {
439 area: Default::default(),
440 block_area: Default::default(),
441 widget_area: Default::default(),
442 tab_title_area: Default::default(),
443 tab_title_areas: Default::default(),
444 tab_title_close_areas: Default::default(),
445 selected: Default::default(),
446 focus: Default::default(),
447 mouse: Default::default(),
448 relocate_popup: Default::default(),
449 non_exhaustive: NonExhaustive,
450 }
451 }
452}
453
454impl Clone for TabbedState {
455 fn clone(&self) -> Self {
456 Self {
457 area: self.area,
458 block_area: self.block_area,
459 widget_area: self.widget_area,
460 tab_title_area: self.tab_title_area,
461 tab_title_areas: self.tab_title_areas.clone(),
462 tab_title_close_areas: self.tab_title_close_areas.clone(),
463 selected: self.selected,
464 focus: self.focus.new_instance(),
465 mouse: Default::default(),
466 relocate_popup: self.relocate_popup,
467 non_exhaustive: NonExhaustive,
468 }
469 }
470}
471
472impl HasFocus for TabbedState {
473 fn build(&self, builder: &mut FocusBuilder) {
474 builder.leaf_widget(self);
475 }
476
477 fn focus(&self) -> FocusFlag {
478 self.focus.clone()
479 }
480
481 fn area(&self) -> Rect {
482 Rect::default()
483 }
484
485 fn navigable(&self) -> Navigation {
486 Navigation::Leave
487 }
488}
489
490impl RelocatableState for TabbedState {
491 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
492 if !self.relocate_popup {
493 self.area = relocate_area(self.area, shift, clip);
494 self.block_area = relocate_area(self.block_area, shift, clip);
495 self.widget_area = relocate_area(self.widget_area, shift, clip);
496 self.tab_title_area = relocate_area(self.tab_title_area, shift, clip);
497 relocate_areas(self.tab_title_areas.as_mut(), shift, clip);
498 relocate_areas(self.tab_title_close_areas.as_mut(), shift, clip);
499 }
500 }
501
502 fn relocate_popup(&mut self, shift: (i16, i16), clip: Rect) {
503 if self.relocate_popup {
504 self.relocate_popup = false;
505 self.area = relocate_area(self.area, shift, clip);
506 self.block_area = relocate_area(self.block_area, shift, clip);
507 self.widget_area = relocate_area(self.widget_area, shift, clip);
508 self.tab_title_area = relocate_area(self.tab_title_area, shift, clip);
509 relocate_areas(self.tab_title_areas.as_mut(), shift, clip);
510 relocate_areas(self.tab_title_close_areas.as_mut(), shift, clip);
511 }
512 }
513}
514
515impl TabbedState {
516 pub fn new() -> Self {
518 Default::default()
519 }
520
521 pub fn named(name: &str) -> Self {
523 let mut z = Self::default();
524 z.focus = z.focus.with_name(name);
525 z
526 }
527
528 pub fn selected(&self) -> Option<usize> {
529 self.selected
530 }
531
532 pub fn select(&mut self, selected: Option<usize>) {
533 self.selected = selected;
534 }
535
536 pub fn next_tab(&mut self) -> bool {
538 let old_selected = self.selected;
539
540 if let Some(selected) = self.selected() {
541 self.selected = Some(min(
542 selected + 1,
543 self.tab_title_areas.len().saturating_sub(1),
544 ));
545 }
546
547 old_selected != self.selected
548 }
549
550 pub fn prev_tab(&mut self) -> bool {
552 let old_selected = self.selected;
553
554 if let Some(selected) = self.selected() {
555 if selected > 0 {
556 self.selected = Some(selected - 1);
557 }
558 }
559
560 old_selected != self.selected
561 }
562}
563
564impl HandleEvent<crossterm::event::Event, Regular, TabbedOutcome> for TabbedState {
566 fn handle(&mut self, event: &crossterm::event::Event, _qualifier: Regular) -> TabbedOutcome {
567 if self.is_focused() {
568 event_flow!(
569 return match event {
570 ct_event!(keycode press Left) => self.prev_tab().into(),
571 ct_event!(keycode press Right) => self.next_tab().into(),
572 ct_event!(keycode press Up) => self.prev_tab().into(),
573 ct_event!(keycode press Down) => self.next_tab().into(),
574 _ => TabbedOutcome::Continue,
575 }
576 );
577 }
578
579 self.handle(event, MouseOnly)
580 }
581}
582
583impl HandleEvent<crossterm::event::Event, MouseOnly, TabbedOutcome> for TabbedState {
584 fn handle(&mut self, event: &crossterm::event::Event, _qualifier: MouseOnly) -> TabbedOutcome {
585 match event {
586 ct_event!(mouse any for e) if self.mouse.hover(&self.tab_title_close_areas, e) => {
587 TabbedOutcome::Changed
588 }
589 ct_event!(mouse any for e) if self.mouse.drag(&[self.tab_title_area], e) => {
590 if let Some(n) = self.mouse.item_at(&self.tab_title_areas, e.column, e.row) {
591 self.select(Some(n));
592 TabbedOutcome::Select(n)
593 } else {
594 TabbedOutcome::Unchanged
595 }
596 }
597 ct_event!(mouse down Left for x, y)
598 if self.tab_title_area.contains((*x, *y).into()) =>
599 {
600 if let Some(sel) = self.mouse.item_at(&self.tab_title_close_areas, *x, *y) {
601 TabbedOutcome::Close(sel)
602 } else if let Some(sel) = self.mouse.item_at(&self.tab_title_areas, *x, *y) {
603 self.select(Some(sel));
604 TabbedOutcome::Select(sel)
605 } else {
606 TabbedOutcome::Continue
607 }
608 }
609
610 _ => TabbedOutcome::Continue,
611 }
612 }
613}
614
615trait TabWidget: Debug {
620 fn layout(
622 &self, area: Rect,
624 tabbed: &Tabbed<'_>,
625 state: &mut TabbedState,
626 );
627
628 fn render(
630 &self, buf: &mut Buffer,
632 tabbed: &Tabbed<'_>,
633 state: &mut TabbedState,
634 );
635}
636
637pub fn handle_events(
641 state: &mut TabbedState,
642 focus: bool,
643 event: &crossterm::event::Event,
644) -> TabbedOutcome {
645 state.focus.set(focus);
646 HandleEvent::handle(state, event, Regular)
647}
648
649pub fn handle_mouse_events(
651 state: &mut TabbedState,
652 event: &crossterm::event::Event,
653) -> TabbedOutcome {
654 HandleEvent::handle(state, event, MouseOnly)
655}