1use egui::{vec2, Response, Sense, Stroke, Ui, Vec2, Widget, WidgetInfo, WidgetText, WidgetType};
4
5use crate::theme::Theme;
6
7#[must_use = "Add with `ui.add(...)`."]
18pub struct TabBar<'a> {
19 selected: &'a mut usize,
20 tabs: Vec<WidgetText>,
21}
22
23impl<'a> std::fmt::Debug for TabBar<'a> {
24 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25 f.debug_struct("TabBar")
26 .field("selected", &*self.selected)
27 .field(
28 "tabs",
29 &self
30 .tabs
31 .iter()
32 .map(|t| t.text().to_string())
33 .collect::<Vec<_>>(),
34 )
35 .finish()
36 }
37}
38
39impl<'a> TabBar<'a> {
40 pub fn new<I, S>(selected: &'a mut usize, tabs: I) -> Self
53 where
54 I: IntoIterator<Item = S>,
55 S: Into<WidgetText>,
56 {
57 Self {
58 selected,
59 tabs: tabs.into_iter().map(Into::into).collect(),
60 }
61 }
62}
63
64impl<'a> Widget for TabBar<'a> {
65 fn ui(self, ui: &mut Ui) -> Response {
66 let theme = Theme::current(ui.ctx());
67 let p = &theme.palette;
68 let t = &theme.typography;
69
70 let response = ui
71 .horizontal(|ui| {
72 ui.spacing_mut().item_spacing = vec2(0.0, 0.0);
73 for (idx, name) in self.tabs.iter().enumerate() {
74 let is_selected = idx == *self.selected;
75 let galley = crate::theme::placeholder_galley(
76 ui,
77 name.text(),
78 t.button,
79 true,
80 f32::INFINITY,
81 );
82
83 let pad_x = theme.control_padding_x;
84 let pad_y = 10.0;
87 let size =
88 Vec2::new(galley.size().x + 2.0 * pad_x, galley.size().y + 2.0 * pad_y);
89 let (rect, resp) = ui.allocate_exact_size(size, Sense::click());
90
91 if resp.clicked() {
92 *self.selected = idx;
93 }
94
95 let text_color = if is_selected {
96 p.sky
97 } else if resp.hovered() {
98 p.text
99 } else {
100 p.text_faint
101 };
102 let text_pos =
103 egui::pos2(rect.min.x + pad_x, rect.center().y - galley.size().y * 0.5);
104 ui.painter().galley(text_pos, galley, text_color);
105
106 let bottom = rect.bottom();
107 if is_selected {
108 let a = egui::pos2(rect.min.x + 4.0, bottom - 1.0);
109 let b = egui::pos2(rect.max.x - 4.0, bottom - 1.0);
110 ui.painter().line_segment([a, b], Stroke::new(2.0, p.sky));
111 }
112 }
113 })
114 .response;
115
116 if ui.is_rect_visible(response.rect) {
117 let bottom = response.rect.bottom();
118 let a = egui::pos2(response.rect.min.x, bottom - 0.5);
119 let b = egui::pos2(response.rect.right(), bottom - 0.5);
120 ui.painter()
121 .line_segment([a, b], Stroke::new(1.0, p.border));
122 }
123
124 response.widget_info(|| WidgetInfo::labeled(WidgetType::Other, true, "tab bar"));
125 response
126 }
127}