Skip to main content

maolan_widgets/
menu.rs

1use iced::{
2    Border, Color, Element, Length, alignment,
3    widget::{button, row, text},
4};
5use iced_fonts::lucide::{chevron_right, square, square_check_big};
6
7pub use iced_aw::{menu_bar, menu_items};
8
9pub fn base_button<'a, Message>(
10    content: impl Into<Element<'a, Message>>,
11    msg: Message,
12) -> button::Button<'a, Message>
13where
14    Message: Clone + 'a,
15{
16    button(content)
17        .padding([4, 8])
18        .style(|theme, status| {
19            use button::{Status, Style};
20
21            let palette = theme.extended_palette();
22            let base = Style {
23                text_color: palette.background.base.text,
24                border: Border::default().rounded(6.0),
25                ..Style::default()
26            };
27            match status {
28                Status::Active => base.with_background(Color::TRANSPARENT),
29                Status::Hovered => base.with_background(Color::from_rgb(
30                    (palette.primary.weak.color.r * 1.2).min(1.0),
31                    (palette.primary.weak.color.g * 1.2).min(1.0),
32                    (palette.primary.weak.color.b * 1.2).min(1.0),
33                )),
34                Status::Disabled => base.with_background(Color::from_rgb(0.5, 0.5, 0.5)),
35                Status::Pressed => base.with_background(palette.primary.weak.color),
36            }
37        })
38        .on_press(msg)
39}
40
41pub fn menu_button<Message>(
42    label: impl Into<String>,
43    width: Option<Length>,
44    height: Option<Length>,
45    msg: Message,
46) -> Element<'static, Message, iced::Theme, iced::Renderer>
47where
48    Message: Clone + 'static,
49{
50    let label = label.into();
51    base_button(
52        text(label)
53            .height(height.unwrap_or(Length::Shrink))
54            .align_y(alignment::Vertical::Center),
55        msg,
56    )
57    .width(width.unwrap_or(Length::Shrink))
58    .height(height.unwrap_or(Length::Shrink))
59    .into()
60}
61
62pub fn menu_dropdown<Message>(
63    label: impl Into<String>,
64    message: Message,
65) -> Element<'static, Message, iced::Theme, iced::Renderer>
66where
67    Message: Clone + 'static,
68{
69    menu_button(label, Some(Length::Shrink), Some(Length::Shrink), message)
70}
71
72pub fn menu_item<Message>(
73    label: impl Into<String>,
74    message: Message,
75) -> Element<'static, Message, iced::Theme, iced::Renderer>
76where
77    Message: Clone + 'static,
78{
79    menu_button(label, Some(Length::Fill), Some(Length::Shrink), message)
80}
81
82pub fn menu_checkbox_item<Message>(
83    label: impl Into<String>,
84    checked: bool,
85    message: Message,
86) -> Element<'static, Message, iced::Theme, iced::Renderer>
87where
88    Message: Clone + 'static,
89{
90    let label = label.into();
91    let icon = if checked {
92        square_check_big()
93    } else {
94        square()
95    };
96    base_button(
97        row![
98            icon.width(20).align_y(alignment::Vertical::Center),
99            text(label)
100                .height(Length::Shrink)
101                .align_y(alignment::Vertical::Center),
102        ]
103        .spacing(6)
104        .align_y(iced::Alignment::Center),
105        message,
106    )
107    .width(Length::Fill)
108    .height(Length::Shrink)
109    .into()
110}
111
112pub fn menu_item_maybe<Message>(
113    label: impl Into<String>,
114    message: Option<Message>,
115) -> Element<'static, Message, iced::Theme, iced::Renderer>
116where
117    Message: Clone + 'static,
118{
119    let label = label.into();
120    let btn = button(
121        text(label)
122            .height(Length::Shrink)
123            .align_y(alignment::Vertical::Center),
124    )
125    .padding([4, 8])
126    .style(|theme: &iced::Theme, status| {
127        use button::{Status, Style};
128
129        let palette = theme.extended_palette();
130        let base = Style {
131            text_color: palette.background.base.text,
132            border: Border::default().rounded(6.0),
133            ..Style::default()
134        };
135        match status {
136            Status::Active => base.with_background(Color::TRANSPARENT),
137            Status::Hovered => base.with_background(Color::from_rgb(
138                palette.primary.weak.color.r * 1.2,
139                palette.primary.weak.color.g * 1.2,
140                palette.primary.weak.color.b * 1.2,
141            )),
142            Status::Disabled => base.with_background(Color::from_rgb(0.5, 0.5, 0.5)),
143            Status::Pressed => base.with_background(palette.primary.weak.color),
144        }
145    })
146    .width(Length::Fill)
147    .height(Length::Shrink);
148    if let Some(message) = message {
149        btn.on_press(message).into()
150    } else {
151        btn.into()
152    }
153}
154
155pub fn submenu<Message>(
156    label: impl Into<String>,
157    msg: Message,
158) -> Element<'static, Message, iced::Theme, iced::Renderer>
159where
160    Message: Clone + 'static,
161{
162    let label = label.into();
163    base_button(
164        row![
165            text(label)
166                .width(Length::Fill)
167                .align_y(alignment::Vertical::Center),
168            chevron_right(),
169        ]
170        .align_y(iced::Alignment::Center),
171        msg,
172    )
173    .width(Length::Fill)
174    .height(Length::Shrink)
175    .into()
176}