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
26pub struct IcedBackend {
28 message_handler: Box<dyn Fn(String, Option<String>) -> Box<dyn CloneableMessage> + 'static>,
29}
30
31impl IcedBackend {
32 pub fn new<F>(handler: F) -> Self
34 where
35 F: Fn(String, Option<String>) -> Box<dyn CloneableMessage> + 'static,
36 {
37 Self {
38 message_handler: Box::new(handler),
39 }
40 }
41}
42
43pub trait CloneableMessage: std::fmt::Debug + Send + Sync + 'static {
45 fn clone_box(&self) -> Box<dyn CloneableMessage>;
46}
47
48impl<T> CloneableMessage for T
49where
50 T: Clone + std::fmt::Debug + Send + Sync + 'static,
51{
52 fn clone_box(&self) -> Box<dyn CloneableMessage> {
53 Box::new(self.clone())
54 }
55}
56
57impl Clone for Box<dyn CloneableMessage> {
58 fn clone(&self) -> Self {
59 self.clone_box()
60 }
61}
62
63impl Backend for IcedBackend {
64 type Widget<'a> = Element<'a, Box<dyn CloneableMessage>, Theme, Renderer>;
65 type Message = Box<dyn CloneableMessage>;
66
67 fn text<'a>(&self, content: &str) -> Self::Widget<'a> {
68 text(content.to_string()).into()
69 }
70
71 fn button<'a>(
72 &self,
73 label: Self::Widget<'a>,
74 on_press: Option<Self::Message>,
75 ) -> Self::Widget<'a> {
76 let btn = button(label);
77 if let Some(msg) = on_press {
78 btn.on_press(msg).into()
79 } else {
80 btn.into()
81 }
82 }
83
84 fn column<'a>(&self, children: Vec<Self::Widget<'a>>) -> Self::Widget<'a> {
85 column(children).into()
86 }
87
88 fn row<'a>(&self, children: Vec<Self::Widget<'a>>) -> Self::Widget<'a> {
89 row(children).into()
90 }
91
92 fn container<'a>(&self, content: Self::Widget<'a>) -> Self::Widget<'a> {
93 column(vec![content]).into()
96 }
97
98 fn scrollable<'a>(&self, content: Self::Widget<'a>) -> Self::Widget<'a> {
99 column(vec![content]).into()
101 }
102
103 fn stack<'a>(&self, children: Vec<Self::Widget<'a>>) -> Self::Widget<'a> {
104 column(children).into()
106 }
107
108 fn text_input<'a>(
109 &self,
110 _placeholder: &str,
111 _value: &str,
112 _on_input: Option<Self::Message>,
113 ) -> Self::Widget<'a> {
114 text("[text_input]").into()
116 }
117
118 fn checkbox<'a>(
119 &self,
120 _label: &str,
121 _is_checked: bool,
122 _on_toggle: Option<Self::Message>,
123 ) -> Self::Widget<'a> {
124 text("[checkbox]").into()
126 }
127
128 fn slider<'a>(
129 &self,
130 _min: f32,
131 _max: f32,
132 _value: f32,
133 _on_change: Option<Self::Message>,
134 ) -> Self::Widget<'a> {
135 text("[slider]").into()
137 }
138
139 fn pick_list<'a>(
140 &self,
141 _options: Vec<&str>,
142 _selected: Option<&str>,
143 _on_select: Option<Self::Message>,
144 ) -> Self::Widget<'a> {
145 text("[pick_list]").into()
147 }
148
149 fn toggler<'a>(
150 &self,
151 _label: &str,
152 _is_active: bool,
153 _on_toggle: Option<Self::Message>,
154 ) -> Self::Widget<'a> {
155 text("[toggler]").into()
157 }
158
159 fn image<'a>(&self, _path: &str) -> Self::Widget<'a> {
160 text("[image]").into()
162 }
163
164 fn svg<'a>(&self, _path: &str) -> Self::Widget<'a> {
165 text("[svg]").into()
167 }
168
169 fn space<'a>(&self) -> Self::Widget<'a> {
170 text("").into()
172 }
173
174 fn rule<'a>(&self) -> Self::Widget<'a> {
175 text("─").into()
177 }
178
179 fn radio<'a>(
180 &self,
181 _label: &str,
182 _value: &str,
183 _selected: Option<&str>,
184 _on_select: Option<Self::Message>,
185 ) -> Self::Widget<'a> {
186 text("[radio]").into()
189 }
190}
191
192pub fn render_with_layout<'a>(
198 node: &WidgetNode,
199 backend: &IcedBackend,
200) -> Element<'a, Box<dyn CloneableMessage>, Theme, Renderer> {
201 use crate::style_mapping::{map_layout_constraints, map_style_properties};
202 use iced::widget::container;
203
204 let widget = render(node, backend);
206
207 let layout = node.layout.as_ref().map(map_layout_constraints);
209
210 let style = node.style.as_ref().map(map_style_properties);
212
213 if layout.is_some() || style.is_some() {
215 let mut container = container(widget);
216
217 if let Some(layout) = layout {
218 container = container
219 .width(layout.width)
220 .height(layout.height)
221 .padding(layout.padding);
222
223 if let Some(align) = layout.align_items {
225 container = container.align_y(align);
226 }
227
228 if let Some(align_x) = layout.align_x {
230 container = container.align_x(align_x);
231 }
232 if let Some(align_y) = layout.align_y {
233 container = container.align_y(align_y);
234 }
235 }
236
237 if let Some(style) = style {
238 container = container.style(move |_theme| style);
239 }
240
241 container.into()
242 } else {
243 widget
244 }
245}
246
247pub fn render<'a>(
248 node: &WidgetNode,
249 backend: &IcedBackend,
250) -> Element<'a, Box<dyn CloneableMessage>, Theme, Renderer> {
251 match node.kind {
252 WidgetKind::Text => {
253 let value = match node.attributes.get("value") {
255 Some(AttributeValue::Static(v)) => v.clone(),
256 Some(AttributeValue::Binding(_)) => {
257 "[binding]".to_string()
259 }
260 Some(AttributeValue::Interpolated(parts)) => {
261 format_interpolated(parts)
263 }
264 None => String::new(),
265 };
266 backend.text(&value)
267 }
268 WidgetKind::Button => {
269 let label_text = match node.attributes.get("label") {
271 Some(AttributeValue::Static(l)) => l.clone(),
272 Some(AttributeValue::Binding(_)) => "[binding]".to_string(),
273 Some(AttributeValue::Interpolated(parts)) => format_interpolated(parts),
274 None => String::new(),
275 };
276 let label = backend.text(&label_text);
277
278 let on_press = node
280 .events
281 .iter()
282 .find(|e| e.event == EventKind::Click)
283 .map(|e| {
284 let handler_name = e.handler.clone();
285 (backend.message_handler)(handler_name, None)
286 });
287
288 backend.button(label, on_press)
289 }
290 WidgetKind::Column => {
291 let children: Vec<_> = node
292 .children
293 .iter()
294 .map(|child| render(child, backend))
295 .collect();
296 backend.column(children)
297 }
298 WidgetKind::Row => {
299 let children: Vec<_> = node
300 .children
301 .iter()
302 .map(|child| render(child, backend))
303 .collect();
304 backend.row(children)
305 }
306 WidgetKind::Container => {
307 let children: Vec<_> = node
308 .children
309 .iter()
310 .map(|child| render(child, backend))
311 .collect();
312 if let Some(first_child) = children.into_iter().next() {
313 backend.container(first_child)
314 } else {
315 backend.container(backend.text(""))
316 }
317 }
318 WidgetKind::Scrollable => {
319 let children: Vec<_> = node
320 .children
321 .iter()
322 .map(|child| render(child, backend))
323 .collect();
324 if let Some(first_child) = children.into_iter().next() {
325 backend.scrollable(first_child)
326 } else {
327 backend.scrollable(backend.text(""))
328 }
329 }
330 WidgetKind::Stack => {
331 let children: Vec<_> = node
332 .children
333 .iter()
334 .map(|child| render(child, backend))
335 .collect();
336 backend.stack(children)
337 }
338 WidgetKind::TextInput => {
339 let placeholder = match node.attributes.get("placeholder") {
340 Some(AttributeValue::Static(v)) => v.clone(),
341 _ => String::new(),
342 };
343 let value = match node.attributes.get("value") {
344 Some(AttributeValue::Static(v)) => v.clone(),
345 Some(AttributeValue::Binding(_)) => "[binding]".to_string(),
346 Some(AttributeValue::Interpolated(parts)) => format_interpolated(parts),
347 None => String::new(),
348 };
349 let on_input = node
351 .events
352 .iter()
353 .find(|e| e.event == EventKind::Input)
354 .map(|e| {
355 let handler_name = e.handler.clone();
356 (backend.message_handler)(handler_name, None)
357 });
358 backend.text_input(&placeholder, &value, on_input)
359 }
360 WidgetKind::Checkbox => {
361 let label = match node.attributes.get("label") {
362 Some(AttributeValue::Static(l)) => l.clone(),
363 _ => String::new(),
364 };
365 let is_checked = match node.attributes.get("checked") {
366 Some(AttributeValue::Static(v)) => v == "true" || v == "1",
367 _ => false,
368 };
369 let on_toggle = node
371 .events
372 .iter()
373 .find(|e| e.event == EventKind::Toggle)
374 .map(|e| {
375 let handler_name = e.handler.clone();
376 (backend.message_handler)(handler_name, None)
377 });
378 backend.checkbox(&label, is_checked, on_toggle)
379 }
380 WidgetKind::Slider => {
381 let min = match node.attributes.get("min") {
382 Some(AttributeValue::Static(v)) => v.parse::<f32>().unwrap_or(0.0),
383 _ => 0.0,
384 };
385 let max = match node.attributes.get("max") {
386 Some(AttributeValue::Static(v)) => v.parse::<f32>().unwrap_or(100.0),
387 _ => 100.0,
388 };
389 let value = match node.attributes.get("value") {
390 Some(AttributeValue::Static(v)) => v.parse::<f32>().unwrap_or(50.0),
391 _ => 50.0,
392 };
393 let on_change = node
395 .events
396 .iter()
397 .find(|e| e.event == EventKind::Change)
398 .map(|e| {
399 let handler_name = e.handler.clone();
400 (backend.message_handler)(handler_name, None)
401 });
402 backend.slider(min, max, value, on_change)
403 }
404 WidgetKind::PickList => {
405 let options_str = match node.attributes.get("options") {
406 Some(AttributeValue::Static(v)) => v.clone(),
407 _ => String::new(),
408 };
409 let options: Vec<&str> = options_str.split(',').collect();
410 let selected = match node.attributes.get("selected") {
411 Some(AttributeValue::Static(v)) => Some(v.as_str()),
412 _ => None,
413 };
414 let on_select = node
416 .events
417 .iter()
418 .find(|e| e.event == EventKind::Select)
419 .map(|e| {
420 let handler_name = e.handler.clone();
421 (backend.message_handler)(handler_name, None)
422 });
423 backend.pick_list(options, selected, on_select)
424 }
425 WidgetKind::Toggler => {
426 let label = match node.attributes.get("label") {
427 Some(AttributeValue::Static(l)) => l.clone(),
428 _ => String::new(),
429 };
430 let is_active = match node.attributes.get("active") {
431 Some(AttributeValue::Static(v)) => v == "true" || v == "1",
432 _ => false,
433 };
434 let on_toggle = node
436 .events
437 .iter()
438 .find(|e| e.event == EventKind::Toggle)
439 .map(|e| {
440 let handler_name = e.handler.clone();
441 (backend.message_handler)(handler_name, None)
442 });
443 backend.toggler(&label, is_active, on_toggle)
444 }
445 WidgetKind::Image => {
446 let path = match node.attributes.get("src") {
447 Some(AttributeValue::Static(v)) => v.clone(),
448 _ => String::new(),
449 };
450 backend.image(&path)
451 }
452 WidgetKind::Svg => {
453 let path = match node.attributes.get("src") {
454 Some(AttributeValue::Static(v)) => v.clone(),
455 _ => String::new(),
456 };
457 backend.svg(&path)
458 }
459 WidgetKind::Space => backend.space(),
460 WidgetKind::Rule => backend.rule(),
461 WidgetKind::Radio => {
462 let label = match node.attributes.get("label") {
463 Some(AttributeValue::Static(l)) => l.clone(),
464 _ => String::new(),
465 };
466 let value = match node.attributes.get("value") {
467 Some(AttributeValue::Static(v)) => v.clone(),
468 _ => String::new(),
469 };
470 backend.radio(&label, &value, None, None)
471 }
472 WidgetKind::Custom(_) => {
473 backend.column(Vec::new())
475 }
476 WidgetKind::ComboBox => backend.column(Vec::new()),
477 WidgetKind::ProgressBar => backend.column(Vec::new()),
478 WidgetKind::Tooltip => backend.column(Vec::new()),
479 WidgetKind::Grid => backend.column(Vec::new()),
480 WidgetKind::Canvas => backend.column(Vec::new()),
481 WidgetKind::Float => backend.column(Vec::new()),
482 WidgetKind::For => backend.column(Vec::new()), }
484}
485
486fn format_interpolated(parts: &[InterpolatedPart]) -> String {
488 let mut result = String::new();
489 for part in parts {
490 match part {
491 InterpolatedPart::Literal(literal) => result.push_str(literal),
492 InterpolatedPart::Binding(_) => result.push_str("[binding]"),
493 }
494 }
495 result
496}