Skip to main content

desktop_example/app/routes/
widgets.rs

1use std::{
2    collections::HashSet,
3    fmt,
4};
5
6use freya::{
7    material_design::{
8        ButtonRippleExt,
9        MenuItemRippleExt,
10        Ripple,
11        TileRippleExt,
12    },
13    prelude::*,
14};
15
16#[derive(PartialEq, Eq, Hash, Clone, Copy)]
17enum Feature {
18    WiFi,
19    Bluetooth,
20    Location,
21}
22
23impl fmt::Display for Feature {
24    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25        match self {
26            Feature::WiFi => write!(f, "Wi-Fi"),
27            Feature::Bluetooth => write!(f, "Bluetooth"),
28            Feature::Location => write!(f, "Location"),
29        }
30    }
31}
32
33#[derive(PartialEq, Eq, Clone, Copy)]
34enum TextSize {
35    Small,
36    Medium,
37    Large,
38}
39
40impl fmt::Display for TextSize {
41    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42        match self {
43            TextSize::Small => write!(f, "Small"),
44            TextSize::Medium => write!(f, "Medium"),
45            TextSize::Large => write!(f, "Large"),
46        }
47    }
48}
49
50#[derive(PartialEq, Eq, Hash, Clone, Copy)]
51enum Tag {
52    Design,
53    Mobile,
54    OpenSource,
55}
56
57impl fmt::Display for Tag {
58    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59        match self {
60            Tag::Design => write!(f, "Design"),
61            Tag::Mobile => write!(f, "Mobile"),
62            Tag::OpenSource => write!(f, "Open Source"),
63        }
64    }
65}
66
67#[derive(PartialEq)]
68pub struct WidgetsDemo;
69
70impl Component for WidgetsDemo {
71    fn render(&self) -> impl IntoElement {
72        let mut theme = use_theme();
73        let is_dark = theme.read().name == "dark";
74
75        let input_text = use_state(String::new);
76        let mut slider_value = use_state(|| 50.0f64);
77
78        let values = use_hook(|| {
79            vec![
80                "Rust".to_string(),
81                "TypeScript".to_string(),
82                "Python".to_string(),
83            ]
84        });
85        let mut selected = use_state(|| 0);
86
87        let mut enabled_features = use_state(|| HashSet::from([Feature::WiFi, Feature::Bluetooth]));
88        let mut text_size = use_state(|| TextSize::Medium);
89
90        let mut selected_tags = use_state(HashSet::<Tag>::new);
91        let mut color = use_state(|| Color::from_hsv(0.0, 1.0, 1.0));
92
93        ScrollView::new()
94            .width(Size::fill())
95            .height(Size::fill())
96            .child(
97                rect()
98                    .width(Size::fill())
99                    .padding(16.)
100                    .spacing(20.)
101                    .child(
102                        rect().spacing(8.).child("Dark Theme").child(
103                            Switch::new()
104                                .expanded()
105                                .toggled(is_dark)
106                                .on_toggle(move |_| {
107                                    if is_dark {
108                                        theme.set(light_theme());
109                                    } else {
110                                        theme.set(dark_theme());
111                                    }
112                                }),
113                        ),
114                    )
115                    .child(
116                        rect()
117                            .spacing(8.)
118                            .child(format!("Slider: {}%", slider_value().floor()))
119                            .child(
120                                Slider::new(move |v| slider_value.set(v))
121                                    .value(slider_value())
122                                    .size(Size::fill()),
123                            )
124                            .child(ProgressBar::new(slider_value().floor() as f32)),
125                    )
126                    .child(
127                        rect().spacing(8.).child("Language").child(
128                            Select::new()
129                                .selected_item(values[selected()].to_string())
130                                .children(values.iter().enumerate().map(|(i, val)| {
131                                    MenuItem::new()
132                                        .selected(selected() == i)
133                                        .on_press(move |_| selected.set(i))
134                                        .ripple()
135                                        .child(val.to_string())
136                                        .into()
137                                })),
138                        ),
139                    )
140                    .child(
141                        rect()
142                            .width(Size::fill())
143                            .spacing(8.)
144                            .child("Text Input")
145                            .child(
146                                Input::new(input_text)
147                                    .expanded()
148                                    .width(Size::fill())
149                                    .flat()
150                                    .placeholder("Type something..."),
151                            )
152                            .child(format!("Value: {}", input_text.read())),
153                    )
154                    .child(
155                        rect()
156                            .width(Size::fill())
157                            .spacing(4.)
158                            .child("Features")
159                            .children([Feature::WiFi, Feature::Bluetooth, Feature::Location].map(
160                                |feature| {
161                                    let is_checked = enabled_features.read().contains(&feature);
162                                    Tile::new()
163                                        .on_select(move |_| {
164                                            if enabled_features.read().contains(&feature) {
165                                                enabled_features.write().remove(&feature);
166                                            } else {
167                                                enabled_features.write().insert(feature);
168                                            }
169                                        })
170                                        .ripple()
171                                        .leading(Checkbox::new().selected(is_checked))
172                                        .child(
173                                            label().text(feature.to_string()).width(Size::fill()),
174                                        )
175                                        .into()
176                                },
177                            )),
178                    )
179                    .child(
180                        rect()
181                            .width(Size::fill())
182                            .spacing(4.)
183                            .child("Text Size")
184                            .children([TextSize::Small, TextSize::Medium, TextSize::Large].map(
185                                |size| {
186                                    Tile::new()
187                                        .on_select(move |_| text_size.set(size))
188                                        .ripple()
189                                        .leading(RadioItem::new().selected(text_size() == size))
190                                        .child(label().text(size.to_string()).width(Size::fill()))
191                                        .into()
192                                },
193                            )),
194                    )
195                    .child(Button::new().expanded().ripple().child("Ripple Button"))
196                    .child(ripple_card(
197                        (230, 230, 240),
198                        (30, 30, 30),
199                        None::<Color>,
200                        "Tap for ripple",
201                    ))
202                    .child(ripple_card(
203                        (255, 240, 240),
204                        (30, 30, 30),
205                        Some((255, 80, 80)),
206                        "Red ripple",
207                    ))
208                    .child(
209                        rect().spacing(8.).child("Card").child(
210                            Card::new()
211                                .width(Size::fill())
212                                .child("This is a card surface with elevated styling."),
213                        ),
214                    )
215                    .child(
216                        rect().spacing(8.).child("Accordion").child(
217                            Accordion::new().header("Click to expand").child(
218                                "Accordion content goes here. You can put any elements inside.",
219                            ),
220                        ),
221                    )
222                    .child(
223                        rect().spacing(8.).child("Tags").child(
224                            rect()
225                                .direction(Direction::Horizontal)
226                                .spacing(8.)
227                                .children([Tag::Design, Tag::Mobile, Tag::OpenSource].map(|tag| {
228                                    let is_selected = selected_tags.read().contains(&tag);
229                                    Chip::new()
230                                        .selected(is_selected)
231                                        .on_press(move |_| {
232                                            if selected_tags.read().contains(&tag) {
233                                                selected_tags.write().remove(&tag);
234                                            } else {
235                                                selected_tags.write().insert(tag);
236                                            }
237                                        })
238                                        .child(tag.to_string())
239                                        .into()
240                                })),
241                        ),
242                    )
243                    .child(
244                        rect()
245                            .spacing(8.)
246                            .child("Loader")
247                            .child(CircularLoader::new()),
248                    )
249                    .child(
250                        rect()
251                            .spacing(8.)
252                            .child("Color Picker")
253                            .child(ColorPicker::new(move |c| color.set(c)).value(color())),
254                    )
255                    .child(
256                        rect().spacing(8.).child("Remote Image").child(
257                            ImageViewer::new("https://picsum.photos/500/1000")
258                                .width(Size::fill())
259                                .aspect_ratio(AspectRatio::Max),
260                        ),
261                    ),
262            )
263    }
264}
265
266fn ripple_card(
267    bg: impl Into<Color>,
268    fg: impl Into<Color>,
269    ripple_color: Option<impl Into<Color>>,
270    text: &str,
271) -> impl IntoElement {
272    let mut ripple = Ripple::new();
273
274    if let Some(color) = ripple_color {
275        ripple = ripple.color(color);
276    }
277
278    ripple.child(
279        rect()
280            .width(Size::fill())
281            .height(Size::px(80.))
282            .center()
283            .background(bg)
284            .corner_radius(12.)
285            .color(fg)
286            .child(text),
287    )
288}