pfx_editor_style/widgets/
tabs.rs1use egui::{Align2, CornerRadius, Id, Rect, Response, Sense, Stroke, Ui, Vec2, Widget};
2
3use super::State;
4use crate::theme::Theme;
5
6pub struct Tabs<'a> {
7 selected: &'a mut Option<usize>,
8 names: &'a [&'a str],
9}
10
11impl<'a> Tabs<'a> {
12 pub fn new(selected: &'a mut Option<usize>, names: &'a [&'a str]) -> Tabs<'a> {
13 Tabs { selected, names }
14 }
15
16 pub fn id(name: &str) -> Id {
17 Id::new(("editor tab", name))
18 }
19}
20
21impl Widget for Tabs<'_> {
22 fn ui(self, ui: &mut Ui) -> Response {
23 let theme = Theme::of(ui.ctx());
24 let text_font = theme.font(theme.size_body);
25 let mut whole: Option<Response> = None;
26 ui.horizontal(|ui| {
27 ui.spacing_mut().item_spacing.x = 2.0;
28 for (index, name) in self.names.iter().enumerate() {
29 let width = ui
30 .painter()
31 .layout_no_wrap((*name).into(), text_font.clone(), theme.text)
32 .size()
33 .x;
34 let size = Vec2::new((width + 24.0).round(), 24.0);
35 let (rect, _) = ui.allocate_exact_size(size, Sense::hover());
36 let mut response = ui.interact(rect, Tabs::id(name), Sense::click());
37 if response.clicked() {
38 *self.selected = Some(index);
39 response.mark_changed();
40 }
41 let state = State::of(&response, *self.selected == Some(index));
42 let (fill, text) = match state {
43 State::Rest => (theme.surface, theme.text2),
44 State::Hovered => (theme.accent_deep, theme.text),
45 State::Active => (theme.bg, theme.text),
46 };
47 let painter = ui.painter();
48 painter.rect_filled(rect, CornerRadius::same(theme.radius), fill);
49 painter.text(
50 rect.center() - Vec2::new(0.0, 1.0),
51 Align2::CENTER_CENTER,
52 name,
53 text_font.clone(),
54 text,
55 );
56 if state == State::Active {
57 let underline = Rect::from_min_max(
58 rect.left_bottom() - Vec2::new(0.0, 2.0),
59 rect.right_bottom(),
60 );
61 painter.rect_filled(underline, CornerRadius::ZERO, theme.accent_bright);
62 } else {
63 painter.hline(
64 rect.x_range(),
65 rect.bottom() - 0.5,
66 Stroke::new(theme.stroke, theme.line),
67 );
68 }
69 whole = Some(match whole.take() {
70 Some(all) => all.union(response),
71 None => response,
72 });
73 }
74 });
75 whole.unwrap_or_else(|| ui.allocate_response(Vec2::ZERO, Sense::hover()))
76 }
77}