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