Skip to main content

pfx_editor_style/widgets/
tabs.rs

1use 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}