desktop_example/app/routes/
widgets.rs1use 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}