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