1pub mod builder;
4pub mod convert;
5pub mod style_mapping;
6pub mod system_theme;
7pub mod theme_adapter;
8
9pub use system_theme::watch_system_theme;
11
12use dampen_core::{AttributeValue, Backend, EventKind, InterpolatedPart, WidgetKind, WidgetNode};
13use iced::widget::{button, column, row, text};
14use iced::{Element, Renderer, Theme};
15
16#[derive(Clone, Debug, PartialEq)]
18pub enum HandlerMessage {
19 Handler(String, Option<String>),
21}
22
23pub use builder::DampenWidgetBuilder;
25
26#[deprecated(
49 since = "0.2.7",
50 note = "Use DampenWidgetBuilder instead. See migration guide in docs/MIGRATION.md"
51)]
52pub struct IcedBackend {
53 message_handler: Box<dyn Fn(String, Option<String>) -> Box<dyn CloneableMessage> + 'static>,
54}
55
56#[allow(deprecated)]
57impl IcedBackend {
58 pub fn new<F>(handler: F) -> Self
60 where
61 F: Fn(String, Option<String>) -> Box<dyn CloneableMessage> + 'static,
62 {
63 Self {
64 message_handler: Box::new(handler),
65 }
66 }
67}
68
69pub trait CloneableMessage: std::fmt::Debug + Send + Sync + 'static {
71 fn clone_box(&self) -> Box<dyn CloneableMessage>;
72}
73
74impl<T> CloneableMessage for T
75where
76 T: Clone + std::fmt::Debug + Send + Sync + 'static,
77{
78 fn clone_box(&self) -> Box<dyn CloneableMessage> {
79 Box::new(self.clone())
80 }
81}
82
83impl Clone for Box<dyn CloneableMessage> {
84 fn clone(&self) -> Self {
85 self.clone_box()
86 }
87}
88
89#[allow(deprecated)]
90impl Backend for IcedBackend {
91 type Widget<'a> = Element<'a, Box<dyn CloneableMessage>, Theme, Renderer>;
92 type Message = Box<dyn CloneableMessage>;
93
94 fn text<'a>(&self, content: &str) -> Self::Widget<'a> {
95 text(content.to_string()).into()
96 }
97
98 fn button<'a>(
99 &self,
100 label: Self::Widget<'a>,
101 on_press: Option<Self::Message>,
102 ) -> Self::Widget<'a> {
103 let btn = button(label);
104 if let Some(msg) = on_press {
105 btn.on_press(msg).into()
106 } else {
107 btn.into()
108 }
109 }
110
111 fn column<'a>(&self, children: Vec<Self::Widget<'a>>) -> Self::Widget<'a> {
112 column(children).into()
113 }
114
115 fn row<'a>(&self, children: Vec<Self::Widget<'a>>) -> Self::Widget<'a> {
116 row(children).into()
117 }
118
119 fn container<'a>(&self, content: Self::Widget<'a>) -> Self::Widget<'a> {
120 column(vec![content]).into()
123 }
124
125 fn scrollable<'a>(&self, content: Self::Widget<'a>) -> Self::Widget<'a> {
126 column(vec![content]).into()
128 }
129
130 fn stack<'a>(&self, children: Vec<Self::Widget<'a>>) -> Self::Widget<'a> {
131 column(children).into()
133 }
134
135 fn text_input<'a>(
136 &self,
137 _placeholder: &str,
138 _value: &str,
139 _on_input: Option<Self::Message>,
140 ) -> Self::Widget<'a> {
141 text("[text_input]").into()
143 }
144
145 fn checkbox<'a>(
146 &self,
147 _label: &str,
148 _is_checked: bool,
149 _on_toggle: Option<Self::Message>,
150 ) -> Self::Widget<'a> {
151 text("[checkbox]").into()
153 }
154
155 fn slider<'a>(
156 &self,
157 _min: f32,
158 _max: f32,
159 _value: f32,
160 _on_change: Option<Self::Message>,
161 ) -> Self::Widget<'a> {
162 text("[slider]").into()
164 }
165
166 fn pick_list<'a>(
167 &self,
168 _options: Vec<&str>,
169 _selected: Option<&str>,
170 _on_select: Option<Self::Message>,
171 ) -> Self::Widget<'a> {
172 text("[pick_list]").into()
174 }
175
176 fn toggler<'a>(
177 &self,
178 _label: &str,
179 _is_active: bool,
180 _on_toggle: Option<Self::Message>,
181 ) -> Self::Widget<'a> {
182 text("[toggler]").into()
184 }
185
186 fn image<'a>(&self, _path: &str) -> Self::Widget<'a> {
187 text("[image]").into()
189 }
190
191 fn svg<'a>(&self, _path: &str) -> Self::Widget<'a> {
192 text("[svg]").into()
194 }
195
196 fn space<'a>(&self) -> Self::Widget<'a> {
197 text("").into()
199 }
200
201 fn rule<'a>(&self) -> Self::Widget<'a> {
202 text("─").into()
204 }
205
206 fn radio<'a>(
207 &self,
208 _label: &str,
209 _value: &str,
210 _selected: Option<&str>,
211 _on_select: Option<Self::Message>,
212 ) -> Self::Widget<'a> {
213 text("[radio]").into()
216 }
217}
218
219#[allow(deprecated)]
225pub fn render_with_layout<'a>(
226 node: &WidgetNode,
227 backend: &IcedBackend,
228) -> Element<'a, Box<dyn CloneableMessage>, Theme, Renderer> {
229 use crate::style_mapping::{map_layout_constraints, map_style_properties};
230 use iced::widget::container;
231
232 let widget = render(node, backend);
234
235 let layout = node.layout.as_ref().map(map_layout_constraints);
237
238 let style = node.style.as_ref().map(map_style_properties);
240
241 if layout.is_some() || style.is_some() {
243 let mut container = container(widget);
244
245 if let Some(layout) = layout {
246 container = container
247 .width(layout.width)
248 .height(layout.height)
249 .padding(layout.padding);
250
251 if let Some(align) = layout.align_items {
253 container = container.align_y(align);
254 }
255
256 if let Some(align_x) = layout.align_x {
258 container = container.align_x(align_x);
259 }
260 if let Some(align_y) = layout.align_y {
261 container = container.align_y(align_y);
262 }
263 }
264
265 if let Some(style) = style {
266 container = container.style(move |_theme| style);
267 }
268
269 container.into()
270 } else {
271 widget
272 }
273}
274
275#[allow(deprecated)]
276pub fn render<'a>(
277 node: &WidgetNode,
278 backend: &IcedBackend,
279) -> Element<'a, Box<dyn CloneableMessage>, Theme, Renderer> {
280 match node.kind {
281 WidgetKind::Text => {
282 let value = match node.attributes.get("value") {
284 Some(AttributeValue::Static(v)) => v.clone(),
285 Some(AttributeValue::Binding(_)) => {
286 "[binding]".to_string()
288 }
289 Some(AttributeValue::Interpolated(parts)) => {
290 format_interpolated(parts)
292 }
293 None => String::new(),
294 };
295 backend.text(&value)
296 }
297 WidgetKind::Button => {
298 let label_text = match node.attributes.get("label") {
300 Some(AttributeValue::Static(l)) => l.clone(),
301 Some(AttributeValue::Binding(_)) => "[binding]".to_string(),
302 Some(AttributeValue::Interpolated(parts)) => format_interpolated(parts),
303 None => String::new(),
304 };
305 let label = backend.text(&label_text);
306
307 let on_press = node
309 .events
310 .iter()
311 .find(|e| e.event == EventKind::Click)
312 .map(|e| {
313 let handler_name = e.handler.clone();
314 (backend.message_handler)(handler_name, None)
315 });
316
317 backend.button(label, on_press)
318 }
319 WidgetKind::Column => {
320 let children: Vec<_> = node
321 .children
322 .iter()
323 .map(|child| render(child, backend))
324 .collect();
325 backend.column(children)
326 }
327 WidgetKind::Row => {
328 let children: Vec<_> = node
329 .children
330 .iter()
331 .map(|child| render(child, backend))
332 .collect();
333 backend.row(children)
334 }
335 WidgetKind::Container => {
336 let children: Vec<_> = node
337 .children
338 .iter()
339 .map(|child| render(child, backend))
340 .collect();
341 if let Some(first_child) = children.into_iter().next() {
342 backend.container(first_child)
343 } else {
344 backend.container(backend.text(""))
345 }
346 }
347 WidgetKind::Scrollable => {
348 let children: Vec<_> = node
349 .children
350 .iter()
351 .map(|child| render(child, backend))
352 .collect();
353 if let Some(first_child) = children.into_iter().next() {
354 backend.scrollable(first_child)
355 } else {
356 backend.scrollable(backend.text(""))
357 }
358 }
359 WidgetKind::Stack => {
360 let children: Vec<_> = node
361 .children
362 .iter()
363 .map(|child| render(child, backend))
364 .collect();
365 backend.stack(children)
366 }
367 WidgetKind::TextInput => {
368 let placeholder = match node.attributes.get("placeholder") {
369 Some(AttributeValue::Static(v)) => v.clone(),
370 _ => String::new(),
371 };
372 let value = match node.attributes.get("value") {
373 Some(AttributeValue::Static(v)) => v.clone(),
374 Some(AttributeValue::Binding(_)) => "[binding]".to_string(),
375 Some(AttributeValue::Interpolated(parts)) => format_interpolated(parts),
376 None => String::new(),
377 };
378 let on_input = node
380 .events
381 .iter()
382 .find(|e| e.event == EventKind::Input)
383 .map(|e| {
384 let handler_name = e.handler.clone();
385 (backend.message_handler)(handler_name, None)
386 });
387 backend.text_input(&placeholder, &value, on_input)
388 }
389 WidgetKind::Checkbox => {
390 let label = match node.attributes.get("label") {
391 Some(AttributeValue::Static(l)) => l.clone(),
392 _ => String::new(),
393 };
394 let is_checked = match node.attributes.get("checked") {
395 Some(AttributeValue::Static(v)) => v == "true" || v == "1",
396 _ => false,
397 };
398 let on_toggle = node
400 .events
401 .iter()
402 .find(|e| e.event == EventKind::Toggle)
403 .map(|e| {
404 let handler_name = e.handler.clone();
405 (backend.message_handler)(handler_name, None)
406 });
407 backend.checkbox(&label, is_checked, on_toggle)
408 }
409 WidgetKind::Slider => {
410 let min = match node.attributes.get("min") {
411 Some(AttributeValue::Static(v)) => v.parse::<f32>().unwrap_or(0.0),
412 _ => 0.0,
413 };
414 let max = match node.attributes.get("max") {
415 Some(AttributeValue::Static(v)) => v.parse::<f32>().unwrap_or(100.0),
416 _ => 100.0,
417 };
418 let value = match node.attributes.get("value") {
419 Some(AttributeValue::Static(v)) => v.parse::<f32>().unwrap_or(50.0),
420 _ => 50.0,
421 };
422 let on_change = node
424 .events
425 .iter()
426 .find(|e| e.event == EventKind::Change)
427 .map(|e| {
428 let handler_name = e.handler.clone();
429 (backend.message_handler)(handler_name, None)
430 });
431 backend.slider(min, max, value, on_change)
432 }
433 WidgetKind::PickList => {
434 let options_str = match node.attributes.get("options") {
435 Some(AttributeValue::Static(v)) => v.clone(),
436 _ => String::new(),
437 };
438 let options: Vec<&str> = options_str.split(',').collect();
439 let selected = match node.attributes.get("selected") {
440 Some(AttributeValue::Static(v)) => Some(v.as_str()),
441 _ => None,
442 };
443 let on_select = node
445 .events
446 .iter()
447 .find(|e| e.event == EventKind::Select)
448 .map(|e| {
449 let handler_name = e.handler.clone();
450 (backend.message_handler)(handler_name, None)
451 });
452 backend.pick_list(options, selected, on_select)
453 }
454 WidgetKind::Toggler => {
455 let label = match node.attributes.get("label") {
456 Some(AttributeValue::Static(l)) => l.clone(),
457 _ => String::new(),
458 };
459 let is_active = match node.attributes.get("active") {
460 Some(AttributeValue::Static(v)) => v == "true" || v == "1",
461 _ => false,
462 };
463 let on_toggle = node
465 .events
466 .iter()
467 .find(|e| e.event == EventKind::Toggle)
468 .map(|e| {
469 let handler_name = e.handler.clone();
470 (backend.message_handler)(handler_name, None)
471 });
472 backend.toggler(&label, is_active, on_toggle)
473 }
474 WidgetKind::Image => {
475 let path = match node.attributes.get("src") {
476 Some(AttributeValue::Static(v)) => v.clone(),
477 _ => String::new(),
478 };
479 backend.image(&path)
480 }
481 WidgetKind::Svg => {
482 let path = match node.attributes.get("src") {
483 Some(AttributeValue::Static(v)) => v.clone(),
484 _ => String::new(),
485 };
486 backend.svg(&path)
487 }
488 WidgetKind::Space => backend.space(),
489 WidgetKind::Rule => backend.rule(),
490 WidgetKind::Radio => {
491 let label = match node.attributes.get("label") {
492 Some(AttributeValue::Static(l)) => l.clone(),
493 _ => String::new(),
494 };
495 let value = match node.attributes.get("value") {
496 Some(AttributeValue::Static(v)) => v.clone(),
497 _ => String::new(),
498 };
499 backend.radio(&label, &value, None, None)
500 }
501 WidgetKind::Custom(_) => {
502 backend.column(Vec::new())
504 }
505 WidgetKind::ComboBox => backend.column(Vec::new()),
506 WidgetKind::ProgressBar => backend.column(Vec::new()),
507 WidgetKind::Tooltip => backend.column(Vec::new()),
508 WidgetKind::Grid => backend.column(Vec::new()),
509 WidgetKind::Canvas => backend.column(Vec::new()),
510 WidgetKind::Float => backend.column(Vec::new()),
511 WidgetKind::For => backend.column(Vec::new()), WidgetKind::If => backend.column(Vec::new()),
513 }
514}
515
516fn format_interpolated(parts: &[InterpolatedPart]) -> String {
518 let mut result = String::new();
519 for part in parts {
520 match part {
521 InterpolatedPart::Literal(literal) => result.push_str(literal),
522 InterpolatedPart::Binding(_) => result.push_str("[binding]"),
523 }
524 }
525 result
526}