Skip to main content

sequoia_seq/
lower.rs

1use std::cell::Cell;
2use std::rc::Rc;
3
4use sequoia_core::{Color, Fill, ImageSource, Length, Point, Rect, Vec2};
5use sequoia_elements::{
6    Button, Checkbox, Container, DateField, DragValue, DropdownButton, Field, MultiSelect,
7    NumberField, ProgressBar, Slider1D, Switch, TextArea, TextField,
8};
9use sequoia_input::{CursorIcon, PointerButton, PointerEvent};
10use sequoia_reactive::{Signal, effect};
11use sequoia_style::{BoxStyle, ButtonStyle, ShadowSpec, Theme};
12use sequoia_view::{
13    Align, AnimSpec, AnimatedSize, AnyView, AsyncView, Axis, Canvas, Column, EventCtx, Grid,
14    GridFlow, GridPlacement, Icon, Image, Indents, Justify, Live, LiveOffset, LiveOpacity,
15    MAX_ANIM_DT, Margined, Masonry, Motion, Overflow, Painter, Panel, Pulse, Rotated, Row, Scaled,
16    ScrollAxis, ScrollView, Shake, Spacer, SplitPanel, Stack, StackPos, Text, Track, View,
17    VirtualList, Wrap, when,
18};
19
20use crate::bindings::{ActionSig, FieldSignal, HostBindings, PropType, SeqRowData};
21use crate::error::SeqError;
22use crate::ir::{
23    ComponentDef, DeferredKind, LengthVal, SeqApp, SeqArg, SeqNode, SeqValue, SeqWindow, StyleDecl,
24    ThemeName,
25};
26use crate::value::Value;
27
28pub fn resolve_theme(file: &str, app: &SeqApp) -> Result<Theme, SeqError> {
29    match &app.theme {
30        ThemeName::Dark => Ok(Theme::dark()),
31        ThemeName::Light => Ok(Theme::light()),
32        ThemeName::File(path) => {
33            let full = theme_file_path(file, path);
34            Theme::from_tokens_file(full.to_str().unwrap_or(path))
35                .map_err(|e| SeqError::new(file, app.app_line, 1, format!("theme: {e}")))
36        }
37    }
38}
39
40fn theme_file_path(seq_file: &str, path: &str) -> std::path::PathBuf {
41    let requested = std::path::Path::new(path);
42    if requested.is_absolute() {
43        return requested.to_path_buf();
44    }
45    match std::path::Path::new(seq_file)
46        .parent()
47        .filter(|p| !p.as_os_str().is_empty())
48    {
49        Some(dir) => dir.join(requested),
50        None => requested.to_path_buf(),
51    }
52}
53
54#[derive(Clone, Copy)]
55pub(crate) enum Resolver<'a> {
56    Static,
57
58    Host(&'a HostBindings),
59}
60
61pub fn lower(file: &str, app: &SeqApp) -> Result<AnyView, SeqError> {
62    if let Some(d) = app.deferred.first() {
63        let what = match d.kind {
64            DeferredKind::Prop => "prop",
65            DeferredKind::On => "callback on",
66            DeferredKind::Style => "blok style",
67        };
68        return Err(SeqError::new(
69            file,
70            d.line,
71            1,
72            format!("{what} '{}' wymaga hosta Rust — inkrement 2", d.name),
73        ));
74    }
75    let resolved;
76    let app = if app.fns.is_empty() {
77        app
78    } else {
79        resolved = crate::resolve_seq_calls(app.clone());
80        &resolved
81    };
82    let app = expand_components(file, app)?;
83    let theme = resolve_theme(file, &app)?;
84    let window = app
85        .windows
86        .first()
87        .ok_or_else(|| SeqError::new(file, 1, 1, "app wymaga min. 1 bloku `window`".to_string()))?;
88    let styles = build_styles(file, &theme, &app.styles)?;
89    lower_window(file, &theme, &styles, window)
90}
91
92pub fn lower_with(file: &str, app: &SeqApp, bindings: &HostBindings) -> Result<AnyView, SeqError> {
93    check_decls(file, app, bindings)?;
94    let resolved;
95    let app = if app.fns.is_empty() {
96        app
97    } else {
98        resolved = crate::resolve_seq_calls(app.clone());
99        &resolved
100    };
101    let owned;
102    let bindings = if app.fns.is_empty() {
103        bindings
104    } else {
105        match bindings.get_seq() {
106            Some(rt) if rt.file() == file => {
107                rt.rebind(&app.fns);
108                bindings
109            }
110            _ => {
111                owned = bindings
112                    .clone()
113                    .with_seq(Rc::new(crate::eval::SeqRuntime::new(
114                        file,
115                        &app.fns,
116                        &app.states,
117                    )));
118                &owned
119            }
120        }
121    };
122    let app = expand_components(file, app)?;
123    let theme = resolve_theme(file, &app)?;
124    let window = app
125        .windows
126        .first()
127        .ok_or_else(|| SeqError::new(file, 1, 1, "app wymaga min. 1 bloku `window`".to_string()))?;
128    let styles = build_styles(file, &theme, &app.styles)?;
129    lower_window_with(file, &theme, &styles, window, bindings)
130}
131
132const MAX_COMP_DEPTH: usize = 32;
133
134const MAX_EXPANDED_NODES: usize = 100_000;
135
136fn take_budget(file: &str, node: &SeqNode, budget: &mut usize) -> Result<(), SeqError> {
137    *budget = budget.saturating_sub(1);
138    if *budget == 0 {
139        return Err(SeqError::new(
140            file,
141            node.line,
142            1,
143            format!("component expansion exceeded {MAX_EXPANDED_NODES} node budget"),
144        ));
145    }
146    Ok(())
147}
148
149pub fn expand_components(file: &str, app: &SeqApp) -> Result<SeqApp, SeqError> {
150    if app.components.is_empty() {
151        return Ok(app.clone());
152    }
153    let mut table: std::collections::HashMap<&str, &ComponentDef> =
154        std::collections::HashMap::new();
155    for c in &app.components {
156        table.insert(c.name.as_str(), c);
157    }
158    let mut out = app.clone();
159    let mut budget = MAX_EXPANDED_NODES;
160    for w in &mut out.windows {
161        w.nodes = expand_nodes(file, &table, &w.nodes, 0, &mut budget)?;
162    }
163    Ok(out)
164}
165
166fn expand_nodes(
167    file: &str,
168    table: &std::collections::HashMap<&str, &ComponentDef>,
169    nodes: &[SeqNode],
170    depth: usize,
171    budget: &mut usize,
172) -> Result<Vec<SeqNode>, SeqError> {
173    let mut out = Vec::new();
174    for n in nodes {
175        if let Some(def) = table.get(n.kind.as_str()) {
176            out.extend(expand_call(file, table, def, n, depth, budget)?);
177        } else {
178            take_budget(file, n, budget)?;
179            let mut n = n.clone();
180            n.children = expand_nodes(file, table, &n.children, depth, budget)?;
181            out.push(n);
182        }
183    }
184    Ok(out)
185}
186
187fn expand_call(
188    file: &str,
189    table: &std::collections::HashMap<&str, &ComponentDef>,
190    def: &ComponentDef,
191    node: &SeqNode,
192    depth: usize,
193    budget: &mut usize,
194) -> Result<Vec<SeqNode>, SeqError> {
195    if depth > MAX_COMP_DEPTH {
196        return Err(SeqError::new(
197            file,
198            node.line,
199            1,
200            format!("component nesting limit exceeded ({MAX_COMP_DEPTH})"),
201        ));
202    }
203
204    let mut pmap: std::collections::HashMap<&str, &SeqValue> = std::collections::HashMap::new();
205    let mut flex: Option<SeqValue> = None;
206    for arg in &node.args {
207        if arg.name == "flex" {
208            flex = Some(arg.value.clone());
209            continue;
210        }
211        let Some(param) = def.params.iter().find(|p| p.name == arg.name) else {
212            return Err(SeqError::new(
213                file,
214                arg.line,
215                1,
216                format!("nieznany parametr '{}' w {}", arg.name, def.name),
217            ));
218        };
219        pmap.insert(param.name.as_str(), &arg.value);
220    }
221    for p in &def.params {
222        if !pmap.contains_key(p.name.as_str()) {
223            return Err(SeqError::new(
224                file,
225                node.line,
226                1,
227                format!("brakuje parametru '{}' w {}", p.name, def.name),
228            ));
229        }
230    }
231
232    let kids = expand_nodes(file, table, &node.children, depth + 1, budget)?;
233
234    let body: Vec<SeqNode> = def.body.iter().map(|b| substitute_node(b, &pmap)).collect();
235    let mut body = splice_children(&body, &kids);
236
237    if let Some(flex_val) = flex {
238        if body.len() == 1 {
239            let root = body.pop().expect("one root");
240            body.push(with_flex(root, node.line, flex_val));
241        } else {
242            take_budget(file, node, budget)?;
243            body = vec![wrap_column(node.line, body, Some(flex_val))];
244        }
245    } else if body.len() != 1 {
246        take_budget(file, node, budget)?;
247        body = vec![wrap_column(node.line, body, None)];
248    }
249
250    expand_nodes(&def.file, table, &body, depth + 1, budget)
251}
252
253fn with_flex(mut node: SeqNode, line: usize, flex: SeqValue) -> SeqNode {
254    node.args.retain(|a| a.name != "flex");
255    node.args.push(SeqArg {
256        name: "flex".to_string(),
257        line,
258        value: flex,
259    });
260    node
261}
262
263fn wrap_column(line: usize, children: Vec<SeqNode>, flex: Option<SeqValue>) -> SeqNode {
264    let mut args = Vec::new();
265    if let Some(value) = flex {
266        args.push(SeqArg {
267            name: "flex".to_string(),
268            line,
269            value,
270        });
271    }
272    SeqNode {
273        kind: "Column".to_string(),
274        line,
275        label: None,
276        args,
277        children,
278    }
279}
280
281fn substitute_node(node: &SeqNode, pmap: &std::collections::HashMap<&str, &SeqValue>) -> SeqNode {
282    let mut out = node.clone();
283    out.args = out
284        .args
285        .into_iter()
286        .map(|mut a| {
287            a.value = substitute_value(&a.value, pmap);
288            a
289        })
290        .collect();
291    out.children = out
292        .children
293        .into_iter()
294        .map(|c| substitute_node(&c, pmap))
295        .collect();
296    out
297}
298
299fn substitute_value(
300    value: &SeqValue,
301    pmap: &std::collections::HashMap<&str, &SeqValue>,
302) -> SeqValue {
303    match value {
304        SeqValue::Ident(name) => pmap
305            .get(name.as_str())
306            .map(|v| (*v).clone())
307            .unwrap_or_else(|| value.clone()),
308        SeqValue::Tuple(items) => {
309            SeqValue::Tuple(items.iter().map(|v| substitute_value(v, pmap)).collect())
310        }
311        SeqValue::Array(items) => {
312            SeqValue::Array(items.iter().map(|v| substitute_value(v, pmap)).collect())
313        }
314        SeqValue::Callback { action, args } => SeqValue::Callback {
315            action: action.clone(),
316            args: args.iter().map(|v| substitute_value(v, pmap)).collect(),
317        },
318        SeqValue::CallSeq { name, args } => SeqValue::CallSeq {
319            name: name.clone(),
320            args: args.iter().map(|v| substitute_value(v, pmap)).collect(),
321        },
322        _ => value.clone(),
323    }
324}
325
326fn splice_children(nodes: &[SeqNode], kids: &[SeqNode]) -> Vec<SeqNode> {
327    let mut out = Vec::new();
328    for n in nodes {
329        if n.kind == "children" {
330            out.extend(kids.iter().cloned());
331        } else {
332            let mut n = n.clone();
333            n.children = splice_children(&n.children, kids);
334            out.push(n);
335        }
336    }
337    out
338}
339
340pub fn check_decls(file: &str, app: &SeqApp, bindings: &HostBindings) -> Result<(), SeqError> {
341    for d in &app.deferred {
342        match d.kind {
343            DeferredKind::Style => {
344                return Err(SeqError::new(
345                    file,
346                    d.line,
347                    1,
348                    format!("style block '{}' - style system later", d.name),
349                ));
350            }
351            DeferredKind::Prop => {
352                let want = d.sig.first().map(String::as_str).unwrap_or("");
353                let want = PropType::from_seq(want).ok_or_else(|| {
354                    SeqError::new(
355                        file,
356                        d.line,
357                        1,
358                        format!("nieznany typ prop '{want}' (String/Bool/Int/Float)"),
359                    )
360                })?;
361                match bindings.prop_type(&d.name) {
362                    Some(got) if got == want => {}
363                    Some(got) => {
364                        return Err(SeqError::new(
365                            file,
366                            d.line,
367                            1,
368                            format!(
369                                "prop '{}' is {} in .seq and host gave {}",
370                                d.name,
371                                want.seq_name(),
372                                got.seq_name()
373                            ),
374                        ));
375                    }
376                    None => {
377                        return Err(SeqError::new(
378                            file,
379                            d.line,
380                            1,
381                            format!(
382                                "prop '{}' ({}) without binding - HostBindings::prop_{}()",
383                                d.name,
384                                want.seq_name(),
385                                want.seq_name().to_lowercase()
386                            ),
387                        ));
388                    }
389                }
390            }
391            DeferredKind::On => {
392                if app.fns.iter().any(|f| f.name == d.name) {
393                    continue;
394                }
395                let want = match d
396                    .sig
397                    .iter()
398                    .map(String::as_str)
399                    .collect::<Vec<_>>()
400                    .as_slice()
401                {
402                    [] => ActionSig::A0,
403                    ["Int"] => ActionSig::Int,
404                    ["Float"] => ActionSig::Float,
405                    ["String"] => ActionSig::Str,
406                    ["Bool"] => ActionSig::Bool,
407                    ["Float", "Float"] => ActionSig::Point,
408                    ["Float", "Float", "String"] => ActionSig::PointNamed,
409                    ["Float", "Float", "Float"] => ActionSig::Wheel,
410                    [t] => {
411                        return Err(SeqError::new(
412                            file,
413                            d.line,
414                            1,
415                            format!("nieznany typ parametru '{t}' (Int/Float/String/Bool)"),
416                        ));
417                    }
418                    _ => {
419                        return Err(SeqError::new(
420                            file,
421                            d.line,
422                            1,
423                            format!(
424                                "callback '{}' — wspierane sygnatury: (), (Int/Float/String/Bool), (Float, Float), (Float, Float, String), (Float, Float, Float)",
425                                d.name
426                            ),
427                        ));
428                    }
429                };
430                match bindings.action_sig(&d.name) {
431                    Some(got)
432                        if got == want || (want == ActionSig::Wheel && got == ActionSig::Pinch) => {
433                    }
434                    Some(_) => {
435                        return Err(SeqError::new(
436                            file,
437                            d.line,
438                            1,
439                            format!("the signature on {} does not match the host", d.name),
440                        ));
441                    }
442                    None => {
443                        return Err(SeqError::new(
444                            file,
445                            d.line,
446                            1,
447                            format!(
448                                "callback '{}' without binding - HostBindings::on_..()",
449                                d.name
450                            ),
451                        ));
452                    }
453                }
454            }
455        }
456    }
457    Ok(())
458}
459
460#[derive(Clone, Copy, Debug, PartialEq, Eq)]
461pub enum TextWeight {
462    Bold,
463
464    Semi,
465
466    Medium,
467
468    Normal,
469
470    Light,
471
472    Black,
473}
474
475#[derive(Clone, Copy, Debug, Default)]
476pub struct SeqTextStyle {
477    pub size: Option<f32>,
478
479    pub color: Option<Color>,
480
481    pub weight: Option<TextWeight>,
482}
483
484#[derive(Clone, Debug, Default)]
485pub struct BuiltStyles {
486    boxes: std::collections::HashMap<String, BoxStyle>,
487    buttons: std::collections::HashMap<String, ButtonStyle>,
488    texts: std::collections::HashMap<String, SeqTextStyle>,
489}
490
491impl BuiltStyles {
492    pub fn empty() -> Self {
493        Self::default()
494    }
495
496    pub fn box_style(&self, name: &str) -> Option<BoxStyle> {
497        self.boxes.get(name).copied()
498    }
499
500    pub fn button_style(&self, name: &str) -> Option<ButtonStyle> {
501        self.buttons.get(name).copied()
502    }
503
504    pub fn text_style(&self, name: &str) -> Option<SeqTextStyle> {
505        self.texts.get(name).copied()
506    }
507}
508
509pub(crate) fn box_style_of(
510    file: &str,
511    styles: &BuiltStyles,
512    node: &SeqNode,
513) -> Result<Option<BoxStyle>, SeqError> {
514    let Some(arg) = node.args.iter().find(|a| a.name == "style") else {
515        return Ok(None);
516    };
517    let name = match &arg.value {
518        SeqValue::Ident(name) | SeqValue::Str(name) => name.clone(),
519        _ => {
520            return Err(SeqError::new(
521                file,
522                arg.line,
523                1,
524                "style: to nazwa bloku (style: Card)",
525            ));
526        }
527    };
528    styles.box_style(&name).map(Some).ok_or_else(|| {
529        SeqError::new(
530            file,
531            arg.line,
532            1,
533            format!("unknown style '{name}' (check `style` blocks)"),
534        )
535    })
536}
537
538fn text_style_of(
539    file: &str,
540    styles: &BuiltStyles,
541    node: &SeqNode,
542) -> Result<Option<SeqTextStyle>, SeqError> {
543    let Some(arg) = node.args.iter().find(|a| a.name == "style") else {
544        return Ok(None);
545    };
546    let name = match &arg.value {
547        SeqValue::Ident(name) | SeqValue::Str(name) => name.clone(),
548        _ => {
549            return Err(SeqError::new(
550                file,
551                arg.line,
552                1,
553                "style: to nazwa bloku (style: Card)",
554            ));
555        }
556    };
557    styles.text_style(&name).map(Some).ok_or_else(|| {
558        SeqError::new(
559            file,
560            arg.line,
561            1,
562            format!("unknown style '{name}' (check `style` blocks)"),
563        )
564    })
565}
566
567fn apply_seq_text_style(mut v: Text, s: SeqTextStyle) -> Text {
568    if let Some(size) = s.size {
569        v = v.size(size);
570    }
571    if let Some(color) = s.color {
572        v = v.color(color);
573    }
574    match s.weight {
575        Some(TextWeight::Bold) => v = v.bold(),
576        Some(TextWeight::Semi) => v = v.semibold(),
577        Some(TextWeight::Medium) => v = v.medium(),
578        Some(TextWeight::Normal) => v = v.normal(),
579        Some(TextWeight::Light) => v = v.light(),
580        Some(TextWeight::Black) => v = v.black(),
581        None => {}
582    }
583    v
584}
585
586pub fn build_styles(
587    file: &str,
588    theme: &Theme,
589    styles: &[StyleDecl],
590) -> Result<BuiltStyles, SeqError> {
591    let mut out = BuiltStyles::empty();
592    let res = Resolver::Static;
593    for s in styles {
594        match s.target.as_str() {
595            "Box" => {
596                let mut v = BoxStyle::new();
597                for base in base_chain(file, styles, s)? {
598                    apply_box_args(file, theme, &res, base, &mut v)?;
599                }
600                apply_box_args(file, theme, &res, s, &mut v)?;
601                out.boxes.insert(s.name.clone(), v);
602            }
603            "Button" => {
604                let mut v = ButtonStyle::new();
605                for base in base_chain(file, styles, s)? {
606                    if let Some(preset) = button_preset(&base.base) {
607                        v = preset;
608                    } else {
609                        apply_button_args(file, theme, &res, base, &mut v)?;
610                    }
611                }
612                if let Some(preset) = button_preset(&s.base) {
613                    v = preset;
614                }
615                apply_button_args(file, theme, &res, s, &mut v)?;
616                out.buttons.insert(s.name.clone(), v);
617            }
618            "Text" => {
619                let mut v = SeqTextStyle::default();
620                for base in base_chain(file, styles, s)? {
621                    apply_text_args(file, theme, &res, base, &mut v)?;
622                }
623                apply_text_args(file, theme, &res, s, &mut v)?;
624                out.texts.insert(s.name.clone(), v);
625            }
626
627            other => {
628                return Err(SeqError::new(
629                    file,
630                    s.line,
631                    1,
632                    format!(
633                        "style '{}': nieznany typ '{other}' (Box/Button/Text)",
634                        s.name
635                    ),
636                ));
637            }
638        }
639    }
640    Ok(out)
641}
642
643fn base_chain<'s>(
644    file: &str,
645    styles: &'s [StyleDecl],
646    start: &StyleDecl,
647) -> Result<Vec<&'s StyleDecl>, SeqError> {
648    let mut chain = Vec::new();
649    let mut next = start.base.clone();
650    let mut seen = vec![start.name.clone()];
651    while let Some(name) = next {
652        if start.target == "Button" && button_preset(&Some(name.clone())).is_some() {
653            break;
654        }
655        if seen.contains(&name) {
656            return Err(SeqError::new(
657                file,
658                start.line,
659                1,
660                format!("style '{}': cykliczne extends", start.name),
661            ));
662        }
663        seen.push(name.clone());
664        let Some(decl) = styles.iter().find(|o| o.name == name) else {
665            return Err(SeqError::new(
666                file,
667                start.line,
668                1,
669                format!("style '{}': nieznana baza '{name}'", start.name),
670            ));
671        };
672        if decl.target != start.target {
673            return Err(SeqError::new(
674                file,
675                start.line,
676                1,
677                format!(
678                    "style '{}': baza '{name}' to {}, a styl to {}",
679                    start.name, decl.target, start.target
680                ),
681            ));
682        }
683        chain.push(decl);
684        next = decl.base.clone();
685        if chain.len() > styles.len() {
686            return Err(SeqError::new(
687                file,
688                start.line,
689                1,
690                format!("style '{}': cykliczne extends", start.name),
691            ));
692        }
693    }
694    chain.reverse();
695    Ok(chain)
696}
697
698fn button_preset(base: &Option<String>) -> Option<ButtonStyle> {
699    match base.as_deref() {
700        Some("primary") => Some(ButtonStyle::PRIMARY),
701        Some("secondary") => Some(ButtonStyle::SECONDARY),
702        Some("danger") => Some(ButtonStyle::DANGER),
703        Some("ghost") => Some(ButtonStyle::GHOST),
704        Some("dark_filled") => Some(ButtonStyle::DARK_FILLED),
705        Some("outline_rose") => Some(ButtonStyle::OUTLINE_ROSE),
706        _ => None,
707    }
708}
709
710fn border_side(
711    file: &str,
712    theme: &Theme,
713    style: &str,
714    arg: &SeqArg,
715) -> Result<(f32, Color), SeqError> {
716    match &arg.value {
717        SeqValue::Array(items) if items.len() == 2 => {
718            let w = match &items[0] {
719                SeqValue::Num(n) => *n,
720                _ => {
721                    return Err(SeqError::new(
722                        file,
723                        arg.line,
724                        1,
725                        format!("'{style}' in style: [width, color]"),
726                    ));
727                }
728            };
729            let c = match &items[1] {
730                SeqValue::Color(hex) => hex_color(file, arg.line, hex)?,
731                SeqValue::Theme(f) => theme_color(file, arg.line, theme, f)?,
732                _ => {
733                    return Err(SeqError::new(
734                        file,
735                        arg.line,
736                        1,
737                        format!("'{style}' in style: [width, color]"),
738                    ));
739                }
740            };
741            Ok((w, c))
742        }
743        _ => Err(SeqError::new(
744            file,
745            arg.line,
746            1,
747            format!("'{style}' in style: [width, color]"),
748        )),
749    }
750}
751
752fn shadow_spec(
753    file: &str,
754    theme: &Theme,
755    style: &str,
756    arg: &SeqArg,
757) -> Result<ShadowSpec, SeqError> {
758    let err = || {
759        SeqError::new(
760            file,
761            arg.line,
762            1,
763            format!("'{style}' w style: [kolor, blur] albo [kolor, blur, ox, oy]"),
764        )
765    };
766    let SeqValue::Array(items) = &arg.value else {
767        return Err(err());
768    };
769    if items.len() != 2 && items.len() != 4 {
770        return Err(err());
771    }
772    let color = match &items[0] {
773        SeqValue::Color(hex) => hex_color(file, arg.line, hex)?,
774        SeqValue::Theme(f) => theme_color(file, arg.line, theme, f)?,
775        _ => return Err(err()),
776    };
777    let num_at = |i: usize| match &items[i] {
778        SeqValue::Num(n) if n.is_finite() => Ok(*n),
779        _ => Err(err()),
780    };
781    let blur = num_at(1)?;
782    let mut spec = ShadowSpec::new(color, blur);
783    if items.len() == 4 {
784        spec = spec.offset(num_at(2)?, num_at(3)?);
785    }
786    Ok(spec)
787}
788
789fn apply_box_args(
790    file: &str,
791    theme: &Theme,
792    res: &Resolver<'_>,
793    decl: &StyleDecl,
794    v: &mut BoxStyle,
795) -> Result<(), SeqError> {
796    for arg in &decl.args {
797        match arg.name.as_str() {
798            "background" => *v = v.background(color(file, theme, arg)?),
799            "radius" => *v = v.radius(num(file, arg, res, None)?),
800            "border" => {
801                let (w, c) = border_pair(file, theme, arg)?;
802                *v = v.border(w, c);
803            }
804            "border_left" => {
805                let (w, c) = border_side(file, theme, "border_left", arg)?;
806                *v = v.border_left(w, c);
807            }
808            "border_right" => {
809                let (w, c) = border_side(file, theme, "border_right", arg)?;
810                *v = v.border_right(w, c);
811            }
812            "border_top" => {
813                let (w, c) = border_side(file, theme, "border_top", arg)?;
814                *v = v.border_top(w, c);
815            }
816            "border_bottom" => {
817                let (w, c) = border_side(file, theme, "border_bottom", arg)?;
818                *v = v.border_bottom(w, c);
819            }
820            "border_x" => {
821                let (w, c) = border_side(file, theme, "border_x", arg)?;
822                *v = v.border_x(w, c);
823            }
824            "border_y" => {
825                let (w, c) = border_side(file, theme, "border_y", arg)?;
826                *v = v.border_y(w, c);
827            }
828            "shadow" => *v = v.shadow(shadow_spec(file, theme, "shadow", arg)?),
829            "inset_shadow" => {
830                *v = v.inset_shadow(shadow_spec(file, theme, "inset_shadow", arg)?);
831            }
832            _ => {
833                return Err(SeqError::new(
834                    file,
835                    arg.line,
836                    1,
837                    format!("nieznane pole '{}' w style Box", arg.name),
838                ));
839            }
840        }
841    }
842    Ok(())
843}
844
845fn apply_button_args(
846    file: &str,
847    theme: &Theme,
848    res: &Resolver<'_>,
849    decl: &StyleDecl,
850    v: &mut ButtonStyle,
851) -> Result<(), SeqError> {
852    for arg in &decl.args {
853        match arg.name.as_str() {
854            "background" => *v = v.background(color(file, theme, arg)?),
855            "hover" => *v = v.hover(color(file, theme, arg)?),
856            "pressed" => *v = v.pressed(color(file, theme, arg)?),
857            "disabled" => *v = v.disabled(color(file, theme, arg)?),
858            "text" => *v = v.text(color(file, theme, arg)?),
859            "text_disabled" => *v = v.text_disabled(color(file, theme, arg)?),
860            "font_size" => *v = v.font_size(num(file, arg, res, None)?),
861            "font_weight" => {
862                let n = num(file, arg, res, None)?;
863                if !(n.is_finite() && n.fract() == 0.0 && (0.0..=1000.0).contains(&n)) {
864                    return Err(SeqError::new(
865                        file,
866                        arg.line,
867                        1,
868                        "'font_weight' in style is an integer 0..=1000",
869                    ));
870                }
871                #[allow(clippy::cast_possible_truncation)]
872                let w = n as u16;
873                *v = v.font_weight(w);
874            }
875            "radius" => *v = v.radius(num(file, arg, res, None)?),
876            "padding" => match &arg.value {
877                SeqValue::Array(items) if items.len() == 2 => {
878                    let (Some(x), Some(y)) = (array_num(&items[0]), array_num(&items[1])) else {
879                        return Err(SeqError::new(
880                            file,
881                            arg.line,
882                            1,
883                            "'padding' w style: [x, y]",
884                        ));
885                    };
886                    *v = v.padding_xy(x, y);
887                }
888                _ => {
889                    return Err(SeqError::new(
890                        file,
891                        arg.line,
892                        1,
893                        "'padding' w style: [x, y]",
894                    ));
895                }
896            },
897            "border" => {
898                let (w, c) = border_pair(file, theme, arg)?;
899                *v = v.border(w, c);
900            }
901            "border_width" => *v = v.border_width(num(file, arg, res, None)?),
902            "border_color" => *v = v.border_color(color(file, theme, arg)?),
903            _ => {
904                return Err(SeqError::new(
905                    file,
906                    arg.line,
907                    1,
908                    format!("nieznane pole '{}' w style Button", arg.name),
909                ));
910            }
911        }
912    }
913    Ok(())
914}
915
916fn array_num(value: &SeqValue) -> Option<f32> {
917    match value {
918        SeqValue::Num(n) if n.is_finite() => Some(*n),
919        _ => None,
920    }
921}
922
923fn apply_text_args(
924    file: &str,
925    theme: &Theme,
926    res: &Resolver<'_>,
927    decl: &StyleDecl,
928    v: &mut SeqTextStyle,
929) -> Result<(), SeqError> {
930    for arg in &decl.args {
931        match arg.name.as_str() {
932            "size" => v.size = Some(num(file, arg, res, None)?),
933            "color" => v.color = Some(color(file, theme, arg)?),
934            "bold" => v.weight = Some(TextWeight::Bold),
935            "semi" => v.weight = Some(TextWeight::Semi),
936            "medium" => v.weight = Some(TextWeight::Medium),
937            "normal" => v.weight = Some(TextWeight::Normal),
938            "light" => v.weight = Some(TextWeight::Light),
939            "black" => v.weight = Some(TextWeight::Black),
940            _ => {
941                return Err(SeqError::new(
942                    file,
943                    arg.line,
944                    1,
945                    format!("nieznane pole '{}' w style Text", arg.name),
946                ));
947            }
948        }
949    }
950    Ok(())
951}
952
953pub fn lower_window(
954    file: &str,
955    theme: &Theme,
956    styles: &BuiltStyles,
957    window: &SeqWindow,
958) -> Result<AnyView, SeqError> {
959    lower_window_res(file, theme, styles, window, &Resolver::Static)
960}
961
962pub fn lower_window_with(
963    file: &str,
964    theme: &Theme,
965    styles: &BuiltStyles,
966    window: &SeqWindow,
967    bindings: &HostBindings,
968) -> Result<AnyView, SeqError> {
969    lower_window_res(file, theme, styles, window, &Resolver::Host(bindings))
970}
971
972fn lower_window_res(
973    file: &str,
974    theme: &Theme,
975    styles: &BuiltStyles,
976    window: &SeqWindow,
977    res: &Resolver<'_>,
978) -> Result<AnyView, SeqError> {
979    let mut kids = Vec::new();
980    for n in &window.nodes {
981        kids.push(lower_node(file, theme, styles, n, res, None)?);
982    }
983    let mut col = Column::new().fill_size();
984    for k in kids {
985        col = col.child(k);
986    }
987    Ok(AnyView::new(col))
988}
989
990fn lower_node(
991    file: &str,
992    theme: &Theme,
993    styles: &BuiltStyles,
994    node: &SeqNode,
995    res: &Resolver<'_>,
996    row: Option<&SeqRowData>,
997) -> Result<AnyView, SeqError> {
998    for arg in &node.args {
999        if let SeqValue::Callback { action, .. } = &arg.value {
1000            match res {
1001                Resolver::Static => {
1002                    return Err(SeqError::new(
1003                        file,
1004                        arg.line,
1005                        1,
1006                        format!("callback '{action}' wymaga hosta Rust — inkrement 2"),
1007                    ));
1008                }
1009                Resolver::Host(_) => {}
1010            }
1011        }
1012        if let SeqValue::CallSeq { name, .. } = &arg.value {
1013            match res {
1014                Resolver::Static => {
1015                    return Err(SeqError::new(
1016                        file,
1017                        arg.line,
1018                        1,
1019                        format!("funkcja '{name}' (.seq) wymaga hosta Rust (lower_with)"),
1020                    ));
1021                }
1022                Resolver::Host(_) => {}
1023            }
1024        }
1025        if let SeqValue::Dynamic(name) = &arg.value {
1026            match res {
1027                Resolver::Static => {
1028                    return Err(SeqError::new(
1029                        file,
1030                        arg.line,
1031                        1,
1032                        format!("dynamic({name}) wymaga hosta Rust — inkrement 2"),
1033                    ));
1034                }
1035
1036                Resolver::Host(_) => {}
1037            }
1038        }
1039        if let SeqValue::Env(name) = &arg.value {
1040            match res {
1041                Resolver::Static => {
1042                    return Err(SeqError::new(
1043                        file,
1044                        arg.line,
1045                        1,
1046                        format!("env.{name} wymaga hosta Rust (HostBindings::env_..)"),
1047                    ));
1048                }
1049                Resolver::Host(_) => {}
1050            }
1051        }
1052        if let SeqValue::ItemField(field) = &arg.value {
1053            match res {
1054                Resolver::Static => {
1055                    return Err(SeqError::new(
1056                        file,
1057                        arg.line,
1058                        1,
1059                        format!("item.{field} wymaga hosta Rust — wiersze List (inkrement 3)"),
1060                    ));
1061                }
1062
1063                Resolver::Host(_) => {}
1064            }
1065        }
1066    }
1067    let built = match node.kind.as_str() {
1068        "Column" => {
1069            if has_click(node) {
1070                return maybe_indent(
1071                    lower_clickable_flex(file, theme, styles, node, res, row, Column::new())?,
1072                    file,
1073                    node,
1074                    res,
1075                    row,
1076                );
1077            }
1078            let v = apply_flex_box(file, theme, node, Column::new(), res, row)?;
1079            Ok(boxed_children(file, theme, styles, node, v, res, row)?)
1080        }
1081        "Row" => {
1082            if has_click(node) {
1083                return maybe_indent(
1084                    lower_clickable_flex(file, theme, styles, node, res, row, Row::new())?,
1085                    file,
1086                    node,
1087                    res,
1088                    row,
1089                );
1090            }
1091            let v = apply_flex_box(file, theme, node, Row::new(), res, row)?;
1092            Ok(boxed_children(file, theme, styles, node, v, res, row)?)
1093        }
1094        "Container" => {
1095            let mut v = Container::new();
1096            if let Some(s) = box_style_of(file, styles, node)? {
1097                v = v.box_style(s);
1098            }
1099            for arg in &node.args {
1100                match arg.name.as_str() {
1101                    "style" => {}
1102                    "width" => v = v.width(len(file, arg, res, row)?),
1103                    "height" => v = v.height(len(file, arg, res, row)?),
1104                    "padding" => v = apply_padding(v, file, arg, res, row)?,
1105                    "padding_x" => v = v.padding_x(num(file, arg, res, row)?),
1106                    "padding_y" => v = v.padding_y(num(file, arg, res, row)?),
1107                    "background" => v = v.background(color(file, theme, arg)?),
1108                    "flex" => v = v.flex(num(file, arg, res, row)?),
1109                    "radius" => v = v.radius(num(file, arg, res, row)?),
1110                    "border" => {
1111                        let (w, c) = border_pair(file, theme, arg)?;
1112                        v = v.border(w, c);
1113                    }
1114                    "border_left" => {
1115                        let (w, c) = border_side(file, theme, "border_left", arg)?;
1116                        v = v.border_left(w, c);
1117                    }
1118                    "border_right" => {
1119                        let (w, c) = border_side(file, theme, "border_right", arg)?;
1120                        v = v.border_right(w, c);
1121                    }
1122                    "border_top" => {
1123                        let (w, c) = border_side(file, theme, "border_top", arg)?;
1124                        v = v.border_top(w, c);
1125                    }
1126                    "border_bottom" => {
1127                        let (w, c) = border_side(file, theme, "border_bottom", arg)?;
1128                        v = v.border_bottom(w, c);
1129                    }
1130                    "border_x" => {
1131                        let (w, c) = border_side(file, theme, "border_x", arg)?;
1132                        v = v.border_x(w, c);
1133                    }
1134                    "border_y" => {
1135                        let (w, c) = border_side(file, theme, "border_y", arg)?;
1136                        v = v.border_y(w, c);
1137                    }
1138                    "min_width" => v = v.min_width(num(file, arg, res, row)?),
1139                    "max_width" => v = v.max_width(num(file, arg, res, row)?),
1140                    "min_height" => v = v.min_height(num(file, arg, res, row)?),
1141                    "max_height" => v = v.max_height(num(file, arg, res, row)?),
1142                    "margin" => v = apply_container_margin(v, file, arg, res, row)?,
1143                    "margin_x" => v = v.margin_x(num(file, arg, res, row)?),
1144                    "margin_y" => v = v.margin_y(num(file, arg, res, row)?),
1145                    "margin_left" => v = v.margin_left(num(file, arg, res, row)?),
1146                    "margin_right" => v = v.margin_right(num(file, arg, res, row)?),
1147                    "margin_top" => v = v.margin_top(num(file, arg, res, row)?),
1148                    "margin_bottom" => v = v.margin_bottom(num(file, arg, res, row)?),
1149                    "overflow" => v = v.overflow(overflow_val(file, arg)?),
1150                    "on_click" => match res {
1151                        Resolver::Static => {
1152                            return Err(SeqError::new(
1153                                file,
1154                                arg.line,
1155                                1,
1156                                "on_click wymaga hosta Rust",
1157                            ));
1158                        }
1159                        Resolver::Host(b) => match &arg.value {
1160                            SeqValue::Callback { .. } | SeqValue::CallSeq { .. } => {
1161                                let fire = wire_action(file, arg.line, b, &arg.value, row)?;
1162                                v = v.on_click(move || fire());
1163                            }
1164                            _ => {
1165                                return Err(SeqError::new(
1166                                    file,
1167                                    arg.line,
1168                                    1,
1169                                    "on_click: { akcja(args) }",
1170                                ));
1171                            }
1172                        },
1173                    },
1174                    "hover" => v = v.hover(color(file, theme, arg)?),
1175                    "hit" => v = v.hit_testable(flag(file, arg, res, row)?),
1176                    "indent" | "indent_x" | "indent_y" | "indent_left" | "indent_right"
1177                    | "indent_top" | "indent_bottom" => {}
1178                    _ => unreachable_checked(file, node, arg)?,
1179                }
1180            }
1181            if let Some(kid) = single_child(file, theme, styles, node, res, row)? {
1182                v = v.child(kid);
1183            }
1184            Ok(AnyView::new(v))
1185        }
1186        "Panel" => {
1187            if !node.children.is_empty() {
1188                return Err(SeqError::new(
1189                    file,
1190                    node.line,
1191                    1,
1192                    "Panel is a leaf (only background/frame, no children) - put content in Container or Column",
1193                ));
1194            }
1195            let mut v = Panel::new();
1196            if let Some(s) = box_style_of(file, styles, node)? {
1197                v = v.box_style(s);
1198            }
1199            for arg in &node.args {
1200                match arg.name.as_str() {
1201                    "style" => {}
1202                    "width" => v = v.width(len(file, arg, res, row)?),
1203                    "height" => v = v.height(len(file, arg, res, row)?),
1204                    "padding" => v = apply_padding(v, file, arg, res, row)?,
1205                    "padding_x" => v = v.padding_x(num(file, arg, res, row)?),
1206                    "padding_y" => v = v.padding_y(num(file, arg, res, row)?),
1207                    "background" => v = v.background(color(file, theme, arg)?),
1208                    "flex" => v = v.flex(num(file, arg, res, row)?),
1209                    "radius" => v = v.radius(num(file, arg, res, row)?),
1210                    "border" => {
1211                        let (w, c) = border_pair(file, theme, arg)?;
1212                        v = v.border(w, c);
1213                    }
1214                    "border_left" => {
1215                        let (w, c) = border_side(file, theme, "border_left", arg)?;
1216                        v = v.border_left(w, c);
1217                    }
1218                    "border_right" => {
1219                        let (w, c) = border_side(file, theme, "border_right", arg)?;
1220                        v = v.border_right(w, c);
1221                    }
1222                    "border_top" => {
1223                        let (w, c) = border_side(file, theme, "border_top", arg)?;
1224                        v = v.border_top(w, c);
1225                    }
1226                    "border_bottom" => {
1227                        let (w, c) = border_side(file, theme, "border_bottom", arg)?;
1228                        v = v.border_bottom(w, c);
1229                    }
1230                    "border_x" => {
1231                        let (w, c) = border_side(file, theme, "border_x", arg)?;
1232                        v = v.border_x(w, c);
1233                    }
1234                    "border_y" => {
1235                        let (w, c) = border_side(file, theme, "border_y", arg)?;
1236                        v = v.border_y(w, c);
1237                    }
1238                    "min_width" => v = v.min_width(num(file, arg, res, row)?),
1239                    "max_width" => v = v.max_width(num(file, arg, res, row)?),
1240                    "min_height" => v = v.min_height(num(file, arg, res, row)?),
1241                    "max_height" => v = v.max_height(num(file, arg, res, row)?),
1242                    "margin" => v = apply_panel_margin(v, file, arg, res, row)?,
1243                    "margin_x" => v = v.margin_x(num(file, arg, res, row)?),
1244                    "margin_y" => v = v.margin_y(num(file, arg, res, row)?),
1245                    "margin_left" => v = v.margin_left(num(file, arg, res, row)?),
1246                    "margin_right" => v = v.margin_right(num(file, arg, res, row)?),
1247                    "margin_top" => v = v.margin_top(num(file, arg, res, row)?),
1248                    "margin_bottom" => v = v.margin_bottom(num(file, arg, res, row)?),
1249                    "overflow" => v = v.overflow(overflow_val(file, arg)?),
1250                    "hit" => v = v.hit_testable(flag(file, arg, res, row)?),
1251                    "indent" | "indent_x" | "indent_y" | "indent_left" | "indent_right"
1252                    | "indent_top" | "indent_bottom" => {}
1253                    _ => unreachable_checked(file, node, arg)?,
1254                }
1255            }
1256            Ok(AnyView::new(v))
1257        }
1258        "ScrollView" => {
1259            let mut axis = ScrollAxis::Vertical;
1260            for arg in &node.args {
1261                match arg.name.as_str() {
1262                    "axis" => {
1263                        axis = match ident(file, arg)?.as_str() {
1264                            "vertical" => ScrollAxis::Vertical,
1265                            "horizontal" => ScrollAxis::Horizontal,
1266                            "both" => ScrollAxis::Both,
1267                            other => {
1268                                return Err(SeqError::new(
1269                                    file,
1270                                    arg.line,
1271                                    1,
1272                                    format!("axis to vertical/horizontal/both, nie '{other}'"),
1273                                ));
1274                            }
1275                        };
1276                    }
1277                    "width" | "height" | "padding" | "padding_x" | "padding_y" | "background" => {
1278                        return Err(SeqError::new(
1279                            file,
1280                            arg.line,
1281                            1,
1282                            format!(
1283                                "propka '{}' on ScrollView in increment 1 - wrap content in Column",
1284                                arg.name
1285                            ),
1286                        ));
1287                    }
1288                    "indent" | "indent_x" | "indent_y" | "indent_left" | "indent_right"
1289                    | "indent_top" | "indent_bottom" => {}
1290                    _ => unreachable_checked(file, node, arg)?,
1291                }
1292            }
1293            let inner = match node.children.len() {
1294                0 => AnyView::new(Column::new()),
1295                1 => lower_node(file, theme, styles, &node.children[0], res, row)?,
1296                _ => {
1297                    let mut col = Column::new();
1298                    for c in &node.children {
1299                        let kid = lower_node(file, theme, styles, c, res, row)?;
1300                        col = match child_flex_weight(file, res, row, c)? {
1301                            Some(w) => col.child_flex(kid, w),
1302                            None => col.child(kid),
1303                        };
1304                    }
1305                    AnyView::new(col)
1306                }
1307            };
1308            let mut v = ScrollView::new(inner);
1309            v = match axis {
1310                ScrollAxis::Vertical => v.vertical(),
1311                ScrollAxis::Horizontal => v.horizontal(),
1312                _ => v.axis(axis),
1313            };
1314            Ok(AnyView::new(v))
1315        }
1316        "Text" => lower_text(file, theme, styles, node, res, row),
1317        "Button" => lower_button(file, styles, node, res, row),
1318        "Spacer" => {
1319            let mut flex = 1.0;
1320            let mut hit: Option<bool> = None;
1321            let mut width: Option<Length> = None;
1322            let mut height: Option<Length> = None;
1323            let mut min_width: Option<f32> = None;
1324            let mut max_width: Option<f32> = None;
1325            let mut min_height: Option<f32> = None;
1326            let mut max_height: Option<f32> = None;
1327            for arg in &node.args {
1328                match arg.name.as_str() {
1329                    "flex" => flex = num(file, arg, res, row)?,
1330                    "width" => width = Some(len(file, arg, res, row)?),
1331                    "height" => height = Some(len(file, arg, res, row)?),
1332                    "min_width" => min_width = Some(num(file, arg, res, row)?),
1333                    "max_width" => max_width = Some(num(file, arg, res, row)?),
1334                    "min_height" => min_height = Some(num(file, arg, res, row)?),
1335                    "max_height" => max_height = Some(num(file, arg, res, row)?),
1336                    "hit" => hit = Some(flag(file, arg, res, row)?),
1337                    "indent" | "indent_x" | "indent_y" | "indent_left" | "indent_right"
1338                    | "indent_top" | "indent_bottom" => {}
1339                    _ => unreachable_checked(file, node, arg)?,
1340                }
1341            }
1342            let mut v = Spacer::flex(flex);
1343            if let Some(on) = hit {
1344                v = v.hit_testable(on);
1345            }
1346            if let Some(w) = width {
1347                v = v.width(w);
1348            }
1349            if let Some(h) = height {
1350                v = v.height(h);
1351            }
1352            if let Some(x) = min_width {
1353                v = v.min_width(x);
1354            }
1355            if let Some(x) = max_width {
1356                v = v.max_width(x);
1357            }
1358            if let Some(x) = min_height {
1359                v = v.min_height(x);
1360            }
1361            if let Some(x) = max_height {
1362                v = v.max_height(x);
1363            }
1364            Ok(AnyView::new(v))
1365        }
1366        "Icon" => {
1367            let name = opt_str(file, node, "name", res, row)?.ok_or_else(|| {
1368                SeqError::new(file, node.line, 1, "Icon wymaga name: \"...\"".to_string())
1369            })?;
1370            let mut v = Icon::new(name);
1371            for arg in &node.args {
1372                match arg.name.as_str() {
1373                    "name" => {}
1374                    "size" => v = v.size(num(file, arg, res, row)?),
1375                    "color" => v = v.color(color(file, theme, arg)?),
1376                    "width" => v = v.width(len(file, arg, res, row)?),
1377                    "height" => v = v.height(len(file, arg, res, row)?),
1378                    "min_width" => v = v.min_width(num(file, arg, res, row)?),
1379                    "max_width" => v = v.max_width(num(file, arg, res, row)?),
1380                    "min_height" => v = v.min_height(num(file, arg, res, row)?),
1381                    "max_height" => v = v.max_height(num(file, arg, res, row)?),
1382                    "margin" => v = apply_icon_margin(v, file, arg, res, row)?,
1383                    "margin_x" => v = v.margin_x(num(file, arg, res, row)?),
1384                    "margin_y" => v = v.margin_y(num(file, arg, res, row)?),
1385                    "margin_left" => v = v.margin_left(num(file, arg, res, row)?),
1386                    "margin_right" => v = v.margin_right(num(file, arg, res, row)?),
1387                    "margin_top" => v = v.margin_top(num(file, arg, res, row)?),
1388                    "margin_bottom" => v = v.margin_bottom(num(file, arg, res, row)?),
1389                    "flex" => {}
1390                    "hit" => v = v.hit_testable(flag(file, arg, res, row)?),
1391                    "indent" | "indent_x" | "indent_y" | "indent_left" | "indent_right"
1392                    | "indent_top" | "indent_bottom" => {}
1393                    _ => unreachable_checked(file, node, arg)?,
1394                }
1395            }
1396            Ok(AnyView::new(v))
1397        }
1398        "Image" => {
1399            let path = opt_str(file, node, "path", res, row)?
1400                .or_else(|| node.label.clone())
1401                .ok_or_else(|| {
1402                    SeqError::new(file, node.line, 1, "Image wymaga path: \"...\"".to_string())
1403                })?;
1404            let mut v = Image::file(path);
1405            for arg in &node.args {
1406                match arg.name.as_str() {
1407                    "path" => {}
1408                    "width" => v = v.width(len(file, arg, res, row)?),
1409                    "height" => v = v.height(len(file, arg, res, row)?),
1410                    "min_width" => v = v.min_width(num(file, arg, res, row)?),
1411                    "max_width" => v = v.max_width(num(file, arg, res, row)?),
1412                    "min_height" => v = v.min_height(num(file, arg, res, row)?),
1413                    "max_height" => v = v.max_height(num(file, arg, res, row)?),
1414                    "margin" => v = apply_image_margin(v, file, arg, res, row)?,
1415                    "margin_x" => v = v.margin_x(num(file, arg, res, row)?),
1416                    "margin_y" => v = v.margin_y(num(file, arg, res, row)?),
1417                    "margin_left" => v = v.margin_left(num(file, arg, res, row)?),
1418                    "margin_right" => v = v.margin_right(num(file, arg, res, row)?),
1419                    "margin_top" => v = v.margin_top(num(file, arg, res, row)?),
1420                    "margin_bottom" => v = v.margin_bottom(num(file, arg, res, row)?),
1421
1422                    "flex" => {}
1423                    "indent" | "indent_x" | "indent_y" | "indent_left" | "indent_right"
1424                    | "indent_top" | "indent_bottom" => {}
1425                    _ => unreachable_checked(file, node, arg)?,
1426                }
1427            }
1428            Ok(AnyView::new(v))
1429        }
1430        "List" => lower_list(file, theme, styles, node, res),
1431        "VirtualList" => lower_virtual_list(file, theme, styles, node, res, row),
1432        "SplitPanel" => lower_split(file, theme, styles, node, res, row),
1433        "Canvas" => lower_canvas(file, theme, node, res, row),
1434        "Checkbox" => lower_checkbox(file, theme, node, res, row),
1435        "Switch" => lower_switch(file, theme, node, res, row),
1436        "TextField" => lower_textfield(file, theme, node, res, row),
1437        "TextArea" => lower_textarea(file, theme, node, res, row),
1438        "Field" => lower_form_field(file, theme, node, res, row),
1439        "NumberField" => lower_number_field(file, theme, node, res, row),
1440        "DateField" => lower_date_field(file, theme, node, res, row),
1441        "MultiSelect" => lower_multi_select(file, theme, node, res, row),
1442        "Slider" => lower_slider(file, theme, node, res, row),
1443        "DragValue" => lower_drag_value(file, theme, node, res, row),
1444        "Dropdown" => lower_dropdown(file, theme, node, res, row),
1445        "Progress" => lower_progress(file, theme, node, res, row),
1446        "Stack" => lower_stack(file, theme, styles, node, res, row),
1447        "Grid" => lower_grid(file, theme, styles, node, res, row),
1448        "Wrap" => lower_wrap(file, theme, styles, node, res, row),
1449        "Masonry" => lower_masonry(file, theme, styles, node, res, row),
1450        "If" => lower_if(file, theme, styles, node, res, row),
1451        "Width" => lower_width(file, theme, styles, node, res, row),
1452        "Height" => lower_height(file, theme, styles, node, res, row),
1453        "Opacity" => lower_opacity(file, theme, styles, node, res, row),
1454        "Scale" => lower_scale(file, theme, styles, node, res, row),
1455        "Rotate" => lower_rotate(file, theme, styles, node, res, row),
1456        "Translate" => lower_translate(file, theme, styles, node, res, row),
1457        "Pulse" => lower_pulse(file, theme, styles, node, res, row),
1458        "Shake" => lower_shake(file, theme, styles, node, res, row),
1459        "Async" => lower_async(file, theme, styles, node, res, row),
1460        "Effect" => lower_effect(file, node, res, row),
1461        other => Err(SeqError::new(
1462            file,
1463            node.line,
1464            1,
1465            format!("nieznany komponent '{other}'"),
1466        )),
1467    };
1468    maybe_indent(built?, file, node, res, row)
1469}
1470
1471fn apply_flex_box<V: FlexBoxOps>(
1472    file: &str,
1473    theme: &Theme,
1474    node: &SeqNode,
1475    mut v: V,
1476    res: &Resolver<'_>,
1477    row: Option<&SeqRowData>,
1478) -> Result<V, SeqError> {
1479    for arg in &node.args {
1480        v = match arg.name.as_str() {
1481            "width" => v.set_width(len(file, arg, res, row)?),
1482            "height" => v.set_height(len(file, arg, res, row)?),
1483            "padding" => v.set_padding(padding_val(file, arg, res, row)?),
1484            "padding_x" => v.set_padding_x(num(file, arg, res, row)?),
1485            "padding_y" => v.set_padding_y(num(file, arg, res, row)?),
1486            "background" => v.set_background(color(file, theme, arg)?),
1487            "flex" => v.set_flex(num(file, arg, res, row)?),
1488            "gap" => v.set_gap(num(file, arg, res, row)?),
1489            "main_gap" => v.set_gap(num(file, arg, res, row)?),
1490            "cross_gap" => v.set_cross_gap(num(file, arg, res, row)?),
1491            "wrap" => v.set_wrap(flag(file, arg, res, row)?),
1492            "align_content" => v.set_align(align_val(file, arg)?),
1493            "align" => v.set_align(align_val(file, arg)?),
1494            "justify" => v.set_justify(justify_val(file, arg)?),
1495            "min_width" => v.set_min_width(num(file, arg, res, row)?),
1496            "max_width" => v.set_max_width(num(file, arg, res, row)?),
1497            "min_height" => v.set_min_height(num(file, arg, res, row)?),
1498            "max_height" => v.set_max_height(num(file, arg, res, row)?),
1499            "margin" => v.set_margin(margin_val(file, arg, res, row)?),
1500            "margin_x" => v.set_margin_x(num(file, arg, res, row)?),
1501            "margin_y" => v.set_margin_y(num(file, arg, res, row)?),
1502            "margin_left" => v.set_margin_left(num(file, arg, res, row)?),
1503            "margin_right" => v.set_margin_right(num(file, arg, res, row)?),
1504            "margin_top" => v.set_margin_top(num(file, arg, res, row)?),
1505            "margin_bottom" => v.set_margin_bottom(num(file, arg, res, row)?),
1506            "overflow" => v.set_overflow(overflow_val(file, arg)?),
1507            "hit" => v.set_hit_testable(flag(file, arg, res, row)?),
1508            "indent" | "indent_x" | "indent_y" | "indent_left" | "indent_right" | "indent_top"
1509            | "indent_bottom" => v,
1510            _ => return unreachable_checked(file, node, arg),
1511        };
1512    }
1513    Ok(v)
1514}
1515
1516fn lower_text(
1517    file: &str,
1518    theme: &Theme,
1519    styles: &BuiltStyles,
1520    node: &SeqNode,
1521    res: &Resolver<'_>,
1522    row: Option<&SeqRowData>,
1523) -> Result<AnyView, SeqError> {
1524    if let Some(t_arg) = find_arg(node, "t") {
1525        match res {
1526            Resolver::Static => {
1527                return Err(SeqError::new(
1528                    file,
1529                    t_arg.line,
1530                    1,
1531                    "t: wymaga hosta Rust (HostBindings::i18n)",
1532                ));
1533            }
1534            Resolver::Host(b) => match &t_arg.value {
1535                SeqValue::Str(key) => {
1536                    let i18n = b.get_i18n().ok_or_else(|| {
1537                        SeqError::new(file, t_arg.line, 1, "t: wymaga HostBindings::i18n()")
1538                    })?;
1539                    let key = key.clone();
1540                    let mut v = Text::with(move || i18n.t(&key));
1541                    v = apply_text_box(file, styles, &mut v, node, res, row, theme)?;
1542                    return Ok(AnyView::new(v));
1543                }
1544                _ => {
1545                    return Err(SeqError::new(
1546                        file,
1547                        t_arg.line,
1548                        1,
1549                        "t: is the key in quotation marks (t: \"save\")",
1550                    ));
1551                }
1552            },
1553        }
1554    }
1555
1556    for arg in &node.args {
1557        if arg.name == "dynamic" {
1558            return match res {
1559                Resolver::Static => Err(SeqError::new(
1560                    file,
1561                    arg.line,
1562                    1,
1563                    "dynamic: wymaga hosta Rust — inkrement 2",
1564                )),
1565                Resolver::Host(b) => match &arg.value {
1566                    SeqValue::Dynamic(name) | SeqValue::Ident(name) => {
1567                        let read = display_of(file, arg.line, b, name)?;
1568                        let mut v = Text::with(move || read());
1569                        v = apply_text_box(file, styles, &mut v, node, res, row, theme)?;
1570                        Ok(AnyView::new(v))
1571                    }
1572                    SeqValue::Env(name) => {
1573                        let read = display_env_of(file, arg.line, b, name)?;
1574                        let mut v = Text::with(move || read());
1575                        v = apply_text_box(file, styles, &mut v, node, res, row, theme)?;
1576                        Ok(AnyView::new(v))
1577                    }
1578                    SeqValue::ItemField(field) => {
1579                        let read = display_field(file, arg.line, row, field)?;
1580                        let mut v = Text::with(move || read());
1581                        v = apply_text_box(file, styles, &mut v, node, res, row, theme)?;
1582                        Ok(AnyView::new(v))
1583                    }
1584                    _ => Err(SeqError::new(
1585                        file,
1586                        arg.line,
1587                        1,
1588                        "dynamic: to nazwa propertu (dynamic: tytul)",
1589                    )),
1590                },
1591            };
1592        }
1593        if arg.name == "text"
1594            && let SeqValue::Dynamic(name) | SeqValue::Ident(name) = &arg.value
1595        {
1596            return match res {
1597                Resolver::Static => Err(SeqError::new(
1598                    file,
1599                    arg.line,
1600                    1,
1601                    "dynamic() wymaga hosta Rust — inkrement 2",
1602                )),
1603                Resolver::Host(b) => {
1604                    let read = display_of(file, arg.line, b, name)?;
1605                    let mut v = Text::with(move || read());
1606                    v = apply_text_box(file, styles, &mut v, node, res, row, theme)?;
1607                    Ok(AnyView::new(v))
1608                }
1609            };
1610        }
1611        if arg.name == "text"
1612            && let SeqValue::Env(name) = &arg.value
1613        {
1614            return match res {
1615                Resolver::Static => Err(SeqError::new(file, arg.line, 1, "env. wymaga hosta Rust")),
1616                Resolver::Host(b) => {
1617                    let read = display_env_of(file, arg.line, b, name)?;
1618                    let mut v = Text::with(move || read());
1619                    v = apply_text_box(file, styles, &mut v, node, res, row, theme)?;
1620                    Ok(AnyView::new(v))
1621                }
1622            };
1623        }
1624    }
1625    let mut v = Text::new(
1626        opt_str(file, node, "text", res, row)?
1627            .unwrap_or_else(|| node.label.clone().unwrap_or_default()),
1628    );
1629    if let Some(s) = text_style_of(file, styles, node)? {
1630        v = apply_seq_text_style(v, s);
1631    }
1632    for arg in &node.args {
1633        match arg.name.as_str() {
1634            "text" | "dynamic" | "t" => {}
1635            "style" => {}
1636            "size" => v = v.size(num(file, arg, res, row)?),
1637            "color" => v = v.color(color(file, theme, arg)?),
1638            "bold" => {
1639                if flag(file, arg, res, row)? {
1640                    v = v.bold();
1641                }
1642            }
1643            "semi" => {
1644                if flag(file, arg, res, row)? {
1645                    v = v.semibold();
1646                }
1647            }
1648            "medium" => {
1649                if flag(file, arg, res, row)? {
1650                    v = v.medium();
1651                }
1652            }
1653            "normal" => {
1654                if flag(file, arg, res, row)? {
1655                    v = v.normal();
1656                }
1657            }
1658            "light" => {
1659                if flag(file, arg, res, row)? {
1660                    v = v.light();
1661                }
1662            }
1663            "black" => {
1664                if flag(file, arg, res, row)? {
1665                    v = v.black();
1666                }
1667            }
1668            "truncate" => v = v.truncate(num(file, arg, res, row)?),
1669
1670            "wrap" => {
1671                let _ = flag(file, arg, res, row)?;
1672            }
1673
1674            "flex" => v = v.flex(num(file, arg, res, row)?),
1675            "width" => v = v.width(len(file, arg, res, row)?),
1676            "height" => v = v.height(len(file, arg, res, row)?),
1677            "min_width" => v = v.min_width(num(file, arg, res, row)?),
1678            "max_width" => v = v.max_width(num(file, arg, res, row)?),
1679            "min_height" => v = v.min_height(num(file, arg, res, row)?),
1680            "max_height" => v = v.max_height(num(file, arg, res, row)?),
1681            "margin" => v = apply_text_margin(v, file, arg, res, row)?,
1682            "margin_x" => v = v.margin_x(num(file, arg, res, row)?),
1683            "margin_y" => v = v.margin_y(num(file, arg, res, row)?),
1684            "margin_left" => v = v.margin_left(num(file, arg, res, row)?),
1685            "margin_right" => v = v.margin_right(num(file, arg, res, row)?),
1686            "margin_top" => v = v.margin_top(num(file, arg, res, row)?),
1687            "margin_bottom" => v = v.margin_bottom(num(file, arg, res, row)?),
1688            "hit" => v = v.hit_testable(flag(file, arg, res, row)?),
1689            "indent" | "indent_x" | "indent_y" | "indent_left" | "indent_right" | "indent_top"
1690            | "indent_bottom" => {}
1691            _ => unreachable_checked(file, node, arg)?,
1692        }
1693    }
1694    Ok(AnyView::new(v))
1695}
1696
1697fn apply_text_box(
1698    file: &str,
1699    styles: &BuiltStyles,
1700    v: &mut Text,
1701    node: &SeqNode,
1702    res: &Resolver<'_>,
1703    row: Option<&SeqRowData>,
1704    theme: &Theme,
1705) -> Result<Text, SeqError> {
1706    let mut out = std::mem::replace(v, Text::new(""));
1707    if let Some(s) = text_style_of(file, styles, node)? {
1708        out = apply_seq_text_style(out, s);
1709    }
1710    for arg in &node.args {
1711        match arg.name.as_str() {
1712            "text" | "dynamic" | "t" => {}
1713            "style" => {}
1714            "size" => out = out.size(num(file, arg, res, row)?),
1715            "color" => out = out.color(color(file, theme, arg)?),
1716            "bold" => {
1717                if flag(file, arg, res, row)? {
1718                    out = out.bold();
1719                }
1720            }
1721            "semi" => {
1722                if flag(file, arg, res, row)? {
1723                    out = out.semibold();
1724                }
1725            }
1726            "medium" => {
1727                if flag(file, arg, res, row)? {
1728                    out = out.medium();
1729                }
1730            }
1731            "normal" => {
1732                if flag(file, arg, res, row)? {
1733                    out = out.normal();
1734                }
1735            }
1736            "light" => {
1737                if flag(file, arg, res, row)? {
1738                    out = out.light();
1739                }
1740            }
1741            "black" => {
1742                if flag(file, arg, res, row)? {
1743                    out = out.black();
1744                }
1745            }
1746            "truncate" => out = out.truncate(num(file, arg, res, row)?),
1747
1748            "wrap" => {
1749                let _ = flag(file, arg, res, row)?;
1750            }
1751
1752            "flex" => out = out.flex(num(file, arg, res, row)?),
1753            "width" => out = out.width(len(file, arg, res, row)?),
1754            "height" => out = out.height(len(file, arg, res, row)?),
1755            "min_width" => out = out.min_width(num(file, arg, res, row)?),
1756            "max_width" => out = out.max_width(num(file, arg, res, row)?),
1757            "min_height" => out = out.min_height(num(file, arg, res, row)?),
1758            "max_height" => out = out.max_height(num(file, arg, res, row)?),
1759            "margin" => out = apply_text_margin(out, file, arg, res, row)?,
1760            "margin_x" => out = out.margin_x(num(file, arg, res, row)?),
1761            "margin_y" => out = out.margin_y(num(file, arg, res, row)?),
1762            "margin_left" => out = out.margin_left(num(file, arg, res, row)?),
1763            "margin_right" => out = out.margin_right(num(file, arg, res, row)?),
1764            "margin_top" => out = out.margin_top(num(file, arg, res, row)?),
1765            "margin_bottom" => out = out.margin_bottom(num(file, arg, res, row)?),
1766            "hit" => out = out.hit_testable(flag(file, arg, res, row)?),
1767            "indent" | "indent_x" | "indent_y" | "indent_left" | "indent_right" | "indent_top"
1768            | "indent_bottom" => {}
1769            _ => unreachable_checked(file, node, arg)?,
1770        }
1771    }
1772    Ok(out)
1773}
1774
1775fn apply_button_margin(
1776    v: Button,
1777    file: &str,
1778    arg: &crate::ir::SeqArg,
1779    res: &Resolver<'_>,
1780    row: Option<&SeqRowData>,
1781) -> Result<Button, SeqError> {
1782    match margin_val(file, arg, res, row)? {
1783        MarginVal::All(n) => Ok(v.margin(n)),
1784        MarginVal::Xy(x, y) => Ok(v.margin_xy(x, y)),
1785    }
1786}
1787
1788fn button_px(
1789    file: &str,
1790    arg: &crate::ir::SeqArg,
1791    res: &Resolver<'_>,
1792    row: Option<&SeqRowData>,
1793) -> Result<Option<f32>, SeqError> {
1794    match &arg.value {
1795        SeqValue::Length(LengthVal::Px(n)) => Ok(Some(*n)),
1796        SeqValue::Length(LengthVal::Fit) => Ok(None),
1797        SeqValue::Ident(prop) | SeqValue::Dynamic(prop) => match res {
1798            Resolver::Static => Err(SeqError::new(
1799                file,
1800                arg.line,
1801                1,
1802                format!("'{}' to px()/fit", arg.name),
1803            )),
1804            Resolver::Host(b) => Ok(Some(prop_num_env(file, arg.line, b, prop)?)),
1805        },
1806        SeqValue::Env(name) => match res {
1807            Resolver::Static => Err(SeqError::new(
1808                file,
1809                arg.line,
1810                1,
1811                format!("'{}' to px()/fit", arg.name),
1812            )),
1813            Resolver::Host(b) => Ok(Some(env_num(file, arg.line, b, name)?)),
1814        },
1815        SeqValue::ItemField(field) => match res {
1816            Resolver::Static => Err(SeqError::new(
1817                file,
1818                arg.line,
1819                1,
1820                format!("'{}' to px()/fit", arg.name),
1821            )),
1822            Resolver::Host(_) => Ok(Some(row_num(file, arg.line, row, field)?)),
1823        },
1824        _ => Err(SeqError::new(
1825            file,
1826            arg.line,
1827            1,
1828            format!("'{}' is px()/fit (stretch by flex: in parent)", arg.name),
1829        )),
1830    }
1831}
1832
1833fn apply_icon_margin(
1834    v: Icon,
1835    file: &str,
1836    arg: &crate::ir::SeqArg,
1837    res: &Resolver<'_>,
1838    row: Option<&SeqRowData>,
1839) -> Result<Icon, SeqError> {
1840    match margin_val(file, arg, res, row)? {
1841        MarginVal::All(n) => Ok(v.margin(n)),
1842        MarginVal::Xy(x, y) => Ok(v.margin_xy(x, y)),
1843    }
1844}
1845
1846fn apply_image_margin(
1847    v: Image,
1848    file: &str,
1849    arg: &crate::ir::SeqArg,
1850    res: &Resolver<'_>,
1851    row: Option<&SeqRowData>,
1852) -> Result<Image, SeqError> {
1853    match margin_val(file, arg, res, row)? {
1854        MarginVal::All(n) => Ok(v.margin(n)),
1855        MarginVal::Xy(x, y) => Ok(v.margin_xy(x, y)),
1856    }
1857}
1858
1859fn apply_text_margin(
1860    v: Text,
1861    file: &str,
1862    arg: &crate::ir::SeqArg,
1863    res: &Resolver<'_>,
1864    row: Option<&SeqRowData>,
1865) -> Result<Text, SeqError> {
1866    match margin_val(file, arg, res, row)? {
1867        MarginVal::All(n) => Ok(v.margin(n)),
1868        MarginVal::Xy(x, y) => Ok(v.margin_xy(x, y)),
1869    }
1870}
1871
1872fn lower_clip(
1873    file: &str,
1874    theme: &Theme,
1875    node: &SeqNode,
1876    res: &Resolver<'_>,
1877    row: Option<&SeqRowData>,
1878) -> Result<DrawLayer, SeqError> {
1879    let r = rect_src_of(file, res, row, node)?;
1880    let radius = opt_num(file, res, row, node, "radius")?;
1881    let has_radius = find_arg(node, "radius").is_some();
1882    let visible = match find_arg(node, "visible") {
1883        None => BoolSrc::Lit(true),
1884        Some(arg) => dyn_bool(file, arg.line, res, row, &arg.value, "visible")?,
1885    };
1886    let mut layers = Vec::new();
1887    let touch_list = vec![r.touch_rc(), radius.touch_rc(), visible.touch_rc()];
1888    for c in &node.children {
1889        layers.push(lower_layer(file, theme, c, res, row)?);
1890    }
1891    Ok(DrawLayer::Clip {
1892        rect: r,
1893        radius,
1894        has_radius,
1895        visible,
1896        touch_extra: touches(&touch_list),
1897        layers,
1898    })
1899}
1900
1901fn lower_button(
1902    file: &str,
1903    styles: &BuiltStyles,
1904    node: &SeqNode,
1905    res: &Resolver<'_>,
1906    row: Option<&SeqRowData>,
1907) -> Result<AnyView, SeqError> {
1908    let mut v = Button::new(node.label.clone().unwrap_or_default());
1909    if let Some(s) = button_style_of(file, styles, node)? {
1910        v = v.style(s);
1911    }
1912    for arg in &node.args {
1913        match arg.name.as_str() {
1914            "style" => {}
1915            "enabled" => v = v.disabled(!flag(file, arg, res, row)?),
1916            "font_size" => v = v.font_size(num(file, arg, res, row)?),
1917            "width" => {
1918                if let Some(px) = button_px(file, arg, res, row)? {
1919                    v = v.width(px);
1920                }
1921            }
1922            "height" => {
1923                if let Some(px) = button_px(file, arg, res, row)? {
1924                    v = v.height(px);
1925                }
1926            }
1927            "min_width" => v = v.min_width(num(file, arg, res, row)?),
1928            "max_width" => v = v.max_width(num(file, arg, res, row)?),
1929            "min_height" => v = v.min_height(num(file, arg, res, row)?),
1930            "max_height" => v = v.max_height(num(file, arg, res, row)?),
1931            "margin" => v = apply_button_margin(v, file, arg, res, row)?,
1932            "margin_x" => v = v.margin_x(num(file, arg, res, row)?),
1933            "margin_y" => v = v.margin_y(num(file, arg, res, row)?),
1934            "margin_left" => v = v.margin_left(num(file, arg, res, row)?),
1935            "margin_right" => v = v.margin_right(num(file, arg, res, row)?),
1936            "margin_top" => v = v.margin_top(num(file, arg, res, row)?),
1937            "margin_bottom" => v = v.margin_bottom(num(file, arg, res, row)?),
1938            "flex" => {}
1939            "on_click" => match res {
1940                Resolver::Static => {
1941                    return Err(SeqError::new(
1942                        file,
1943                        arg.line,
1944                        1,
1945                        "on_click wymaga hosta Rust — inkrement 2",
1946                    ));
1947                }
1948                Resolver::Host(b) => match &arg.value {
1949                    SeqValue::Callback { .. } | SeqValue::CallSeq { .. } => {
1950                        let fire = wire_action(file, arg.line, b, &arg.value, row)?;
1951                        v = v.on_click(move || fire());
1952                    }
1953                    _ => {
1954                        return Err(SeqError::new(
1955                            file,
1956                            arg.line,
1957                            1,
1958                            "on_click: { akcja(args) }",
1959                        ));
1960                    }
1961                },
1962            },
1963            "indent" | "indent_x" | "indent_y" | "indent_left" | "indent_right" | "indent_top"
1964            | "indent_bottom" => {}
1965            _ => unreachable_checked(file, node, arg)?,
1966        }
1967    }
1968    Ok(AnyView::new(v))
1969}
1970
1971fn without_placement(node: &SeqNode) -> SeqNode {
1972    SeqNode {
1973        kind: node.kind.clone(),
1974        line: node.line,
1975        label: node.label.clone(),
1976        args: node
1977            .args
1978            .iter()
1979            .filter(|a| {
1980                !matches!(
1981                    a.name.as_str(),
1982                    "at" | "offset" | "z" | "col" | "row" | "col_span" | "row_span"
1983                )
1984            })
1985            .cloned()
1986            .collect(),
1987        children: node.children.clone(),
1988    }
1989}
1990
1991fn stack_pos_of(file: &str, node: &SeqNode) -> Result<StackPos, SeqError> {
1992    match find_arg(node, "at") {
1993        None => Ok(StackPos::default()),
1994        Some(arg) => match &arg.value {
1995            SeqValue::Ident(name) => match name.as_str() {
1996                "top" => Ok(StackPos::top()),
1997                "bottom" => Ok(StackPos::bottom()),
1998                "left" => Ok(StackPos::left()),
1999                "right" => Ok(StackPos::right()),
2000                "top_left" => Ok(StackPos::top_left()),
2001                "top_right" => Ok(StackPos::top_right()),
2002                "bottom_left" => Ok(StackPos::bottom_left()),
2003                "bottom_right" => Ok(StackPos::bottom_right()),
2004                "fill" => Ok(StackPos::fill()),
2005                "center" => Ok(StackPos::default()),
2006                other => Err(SeqError::new(
2007                    file,
2008                    arg.line,
2009                    1,
2010                    format!(
2011                        "at to top/bottom/left/right/top_left/top_right/bottom_left/bottom_right/fill/center, nie '{other}'"
2012                    ),
2013                )),
2014            },
2015            _ => Err(SeqError::new(
2016                file,
2017                arg.line,
2018                1,
2019                "at: to nazwa (at: top_left)",
2020            )),
2021        },
2022    }
2023}
2024
2025fn stack_offset_of(
2026    file: &str,
2027    res: &Resolver<'_>,
2028    row: Option<&SeqRowData>,
2029    node: &SeqNode,
2030) -> Result<(f32, f32), SeqError> {
2031    match find_arg(node, "offset") {
2032        None => Ok((0.0, 0.0)),
2033        Some(arg) => match &arg.value {
2034            SeqValue::Tuple(items) if items.len() == 2 => {
2035                let x = dyn_f32(file, arg.line, res, row, &items[0], "offset.dx")?;
2036                let y = dyn_f32(file, arg.line, res, row, &items[1], "offset.dy")?;
2037                Ok((x.val(), y.val()))
2038            }
2039            _ => Err(SeqError::new(
2040                file,
2041                arg.line,
2042                1,
2043                "offset: to krotka (dx, dy)",
2044            )),
2045        },
2046    }
2047}
2048
2049fn stack_z_of(
2050    file: &str,
2051    res: &Resolver<'_>,
2052    _row: Option<&SeqRowData>,
2053    node: &SeqNode,
2054) -> Result<i32, SeqError> {
2055    match find_arg(node, "z") {
2056        None => Ok(0),
2057        Some(arg) => match &arg.value {
2058            SeqValue::Num(n) => {
2059                if n.is_finite() {
2060                    #[allow(clippy::cast_possible_truncation)]
2061                    Ok(*n as i32)
2062                } else {
2063                    Err(SeqError::new(file, arg.line, 1, "'z' must be finished"))
2064                }
2065            }
2066            SeqValue::Ident(prop) | SeqValue::Dynamic(prop) => match res {
2067                Resolver::Static => Err(SeqError::new(file, arg.line, 1, "'z' to liczba")),
2068                Resolver::Host(b) => {
2069                    let v = prop_num_env(file, arg.line, b, prop)?;
2070                    #[allow(clippy::cast_possible_truncation)]
2071                    Ok(v as i32)
2072                }
2073            },
2074            _ => Err(SeqError::new(file, arg.line, 1, "'z' is an integer")),
2075        },
2076    }
2077}
2078
2079fn lower_stack(
2080    file: &str,
2081    theme: &Theme,
2082    styles: &BuiltStyles,
2083    node: &SeqNode,
2084    res: &Resolver<'_>,
2085    row: Option<&SeqRowData>,
2086) -> Result<AnyView, SeqError> {
2087    let mut v = Stack::new();
2088    for arg in &node.args {
2089        match arg.name.as_str() {
2090            "width" => v = v.width(len(file, arg, res, row)?),
2091            "height" => v = v.height(len(file, arg, res, row)?),
2092            "padding" => match padding_val(file, arg, res, row)? {
2093                PaddingVal::All(n) => v = v.padding(n),
2094                PaddingVal::Xy(x, y) => v = v.padding_xy(x, y),
2095            },
2096            "padding_x" => v = v.padding_x(num(file, arg, res, row)?),
2097            "padding_y" => v = v.padding_y(num(file, arg, res, row)?),
2098            "background" => v = v.background(color(file, theme, arg)?),
2099            "flex" => v = v.flex(num(file, arg, res, row)?),
2100            "radius" => v = v.radius(num(file, arg, res, row)?),
2101            "border" => {
2102                let (w, c) = border_pair(file, theme, arg)?;
2103                v = v.border(w, c);
2104            }
2105            "min_width" => v = v.min_width(num(file, arg, res, row)?),
2106            "max_width" => v = v.max_width(num(file, arg, res, row)?),
2107            "min_height" => v = v.min_height(num(file, arg, res, row)?),
2108            "max_height" => v = v.max_height(num(file, arg, res, row)?),
2109            "margin" => match margin_val(file, arg, res, row)? {
2110                MarginVal::All(n) => v = v.margin(n),
2111                MarginVal::Xy(x, y) => v = v.margin_xy(x, y),
2112            },
2113            "margin_x" => v = v.margin_x(num(file, arg, res, row)?),
2114            "margin_y" => v = v.margin_y(num(file, arg, res, row)?),
2115            "margin_left" => v = v.margin_left(num(file, arg, res, row)?),
2116            "margin_right" => v = v.margin_right(num(file, arg, res, row)?),
2117            "margin_top" => v = v.margin_top(num(file, arg, res, row)?),
2118            "margin_bottom" => v = v.margin_bottom(num(file, arg, res, row)?),
2119            "overflow" => v = v.overflow(overflow_val(file, arg)?),
2120            "hit" => v = v.hit_testable(flag(file, arg, res, row)?),
2121            "indent" | "indent_x" | "indent_y" | "indent_left" | "indent_right" | "indent_top"
2122            | "indent_bottom" => {}
2123            _ => unreachable_checked(file, node, arg)?,
2124        }
2125    }
2126    for c in &node.children {
2127        let pos = stack_pos_of(file, c)?;
2128        let (dx, dy) = stack_offset_of(file, res, row, c)?;
2129        let z = stack_z_of(file, res, row, c)?;
2130        let pos = pos.offset(dx, dy).z(z);
2131        let clean = without_placement(c);
2132        let kid = lower_node(file, theme, styles, &clean, res, row)?;
2133
2134        let _ = child_flex_weight(file, res, row, c)?;
2135        v = v.child_at(kid, pos);
2136    }
2137    Ok(AnyView::new(v))
2138}
2139
2140fn track_of(
2141    file: &str,
2142    line: usize,
2143    res: &Resolver<'_>,
2144    row: Option<&SeqRowData>,
2145    value: &SeqValue,
2146) -> Result<Track, SeqError> {
2147    match value {
2148        SeqValue::Length(l) => match *l {
2149            LengthVal::Fit => Ok(Track::Fit),
2150            LengthVal::Px(n) => Ok(Track::Px(n)),
2151            LengthVal::Fill(w) => Ok(Track::Fill(w)),
2152            LengthVal::Percent(p) => Ok(Track::Percent(p)),
2153        },
2154        SeqValue::Num(n) => Ok(Track::Px(*n)),
2155        SeqValue::Ident(prop) | SeqValue::Dynamic(prop) => match res {
2156            Resolver::Static => Err(SeqError::new(file, line, 1, "tor to px()/fill()/fit")),
2157            Resolver::Host(b) => Ok(Track::Px(prop_num_env(file, line, b, prop)?)),
2158        },
2159        SeqValue::Env(name) => match res {
2160            Resolver::Static => Err(SeqError::new(file, line, 1, "tor to px()/fill()/fit")),
2161            Resolver::Host(b) => Ok(Track::Px(env_num(file, line, b, name)?)),
2162        },
2163        SeqValue::ItemField(field) => match res {
2164            Resolver::Static => Err(SeqError::new(file, line, 1, "tor to px()/fill()/fit")),
2165            Resolver::Host(_) => Ok(Track::Px(row_num(file, line, row, field)?)),
2166        },
2167        _ => Err(SeqError::new(
2168            file,
2169            line,
2170            1,
2171            "tor to px(n)/fill(w)/fit/percent(n)",
2172        )),
2173    }
2174}
2175
2176fn tracks_of(
2177    file: &str,
2178    res: &Resolver<'_>,
2179    row: Option<&SeqRowData>,
2180    node: &SeqNode,
2181    name: &str,
2182) -> Result<Vec<Track>, SeqError> {
2183    match find_arg(node, name) {
2184        None => Ok(vec![Track::Fill(1.0)]),
2185        Some(arg) => match &arg.value {
2186            SeqValue::Array(items) if !items.is_empty() => items
2187                .iter()
2188                .map(|it| track_of(file, arg.line, res, row, it))
2189                .collect(),
2190            _ => Err(SeqError::new(
2191                file,
2192                arg.line,
2193                1,
2194                format!("'{name}' to [px(100), fill(1), ...] (min. 1 tor)"),
2195            )),
2196        },
2197    }
2198}
2199
2200fn grid_int(
2201    file: &str,
2202    res: &Resolver<'_>,
2203    _row: Option<&SeqRowData>,
2204    node: &SeqNode,
2205    name: &str,
2206) -> Result<Option<usize>, SeqError> {
2207    match find_arg(node, name) {
2208        None => Ok(None),
2209        Some(arg) => match &arg.value {
2210            SeqValue::Num(n) => {
2211                if n.is_finite() && *n >= 0.0 {
2212                    #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
2213                    Ok(Some(*n as usize))
2214                } else {
2215                    Err(SeqError::new(
2216                        file,
2217                        arg.line,
2218                        1,
2219                        format!("'{name}' to liczba >= 0"),
2220                    ))
2221                }
2222            }
2223            SeqValue::Ident(prop) | SeqValue::Dynamic(prop) => match res {
2224                Resolver::Static => Err(SeqError::new(
2225                    file,
2226                    arg.line,
2227                    1,
2228                    format!("'{name}' to liczba"),
2229                )),
2230                Resolver::Host(b) => {
2231                    let v = prop_num_env(file, arg.line, b, prop)?;
2232                    if v.is_finite() && v >= 0.0 {
2233                        #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
2234                        Ok(Some(v as usize))
2235                    } else {
2236                        Err(SeqError::new(
2237                            file,
2238                            arg.line,
2239                            1,
2240                            format!("'{name}' to liczba >= 0"),
2241                        ))
2242                    }
2243                }
2244            },
2245            _ => Err(SeqError::new(
2246                file,
2247                arg.line,
2248                1,
2249                format!("'{name}' to liczba"),
2250            )),
2251        },
2252    }
2253}
2254
2255fn lower_grid(
2256    file: &str,
2257    theme: &Theme,
2258    styles: &BuiltStyles,
2259    node: &SeqNode,
2260    res: &Resolver<'_>,
2261    row: Option<&SeqRowData>,
2262) -> Result<AnyView, SeqError> {
2263    let columns = tracks_of(file, res, row, node, "columns")?;
2264    let mut v = Grid::new(columns);
2265    for arg in &node.args {
2266        match arg.name.as_str() {
2267            "columns" => {}
2268            "rows" => {
2269                v = match &arg.value {
2270                    SeqValue::Array(items) if !items.is_empty() => {
2271                        let tracks: Vec<Track> = items
2272                            .iter()
2273                            .map(|it| track_of(file, arg.line, res, row, it))
2274                            .collect::<Result<_, _>>()?;
2275                        v.rows(tracks)
2276                    }
2277                    _ => {
2278                        return Err(SeqError::new(
2279                            file,
2280                            arg.line,
2281                            1,
2282                            "'rows' to [px(40), fill(1), ...]",
2283                        ));
2284                    }
2285                };
2286            }
2287            "width" => v = v.width(len(file, arg, res, row)?),
2288            "height" => v = v.height(len(file, arg, res, row)?),
2289            "padding" => match padding_val(file, arg, res, row)? {
2290                PaddingVal::All(n) => v = v.padding(n),
2291                PaddingVal::Xy(x, y) => v = v.padding_xy(x, y),
2292            },
2293            "padding_x" => v = v.padding_x(num(file, arg, res, row)?),
2294            "padding_y" => v = v.padding_y(num(file, arg, res, row)?),
2295            "background" => v = v.background(color(file, theme, arg)?),
2296            "flex" => v = v.flex(num(file, arg, res, row)?),
2297            "radius" => v = v.radius(num(file, arg, res, row)?),
2298            "border" => {
2299                let (w, c) = border_pair(file, theme, arg)?;
2300                v = v.border(w, c);
2301            }
2302            "gap" => v = v.gap(num(file, arg, res, row)?),
2303            "column_gap" => v = v.column_gap(num(file, arg, res, row)?),
2304            "row_gap" => v = v.row_gap(num(file, arg, res, row)?),
2305            "auto_flow" => {
2306                v = match ident(file, arg)?.as_str() {
2307                    "row" => v.auto_flow(GridFlow::Row),
2308                    "column" => v.column_flow(),
2309                    other => {
2310                        return Err(SeqError::new(
2311                            file,
2312                            arg.line,
2313                            1,
2314                            format!("auto_flow to row/column, nie '{other}'"),
2315                        ));
2316                    }
2317                };
2318            }
2319            "align" => v = v.align_items(align_val(file, arg)?),
2320            "justify" => v = v.justify_items(align_val(file, arg)?),
2321            "min_width" => v = v.min_width(num(file, arg, res, row)?),
2322            "max_width" => v = v.max_width(num(file, arg, res, row)?),
2323            "min_height" => v = v.min_height(num(file, arg, res, row)?),
2324            "max_height" => v = v.max_height(num(file, arg, res, row)?),
2325            "margin" => match margin_val(file, arg, res, row)? {
2326                MarginVal::All(n) => v = v.margin(n),
2327                MarginVal::Xy(x, y) => v = v.margin_xy(x, y),
2328            },
2329            "margin_x" => v = v.margin_x(num(file, arg, res, row)?),
2330            "margin_y" => v = v.margin_y(num(file, arg, res, row)?),
2331            "margin_left" => v = v.margin_left(num(file, arg, res, row)?),
2332            "margin_right" => v = v.margin_right(num(file, arg, res, row)?),
2333            "margin_top" => v = v.margin_top(num(file, arg, res, row)?),
2334            "margin_bottom" => v = v.margin_bottom(num(file, arg, res, row)?),
2335            "overflow" => v = v.overflow(overflow_val(file, arg)?),
2336            "indent" | "indent_x" | "indent_y" | "indent_left" | "indent_right" | "indent_top"
2337            | "indent_bottom" => {}
2338            _ => unreachable_checked(file, node, arg)?,
2339        }
2340    }
2341    for c in &node.children {
2342        let clean = without_placement(c);
2343        let kid = lower_node(file, theme, styles, &clean, res, row)?;
2344        let _ = child_flex_weight(file, res, row, c)?;
2345        let col = grid_int(file, res, row, c, "col")?;
2346        let row_i = grid_int(file, res, row, c, "row")?;
2347        let col_span = grid_int(file, res, row, c, "col_span")?.unwrap_or(1).max(1);
2348        let row_span = grid_int(file, res, row, c, "row_span")?.unwrap_or(1).max(1);
2349        match (col, row_i) {
2350            (Some(cc), Some(rr)) => {
2351                v = v.child_at(
2352                    kid,
2353                    GridPlacement::new().at(cc, rr).span(col_span, row_span),
2354                );
2355            }
2356            (Some(cc), None) => {
2357                v = v.child_at(
2358                    kid,
2359                    GridPlacement::new().column(cc).span(col_span, row_span),
2360                );
2361            }
2362            (None, Some(rr)) => {
2363                v = v.child_at(kid, GridPlacement::new().row(rr).span(col_span, row_span));
2364            }
2365            (None, None) if col_span > 1 || row_span > 1 => {
2366                v = v.child_at(kid, GridPlacement::new().span(col_span, row_span));
2367            }
2368            (None, None) => {
2369                v = v.child(kid);
2370            }
2371        }
2372    }
2373    Ok(AnyView::new(v))
2374}
2375
2376fn lower_wrap(
2377    file: &str,
2378    theme: &Theme,
2379    styles: &BuiltStyles,
2380    node: &SeqNode,
2381    res: &Resolver<'_>,
2382    row: Option<&SeqRowData>,
2383) -> Result<AnyView, SeqError> {
2384    let mut v = Wrap::new();
2385    for arg in &node.args {
2386        match arg.name.as_str() {
2387            "width" => v = v.width(len(file, arg, res, row)?),
2388            "height" => v = v.height(len(file, arg, res, row)?),
2389            "padding" => match padding_val(file, arg, res, row)? {
2390                PaddingVal::All(n) => v = v.padding(n),
2391                PaddingVal::Xy(x, y) => v = v.padding_xy(x, y),
2392            },
2393            "padding_x" => v = v.padding_x(num(file, arg, res, row)?),
2394            "padding_y" => v = v.padding_y(num(file, arg, res, row)?),
2395            "background" => v = v.background(color(file, theme, arg)?),
2396            "flex" => v = v.flex(num(file, arg, res, row)?),
2397            "axis" => {
2398                v = match ident(file, arg)?.as_str() {
2399                    "horizontal" => v.axis(Axis::Horizontal),
2400                    "vertical" => v.axis(Axis::Vertical),
2401                    other => {
2402                        return Err(SeqError::new(
2403                            file,
2404                            arg.line,
2405                            1,
2406                            format!("axis to horizontal/vertical, nie '{other}'"),
2407                        ));
2408                    }
2409                };
2410            }
2411            "gap" => v = v.gap(num(file, arg, res, row)?),
2412            "column_gap" => v = v.column_gap(num(file, arg, res, row)?),
2413            "row_gap" => v = v.row_gap(num(file, arg, res, row)?),
2414            "align" => v = v.align_items(align_val(file, arg)?),
2415            "min_width" => v = v.min_width(num(file, arg, res, row)?),
2416            "max_width" => v = v.max_width(num(file, arg, res, row)?),
2417            "min_height" => v = v.min_height(num(file, arg, res, row)?),
2418            "max_height" => v = v.max_height(num(file, arg, res, row)?),
2419            "margin" => match margin_val(file, arg, res, row)? {
2420                MarginVal::All(n) => v = v.margin(n),
2421                MarginVal::Xy(x, y) => v = v.margin_xy(x, y),
2422            },
2423            "margin_x" => v = v.margin_x(num(file, arg, res, row)?),
2424            "margin_y" => v = v.margin_y(num(file, arg, res, row)?),
2425            "margin_left" => v = v.margin_left(num(file, arg, res, row)?),
2426            "margin_right" => v = v.margin_right(num(file, arg, res, row)?),
2427            "margin_top" => v = v.margin_top(num(file, arg, res, row)?),
2428            "margin_bottom" => v = v.margin_bottom(num(file, arg, res, row)?),
2429            "overflow" => v = v.overflow(overflow_val(file, arg)?),
2430            "hit" => v = v.hit_testable(flag(file, arg, res, row)?),
2431            "indent" | "indent_x" | "indent_y" | "indent_left" | "indent_right" | "indent_top"
2432            | "indent_bottom" => {}
2433            _ => unreachable_checked(file, node, arg)?,
2434        }
2435    }
2436    for c in &node.children {
2437        let clean = without_placement(c);
2438        let kid = lower_node(file, theme, styles, &clean, res, row)?;
2439        let _ = child_flex_weight(file, res, row, c)?;
2440        v = v.child(kid);
2441    }
2442    Ok(AnyView::new(v))
2443}
2444
2445fn lower_masonry(
2446    file: &str,
2447    theme: &Theme,
2448    styles: &BuiltStyles,
2449    node: &SeqNode,
2450    res: &Resolver<'_>,
2451    row: Option<&SeqRowData>,
2452) -> Result<AnyView, SeqError> {
2453    let columns = count_val(file, node, res, row, "columns")?
2454        .unwrap_or(2)
2455        .max(1);
2456    let mut v = Masonry::new(columns);
2457    for arg in &node.args {
2458        match arg.name.as_str() {
2459            "columns" => {}
2460            "width" => v = v.width(len(file, arg, res, row)?),
2461            "height" => v = v.height(len(file, arg, res, row)?),
2462            "padding" => match padding_val(file, arg, res, row)? {
2463                PaddingVal::All(n) => v = v.padding(n),
2464                PaddingVal::Xy(x, y) => v = v.padding_xy(x, y),
2465            },
2466            "padding_x" => v = v.padding_x(num(file, arg, res, row)?),
2467            "padding_y" => v = v.padding_y(num(file, arg, res, row)?),
2468            "background" => v = v.background(color(file, theme, arg)?),
2469            "flex" => v = v.flex(num(file, arg, res, row)?),
2470            "gap" => v = v.gap(num(file, arg, res, row)?),
2471            "column_gap" => v = v.column_gap(num(file, arg, res, row)?),
2472            "row_gap" => v = v.row_gap(num(file, arg, res, row)?),
2473            "align" => v = v.align_items(align_val(file, arg)?),
2474            "min_width" => v = v.min_width(num(file, arg, res, row)?),
2475            "max_width" => v = v.max_width(num(file, arg, res, row)?),
2476            "min_height" => v = v.min_height(num(file, arg, res, row)?),
2477            "max_height" => v = v.max_height(num(file, arg, res, row)?),
2478            "margin" => match margin_val(file, arg, res, row)? {
2479                MarginVal::All(n) => v = v.margin(n),
2480                MarginVal::Xy(x, y) => v = v.margin_xy(x, y),
2481            },
2482            "margin_x" => v = v.margin_x(num(file, arg, res, row)?),
2483            "margin_y" => v = v.margin_y(num(file, arg, res, row)?),
2484            "margin_left" => v = v.margin_left(num(file, arg, res, row)?),
2485            "margin_right" => v = v.margin_right(num(file, arg, res, row)?),
2486            "margin_top" => v = v.margin_top(num(file, arg, res, row)?),
2487            "margin_bottom" => v = v.margin_bottom(num(file, arg, res, row)?),
2488            "overflow" => v = v.overflow(overflow_val(file, arg)?),
2489            "hit" => v = v.hit_testable(flag(file, arg, res, row)?),
2490            "indent" | "indent_x" | "indent_y" | "indent_left" | "indent_right" | "indent_top"
2491            | "indent_bottom" => {}
2492            _ => unreachable_checked(file, node, arg)?,
2493        }
2494    }
2495    for c in &node.children {
2496        let clean = without_placement(c);
2497        let kid = lower_node(file, theme, styles, &clean, res, row)?;
2498        let _ = child_flex_weight(file, res, row, c)?;
2499        v = v.child(kid);
2500    }
2501    Ok(AnyView::new(v))
2502}
2503
2504fn when_signal(
2505    file: &str,
2506    line: usize,
2507    res: &Resolver<'_>,
2508    row: Option<&SeqRowData>,
2509    value: &SeqValue,
2510) -> Result<Signal<bool>, SeqError> {
2511    match value {
2512        SeqValue::Bool(b) => Ok(Signal::new(*b)),
2513        SeqValue::Ident(prop) | SeqValue::Dynamic(prop) => match res {
2514            Resolver::Static => Err(SeqError::new(
2515                file,
2516                line,
2517                1,
2518                "'when' z propertu wymaga hosta",
2519            )),
2520            Resolver::Host(b) => {
2521                if let Some(sig) = b.get_bool(prop) {
2522                    Ok(sig)
2523                } else if let Some(m) = b.get_memo_bool(prop) {
2524                    let sig = Signal::new(m.get_untracked());
2525                    let s2 = sig.clone();
2526                    effect(move || {
2527                        let v = m.get();
2528                        s2.set(v);
2529                    })
2530                    .forget();
2531                    Ok(sig)
2532                } else {
2533                    Err(prop_err(file, line, prop, "Bool"))
2534                }
2535            }
2536        },
2537        SeqValue::Env(name) => match res {
2538            Resolver::Static => Err(SeqError::new(
2539                file,
2540                line,
2541                1,
2542                "'when' from environment requires host",
2543            )),
2544            Resolver::Host(b) => b.get_env_bool(name).ok_or_else(|| {
2545                SeqError::new(
2546                    file,
2547                    line,
2548                    1,
2549                    format!("unknown env.{name} or wrong type (here you need: Bool)"),
2550                )
2551            }),
2552        },
2553        SeqValue::ItemField(field) => match res {
2554            Resolver::Static => Err(SeqError::new(
2555                file,
2556                line,
2557                1,
2558                "'when' z wiersza wymaga hosta",
2559            )),
2560            Resolver::Host(_) => match row_value(file, line, row, field)? {
2561                RowVal::Sig(FieldSignal::Bool(sig)) => Ok(sig),
2562                _ => Err(SeqError::new(
2563                    file,
2564                    line,
2565                    1,
2566                    format!("pole wiersza '{field}' to nie Bool"),
2567                )),
2568            },
2569        },
2570        _ => Err(SeqError::new(
2571            file,
2572            line,
2573            1,
2574            "'when' to true/false albo dynamic(nazwa)",
2575        )),
2576    }
2577}
2578
2579fn lower_if(
2580    file: &str,
2581    theme: &Theme,
2582    styles: &BuiltStyles,
2583    node: &SeqNode,
2584    res: &Resolver<'_>,
2585    row: Option<&SeqRowData>,
2586) -> Result<AnyView, SeqError> {
2587    let when_arg = find_arg(node, "when").ok_or_else(|| {
2588        SeqError::new(
2589            file,
2590            node.line,
2591            1,
2592            "If wymaga when: true/false/dynamic()".to_string(),
2593        )
2594    })?;
2595
2596    if let SeqValue::Bool(b) = &when_arg.value {
2597        if !b {
2598            return Ok(AnyView::new(Column::new()));
2599        }
2600        return match node.children.as_slice() {
2601            [] => Ok(AnyView::new(Column::new())),
2602            [single] => lower_node(file, theme, styles, single, res, row),
2603            _ => {
2604                let mut col = Column::new();
2605                for c in &node.children {
2606                    let kid = lower_node(file, theme, styles, c, res, row)?;
2607                    col = match child_flex_weight(file, res, row, c)? {
2608                        Some(w) => col.child_flex(kid, w),
2609                        None => col.child(kid),
2610                    };
2611                }
2612                Ok(AnyView::new(col))
2613            }
2614        };
2615    }
2616    let bindings = match res {
2617        Resolver::Static => {
2618            return Err(SeqError::new(
2619                file,
2620                when_arg.line,
2621                1,
2622                "If(when: prop) wymaga hosta Rust (HostBindings::prop_bool)",
2623            ));
2624        }
2625        Resolver::Host(b) => (*b).clone(),
2626    };
2627    let cond = when_signal(file, when_arg.line, res, row, &when_arg.value)?;
2628    let template = node.children.clone();
2629    let file_owned = file.to_string();
2630    let theme_owned = *theme;
2631    let styles_owned = styles.clone();
2632    let branch = move || -> AnyView {
2633        let res = Resolver::Host(&bindings);
2634        match template.as_slice() {
2635            [] => AnyView::new(Column::new()),
2636            [single] => lower_node(&file_owned, &theme_owned, &styles_owned, single, &res, row)
2637                .unwrap_or_else(|e| AnyView::new(Text::new(format!("seq: {e}")))),
2638            _ => {
2639                let mut col = Column::new();
2640                for c in &template {
2641                    match lower_node(&file_owned, &theme_owned, &styles_owned, c, &res, row) {
2642                        Ok(kid) => {
2643                            let w = child_flex_weight(&file_owned, &res, row, c).unwrap_or(None);
2644                            col = match w {
2645                                Some(w) => col.child_flex(kid, w),
2646                                None => col.child(kid),
2647                            };
2648                        }
2649                        Err(e) => {
2650                            col = col.child(Text::new(format!("seq: {e}")));
2651                        }
2652                    }
2653                }
2654                AnyView::new(col)
2655            }
2656        }
2657    };
2658
2659    let _ = cond.get();
2660    let mut view = when(cond, branch());
2661    if let Some(arg) = find_arg(node, "enter") {
2662        let spec = anim_spec_of(file, arg)?;
2663        view = view.enter(Motion::from(spec));
2664    }
2665    if let Some(arg) = find_arg(node, "exit") {
2666        let spec = anim_spec_of(file, arg)?;
2667        view = view.exit(Motion::from(spec));
2668    }
2669    Ok(AnyView::new(view))
2670}
2671
2672fn lower_animation_child(
2673    file: &str,
2674    theme: &Theme,
2675    styles: &BuiltStyles,
2676    node: &SeqNode,
2677    res: &Resolver<'_>,
2678    row: Option<&SeqRowData>,
2679) -> Result<AnyView, SeqError> {
2680    match node.children.as_slice() {
2681        [] => Ok(AnyView::new(Column::new())),
2682        [single] => lower_node(file, theme, styles, single, res, row),
2683        many => {
2684            let mut col = Column::new();
2685            for c in many {
2686                let kid = lower_node(file, theme, styles, c, res, row)?;
2687                col = col.child(kid);
2688            }
2689            Ok(AnyView::new(col))
2690        }
2691    }
2692}
2693
2694fn anim_spec_of(file: &str, arg: &SeqArg) -> Result<AnimSpec, SeqError> {
2695    let name = match &arg.value {
2696        SeqValue::Ident(s) | SeqValue::Dynamic(s) => s.as_str(),
2697        _ => {
2698            return Err(SeqError::new(
2699                file,
2700                arg.line,
2701                1,
2702                "animate/enter/exit: nazwa presetu (fast/smooth/snappy/gentle/slow)".to_string(),
2703            ));
2704        }
2705    };
2706    Ok(match name {
2707        "fast" => AnimSpec::FAST,
2708        "smooth" => AnimSpec::SMOOTH,
2709        "snappy" => AnimSpec::SNAPPY,
2710        "gentle" => AnimSpec::GENTLE,
2711        "slow" => AnimSpec::SLOW,
2712        _ => {
2713            return Err(SeqError::new(
2714                file,
2715                arg.line,
2716                1,
2717                format!("nieznany preset animacji '{name}'"),
2718            ));
2719        }
2720    })
2721}
2722
2723fn lower_opacity(
2724    file: &str,
2725    theme: &Theme,
2726    styles: &BuiltStyles,
2727    node: &SeqNode,
2728    res: &Resolver<'_>,
2729    row: Option<&SeqRowData>,
2730) -> Result<AnyView, SeqError> {
2731    let child = lower_animation_child(file, theme, styles, node, res, row)?;
2732    let sig = if let Some(arg) = find_arg(node, "value") {
2733        bridge_f32(file, arg.line, res, row, &arg.value, "value")?
2734    } else {
2735        Signal::new(1.0_f32)
2736    };
2737    let mut v = LiveOpacity::new(child, Live::from(sig));
2738    if let Some(arg) = find_arg(node, "animate") {
2739        v = v.animate(anim_spec_of(file, arg)?);
2740    }
2741    Ok(AnyView::new(v))
2742}
2743
2744fn lower_scale(
2745    file: &str,
2746    theme: &Theme,
2747    styles: &BuiltStyles,
2748    node: &SeqNode,
2749    res: &Resolver<'_>,
2750    row: Option<&SeqRowData>,
2751) -> Result<AnyView, SeqError> {
2752    let child = lower_animation_child(file, theme, styles, node, res, row)?;
2753    let sig = if let Some(arg) = find_arg(node, "value") {
2754        bridge_f32(file, arg.line, res, row, &arg.value, "value")?
2755    } else {
2756        Signal::new(1.0_f32)
2757    };
2758    let mut v = Scaled::new(child, Live::from(sig));
2759    if let Some(arg) = find_arg(node, "animate") {
2760        v = v.animate(anim_spec_of(file, arg)?);
2761    }
2762    Ok(AnyView::new(v))
2763}
2764
2765fn lower_rotate(
2766    file: &str,
2767    theme: &Theme,
2768    styles: &BuiltStyles,
2769    node: &SeqNode,
2770    res: &Resolver<'_>,
2771    row: Option<&SeqRowData>,
2772) -> Result<AnyView, SeqError> {
2773    let child = lower_animation_child(file, theme, styles, node, res, row)?;
2774    let sig = if let Some(arg) = find_arg(node, "value") {
2775        bridge_f32(file, arg.line, res, row, &arg.value, "value")?
2776    } else {
2777        Signal::new(0.0_f32)
2778    };
2779    let mut v = Rotated::new(child, Live::from(sig));
2780    if let Some(arg) = find_arg(node, "animate") {
2781        v = v.animate(anim_spec_of(file, arg)?);
2782    }
2783    Ok(AnyView::new(v))
2784}
2785
2786fn lower_width(
2787    file: &str,
2788    theme: &Theme,
2789    styles: &BuiltStyles,
2790    node: &SeqNode,
2791    res: &Resolver<'_>,
2792    row: Option<&SeqRowData>,
2793) -> Result<AnyView, SeqError> {
2794    let child = lower_animation_child(file, theme, styles, node, res, row)?;
2795    let sig = if let Some(arg) = find_arg(node, "value") {
2796        bridge_f32(file, arg.line, res, row, &arg.value, "value")?
2797    } else {
2798        Signal::new(0.0_f32)
2799    };
2800    let mut v = AnimatedSize::width(child, Live::from(sig));
2801    if let Some(arg) = find_arg(node, "animate") {
2802        v = v.animate(anim_spec_of(file, arg)?);
2803    }
2804    Ok(AnyView::new(v))
2805}
2806
2807fn lower_height(
2808    file: &str,
2809    theme: &Theme,
2810    styles: &BuiltStyles,
2811    node: &SeqNode,
2812    res: &Resolver<'_>,
2813    row: Option<&SeqRowData>,
2814) -> Result<AnyView, SeqError> {
2815    let child = lower_animation_child(file, theme, styles, node, res, row)?;
2816    let sig = if let Some(arg) = find_arg(node, "value") {
2817        bridge_f32(file, arg.line, res, row, &arg.value, "value")?
2818    } else {
2819        Signal::new(0.0_f32)
2820    };
2821    let mut v = AnimatedSize::height(child, Live::from(sig));
2822    if let Some(arg) = find_arg(node, "animate") {
2823        v = v.animate(anim_spec_of(file, arg)?);
2824    }
2825    Ok(AnyView::new(v))
2826}
2827
2828fn lower_translate(
2829    file: &str,
2830    theme: &Theme,
2831    styles: &BuiltStyles,
2832    node: &SeqNode,
2833    res: &Resolver<'_>,
2834    row: Option<&SeqRowData>,
2835) -> Result<AnyView, SeqError> {
2836    let child = lower_animation_child(file, theme, styles, node, res, row)?;
2837    let x_sig = if let Some(arg) = find_arg(node, "x") {
2838        bridge_f32(file, arg.line, res, row, &arg.value, "x")?
2839    } else {
2840        Signal::new(0.0_f32)
2841    };
2842    let y_sig = if let Some(arg) = find_arg(node, "y") {
2843        bridge_f32(file, arg.line, res, row, &arg.value, "y")?
2844    } else {
2845        Signal::new(0.0_f32)
2846    };
2847    let x_live = x_sig.clone();
2848    let y_live = y_sig.clone();
2849    let live = Live::<Vec2>::derived(move || Vec2::new(x_live.get(), y_live.get()));
2850    let mut v = LiveOffset::new(child, live);
2851    if let Some(arg) = find_arg(node, "animate") {
2852        v = v.animate(anim_spec_of(file, arg)?);
2853    }
2854    Ok(AnyView::new(v))
2855}
2856
2857fn lower_pulse(
2858    file: &str,
2859    theme: &Theme,
2860    styles: &BuiltStyles,
2861    node: &SeqNode,
2862    res: &Resolver<'_>,
2863    row: Option<&SeqRowData>,
2864) -> Result<AnyView, SeqError> {
2865    let child = lower_animation_child(file, theme, styles, node, res, row)?;
2866    let mut v = Pulse::new(child);
2867    if let Some(arg) = find_arg(node, "min") {
2868        v = v.min_opacity(num(file, arg, res, row)?);
2869    }
2870    if let Some(arg) = find_arg(node, "period") {
2871        v = v.period_duration(std::time::Duration::from_secs_f32(num(
2872            file, arg, res, row,
2873        )?));
2874    }
2875    Ok(AnyView::new(v))
2876}
2877
2878fn lower_shake(
2879    file: &str,
2880    theme: &Theme,
2881    styles: &BuiltStyles,
2882    node: &SeqNode,
2883    res: &Resolver<'_>,
2884    row: Option<&SeqRowData>,
2885) -> Result<AnyView, SeqError> {
2886    let child = lower_animation_child(file, theme, styles, node, res, row)?;
2887    let mut v = Shake::new(child);
2888    if let Some(arg) = find_arg(node, "amplitude") {
2889        v = v.amplitude(num(file, arg, res, row)?);
2890    }
2891    if let Some(arg) = find_arg(node, "period") {
2892        v = v.period_duration(std::time::Duration::from_secs_f32(num(
2893            file, arg, res, row,
2894        )?));
2895    }
2896    if let Some(arg) = find_arg(node, "duration") {
2897        v = v.total_duration(std::time::Duration::from_secs_f32(num(
2898            file, arg, res, row,
2899        )?));
2900    }
2901    if let Some(arg) = find_arg(node, "delay") {
2902        v = v.delay_duration(std::time::Duration::from_secs_f32(num(
2903            file, arg, res, row,
2904        )?));
2905    }
2906    Ok(AnyView::new(v))
2907}
2908
2909fn lower_async(
2910    file: &str,
2911    theme: &Theme,
2912    styles: &BuiltStyles,
2913    node: &SeqNode,
2914    res: &Resolver<'_>,
2915    row: Option<&SeqRowData>,
2916) -> Result<AnyView, SeqError> {
2917    let source = find_arg(node, "source").ok_or_else(|| {
2918        SeqError::new(file, node.line, 1, "Async wymaga source: nazwa".to_string())
2919    })?;
2920    let source_name = match &source.value {
2921        SeqValue::Ident(name) => name.clone(),
2922        _ => {
2923            return Err(SeqError::new(
2924                file,
2925                source.line,
2926                1,
2927                "source: to nazwa zasobu z hosta (source: users)",
2928            ));
2929        }
2930    };
2931    let bindings = match res {
2932        Resolver::Static => {
2933            return Err(SeqError::new(
2934                file,
2935                node.line,
2936                1,
2937                "Async(source:) wymaga hosta Rust (HostBindings::resource())",
2938            ));
2939        }
2940        Resolver::Host(b) => (*b).clone(),
2941    };
2942    let resource = bindings.get_resource(&source_name).ok_or_else(|| {
2943        SeqError::new(
2944            file,
2945            source.line,
2946            1,
2947            format!("unknown resource '{source_name}' (HostBindings::resource())"),
2948        )
2949    })?;
2950
2951    let mut loading: Vec<SeqNode> = Vec::new();
2952    let mut data: Vec<SeqNode> = Vec::new();
2953    let mut error: Vec<SeqNode> = Vec::new();
2954    let mut idle: Vec<SeqNode> = Vec::new();
2955    let mut cancelled: Vec<SeqNode> = Vec::new();
2956    for c in &node.children {
2957        match c.kind.as_str() {
2958            "Loading" => loading.extend(c.children.clone()),
2959            "Data" => data.extend(c.children.clone()),
2960            "Error" => error.extend(c.children.clone()),
2961            "Idle" => idle.extend(c.children.clone()),
2962            "Cancelled" => cancelled.extend(c.children.clone()),
2963            other => {
2964                return Err(SeqError::new(
2965                    file,
2966                    c.line,
2967                    1,
2968                    format!("interior Async is Loading/Data/Error/Idle/Cancelled, not '{other}'"),
2969                ));
2970            }
2971        }
2972    }
2973
2974    let data_sig = Signal::new(String::new());
2975    let error_sig = Signal::new(String::new());
2976    {
2977        let res_sync = resource.clone();
2978        let data_sig = data_sig.clone();
2979        let error_sig = error_sig.clone();
2980        effect(move || match res_sync.state().get() {
2981            sequoia_async::AsyncState::Ready(v) => data_sig.set(v.clone()),
2982            sequoia_async::AsyncState::Failed(e) => error_sig.set(e.clone()),
2983            _ => {}
2984        })
2985        .forget();
2986    }
2987    let lower_once = |items: &[SeqNode], extra: Option<(&str, Signal<String>)>| -> AnyView {
2988        let mut b2 = bindings.clone();
2989        if let Some((k, sig)) = extra {
2990            b2 = b2.prop_str(k, sig);
2991        }
2992        let res = Resolver::Host(&b2);
2993        match items {
2994            [] => AnyView::new(Column::new()),
2995            [single] => lower_node(file, theme, styles, single, &res, row)
2996                .unwrap_or_else(|e| AnyView::new(Text::new(format!("seq: {e}")))),
2997            _ => {
2998                let mut col = Column::new();
2999                for n in items {
3000                    match lower_node(file, theme, styles, n, &res, row) {
3001                        Ok(kid) => {
3002                            let w = child_flex_weight(file, &res, row, n).unwrap_or(None);
3003                            col = match w {
3004                                Some(w) => col.child_flex(kid, w),
3005                                None => col.child(kid),
3006                            };
3007                        }
3008                        Err(e) => {
3009                            col = col.child(Text::new(format!("seq: {e}")));
3010                        }
3011                    }
3012                }
3013                AnyView::new(col)
3014            }
3015        }
3016    };
3017    let loading_view = lower_once(&loading, None);
3018    let data_view = lower_once(&data, Some(("data", data_sig)));
3019    let error_view = lower_once(&error, Some(("error", error_sig)));
3020    let mut view = AsyncView::new(resource);
3021    {
3022        let v = loading_view.clone();
3023        view = view.loading(move || v.clone());
3024    }
3025    {
3026        let v = data_view.clone();
3027        view = view.data(move |_: &String| v.clone());
3028    }
3029    {
3030        let v = error_view.clone();
3031        view = view.error(move |_: &str| v.clone());
3032    }
3033    if !idle.is_empty() {
3034        let v = lower_once(&idle, None);
3035        view = view.idle(move || v.clone());
3036    }
3037    if !cancelled.is_empty() {
3038        let v = lower_once(&cancelled, None);
3039        view = view.cancelled(move || v.clone());
3040    }
3041    Ok(AnyView::new(view))
3042}
3043
3044fn lower_effect(
3045    file: &str,
3046    node: &SeqNode,
3047    res: &Resolver<'_>,
3048    row: Option<&SeqRowData>,
3049) -> Result<AnyView, SeqError> {
3050    let track = find_arg(node, "track").ok_or_else(|| {
3051        SeqError::new(
3052            file,
3053            node.line,
3054            1,
3055            "Effect wymaga track: dynamic()".to_string(),
3056        )
3057    })?;
3058    let on_run = find_arg(node, "on_run").ok_or_else(|| {
3059        SeqError::new(
3060            file,
3061            node.line,
3062            1,
3063            "Effect wymaga on_run: { akcja() }".to_string(),
3064        )
3065    })?;
3066    let bindings = match res {
3067        Resolver::Static => {
3068            return Err(SeqError::new(
3069                file,
3070                node.line,
3071                1,
3072                "Effect wymaga hosta Rust",
3073            ));
3074        }
3075        Resolver::Host(b) => (*b).clone(),
3076    };
3077    let touch = track_touch(file, track.line, &bindings, row, &track.value)?;
3078    let fire: Rc<dyn Fn()> = match &on_run.value {
3079        SeqValue::Callback { action, args } => {
3080            if !args.is_empty() {
3081                return Err(SeqError::new(
3082                    file,
3083                    on_run.line,
3084                    1,
3085                    "on_run: no arguments (read status via track:)",
3086                ));
3087            }
3088            bindings.get_action0(action).ok_or_else(|| {
3089                SeqError::new(
3090                    file,
3091                    on_run.line,
3092                    1,
3093                    format!("unknown callback '{action}' (missing on host: on)"),
3094                )
3095            })?
3096        }
3097        SeqValue::CallSeq { name, args } => {
3098            if !args.is_empty() {
3099                return Err(SeqError::new(
3100                    file,
3101                    on_run.line,
3102                    1,
3103                    "on_run: no arguments (read status via track:)",
3104                ));
3105            }
3106            seq_fire(file, on_run.line, &bindings, name, Vec::new())?
3107        }
3108        _ => {
3109            return Err(SeqError::new(
3110                file,
3111                on_run.line,
3112                1,
3113                "on_run: { akcja(args) }",
3114            ));
3115        }
3116    };
3117    let seen = std::rc::Rc::new(std::cell::Cell::new(false));
3118    effect(move || {
3119        touch();
3120        if seen.get() {
3121            fire();
3122        } else {
3123            seen.set(true);
3124        }
3125    })
3126    .forget();
3127    Ok(AnyView::new(Column::new()))
3128}
3129
3130fn track_touch(
3131    file: &str,
3132    line: usize,
3133    b: &HostBindings,
3134    row: Option<&SeqRowData>,
3135    value: &SeqValue,
3136) -> Result<Rc<dyn Fn()>, SeqError> {
3137    match value {
3138        SeqValue::Bool(_) | SeqValue::Num(_) | SeqValue::Str(_) | SeqValue::Color(_) => {
3139            Ok(Rc::new(|| {}))
3140        }
3141        SeqValue::Ident(name) | SeqValue::Dynamic(name) => {
3142            if let Some(sig) = b.get_str(name) {
3143                Ok(Rc::new(move || {
3144                    sig.get();
3145                }))
3146            } else if let Some(m) = b.get_memo_str(name) {
3147                Ok(Rc::new(move || {
3148                    m.get();
3149                }))
3150            } else if let Some(sig) = b.get_bool(name) {
3151                Ok(Rc::new(move || {
3152                    sig.get();
3153                }))
3154            } else if let Some(m) = b.get_memo_bool(name) {
3155                Ok(Rc::new(move || {
3156                    m.get();
3157                }))
3158            } else if let Some(sig) = b.get_int(name) {
3159                Ok(Rc::new(move || {
3160                    sig.get();
3161                }))
3162            } else if let Some(m) = b.get_memo_int(name) {
3163                Ok(Rc::new(move || {
3164                    m.get();
3165                }))
3166            } else if let Some(sig) = b.get_float(name) {
3167                Ok(Rc::new(move || {
3168                    sig.get();
3169                }))
3170            } else if let Some(m) = b.get_memo_float(name) {
3171                Ok(Rc::new(move || {
3172                    m.get();
3173                }))
3174            } else if let Some(sig) = b.get_env_str(name) {
3175                Ok(Rc::new(move || {
3176                    sig.get();
3177                }))
3178            } else if let Some(sig) = b.get_env_bool(name) {
3179                Ok(Rc::new(move || {
3180                    sig.get();
3181                }))
3182            } else if let Some(sig) = b.get_env_int(name) {
3183                Ok(Rc::new(move || {
3184                    sig.get();
3185                }))
3186            } else if let Some(sig) = b.get_env_float(name) {
3187                Ok(Rc::new(move || {
3188                    sig.get();
3189                }))
3190            } else {
3191                Err(prop_err(file, line, name, "String/Bool/Int/Float"))
3192            }
3193        }
3194        SeqValue::Env(name) => {
3195            if let Some(sig) = b.get_env_str(name) {
3196                Ok(Rc::new(move || {
3197                    sig.get();
3198                }))
3199            } else if let Some(sig) = b.get_env_bool(name) {
3200                Ok(Rc::new(move || {
3201                    sig.get();
3202                }))
3203            } else if let Some(sig) = b.get_env_int(name) {
3204                Ok(Rc::new(move || {
3205                    sig.get();
3206                }))
3207            } else if let Some(sig) = b.get_env_float(name) {
3208                Ok(Rc::new(move || {
3209                    sig.get();
3210                }))
3211            } else {
3212                Err(SeqError::new(
3213                    file,
3214                    line,
3215                    1,
3216                    format!("unknown env.{name} (missing on host)"),
3217                ))
3218            }
3219        }
3220        SeqValue::ItemField(field) => match row_value(file, line, row, field)? {
3221            RowVal::Sig(FieldSignal::Str(sig)) => Ok(Rc::new(move || {
3222                sig.get();
3223            })),
3224            RowVal::Sig(FieldSignal::Bool(sig)) => Ok(Rc::new(move || {
3225                sig.get();
3226            })),
3227            RowVal::Sig(FieldSignal::Int(sig)) => Ok(Rc::new(move || {
3228                sig.get();
3229            })),
3230            RowVal::Sig(FieldSignal::Float(sig)) => Ok(Rc::new(move || {
3231                sig.get();
3232            })),
3233            RowVal::Id(_) => Ok(Rc::new(|| {})),
3234        },
3235        _ => Err(SeqError::new(
3236            file,
3237            line,
3238            1,
3239            "'track' to dynamic(nazwa)/env.nazwa/item.pole",
3240        )),
3241    }
3242}
3243
3244fn lower_checkable(
3245    file: &str,
3246    node: &SeqNode,
3247    res: &Resolver<'_>,
3248    row: Option<&SeqRowData>,
3249    is_switch: bool,
3250) -> Result<AnyView, SeqError> {
3251    let what = if is_switch { "Switch" } else { "Checkbox" };
3252    let checked_arg = find_arg(node, "checked").ok_or_else(|| {
3253        SeqError::new(
3254            file,
3255            node.line,
3256            1,
3257            format!("{what} wymaga checked: true/false/prop"),
3258        )
3259    })?;
3260    let sig = writable_bool(
3261        file,
3262        checked_arg.line,
3263        res,
3264        row,
3265        &checked_arg.value,
3266        "checked",
3267    )?;
3268    let label = opt_str(file, node, "label", res, row)?;
3269    let mut font_size: Option<f32> = None;
3270    let mut disabled = false;
3271    let mut margin: Option<MarginVal> = None;
3272    let mut margin_x: Option<f32> = None;
3273    let mut margin_y: Option<f32> = None;
3274    let mut margin_left: Option<f32> = None;
3275    let mut margin_right: Option<f32> = None;
3276    let mut margin_top: Option<f32> = None;
3277    let mut margin_bottom: Option<f32> = None;
3278    let mut toggle: Option<Rc<dyn Fn()>> = None;
3279    for arg in &node.args {
3280        match arg.name.as_str() {
3281            "checked" => {}
3282            "label" => {}
3283            "font_size" => font_size = Some(num(file, arg, res, row)?),
3284            "enabled" => disabled = !flag(file, arg, res, row)?,
3285            "margin" => margin = Some(margin_val(file, arg, res, row)?),
3286            "margin_x" => margin_x = Some(num(file, arg, res, row)?),
3287            "margin_y" => margin_y = Some(num(file, arg, res, row)?),
3288            "margin_left" => margin_left = Some(num(file, arg, res, row)?),
3289            "margin_right" => margin_right = Some(num(file, arg, res, row)?),
3290            "margin_top" => margin_top = Some(num(file, arg, res, row)?),
3291            "margin_bottom" => margin_bottom = Some(num(file, arg, res, row)?),
3292            "on_toggle" => match res {
3293                Resolver::Static => {
3294                    return Err(SeqError::new(
3295                        file,
3296                        arg.line,
3297                        1,
3298                        "on_toggle wymaga hosta Rust",
3299                    ));
3300                }
3301                Resolver::Host(b) => match &arg.value {
3302                    SeqValue::Callback { .. } | SeqValue::CallSeq { .. } => {
3303                        let live = LiveVal::Bool(sig.clone());
3304                        toggle = Some(wire_live_action(
3305                            file,
3306                            arg.line,
3307                            b,
3308                            &arg.value,
3309                            &live,
3310                            "on_toggle",
3311                        )?);
3312                    }
3313                    _ => {
3314                        return Err(SeqError::new(
3315                            file,
3316                            arg.line,
3317                            1,
3318                            "on_toggle: { akcja() } albo { akcja(value) }",
3319                        ));
3320                    }
3321                },
3322            },
3323            "flex" => {}
3324            "indent" | "indent_x" | "indent_y" | "indent_left" | "indent_right" | "indent_top"
3325            | "indent_bottom" => {}
3326            _ => unreachable_checked(file, node, arg)?,
3327        }
3328    }
3329    if is_switch {
3330        let mut v = Switch::new(sig);
3331        if let Some(label) = label {
3332            v = v.label(label);
3333        }
3334        if let Some(size) = font_size {
3335            v = v.font_size(size);
3336        }
3337        v = v.disabled(disabled);
3338        if let Some(fire) = toggle {
3339            v = v.on_toggle(move |_| fire());
3340        }
3341        v = apply_switch_margin(
3342            v,
3343            margin,
3344            margin_x,
3345            margin_y,
3346            margin_left,
3347            margin_right,
3348            margin_top,
3349            margin_bottom,
3350        );
3351        Ok(AnyView::new(v))
3352    } else {
3353        let mut v = Checkbox::new(sig);
3354        if let Some(label) = label {
3355            v = v.label(label);
3356        }
3357        if let Some(size) = font_size {
3358            v = v.font_size(size);
3359        }
3360        v = v.disabled(disabled);
3361        if let Some(fire) = toggle {
3362            v = v.on_toggle(move |_| fire());
3363        }
3364        v = apply_checkbox_margin(
3365            v,
3366            margin,
3367            margin_x,
3368            margin_y,
3369            margin_left,
3370            margin_right,
3371            margin_top,
3372            margin_bottom,
3373        );
3374        Ok(AnyView::new(v))
3375    }
3376}
3377
3378#[allow(clippy::too_many_arguments)]
3379fn apply_checkbox_margin(
3380    mut v: Checkbox,
3381    margin: Option<MarginVal>,
3382    margin_x: Option<f32>,
3383    margin_y: Option<f32>,
3384    margin_left: Option<f32>,
3385    margin_right: Option<f32>,
3386    margin_top: Option<f32>,
3387    margin_bottom: Option<f32>,
3388) -> Checkbox {
3389    if let Some(m) = margin {
3390        v = match m {
3391            MarginVal::All(n) => v.margin(n),
3392            MarginVal::Xy(x, y) => v.margin_xy(x, y),
3393        };
3394    }
3395    if let Some(x) = margin_x {
3396        v = v.margin_x(x);
3397    }
3398    if let Some(y) = margin_y {
3399        v = v.margin_y(y);
3400    }
3401    if let Some(x) = margin_left {
3402        v = v.margin_left(x);
3403    }
3404    if let Some(x) = margin_right {
3405        v = v.margin_right(x);
3406    }
3407    if let Some(y) = margin_top {
3408        v = v.margin_top(y);
3409    }
3410    if let Some(y) = margin_bottom {
3411        v = v.margin_bottom(y);
3412    }
3413    v
3414}
3415
3416#[allow(clippy::too_many_arguments)]
3417fn apply_switch_margin(
3418    mut v: Switch,
3419    margin: Option<MarginVal>,
3420    margin_x: Option<f32>,
3421    margin_y: Option<f32>,
3422    margin_left: Option<f32>,
3423    margin_right: Option<f32>,
3424    margin_top: Option<f32>,
3425    margin_bottom: Option<f32>,
3426) -> Switch {
3427    if let Some(m) = margin {
3428        v = match m {
3429            MarginVal::All(n) => v.margin(n),
3430            MarginVal::Xy(x, y) => v.margin_xy(x, y),
3431        };
3432    }
3433    if let Some(x) = margin_x {
3434        v = v.margin_x(x);
3435    }
3436    if let Some(y) = margin_y {
3437        v = v.margin_y(y);
3438    }
3439    if let Some(x) = margin_left {
3440        v = v.margin_left(x);
3441    }
3442    if let Some(x) = margin_right {
3443        v = v.margin_right(x);
3444    }
3445    if let Some(y) = margin_top {
3446        v = v.margin_top(y);
3447    }
3448    if let Some(y) = margin_bottom {
3449        v = v.margin_bottom(y);
3450    }
3451    v
3452}
3453
3454fn lower_checkbox(
3455    file: &str,
3456    _theme: &Theme,
3457    node: &SeqNode,
3458    res: &Resolver<'_>,
3459    row: Option<&SeqRowData>,
3460) -> Result<AnyView, SeqError> {
3461    lower_checkable(file, node, res, row, false)
3462}
3463
3464fn lower_switch(
3465    file: &str,
3466    _theme: &Theme,
3467    node: &SeqNode,
3468    res: &Resolver<'_>,
3469    row: Option<&SeqRowData>,
3470) -> Result<AnyView, SeqError> {
3471    lower_checkable(file, node, res, row, true)
3472}
3473
3474fn lower_textfield(
3475    file: &str,
3476    _theme: &Theme,
3477    node: &SeqNode,
3478    res: &Resolver<'_>,
3479    row: Option<&SeqRowData>,
3480) -> Result<AnyView, SeqError> {
3481    let value_arg = find_arg(node, "value").ok_or_else(|| {
3482        SeqError::new(
3483            file,
3484            node.line,
3485            1,
3486            "TextField wymaga value: prop".to_string(),
3487        )
3488    })?;
3489    let sig = writable_str(file, value_arg.line, res, row, &value_arg.value, "value")?;
3490    let mut v = TextField::new(sig.clone());
3491    for arg in &node.args {
3492        match arg.name.as_str() {
3493            "value" => {}
3494            "placeholder" => {
3495                if let Some(text) = opt_str(file, node, "placeholder", res, row)? {
3496                    v = v.placeholder(text);
3497                }
3498            }
3499            "font_size" => v = v.font_size(num(file, arg, res, row)?),
3500            "width" => v = v.width(len(file, arg, res, row)?),
3501            "height" => v = v.height(len(file, arg, res, row)?),
3502            "min_width" => v = v.min_width(num(file, arg, res, row)?),
3503            "max_width" => v = v.max_width(num(file, arg, res, row)?),
3504            "max_height" => v = v.max_height(num(file, arg, res, row)?),
3505            "margin" => match margin_val(file, arg, res, row)? {
3506                MarginVal::All(n) => v = v.margin(n),
3507                MarginVal::Xy(x, y) => v = v.margin_xy(x, y),
3508            },
3509            "margin_x" => v = v.margin_x(num(file, arg, res, row)?),
3510            "margin_y" => v = v.margin_y(num(file, arg, res, row)?),
3511            "margin_left" => v = v.margin_left(num(file, arg, res, row)?),
3512            "margin_right" => v = v.margin_right(num(file, arg, res, row)?),
3513            "margin_top" => v = v.margin_top(num(file, arg, res, row)?),
3514            "margin_bottom" => v = v.margin_bottom(num(file, arg, res, row)?),
3515            "on_submit" => match res {
3516                Resolver::Static => {
3517                    return Err(SeqError::new(
3518                        file,
3519                        arg.line,
3520                        1,
3521                        "on_submit wymaga hosta Rust",
3522                    ));
3523                }
3524                Resolver::Host(b) => match &arg.value {
3525                    SeqValue::Callback { .. } | SeqValue::CallSeq { .. } => {
3526                        let live = LiveVal::Str(sig.clone());
3527                        let fire =
3528                            wire_live_action(file, arg.line, b, &arg.value, &live, "on_submit")?;
3529                        v = v.on_submit(move |_| fire());
3530                    }
3531                    _ => {
3532                        return Err(SeqError::new(
3533                            file,
3534                            arg.line,
3535                            1,
3536                            "on_submit: { akcja() } albo { akcja(value) }",
3537                        ));
3538                    }
3539                },
3540            },
3541            "flex" => {}
3542            "indent" | "indent_x" | "indent_y" | "indent_left" | "indent_right" | "indent_top"
3543            | "indent_bottom" => {}
3544            _ => unreachable_checked(file, node, arg)?,
3545        }
3546    }
3547    Ok(AnyView::new(v))
3548}
3549
3550#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
3551fn count_val(
3552    file: &str,
3553    node: &SeqNode,
3554    res: &Resolver<'_>,
3555    row: Option<&SeqRowData>,
3556    name: &str,
3557) -> Result<Option<usize>, SeqError> {
3558    let Some(arg) = find_arg(node, name) else {
3559        return Ok(None);
3560    };
3561    let err = || SeqError::new(file, arg.line, 1, format!("'{name}' is an integer >= 0"));
3562    match &arg.value {
3563        SeqValue::Num(n) => {
3564            if n.is_finite() && *n >= 0.0 && n.fract() == 0.0 {
3565                Ok(Some(*n as usize))
3566            } else {
3567                Err(err())
3568            }
3569        }
3570        SeqValue::Ident(prop) | SeqValue::Dynamic(prop) => match res {
3571            Resolver::Static => Err(SeqError::new(
3572                file,
3573                arg.line,
3574                1,
3575                format!("'{name}' z propertu wymaga hosta"),
3576            )),
3577            Resolver::Host(b) => b
3578                .get_int(prop)
3579                .map(|sig| sig.get_untracked())
3580                .or_else(|| b.get_memo_int(prop).map(|m| m.get_untracked()))
3581                .or_else(|| b.get_env_int(prop).map(|sig| sig.get_untracked()))
3582                .map(|v| Some(v.max(0) as usize))
3583                .ok_or_else(|| prop_err(file, arg.line, prop, "Int")),
3584        },
3585        SeqValue::Env(env_name) => match res {
3586            Resolver::Static => Err(SeqError::new(
3587                file,
3588                arg.line,
3589                1,
3590                format!("'{name}' from environment requires host"),
3591            )),
3592            Resolver::Host(b) => b
3593                .get_env_int(env_name)
3594                .map(|sig| Some(sig.get_untracked().max(0) as usize))
3595                .ok_or_else(|| {
3596                    SeqError::new(
3597                        file,
3598                        arg.line,
3599                        1,
3600                        format!("unknown env.{env_name} or wrong type (here you need: Int)"),
3601                    )
3602                }),
3603        },
3604        SeqValue::ItemField(field) => match res {
3605            Resolver::Static => Err(SeqError::new(
3606                file,
3607                arg.line,
3608                1,
3609                format!("'{name}' z wiersza wymaga hosta"),
3610            )),
3611            Resolver::Host(_) => match row_value(file, arg.line, row, field)? {
3612                RowVal::Sig(FieldSignal::Int(sig)) => Ok(Some(sig.get_untracked().max(0) as usize)),
3613                RowVal::Id(id) => Ok(Some(id.max(0) as usize)),
3614                _ => Err(SeqError::new(
3615                    file,
3616                    arg.line,
3617                    1,
3618                    format!("pole wiersza '{field}' to nie Int"),
3619                )),
3620            },
3621        },
3622        _ => Err(err()),
3623    }
3624}
3625
3626fn label_arg(
3627    file: &str,
3628    node: &SeqNode,
3629    res: &Resolver<'_>,
3630    row: Option<&SeqRowData>,
3631    kind: &str,
3632) -> Result<String, SeqError> {
3633    opt_str(file, node, "label", res, row)?
3634        .ok_or_else(|| SeqError::new(file, node.line, 1, format!("{kind} wymaga label: \"...\"")))
3635}
3636
3637fn str_list_arg(file: &str, node: &SeqNode, name: &str) -> Result<Vec<String>, SeqError> {
3638    let arg = find_arg(node, name).ok_or_else(|| {
3639        SeqError::new(
3640            file,
3641            node.line,
3642            1,
3643            format!("{0} wymaga {name}: [\"...\", ...]", node.kind),
3644        )
3645    })?;
3646    match &arg.value {
3647        SeqValue::Array(items) if !items.is_empty() => items
3648            .iter()
3649            .map(|it| match it {
3650                SeqValue::Str(s) => Ok(s.clone()),
3651                _ => Err(SeqError::new(
3652                    file,
3653                    arg.line,
3654                    1,
3655                    format!("'{name}' to [\"...\", ...] (same napisy)"),
3656                )),
3657            })
3658            .collect(),
3659        _ => Err(SeqError::new(
3660            file,
3661            arg.line,
3662            1,
3663            format!("'{name}' to niepusta [\"...\", ...]"),
3664        )),
3665    }
3666}
3667
3668fn lower_textarea(
3669    file: &str,
3670    _theme: &Theme,
3671    node: &SeqNode,
3672    res: &Resolver<'_>,
3673    row: Option<&SeqRowData>,
3674) -> Result<AnyView, SeqError> {
3675    let value_arg = find_arg(node, "value").ok_or_else(|| {
3676        SeqError::new(
3677            file,
3678            node.line,
3679            1,
3680            "TextArea wymaga value: prop".to_string(),
3681        )
3682    })?;
3683    let sig = writable_str(file, value_arg.line, res, row, &value_arg.value, "value")?;
3684    let mut v = TextArea::new(sig.clone());
3685    for arg in &node.args {
3686        match arg.name.as_str() {
3687            "value" => {}
3688            "placeholder" => {
3689                if let Some(text) = opt_str(file, node, "placeholder", res, row)? {
3690                    v = v.placeholder(text);
3691                }
3692            }
3693            "max_length" => {
3694                if let Some(n) = count_val(file, node, res, row, "max_length")? {
3695                    v = v.max_length(n);
3696                }
3697            }
3698            "min_lines" => {
3699                if let Some(n) = count_val(file, node, res, row, "min_lines")? {
3700                    v = v.min_lines(n.max(1));
3701                }
3702            }
3703            "max_lines" => {
3704                if let Some(n) = count_val(file, node, res, row, "max_lines")? {
3705                    v = v.max_lines(n.max(1));
3706                }
3707            }
3708            "font_size" => v = v.font_size(num(file, arg, res, row)?),
3709            "width" => v = v.width(len(file, arg, res, row)?),
3710            "height" => v = v.height(len(file, arg, res, row)?),
3711            "min_width" => v = v.min_width(num(file, arg, res, row)?),
3712            "max_width" => v = v.max_width(num(file, arg, res, row)?),
3713            "min_height" => v = v.min_height(num(file, arg, res, row)?),
3714            "max_height" => v = v.max_height(num(file, arg, res, row)?),
3715            "flex" => {}
3716            "margin" => match margin_val(file, arg, res, row)? {
3717                MarginVal::All(n) => v = v.margin(n),
3718                MarginVal::Xy(x, y) => v = v.margin_xy(x, y),
3719            },
3720            "margin_x" => v = v.margin_x(num(file, arg, res, row)?),
3721            "margin_y" => v = v.margin_y(num(file, arg, res, row)?),
3722            "margin_left" => v = v.margin_left(num(file, arg, res, row)?),
3723            "margin_right" => v = v.margin_right(num(file, arg, res, row)?),
3724            "margin_top" => v = v.margin_top(num(file, arg, res, row)?),
3725            "margin_bottom" => v = v.margin_bottom(num(file, arg, res, row)?),
3726            "on_edit_end" => match res {
3727                Resolver::Static => {
3728                    return Err(SeqError::new(
3729                        file,
3730                        arg.line,
3731                        1,
3732                        "on_edit_end wymaga hosta Rust",
3733                    ));
3734                }
3735                Resolver::Host(b) => match &arg.value {
3736                    SeqValue::Callback { .. } | SeqValue::CallSeq { .. } => {
3737                        let live = LiveVal::Str(sig.clone());
3738                        let fire =
3739                            wire_live_action(file, arg.line, b, &arg.value, &live, "on_edit_end")?;
3740                        v = v.on_edit_end(move |_| fire());
3741                    }
3742                    _ => {
3743                        return Err(SeqError::new(
3744                            file,
3745                            arg.line,
3746                            1,
3747                            "on_edit_end: { akcja() } albo { akcja(value) }",
3748                        ));
3749                    }
3750                },
3751            },
3752            "indent" | "indent_x" | "indent_y" | "indent_left" | "indent_right" | "indent_top"
3753            | "indent_bottom" => {}
3754            _ => unreachable_checked(file, node, arg)?,
3755        }
3756    }
3757    Ok(AnyView::new(v))
3758}
3759
3760fn lower_form_field(
3761    file: &str,
3762    _theme: &Theme,
3763    node: &SeqNode,
3764    res: &Resolver<'_>,
3765    row: Option<&SeqRowData>,
3766) -> Result<AnyView, SeqError> {
3767    let value_arg = find_arg(node, "value")
3768        .ok_or_else(|| SeqError::new(file, node.line, 1, "Field wymaga value: prop".to_string()))?;
3769    let sig = writable_str(file, value_arg.line, res, row, &value_arg.value, "value")?;
3770    let label = label_arg(file, node, res, row, "Field")?;
3771    let mut v = Field::new(sig.clone(), label);
3772    for arg in &node.args {
3773        match arg.name.as_str() {
3774            "value" | "label" => {}
3775            "placeholder" => {
3776                if let Some(text) = opt_str(file, node, "placeholder", res, row)? {
3777                    v = v.placeholder(text);
3778                }
3779            }
3780            _ => unreachable_checked(file, node, arg)?,
3781        }
3782    }
3783    Ok(AnyView::new(v))
3784}
3785
3786fn lower_number_field(
3787    file: &str,
3788    _theme: &Theme,
3789    node: &SeqNode,
3790    res: &Resolver<'_>,
3791    row: Option<&SeqRowData>,
3792) -> Result<AnyView, SeqError> {
3793    let value_arg = find_arg(node, "value").ok_or_else(|| {
3794        SeqError::new(
3795            file,
3796            node.line,
3797            1,
3798            "NumberField wymaga value: prop".to_string(),
3799        )
3800    })?;
3801    let sig = bridge_f32(file, value_arg.line, res, row, &value_arg.value, "value")?;
3802    let label = label_arg(file, node, res, row, "NumberField")?;
3803    let mut v = NumberField::new(sig.clone(), label);
3804    let mut min = f32::NEG_INFINITY;
3805    let mut max = f32::INFINITY;
3806    for arg in &node.args {
3807        match arg.name.as_str() {
3808            "value" | "label" => {}
3809            "min" => min = num(file, arg, res, row)?,
3810            "max" => max = num(file, arg, res, row)?,
3811            "step" => v = v.step(num(file, arg, res, row)?),
3812            "precision" => {
3813                if let Some(n) = count_val(file, node, res, row, "precision")? {
3814                    v = v.precision(n);
3815                }
3816            }
3817            _ => unreachable_checked(file, node, arg)?,
3818        }
3819    }
3820    if min > max {
3821        return Err(SeqError::new(
3822            file,
3823            node.line,
3824            1,
3825            "NumberField wymaga min <= max",
3826        ));
3827    }
3828    v = v.min(min).max(max);
3829    Ok(AnyView::new(v))
3830}
3831
3832fn lower_date_field(
3833    file: &str,
3834    _theme: &Theme,
3835    node: &SeqNode,
3836    res: &Resolver<'_>,
3837    row: Option<&SeqRowData>,
3838) -> Result<AnyView, SeqError> {
3839    let value_arg = find_arg(node, "value").ok_or_else(|| {
3840        SeqError::new(
3841            file,
3842            node.line,
3843            1,
3844            "DateField wymaga value: prop".to_string(),
3845        )
3846    })?;
3847    let sig = writable_str(file, value_arg.line, res, row, &value_arg.value, "value")?;
3848    let label = label_arg(file, node, res, row, "DateField")?;
3849    let v = DateField::new(sig.clone(), label);
3850    for arg in &node.args {
3851        match arg.name.as_str() {
3852            "value" | "label" => {}
3853            _ => unreachable_checked(file, node, arg)?,
3854        }
3855    }
3856    Ok(AnyView::new(v))
3857}
3858
3859fn lower_multi_select(
3860    file: &str,
3861    _theme: &Theme,
3862    node: &SeqNode,
3863    res: &Resolver<'_>,
3864    row: Option<&SeqRowData>,
3865) -> Result<AnyView, SeqError> {
3866    let options = str_list_arg(file, node, "options")?;
3867    let label = label_arg(file, node, res, row, "MultiSelect")?;
3868    let mut v = MultiSelect::new(options.clone(), label);
3869    if let Some(n) = count_val(file, node, res, row, "min_selected")? {
3870        if n > options.len() {
3871            return Err(SeqError::new(
3872                file,
3873                node.line,
3874                1,
3875                format!(
3876                    "min_selected ({n}) over number of options ({})",
3877                    options.len()
3878                ),
3879            ));
3880        }
3881        v = v.min_selected(n);
3882    }
3883    for arg in &node.args {
3884        match arg.name.as_str() {
3885            "options" | "label" | "min_selected" => {}
3886            _ => unreachable_checked(file, node, arg)?,
3887        }
3888    }
3889    Ok(AnyView::new(v))
3890}
3891
3892fn lower_drag_value(
3893    file: &str,
3894    _theme: &Theme,
3895    node: &SeqNode,
3896    res: &Resolver<'_>,
3897    row: Option<&SeqRowData>,
3898) -> Result<AnyView, SeqError> {
3899    let value_arg = find_arg(node, "value").ok_or_else(|| {
3900        SeqError::new(
3901            file,
3902            node.line,
3903            1,
3904            "DragValue wymaga value: prop".to_string(),
3905        )
3906    })?;
3907    let sig = bridge_f32(file, value_arg.line, res, row, &value_arg.value, "value")?;
3908    let mut v = DragValue::new(sig.clone());
3909    let mut min = f32::NEG_INFINITY;
3910    let mut max = f32::INFINITY;
3911    for arg in &node.args {
3912        match arg.name.as_str() {
3913            "value" => {}
3914            "min" => min = num(file, arg, res, row)?,
3915            "max" => max = num(file, arg, res, row)?,
3916            "step" => v = v.step(num(file, arg, res, row)?),
3917            "precision" => {
3918                if let Some(n) = count_val(file, node, res, row, "precision")? {
3919                    v = v.precision(n);
3920                }
3921            }
3922            "width" => v = v.width(len(file, arg, res, row)?),
3923            "height" => v = v.height(len(file, arg, res, row)?),
3924            "min_width" => v = v.min_width(num(file, arg, res, row)?),
3925            "max_width" => v = v.max_width(num(file, arg, res, row)?),
3926            "min_height" => v = v.min_height(num(file, arg, res, row)?),
3927            "max_height" => v = v.max_height(num(file, arg, res, row)?),
3928            "flex" => {}
3929            "margin" => match margin_val(file, arg, res, row)? {
3930                MarginVal::All(n) => v = v.margin(n),
3931                MarginVal::Xy(x, y) => v = v.margin_xy(x, y),
3932            },
3933            "margin_x" => v = v.margin_x(num(file, arg, res, row)?),
3934            "margin_y" => v = v.margin_y(num(file, arg, res, row)?),
3935            "margin_left" => v = v.margin_left(num(file, arg, res, row)?),
3936            "margin_right" => v = v.margin_right(num(file, arg, res, row)?),
3937            "margin_top" => v = v.margin_top(num(file, arg, res, row)?),
3938            "margin_bottom" => v = v.margin_bottom(num(file, arg, res, row)?),
3939            "on_commit" => match res {
3940                Resolver::Static => {
3941                    return Err(SeqError::new(
3942                        file,
3943                        arg.line,
3944                        1,
3945                        "on_commit wymaga hosta Rust",
3946                    ));
3947                }
3948                Resolver::Host(b) => match &arg.value {
3949                    SeqValue::Callback { .. } | SeqValue::CallSeq { .. } => {
3950                        let live = LiveVal::F32(sig.clone());
3951                        let fire =
3952                            wire_live_action(file, arg.line, b, &arg.value, &live, "on_commit")?;
3953                        v = v.on_commit(move |_| fire());
3954                    }
3955                    _ => {
3956                        return Err(SeqError::new(
3957                            file,
3958                            arg.line,
3959                            1,
3960                            "on_commit: { akcja() } albo { akcja(value) }",
3961                        ));
3962                    }
3963                },
3964            },
3965            "indent" | "indent_x" | "indent_y" | "indent_left" | "indent_right" | "indent_top"
3966            | "indent_bottom" => {}
3967            _ => unreachable_checked(file, node, arg)?,
3968        }
3969    }
3970    if min > max {
3971        return Err(SeqError::new(
3972            file,
3973            node.line,
3974            1,
3975            "DragValue wymaga min <= max",
3976        ));
3977    }
3978    v = v.min(min).max(max);
3979    Ok(AnyView::new(v))
3980}
3981
3982fn lower_slider(
3983    file: &str,
3984    _theme: &Theme,
3985    node: &SeqNode,
3986    res: &Resolver<'_>,
3987    row: Option<&SeqRowData>,
3988) -> Result<AnyView, SeqError> {
3989    let value_arg = find_arg(node, "value").ok_or_else(|| {
3990        SeqError::new(file, node.line, 1, "Slider wymaga value: prop".to_string())
3991    })?;
3992    let sig = bridge_f32(file, value_arg.line, res, row, &value_arg.value, "value")?;
3993    let mut min = 0.0;
3994    let mut max = 1.0;
3995    if let Some(arg) = find_arg(node, "min") {
3996        min = num(file, arg, res, row)?;
3997    }
3998    if let Some(arg) = find_arg(node, "max") {
3999        max = num(file, arg, res, row)?;
4000    }
4001    if !(min.is_finite() && max.is_finite() && min < max) {
4002        return Err(SeqError::new(
4003            file,
4004            node.line,
4005            1,
4006            "Slider requires min < max (finished)",
4007        ));
4008    }
4009    let mut v = Slider1D::new(sig.clone(), min, max);
4010    for arg in &node.args {
4011        match arg.name.as_str() {
4012            "value" | "min" | "max" => {}
4013            "width" => v = v.width(len(file, arg, res, row)?),
4014            "height" => v = v.height(len(file, arg, res, row)?),
4015            "margin" => match margin_val(file, arg, res, row)? {
4016                MarginVal::All(n) => v = v.margin(n),
4017                MarginVal::Xy(x, y) => v = v.margin_xy(x, y),
4018            },
4019            "margin_x" => v = v.margin_x(num(file, arg, res, row)?),
4020            "margin_y" => v = v.margin_y(num(file, arg, res, row)?),
4021            "margin_left" => v = v.margin_left(num(file, arg, res, row)?),
4022            "margin_right" => v = v.margin_right(num(file, arg, res, row)?),
4023            "margin_top" => v = v.margin_top(num(file, arg, res, row)?),
4024            "margin_bottom" => v = v.margin_bottom(num(file, arg, res, row)?),
4025            "on_change" => match res {
4026                Resolver::Static => {
4027                    return Err(SeqError::new(
4028                        file,
4029                        arg.line,
4030                        1,
4031                        "on_change wymaga hosta Rust",
4032                    ));
4033                }
4034                Resolver::Host(b) => match &arg.value {
4035                    SeqValue::Callback { .. } | SeqValue::CallSeq { .. } => {
4036                        let live = LiveVal::F32(sig.clone());
4037                        let fire =
4038                            wire_live_action(file, arg.line, b, &arg.value, &live, "on_change")?;
4039                        v = v.on_commit(move |_| fire());
4040                    }
4041                    _ => {
4042                        return Err(SeqError::new(
4043                            file,
4044                            arg.line,
4045                            1,
4046                            "on_change: { akcja() } albo { akcja(value) }",
4047                        ));
4048                    }
4049                },
4050            },
4051            "flex" => {}
4052            "indent" | "indent_x" | "indent_y" | "indent_left" | "indent_right" | "indent_top"
4053            | "indent_bottom" => {}
4054            _ => unreachable_checked(file, node, arg)?,
4055        }
4056    }
4057    Ok(AnyView::new(v))
4058}
4059
4060fn lower_dropdown(
4061    file: &str,
4062    _theme: &Theme,
4063    node: &SeqNode,
4064    res: &Resolver<'_>,
4065    row: Option<&SeqRowData>,
4066) -> Result<AnyView, SeqError> {
4067    let sel_arg = find_arg(node, "selected").ok_or_else(|| {
4068        SeqError::new(
4069            file,
4070            node.line,
4071            1,
4072            "Dropdown wymaga selected: prop".to_string(),
4073        )
4074    })?;
4075    let sig = bridge_usize(file, sel_arg.line, res, row, &sel_arg.value, "selected")?;
4076    let items_arg = find_arg(node, "items").ok_or_else(|| {
4077        SeqError::new(
4078            file,
4079            node.line,
4080            1,
4081            "Dropdown wymaga items: [\"A\", \"B\"]".to_string(),
4082        )
4083    })?;
4084    let options = match &items_arg.value {
4085        SeqValue::Array(items) => items
4086            .iter()
4087            .map(|it| match it {
4088                SeqValue::Str(s) => Ok(s.clone()),
4089                _ => Err(SeqError::new(
4090                    file,
4091                    items_arg.line,
4092                    1,
4093                    "items: to [\"tekst\", ...]",
4094                )),
4095            })
4096            .collect::<Result<Vec<_>, _>>()?,
4097        _ => {
4098            return Err(SeqError::new(
4099                file,
4100                items_arg.line,
4101                1,
4102                "items: to [\"tekst\", ...]",
4103            ));
4104        }
4105    };
4106    let mut v = DropdownButton::new(sig.clone(), options);
4107    for arg in &node.args {
4108        match arg.name.as_str() {
4109            "selected" | "items" => {}
4110            "placeholder" => {
4111                if let Some(text) = opt_str(file, node, "placeholder", res, row)? {
4112                    v = v.placeholder(text);
4113                }
4114            }
4115            "font_size" => v = v.font_size(num(file, arg, res, row)?),
4116            "min_width" => v = v.min_width(num(file, arg, res, row)?),
4117            "max_width" => v = v.max_width(num(file, arg, res, row)?),
4118            "min_height" => v = v.min_height(num(file, arg, res, row)?),
4119            "max_height" => v = v.max_height(num(file, arg, res, row)?),
4120            "enabled" => v = v.disabled(!flag(file, arg, res, row)?),
4121            "margin" => match margin_val(file, arg, res, row)? {
4122                MarginVal::All(n) => v = v.margin(n),
4123                MarginVal::Xy(x, y) => v = v.margin_xy(x, y),
4124            },
4125            "margin_x" => v = v.margin_x(num(file, arg, res, row)?),
4126            "margin_y" => v = v.margin_y(num(file, arg, res, row)?),
4127            "margin_left" => v = v.margin_left(num(file, arg, res, row)?),
4128            "margin_right" => v = v.margin_right(num(file, arg, res, row)?),
4129            "margin_top" => v = v.margin_top(num(file, arg, res, row)?),
4130            "margin_bottom" => v = v.margin_bottom(num(file, arg, res, row)?),
4131            "on_select" => match res {
4132                Resolver::Static => {
4133                    return Err(SeqError::new(
4134                        file,
4135                        arg.line,
4136                        1,
4137                        "on_select wymaga hosta Rust",
4138                    ));
4139                }
4140                Resolver::Host(b) => match &arg.value {
4141                    SeqValue::Callback { .. } | SeqValue::CallSeq { .. } => {
4142                        let live = LiveVal::Usize(sig.clone());
4143                        let fire =
4144                            wire_live_action(file, arg.line, b, &arg.value, &live, "on_select")?;
4145                        v = v.on_select(move |_| fire());
4146                    }
4147                    _ => {
4148                        return Err(SeqError::new(
4149                            file,
4150                            arg.line,
4151                            1,
4152                            "on_select: { akcja() } albo { akcja(value) }",
4153                        ));
4154                    }
4155                },
4156            },
4157            "flex" => {}
4158            "indent" | "indent_x" | "indent_y" | "indent_left" | "indent_right" | "indent_top"
4159            | "indent_bottom" => {}
4160            _ => unreachable_checked(file, node, arg)?,
4161        }
4162    }
4163    Ok(AnyView::new(v))
4164}
4165
4166fn lower_progress(
4167    file: &str,
4168    _theme: &Theme,
4169    node: &SeqNode,
4170    res: &Resolver<'_>,
4171    row: Option<&SeqRowData>,
4172) -> Result<AnyView, SeqError> {
4173    let value_arg = find_arg(node, "value").ok_or_else(|| {
4174        SeqError::new(
4175            file,
4176            node.line,
4177            1,
4178            "Progress wymaga value: prop".to_string(),
4179        )
4180    })?;
4181    let sig = live_f32(file, value_arg.line, res, row, &value_arg.value, "value")?;
4182    let mut v = ProgressBar::new(sig);
4183    for arg in &node.args {
4184        match arg.name.as_str() {
4185            "value" => {}
4186            "height" => v = v.height(num(file, arg, res, row)?),
4187            "track" => v = v.track(color(file, _theme, arg)?),
4188            "fill" => v = v.fill(color(file, _theme, arg)?),
4189            "flex" => {}
4190            "indent" | "indent_x" | "indent_y" | "indent_left" | "indent_right" | "indent_top"
4191            | "indent_bottom" => {}
4192            _ => unreachable_checked(file, node, arg)?,
4193        }
4194    }
4195    Ok(AnyView::new(v))
4196}
4197
4198fn writable_bool(
4199    file: &str,
4200    line: usize,
4201    res: &Resolver<'_>,
4202    row: Option<&SeqRowData>,
4203    value: &SeqValue,
4204    what: &str,
4205) -> Result<Signal<bool>, SeqError> {
4206    match value {
4207        SeqValue::Bool(b) => Ok(Signal::new(*b)),
4208        SeqValue::Ident(prop) | SeqValue::Dynamic(prop) => match res {
4209            Resolver::Static => Err(SeqError::new(
4210                file,
4211                line,
4212                1,
4213                format!("'{what}' z propertu wymaga hosta"),
4214            )),
4215            Resolver::Host(b) => {
4216                if let Some(sig) = b.get_bool(prop) {
4217                    Ok(sig)
4218                } else if b.get_memo_bool(prop).is_some() {
4219                    Err(SeqError::new(
4220                        file,
4221                        line,
4222                        1,
4223                        format!("'{what}' z memo jest tylko do odczytu — podepnij prop Bool"),
4224                    ))
4225                } else {
4226                    Err(prop_err(file, line, prop, "Bool"))
4227                }
4228            }
4229        },
4230        SeqValue::Env(name) => match res {
4231            Resolver::Static => Err(SeqError::new(
4232                file,
4233                line,
4234                1,
4235                format!("'{what}' from environment requires host"),
4236            )),
4237            Resolver::Host(b) => b.get_env_bool(name).ok_or_else(|| {
4238                SeqError::new(
4239                    file,
4240                    line,
4241                    1,
4242                    format!("unknown env.{name} or wrong type (here you need: Bool)"),
4243                )
4244            }),
4245        },
4246        SeqValue::ItemField(field) => match res {
4247            Resolver::Static => Err(SeqError::new(
4248                file,
4249                line,
4250                1,
4251                format!("'{what}' z wiersza wymaga hosta"),
4252            )),
4253            Resolver::Host(_) => match row_value(file, line, row, field)? {
4254                RowVal::Sig(FieldSignal::Bool(sig)) => Ok(sig),
4255                _ => Err(SeqError::new(
4256                    file,
4257                    line,
4258                    1,
4259                    format!("pole wiersza '{field}' to nie Bool"),
4260                )),
4261            },
4262        },
4263        _ => Err(SeqError::new(
4264            file,
4265            line,
4266            1,
4267            format!("'{what}' to true/false albo nazwa propertu"),
4268        )),
4269    }
4270}
4271
4272fn writable_str(
4273    file: &str,
4274    line: usize,
4275    res: &Resolver<'_>,
4276    row: Option<&SeqRowData>,
4277    value: &SeqValue,
4278    what: &str,
4279) -> Result<Signal<String>, SeqError> {
4280    match value {
4281        SeqValue::Str(s) => Ok(Signal::new(s.clone())),
4282        SeqValue::Ident(prop) | SeqValue::Dynamic(prop) => match res {
4283            Resolver::Static => Err(SeqError::new(
4284                file,
4285                line,
4286                1,
4287                format!("'{what}' z propertu wymaga hosta"),
4288            )),
4289            Resolver::Host(b) => {
4290                if let Some(sig) = b.get_str(prop) {
4291                    Ok(sig)
4292                } else if b.get_memo_str(prop).is_some() {
4293                    Err(SeqError::new(
4294                        file,
4295                        line,
4296                        1,
4297                        format!("'{what}' z memo jest tylko do odczytu — podepnij prop String"),
4298                    ))
4299                } else {
4300                    Err(prop_err(file, line, prop, "String"))
4301                }
4302            }
4303        },
4304        SeqValue::Env(name) => match res {
4305            Resolver::Static => Err(SeqError::new(
4306                file,
4307                line,
4308                1,
4309                format!("'{what}' from environment requires host"),
4310            )),
4311            Resolver::Host(b) => b.get_env_str(name).ok_or_else(|| {
4312                SeqError::new(
4313                    file,
4314                    line,
4315                    1,
4316                    format!("unknown env.{name} or wrong type (here you need: String)"),
4317                )
4318            }),
4319        },
4320        SeqValue::ItemField(field) => match res {
4321            Resolver::Static => Err(SeqError::new(
4322                file,
4323                line,
4324                1,
4325                format!("'{what}' z wiersza wymaga hosta"),
4326            )),
4327            Resolver::Host(_) => match row_value(file, line, row, field)? {
4328                RowVal::Sig(FieldSignal::Str(sig)) => Ok(sig),
4329                _ => Err(SeqError::new(
4330                    file,
4331                    line,
4332                    1,
4333                    format!("pole wiersza '{field}' to tekst (String), nie '{field}'"),
4334                )),
4335            },
4336        },
4337        _ => Err(SeqError::new(
4338            file,
4339            line,
4340            1,
4341            format!("'{what}' to napis albo nazwa propertu"),
4342        )),
4343    }
4344}
4345
4346#[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)]
4347fn bridge_f32(
4348    file: &str,
4349    line: usize,
4350    res: &Resolver<'_>,
4351    row: Option<&SeqRowData>,
4352    value: &SeqValue,
4353    what: &str,
4354) -> Result<Signal<f32>, SeqError> {
4355    match value {
4356        SeqValue::Num(n) => Ok(Signal::new(*n)),
4357        SeqValue::Ident(prop) | SeqValue::Dynamic(prop) => match res {
4358            Resolver::Static => Err(SeqError::new(
4359                file,
4360                line,
4361                1,
4362                format!("'{what}' z propertu wymaga hosta"),
4363            )),
4364            Resolver::Host(b) => {
4365                if let Some(host) = b.get_float(prop) {
4366                    Ok(sync_f64_f32(&host))
4367                } else if let Some(host) = b.get_int(prop) {
4368                    Ok(sync_i64_f32(&host))
4369                } else if b.get_memo_float(prop).is_some() || b.get_memo_int(prop).is_some() {
4370                    Err(SeqError::new(
4371                        file,
4372                        line,
4373                        1,
4374                        format!("'{what}' z memo jest tylko do odczytu — podepnij prop Float/Int"),
4375                    ))
4376                } else {
4377                    Err(prop_err(file, line, prop, "Float/Int"))
4378                }
4379            }
4380        },
4381        SeqValue::Env(name) => match res {
4382            Resolver::Static => Err(SeqError::new(
4383                file,
4384                line,
4385                1,
4386                format!("'{what}' from environment requires host"),
4387            )),
4388            Resolver::Host(b) => {
4389                if let Some(host) = b.get_env_float(name) {
4390                    Ok(sync_f64_f32(&host))
4391                } else if let Some(host) = b.get_env_int(name) {
4392                    Ok(sync_i64_f32(&host))
4393                } else {
4394                    Err(SeqError::new(
4395                        file,
4396                        line,
4397                        1,
4398                        format!("unknown env.{name} or wrong type (here you need: Float/Int)"),
4399                    ))
4400                }
4401            }
4402        },
4403        SeqValue::ItemField(field) => match res {
4404            Resolver::Static => Err(SeqError::new(
4405                file,
4406                line,
4407                1,
4408                format!("'{what}' z wiersza wymaga hosta"),
4409            )),
4410            Resolver::Host(_) => match row_value(file, line, row, field)? {
4411                RowVal::Sig(FieldSignal::Float(host)) => Ok(sync_f64_f32(&host)),
4412                RowVal::Sig(FieldSignal::Int(host)) => Ok(sync_i64_f32(&host)),
4413                _ => Err(SeqError::new(
4414                    file,
4415                    line,
4416                    1,
4417                    format!("pole wiersza '{field}' to nie liczba (Float/Int)"),
4418                )),
4419            },
4420        },
4421        _ => Err(SeqError::new(
4422            file,
4423            line,
4424            1,
4425            format!("'{what}' to liczba albo nazwa propertu"),
4426        )),
4427    }
4428}
4429
4430#[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)]
4431fn sync_f64_f32(host: &Signal<f64>) -> Signal<f32> {
4432    let local = Signal::new(host.get_untracked() as f32);
4433    let (h2, l2) = (host.clone(), local.clone());
4434    effect(move || l2.set(h2.get() as f32)).forget();
4435    let (h3, l3) = (host.clone(), local.clone());
4436    effect(move || h3.set(l3.get() as f64)).forget();
4437    local
4438}
4439
4440#[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)]
4441fn sync_i64_f32(host: &Signal<i64>) -> Signal<f32> {
4442    let local = Signal::new(host.get_untracked() as f32);
4443    let (h2, l2) = (host.clone(), local.clone());
4444    effect(move || l2.set(h2.get() as f32)).forget();
4445    let (h3, l3) = (host.clone(), local.clone());
4446    effect(move || h3.set(l3.get() as i64)).forget();
4447    local
4448}
4449
4450#[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)]
4451fn live_f32(
4452    file: &str,
4453    line: usize,
4454    res: &Resolver<'_>,
4455    row: Option<&SeqRowData>,
4456    value: &SeqValue,
4457    what: &str,
4458) -> Result<Signal<f32>, SeqError> {
4459    match value {
4460        SeqValue::Num(n) => Ok(Signal::new(*n)),
4461        SeqValue::Ident(prop) | SeqValue::Dynamic(prop) => match res {
4462            Resolver::Static => Err(SeqError::new(
4463                file,
4464                line,
4465                1,
4466                format!("'{what}' z propertu wymaga hosta"),
4467            )),
4468            Resolver::Host(b) => {
4469                if let Some(host) = b.get_float(prop) {
4470                    let local = Signal::new(host.get_untracked() as f32);
4471                    let l2 = local.clone();
4472                    effect(move || l2.set(host.get() as f32)).forget();
4473                    Ok(local)
4474                } else if let Some(host) = b.get_int(prop) {
4475                    let local = Signal::new(host.get_untracked() as f32);
4476                    let l2 = local.clone();
4477                    effect(move || l2.set(host.get() as f32)).forget();
4478                    Ok(local)
4479                } else if let Some(m) = b.get_memo_float(prop) {
4480                    let local = Signal::new(m.get_untracked() as f32);
4481                    let l2 = local.clone();
4482                    effect(move || l2.set(m.get() as f32)).forget();
4483                    Ok(local)
4484                } else if let Some(m) = b.get_memo_int(prop) {
4485                    let local = Signal::new(m.get_untracked() as f32);
4486                    let l2 = local.clone();
4487                    effect(move || l2.set(m.get() as f32)).forget();
4488                    Ok(local)
4489                } else {
4490                    Err(prop_err(file, line, prop, "Float/Int"))
4491                }
4492            }
4493        },
4494        SeqValue::Env(name) => match res {
4495            Resolver::Static => Err(SeqError::new(
4496                file,
4497                line,
4498                1,
4499                format!("'{what}' from environment requires host"),
4500            )),
4501            Resolver::Host(b) => {
4502                if let Some(host) = b.get_env_float(name) {
4503                    let local = Signal::new(host.get_untracked() as f32);
4504                    let l2 = local.clone();
4505                    effect(move || l2.set(host.get() as f32)).forget();
4506                    Ok(local)
4507                } else if let Some(host) = b.get_env_int(name) {
4508                    let local = Signal::new(host.get_untracked() as f32);
4509                    let l2 = local.clone();
4510                    effect(move || l2.set(host.get() as f32)).forget();
4511                    Ok(local)
4512                } else {
4513                    Err(SeqError::new(
4514                        file,
4515                        line,
4516                        1,
4517                        format!("unknown env.{name} or wrong type (here you need: Float/Int)"),
4518                    ))
4519                }
4520            }
4521        },
4522        SeqValue::ItemField(field) => match res {
4523            Resolver::Static => Err(SeqError::new(
4524                file,
4525                line,
4526                1,
4527                format!("'{what}' z wiersza wymaga hosta"),
4528            )),
4529            Resolver::Host(_) => match row_value(file, line, row, field)? {
4530                RowVal::Sig(FieldSignal::Float(host)) => {
4531                    let local = Signal::new(host.get_untracked() as f32);
4532                    let l2 = local.clone();
4533                    effect(move || l2.set(host.get() as f32)).forget();
4534                    Ok(local)
4535                }
4536                RowVal::Sig(FieldSignal::Int(host)) => {
4537                    let local = Signal::new(host.get_untracked() as f32);
4538                    let l2 = local.clone();
4539                    effect(move || l2.set(host.get() as f32)).forget();
4540                    Ok(local)
4541                }
4542                _ => Err(SeqError::new(
4543                    file,
4544                    line,
4545                    1,
4546                    format!("pole wiersza '{field}' to nie liczba (Float/Int)"),
4547                )),
4548            },
4549        },
4550        _ => Err(SeqError::new(
4551            file,
4552            line,
4553            1,
4554            format!("'{what}' to liczba albo nazwa propertu"),
4555        )),
4556    }
4557}
4558
4559#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
4560fn bridge_usize(
4561    file: &str,
4562    line: usize,
4563    res: &Resolver<'_>,
4564    row: Option<&SeqRowData>,
4565    value: &SeqValue,
4566    what: &str,
4567) -> Result<Signal<usize>, SeqError> {
4568    match value {
4569        SeqValue::Num(n) => {
4570            if n.is_finite() && *n >= 0.0 {
4571                Ok(Signal::new(*n as usize))
4572            } else {
4573                Err(SeqError::new(
4574                    file,
4575                    line,
4576                    1,
4577                    format!("'{what}' to liczba >= 0"),
4578                ))
4579            }
4580        }
4581        SeqValue::Ident(prop) | SeqValue::Dynamic(prop) => match res {
4582            Resolver::Static => Err(SeqError::new(
4583                file,
4584                line,
4585                1,
4586                format!("'{what}' z propertu wymaga hosta"),
4587            )),
4588            Resolver::Host(b) => {
4589                if let Some(host) = b.get_int(prop) {
4590                    let local = Signal::new(host.get_untracked().max(0) as usize);
4591                    let (h2, l2) = (host.clone(), local.clone());
4592                    effect(move || l2.set(h2.get().max(0) as usize)).forget();
4593                    let (h3, l3) = (host.clone(), local.clone());
4594                    effect(move || h3.set(l3.get() as i64)).forget();
4595                    Ok(local)
4596                } else if b.get_memo_int(prop).is_some() {
4597                    Err(SeqError::new(
4598                        file,
4599                        line,
4600                        1,
4601                        format!("'{what}' z memo jest tylko do odczytu — podepnij prop Int"),
4602                    ))
4603                } else {
4604                    Err(prop_err(file, line, prop, "Int"))
4605                }
4606            }
4607        },
4608        SeqValue::Env(name) => match res {
4609            Resolver::Static => Err(SeqError::new(
4610                file,
4611                line,
4612                1,
4613                format!("'{what}' from environment requires host"),
4614            )),
4615            Resolver::Host(b) => {
4616                if let Some(host) = b.get_env_int(name) {
4617                    let local = Signal::new(host.get_untracked().max(0) as usize);
4618                    let (h2, l2) = (host.clone(), local.clone());
4619                    effect(move || l2.set(h2.get().max(0) as usize)).forget();
4620                    let (h3, l3) = (host.clone(), local.clone());
4621                    effect(move || h3.set(l3.get() as i64)).forget();
4622                    Ok(local)
4623                } else {
4624                    Err(SeqError::new(
4625                        file,
4626                        line,
4627                        1,
4628                        format!("unknown env.{name} or wrong type (here you need: Int)"),
4629                    ))
4630                }
4631            }
4632        },
4633        SeqValue::ItemField(field) => match res {
4634            Resolver::Static => Err(SeqError::new(
4635                file,
4636                line,
4637                1,
4638                format!("'{what}' z wiersza wymaga hosta"),
4639            )),
4640            Resolver::Host(_) => match row_value(file, line, row, field)? {
4641                RowVal::Sig(FieldSignal::Int(host)) => {
4642                    let local = Signal::new(host.get_untracked().max(0) as usize);
4643                    let (h2, l2) = (host.clone(), local.clone());
4644                    effect(move || l2.set(h2.get().max(0) as usize)).forget();
4645                    let (h3, l3) = (host.clone(), local.clone());
4646                    effect(move || h3.set(l3.get() as i64)).forget();
4647                    Ok(local)
4648                }
4649                _ => Err(SeqError::new(
4650                    file,
4651                    line,
4652                    1,
4653                    format!("pole wiersza '{field}' to nie Int"),
4654                )),
4655            },
4656        },
4657        _ => Err(SeqError::new(
4658            file,
4659            line,
4660            1,
4661            format!("'{what}' to liczba albo nazwa propertu"),
4662        )),
4663    }
4664}
4665
4666#[derive(Clone)]
4667enum LiveVal {
4668    Bool(Signal<bool>),
4669    Str(Signal<String>),
4670    Usize(Signal<usize>),
4671    F32(Signal<f32>),
4672}
4673
4674impl LiveVal {
4675    #[allow(clippy::cast_possible_truncation)]
4676    fn current(&self) -> Value {
4677        match self {
4678            Self::Bool(s) => Value::Bool(s.get_untracked()),
4679            Self::Str(s) => Value::Str(Rc::new(s.get_untracked())),
4680            Self::Usize(s) => Value::Int(s.get_untracked() as i64),
4681            Self::F32(s) => Value::Float(f64::from(s.get_untracked())),
4682        }
4683    }
4684}
4685
4686#[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)]
4687fn wire_live_action(
4688    file: &str,
4689    line: usize,
4690    b: &HostBindings,
4691    value: &SeqValue,
4692    live: &LiveVal,
4693    what: &str,
4694) -> Result<Rc<dyn Fn()>, SeqError> {
4695    if let SeqValue::CallSeq { name, args } = value {
4696        if args.is_empty() {
4697            return seq_fire(file, line, b, name, Vec::new());
4698        }
4699        if args.len() == 1 && matches!(&args[0], SeqValue::Ident(n) if n == "value") {
4700            return seq_fire_live(file, line, b, name, live);
4701        }
4702        return Err(SeqError::new(
4703            file,
4704            line,
4705            1,
4706            format!("'{what}' to {{ fn() }} albo {{ fn(value) }}"),
4707        ));
4708    }
4709    let SeqValue::Callback { action, args } = value else {
4710        return Err(SeqError::new(
4711            file,
4712            line,
4713            1,
4714            format!("'{what}': {{ akcja() }}"),
4715        ));
4716    };
4717    if args.is_empty() {
4718        return b.get_action0(action).ok_or_else(|| {
4719            SeqError::new(
4720                file,
4721                line,
4722                1,
4723                format!("unknown callback '{action}' (missing on host)"),
4724            )
4725        });
4726    }
4727    if args.len() == 1 && matches!(&args[0], SeqValue::Ident(name) if name == "value") {
4728        return match live {
4729            LiveVal::Bool(sig) => {
4730                let f = b.get_action_bool(action).ok_or_else(|| {
4731                    SeqError::new(
4732                        file,
4733                        line,
4734                        1,
4735                        format!("callback '{action}' called from Bool and the host does not have it (on_bool)"),
4736                    )
4737                })?;
4738                let sig = sig.clone();
4739                Ok(Rc::new(move || f(sig.get())))
4740            }
4741            LiveVal::Str(sig) => {
4742                let f = b.get_action_str(action).ok_or_else(|| {
4743                    SeqError::new(
4744                        file,
4745                        line,
4746                        1,
4747                        format!("callback '{action}' called with text, but the host does not have it (on_str)"),
4748                    )
4749                })?;
4750                let sig = sig.clone();
4751                Ok(Rc::new(move || f(sig.get())))
4752            }
4753            LiveVal::Usize(sig) => {
4754                let f = b.get_action_int(action).ok_or_else(|| {
4755                    SeqError::new(
4756                        file,
4757                        line,
4758                        1,
4759                        format!(
4760                            "callback '{action}' called from Int and host doesn't have it (on_int)"
4761                        ),
4762                    )
4763                })?;
4764                let sig = sig.clone();
4765                Ok(Rc::new(move || f(sig.get_untracked() as i64)))
4766            }
4767            LiveVal::F32(sig) => {
4768                let f = b.get_action_float(action).ok_or_else(|| {
4769                    SeqError::new(
4770                        file,
4771                        line,
4772                        1,
4773                        format!("callback '{action}' called from Float and host does not have it (on_float)"),
4774                    )
4775                })?;
4776                let sig = sig.clone();
4777                Ok(Rc::new(move || f(f64::from(sig.get()))))
4778            }
4779        };
4780    }
4781    Err(SeqError::new(
4782        file,
4783        line,
4784        1,
4785        format!("'{what}' to {{ akcja() }} albo {{ akcja(value) }}"),
4786    ))
4787}
4788
4789fn display_of(
4790    file: &str,
4791    line: usize,
4792    b: &HostBindings,
4793    name: &str,
4794) -> Result<Rc<dyn Fn() -> String>, SeqError> {
4795    if let Some(sig) = b.get_str(name) {
4796        Ok(Rc::new(move || sig.get()))
4797    } else if let Some(m) = b.get_memo_str(name) {
4798        Ok(Rc::new(move || m.get()))
4799    } else if let Some(sig) = b.get_bool(name) {
4800        Ok(Rc::new(move || sig.get().to_string()))
4801    } else if let Some(m) = b.get_memo_bool(name) {
4802        Ok(Rc::new(move || m.get().to_string()))
4803    } else if let Some(sig) = b.get_int(name) {
4804        Ok(Rc::new(move || sig.get().to_string()))
4805    } else if let Some(m) = b.get_memo_int(name) {
4806        Ok(Rc::new(move || m.get().to_string()))
4807    } else if let Some(sig) = b.get_float(name) {
4808        Ok(Rc::new(move || sig.get().to_string()))
4809    } else if let Some(m) = b.get_memo_float(name) {
4810        Ok(Rc::new(move || m.get().to_string()))
4811    } else if let Some(sig) = b.get_env_str(name) {
4812        Ok(Rc::new(move || sig.get()))
4813    } else if let Some(sig) = b.get_env_bool(name) {
4814        Ok(Rc::new(move || sig.get().to_string()))
4815    } else if let Some(sig) = b.get_env_int(name) {
4816        Ok(Rc::new(move || sig.get().to_string()))
4817    } else if let Some(sig) = b.get_env_float(name) {
4818        Ok(Rc::new(move || sig.get().to_string()))
4819    } else {
4820        Err(SeqError::new(
4821            file,
4822            line,
4823            1,
4824            format!("unknown prop '{name}' (missing on host)"),
4825        ))
4826    }
4827}
4828
4829fn display_env_of(
4830    file: &str,
4831    line: usize,
4832    b: &HostBindings,
4833    name: &str,
4834) -> Result<Rc<dyn Fn() -> String>, SeqError> {
4835    if let Some(sig) = b.get_env_str(name) {
4836        Ok(Rc::new(move || sig.get()))
4837    } else if let Some(sig) = b.get_env_bool(name) {
4838        Ok(Rc::new(move || sig.get().to_string()))
4839    } else if let Some(sig) = b.get_env_int(name) {
4840        Ok(Rc::new(move || sig.get().to_string()))
4841    } else if let Some(sig) = b.get_env_float(name) {
4842        Ok(Rc::new(move || sig.get().to_string()))
4843    } else {
4844        Err(SeqError::new(
4845            file,
4846            line,
4847            1,
4848            format!("unknown env.{name} (missing on host: HostBindings::env_..)"),
4849        ))
4850    }
4851}
4852
4853fn display_field(
4854    file: &str,
4855    line: usize,
4856    row: Option<&SeqRowData>,
4857    field: &str,
4858) -> Result<Rc<dyn Fn() -> String>, SeqError> {
4859    match row_value(file, line, row, field)? {
4860        RowVal::Sig(FieldSignal::Str(sig)) => Ok(Rc::new(move || sig.get())),
4861        RowVal::Sig(FieldSignal::Bool(sig)) => Ok(Rc::new(move || sig.get().to_string())),
4862        RowVal::Sig(FieldSignal::Int(sig)) => Ok(Rc::new(move || sig.get().to_string())),
4863        RowVal::Sig(FieldSignal::Float(sig)) => Ok(Rc::new(move || sig.get().to_string())),
4864        RowVal::Id(id) => Ok(Rc::new(move || id.to_string())),
4865    }
4866}
4867
4868enum ArgVal {
4869    Int(i64),
4870    Float(f64),
4871    Str(String),
4872    Bool(bool),
4873}
4874
4875impl ArgVal {
4876    fn to_value(&self) -> Value {
4877        match self {
4878            Self::Int(i) => Value::Int(*i),
4879            Self::Float(f) => Value::Float(*f),
4880            Self::Str(s) => Value::Str(Rc::new(s.clone())),
4881            Self::Bool(b) => Value::Bool(*b),
4882        }
4883    }
4884}
4885
4886fn arg_val(
4887    file: &str,
4888    line: usize,
4889    arg: &SeqValue,
4890    row: Option<&SeqRowData>,
4891) -> Result<ArgVal, SeqError> {
4892    match arg {
4893        SeqValue::Num(n) => {
4894            if n.fract() == 0.0 {
4895                #[allow(clippy::cast_possible_truncation)]
4896                Ok(ArgVal::Int(*n as i64))
4897            } else {
4898                Ok(ArgVal::Float(f64::from(*n)))
4899            }
4900        }
4901        SeqValue::Str(s) => Ok(ArgVal::Str(s.clone())),
4902        SeqValue::Bool(x) => Ok(ArgVal::Bool(*x)),
4903        SeqValue::ItemField(field) => match row_value(file, line, row, field)? {
4904            RowVal::Id(id) => Ok(ArgVal::Int(id)),
4905            RowVal::Sig(FieldSignal::Int(sig)) => Ok(ArgVal::Int(sig.get_untracked())),
4906            RowVal::Sig(FieldSignal::Float(sig)) => Ok(ArgVal::Float(sig.get_untracked())),
4907            RowVal::Sig(FieldSignal::Str(sig)) => Ok(ArgVal::Str(sig.get_untracked())),
4908            RowVal::Sig(FieldSignal::Bool(sig)) => Ok(ArgVal::Bool(sig.get_untracked())),
4909        },
4910        _ => Err(SeqError::new(
4911            file,
4912            line,
4913            1,
4914            "argument to liczba/tekst/Bool/value/item.pole".to_string(),
4915        )),
4916    }
4917}
4918
4919fn seq_target(
4920    file: &str,
4921    line: usize,
4922    b: &HostBindings,
4923    name: &str,
4924) -> Result<(Rc<crate::eval::SeqRuntime>, HostBindings, String), SeqError> {
4925    let rt = b.get_seq().ok_or_else(|| {
4926        SeqError::new(
4927            file,
4928            line,
4929            1,
4930            format!("funkcja '{name}' wymaga hosta (lower_with z runtime .seq)"),
4931        )
4932    })?;
4933    if !rt.has_fn(name) {
4934        return Err(SeqError::new(
4935            file,
4936            line,
4937            1,
4938            format!("nieznana funkcja '{name}'"),
4939        ));
4940    }
4941    Ok((rt, b.clone(), name.to_string()))
4942}
4943
4944fn seq_fire(
4945    file: &str,
4946    line: usize,
4947    b: &HostBindings,
4948    name: &str,
4949    const_args: Vec<Value>,
4950) -> Result<Rc<dyn Fn()>, SeqError> {
4951    let (rt, bb, n) = seq_target(file, line, b, name)?;
4952    Ok(Rc::new(move || {
4953        if let Err(e) = rt.call(&bb, &n, const_args.clone()) {
4954            eprintln!("seq: {e}");
4955        }
4956    }))
4957}
4958
4959fn seq_fire_live(
4960    file: &str,
4961    line: usize,
4962    b: &HostBindings,
4963    name: &str,
4964    live: &LiveVal,
4965) -> Result<Rc<dyn Fn()>, SeqError> {
4966    let (rt, bb, n) = seq_target(file, line, b, name)?;
4967    let live = live.clone();
4968    Ok(Rc::new(move || {
4969        let v = live.current();
4970        if let Err(e) = rt.call(&bb, &n, vec![v]) {
4971            eprintln!("seq: {e}");
4972        }
4973    }))
4974}
4975
4976fn seq_fire_pt(
4977    file: &str,
4978    line: usize,
4979    b: &HostBindings,
4980    name: &str,
4981) -> Result<Rc<dyn Fn(f64, f64)>, SeqError> {
4982    let (rt, bb, n) = seq_target(file, line, b, name)?;
4983    Ok(Rc::new(move |x, y| {
4984        if let Err(e) = rt.call(&bb, &n, vec![Value::Float(x), Value::Float(y)]) {
4985            eprintln!("seq: {e}");
4986        }
4987    }))
4988}
4989
4990fn seq_fire_name(
4991    file: &str,
4992    line: usize,
4993    b: &HostBindings,
4994    name: &str,
4995) -> Result<Rc<dyn Fn(f64, f64, String)>, SeqError> {
4996    let (rt, bb, n) = seq_target(file, line, b, name)?;
4997    Ok(Rc::new(move |_x, _y, hit: String| {
4998        if let Err(e) = rt.call(&bb, &n, vec![Value::Str(Rc::new(hit))]) {
4999            eprintln!("seq: {e}");
5000        }
5001    }))
5002}
5003
5004fn seq_fire_pt_named(
5005    file: &str,
5006    line: usize,
5007    b: &HostBindings,
5008    name: &str,
5009) -> Result<Rc<dyn Fn(f64, f64, String)>, SeqError> {
5010    let (rt, bb, n) = seq_target(file, line, b, name)?;
5011    Ok(Rc::new(move |x, y, hit: String| {
5012        if let Err(e) = rt.call(
5013            &bb,
5014            &n,
5015            vec![Value::Float(x), Value::Float(y), Value::Str(Rc::new(hit))],
5016        ) {
5017            eprintln!("seq: {e}");
5018        }
5019    }))
5020}
5021
5022fn seq_fire_wheel(
5023    file: &str,
5024    line: usize,
5025    b: &HostBindings,
5026    name: &str,
5027) -> Result<crate::bindings::WheelAction, SeqError> {
5028    let (rt, bb, n) = seq_target(file, line, b, name)?;
5029    Ok(Rc::new(move |x, y, d| {
5030        if let Err(e) = rt.call(
5031            &bb,
5032            &n,
5033            vec![Value::Float(x), Value::Float(y), Value::Float(d)],
5034        ) {
5035            eprintln!("seq: {e}");
5036        }
5037    }))
5038}
5039
5040fn wire_action(
5041    file: &str,
5042    line: usize,
5043    b: &HostBindings,
5044    value: &SeqValue,
5045    row: Option<&SeqRowData>,
5046) -> Result<Rc<dyn Fn()>, SeqError> {
5047    if let SeqValue::CallSeq { name, args } = value {
5048        if args.len() > 1 {
5049            return Err(SeqError::new(
5050                file,
5051                line,
5052                1,
5053                format!("function '{name}' with 2+ arguments - later"),
5054            ));
5055        }
5056        if args.is_empty() {
5057            return seq_fire(file, line, b, name, Vec::new());
5058        }
5059        let arg = arg_val(file, line, &args[0], row)?;
5060        return seq_fire(file, line, b, name, vec![arg.to_value()]);
5061    }
5062    let SeqValue::Callback { action, args } = value else {
5063        return Err(SeqError::new(file, line, 1, "{ akcja(args) }"));
5064    };
5065    if args.len() > 1 {
5066        return Err(SeqError::new(
5067            file,
5068            line,
5069            1,
5070            format!("callback '{action}' with 2+ arguments - later"),
5071        ));
5072    }
5073    if args.is_empty() {
5074        let f = b.get_action0(action).ok_or_else(|| {
5075            SeqError::new(
5076                file,
5077                line,
5078                1,
5079                format!("unknown callback '{action}' (missing on host)"),
5080            )
5081        })?;
5082        return Ok(Rc::new(move || f()));
5083    }
5084    let arg = arg_val(file, line, &args[0], row)?;
5085    match arg {
5086        ArgVal::Int(i) => {
5087            if let Some(f) = b.get_action_int(action) {
5088                return Ok(Rc::new(move || f(i)));
5089            }
5090
5091            #[allow(clippy::cast_precision_loss)]
5092            let x = i as f64;
5093            if let Some(f) = b.get_action_float(action) {
5094                return Ok(Rc::new(move || f(x)));
5095            }
5096            Err(SeqError::new(
5097                file,
5098                line,
5099                1,
5100                format!(
5101                    "callback '{action}' called from Int and host doesn't have it (on_int/on_float)"
5102                ),
5103            ))
5104        }
5105        ArgVal::Float(x) => {
5106            let f = b.get_action_float(action).ok_or_else(|| {
5107                SeqError::new(
5108                    file,
5109                    line,
5110                    1,
5111                    format!(
5112                        "callback '{action}' called from Float and host does not have it (on_float)"
5113                    ),
5114                )
5115            })?;
5116            Ok(Rc::new(move || f(x)))
5117        }
5118        ArgVal::Str(s) => {
5119            let f = b.get_action_str(action).ok_or_else(|| {
5120                SeqError::new(
5121                    file,
5122                    line,
5123                    1,
5124                    format!("callback '{action}' called with text, but the host does not have it (on_str)"),
5125                )
5126            })?;
5127            Ok(Rc::new(move || f(s.clone())))
5128        }
5129        ArgVal::Bool(x) => {
5130            let f = b.get_action_bool(action).ok_or_else(|| {
5131                SeqError::new(
5132                    file,
5133                    line,
5134                    1,
5135                    format!("callback '{action}' called from Bool and the host does not have it (on_bool)"),
5136                )
5137            })?;
5138            Ok(Rc::new(move || f(x)))
5139        }
5140    }
5141}
5142
5143fn lower_list(
5144    file: &str,
5145    theme: &Theme,
5146    styles: &BuiltStyles,
5147    node: &SeqNode,
5148    res: &Resolver<'_>,
5149) -> Result<AnyView, SeqError> {
5150    let items = find_arg(node, "items").ok_or_else(|| {
5151        SeqError::new(
5152            file,
5153            node.line,
5154            1,
5155            "List wymaga items: nazwa_propertu".to_string(),
5156        )
5157    })?;
5158    let items_prop = match &items.value {
5159        SeqValue::Ident(name) => name.clone(),
5160        _ => {
5161            return Err(SeqError::new(
5162                file,
5163                items.line,
5164                1,
5165                "items: to nazwa propertu z hosta (items: warstwy)",
5166            ));
5167        }
5168    };
5169    if let Some(key) = find_arg(node, "key") {
5170        match &key.value {
5171            SeqValue::Ident(name) if name == "id" => {}
5172            _ => {
5173                return Err(SeqError::new(
5174                    file,
5175                    key.line,
5176                    1,
5177                    "key: tylko id w inkrementcie 3",
5178                ));
5179            }
5180        }
5181    }
5182    let bindings = match res {
5183        Resolver::Static => {
5184            return Err(SeqError::new(
5185                file,
5186                node.line,
5187                1,
5188                "List(items:) wymaga hosta Rust (HostBindings::rows())",
5189            ));
5190        }
5191        Resolver::Host(b) => (*b).clone(),
5192    };
5193    let get_rows = bindings.get_rows(&items_prop).ok_or_else(|| {
5194        SeqError::new(
5195            file,
5196            items.line,
5197            1,
5198            format!("nieznany prop '{items_prop}' (HostBindings::rows())"),
5199        )
5200    })?;
5201    let template = node.children.clone();
5202    let file_owned = file.to_string();
5203    let theme_owned = *theme;
5204    let styles_owned = styles.clone();
5205    let list = sequoia_view::List::new(move || get_rows())
5206        .key(|row: &SeqRowData| {
5207            #[allow(clippy::cast_sign_loss)]
5208            let _ = row.id as u64;
5209            row.id as u64
5210        })
5211        .item(move |row: &SeqRowData| {
5212            let res = Resolver::Host(&bindings);
5213            match lower_row_template(
5214                &file_owned,
5215                &theme_owned,
5216                &styles_owned,
5217                &res,
5218                row,
5219                &template,
5220            ) {
5221                Ok(v) => v,
5222                Err(e) => AnyView::new(Text::new(format!("seq: {e}"))),
5223            }
5224        });
5225    Ok(AnyView::new(list))
5226}
5227
5228#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
5229fn lower_virtual_list(
5230    file: &str,
5231    theme: &Theme,
5232    styles: &BuiltStyles,
5233    node: &SeqNode,
5234    res: &Resolver<'_>,
5235    row: Option<&SeqRowData>,
5236) -> Result<AnyView, SeqError> {
5237    let items = find_arg(node, "items").ok_or_else(|| {
5238        SeqError::new(
5239            file,
5240            node.line,
5241            1,
5242            "VirtualList wymaga items: nazwa_propertu".to_string(),
5243        )
5244    })?;
5245    let items_prop = match &items.value {
5246        SeqValue::Ident(name) => name.clone(),
5247        _ => {
5248            return Err(SeqError::new(
5249                file,
5250                items.line,
5251                1,
5252                "items: to nazwa propertu z hosta (items: warstwy)",
5253            ));
5254        }
5255    };
5256    if let Some(key) = find_arg(node, "key") {
5257        match &key.value {
5258            SeqValue::Ident(name) if name == "id" => {}
5259            _ => {
5260                return Err(SeqError::new(
5261                    file,
5262                    key.line,
5263                    1,
5264                    "key: tylko id w inkrementcie 3",
5265                ));
5266            }
5267        }
5268    }
5269    let mut item_height: Option<f32> = None;
5270    if let Some(arg) = find_arg(node, "item_height") {
5271        let h = num(file, arg, res, row)?;
5272        if !h.is_finite() || h <= 0.0 {
5273            return Err(SeqError::new(
5274                file,
5275                arg.line,
5276                1,
5277                "'item_height' to dodatnia liczba",
5278            ));
5279        }
5280        item_height = Some(h);
5281    }
5282
5283    let mut overscan: Option<usize> = None;
5284    if let Some(arg) = find_arg(node, "overscan") {
5285        let n = num(file, arg, res, row)?;
5286        if !n.is_finite() || n < 0.0 {
5287            return Err(SeqError::new(
5288                file,
5289                arg.line,
5290                1,
5291                "'overscan' to liczba >= 0",
5292            ));
5293        }
5294        overscan = Some(n as usize);
5295    }
5296    let bindings = match res {
5297        Resolver::Static => {
5298            return Err(SeqError::new(
5299                file,
5300                node.line,
5301                1,
5302                "VirtualList(items:) wymaga hosta Rust (HostBindings::rows())",
5303            ));
5304        }
5305        Resolver::Host(b) => (*b).clone(),
5306    };
5307    let get_rows = bindings.get_rows(&items_prop).ok_or_else(|| {
5308        SeqError::new(
5309            file,
5310            items.line,
5311            1,
5312            format!("nieznany prop '{items_prop}' (HostBindings::rows())"),
5313        )
5314    })?;
5315    let template = node.children.clone();
5316    let file_owned = file.to_string();
5317    let theme_owned = *theme;
5318    let styles_owned = styles.clone();
5319    let mut list = VirtualList::new(move || get_rows())
5320        .key(|row: &SeqRowData| {
5321            #[allow(clippy::cast_sign_loss)]
5322            let _ = row.id as u64;
5323            row.id as u64
5324        })
5325        .item(move |row: &SeqRowData| {
5326            let res = Resolver::Host(&bindings);
5327            match lower_row_template(
5328                &file_owned,
5329                &theme_owned,
5330                &styles_owned,
5331                &res,
5332                row,
5333                &template,
5334            ) {
5335                Ok(v) => v,
5336                Err(e) => AnyView::new(Text::new(format!("seq: {e}"))),
5337            }
5338        });
5339    if let Some(h) = item_height {
5340        list = list.item_height(h);
5341    }
5342    if let Some(n) = overscan {
5343        list = list.overscan(n);
5344    }
5345    Ok(AnyView::new(list))
5346}
5347
5348fn lower_row_template(
5349    file: &str,
5350    theme: &Theme,
5351    styles: &BuiltStyles,
5352    res: &Resolver<'_>,
5353    row: &SeqRowData,
5354    template: &[SeqNode],
5355) -> Result<AnyView, SeqError> {
5356    match template {
5357        [] => Ok(AnyView::new(Column::new())),
5358        [single] => lower_node(file, theme, styles, single, res, Some(row)),
5359        _ => {
5360            let mut col = Column::new();
5361            for n in template {
5362                col = col.child(lower_node(file, theme, styles, n, res, Some(row))?);
5363            }
5364            Ok(AnyView::new(col))
5365        }
5366    }
5367}
5368
5369#[derive(Clone)]
5370enum NumSrc {
5371    Lit(f32),
5372    Dyn(Rc<dyn Fn() -> f32>),
5373}
5374
5375impl NumSrc {
5376    fn val(&self) -> f32 {
5377        match self {
5378            Self::Lit(v) => *v,
5379            Self::Dyn(f) => f(),
5380        }
5381    }
5382
5383    fn touch(&self) {
5384        if let Self::Dyn(f) = self {
5385            f();
5386        }
5387    }
5388
5389    fn touch_rc(&self) -> Rc<dyn Fn()> {
5390        let this = self.clone();
5391        Rc::new(move || this.touch())
5392    }
5393}
5394
5395#[derive(Clone)]
5396enum BoolSrc {
5397    Lit(bool),
5398    Dyn(Rc<dyn Fn() -> bool>),
5399}
5400
5401impl BoolSrc {
5402    fn val(&self) -> bool {
5403        match self {
5404            Self::Lit(v) => *v,
5405            Self::Dyn(f) => f(),
5406        }
5407    }
5408
5409    fn touch(&self) {
5410        if let Self::Dyn(f) = self {
5411            f();
5412        }
5413    }
5414
5415    fn touch_rc(&self) -> Rc<dyn Fn()> {
5416        let this = self.clone();
5417        Rc::new(move || this.touch())
5418    }
5419}
5420
5421#[derive(Clone)]
5422struct PtSrc {
5423    x: NumSrc,
5424    y: NumSrc,
5425}
5426
5427impl PtSrc {
5428    fn val(&self) -> Point {
5429        Point::new(self.x.val(), self.y.val())
5430    }
5431
5432    fn touch(&self) {
5433        self.x.touch();
5434        self.y.touch();
5435    }
5436
5437    fn touch_rc(&self) -> Rc<dyn Fn()> {
5438        let this = self.clone();
5439        Rc::new(move || this.touch())
5440    }
5441}
5442
5443#[derive(Clone)]
5444struct RectSrc {
5445    x: NumSrc,
5446    y: NumSrc,
5447    w: NumSrc,
5448    h: NumSrc,
5449}
5450
5451impl RectSrc {
5452    fn val(&self) -> Rect {
5453        Rect::from_xywh(self.x.val(), self.y.val(), self.w.val(), self.h.val())
5454    }
5455
5456    fn touch(&self) {
5457        self.x.touch();
5458        self.y.touch();
5459        self.w.touch();
5460        self.h.touch();
5461    }
5462
5463    fn touch_rc(&self) -> Rc<dyn Fn()> {
5464        let this = self.clone();
5465        Rc::new(move || this.touch())
5466    }
5467}
5468
5469#[derive(Clone)]
5470struct ShapeDraw {
5471    draw: Rc<dyn Fn(&mut Painter)>,
5472    touch: Rc<dyn Fn()>,
5473    hits: Vec<HitTest>,
5474}
5475
5476type HitTest = (String, Rc<dyn Fn(Point) -> bool>);
5477
5478fn dist_seg(p: Point, a: Point, b: Point) -> f32 {
5479    let abx = b.x - a.x;
5480    let aby = b.y - a.y;
5481    let len2 = abx * abx + aby * aby;
5482    if len2 <= f32::EPSILON {
5483        return ((p.x - a.x).powi(2) + (p.y - a.y).powi(2)).sqrt();
5484    }
5485    let t = (((p.x - a.x) * abx + (p.y - a.y) * aby) / len2).clamp(0.0, 1.0);
5486    let dx = p.x - (a.x + abx * t);
5487    let dy = p.y - (a.y + aby * t);
5488    (dx * dx + dy * dy).sqrt()
5489}
5490
5491fn point_in_poly(p: Point, pts: &[Point]) -> bool {
5492    let mut inside = false;
5493    let n = pts.len();
5494    let mut j = n.saturating_sub(1);
5495    for i in 0..n {
5496        let a = pts[i];
5497        let b = pts[j];
5498        if (a.y > p.y) != (b.y > p.y) && p.x < (b.x - a.x) * (p.y - a.y) / (b.y - a.y) + a.x {
5499            inside = !inside;
5500        }
5501        j = i;
5502    }
5503    inside
5504}
5505
5506fn cubic(p0: Point, c0: Point, c1: Point, p1: Point, t: f32) -> Point {
5507    let u = 1.0 - t;
5508    Point::new(
5509        u * u * u * p0.x + 3.0 * u * u * t * c0.x + 3.0 * u * t * t * c1.x + t * t * t * p1.x,
5510        u * u * u * p0.y + 3.0 * u * u * t * c0.y + 3.0 * u * t * t * c1.y + t * t * t * p1.y,
5511    )
5512}
5513
5514fn dyn_f32(
5515    file: &str,
5516    line: usize,
5517    res: &Resolver<'_>,
5518    row: Option<&SeqRowData>,
5519    value: &SeqValue,
5520    what: &str,
5521) -> Result<NumSrc, SeqError> {
5522    match value {
5523        SeqValue::Num(n) => {
5524            if n.is_finite() {
5525                Ok(NumSrc::Lit(*n))
5526            } else {
5527                Err(SeqError::new(
5528                    file,
5529                    line,
5530                    1,
5531                    format!("'{what}' must be finished"),
5532                ))
5533            }
5534        }
5535        SeqValue::Ident(prop) => match res {
5536            Resolver::Static => Err(SeqError::new(
5537                file,
5538                line,
5539                1,
5540                format!("'{what}' z propertu wymaga hosta — inkrement 2"),
5541            )),
5542            Resolver::Host(b) => {
5543                if let Some(cell) = b.get_tick_angle(prop) {
5544                    return Ok(NumSrc::Dyn(Rc::new(move || cell.get())));
5545                }
5546                if let Some(sig) = b.get_float(prop) {
5547                    Ok(NumSrc::Dyn(Rc::new(move || sig.get() as f32)))
5548                } else if let Some(sig) = b.get_int(prop) {
5549                    #[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)]
5550                    let _ = ();
5551                    Ok(NumSrc::Dyn(Rc::new(move || sig.get() as f32)))
5552                } else if let Some(m) = b.get_memo_float(prop) {
5553                    Ok(NumSrc::Dyn(Rc::new(move || m.get() as f32)))
5554                } else if let Some(m) = b.get_memo_int(prop) {
5555                    #[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)]
5556                    let _ = ();
5557                    Ok(NumSrc::Dyn(Rc::new(move || m.get() as f32)))
5558                } else {
5559                    Err(prop_err(file, line, prop, "Float/Int"))
5560                }
5561            }
5562        },
5563        SeqValue::ItemField(field) => match res {
5564            Resolver::Static => Err(SeqError::new(
5565                file,
5566                line,
5567                1,
5568                format!("'{what}' z wiersza wymaga hosta"),
5569            )),
5570            Resolver::Host(_) => match row_value(file, line, row, field)? {
5571                RowVal::Sig(FieldSignal::Int(sig)) => {
5572                    #[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)]
5573                    let _ = ();
5574                    Ok(NumSrc::Dyn(Rc::new(move || sig.get() as f32)))
5575                }
5576                RowVal::Sig(FieldSignal::Float(sig)) => {
5577                    Ok(NumSrc::Dyn(Rc::new(move || sig.get() as f32)))
5578                }
5579                RowVal::Id(id) => {
5580                    #[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)]
5581                    let _ = ();
5582                    Ok(NumSrc::Lit(id as f32))
5583                }
5584                _ => Err(SeqError::new(
5585                    file,
5586                    line,
5587                    1,
5588                    format!("pole wiersza '{field}' to nie liczba (Int/Float/id)"),
5589                )),
5590            },
5591        },
5592        SeqValue::Dynamic(name) => match res {
5593            Resolver::Static => Err(SeqError::new(
5594                file,
5595                line,
5596                1,
5597                format!("dynamic({name}) wymaga hosta Rust"),
5598            )),
5599            Resolver::Host(b) => {
5600                if let Some(cell) = b.get_tick_angle(name) {
5601                    return Ok(NumSrc::Dyn(Rc::new(move || cell.get())));
5602                }
5603                let sig = b.get_float(name).or_else(|| None);
5604                match sig {
5605                    Some(sig) => Ok(NumSrc::Dyn(Rc::new(move || sig.get() as f32))),
5606                    None => match b.get_int(name) {
5607                        Some(sig) => {
5608                            #[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)]
5609                            let _ = ();
5610                            Ok(NumSrc::Dyn(Rc::new(move || sig.get() as f32)))
5611                        }
5612                        None => match b.get_memo_float(name) {
5613                            Some(m) => Ok(NumSrc::Dyn(Rc::new(move || m.get() as f32))),
5614                            None => match b.get_memo_int(name) {
5615                                Some(m) => {
5616                                    #[allow(
5617                                        clippy::cast_possible_truncation,
5618                                        clippy::cast_precision_loss
5619                                    )]
5620                                    let _ = ();
5621                                    Ok(NumSrc::Dyn(Rc::new(move || m.get() as f32)))
5622                                }
5623                                None => Err(prop_err(file, line, name, "Float/Int")),
5624                            },
5625                        },
5626                    },
5627                }
5628            }
5629        },
5630        SeqValue::Env(name) => match res {
5631            Resolver::Static => Err(SeqError::new(
5632                file,
5633                line,
5634                1,
5635                format!("env.{name} wymaga hosta Rust"),
5636            )),
5637            Resolver::Host(b) => {
5638                if let Some(sig) = b.get_env_float(name) {
5639                    Ok(NumSrc::Dyn(Rc::new(move || sig.get() as f32)))
5640                } else if let Some(sig) = b.get_env_int(name) {
5641                    #[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)]
5642                    let _ = ();
5643                    Ok(NumSrc::Dyn(Rc::new(move || sig.get() as f32)))
5644                } else {
5645                    Err(SeqError::new(
5646                        file,
5647                        line,
5648                        1,
5649                        format!("unknown env.{name} or wrong type (here you need: Float/Int)"),
5650                    ))
5651                }
5652            }
5653        },
5654        _ => Err(SeqError::new(
5655            file,
5656            line,
5657            1,
5658            format!("'{what}' to liczba albo dynamic(nazwa)"),
5659        )),
5660    }
5661}
5662
5663fn dyn_bool(
5664    file: &str,
5665    line: usize,
5666    res: &Resolver<'_>,
5667    row: Option<&SeqRowData>,
5668    value: &SeqValue,
5669    what: &str,
5670) -> Result<BoolSrc, SeqError> {
5671    match value {
5672        SeqValue::Bool(b) => Ok(BoolSrc::Lit(*b)),
5673        SeqValue::Ident(prop) | SeqValue::Dynamic(prop) => match res {
5674            Resolver::Static => Err(SeqError::new(
5675                file,
5676                line,
5677                1,
5678                format!("'{what}' z propertu wymaga hosta"),
5679            )),
5680            Resolver::Host(b) => b
5681                .get_bool(prop)
5682                .map(|sig| BoolSrc::Dyn(Rc::new(move || sig.get())))
5683                .or_else(|| {
5684                    b.get_memo_bool(prop)
5685                        .map(|m| BoolSrc::Dyn(Rc::new(move || m.get())))
5686                })
5687                .ok_or_else(|| prop_err(file, line, prop, "Bool")),
5688        },
5689        SeqValue::Env(name) => match res {
5690            Resolver::Static => Err(SeqError::new(
5691                file,
5692                line,
5693                1,
5694                format!("'{what}' from environment requires host"),
5695            )),
5696            Resolver::Host(b) => b
5697                .get_env_bool(name)
5698                .map(|sig| BoolSrc::Dyn(Rc::new(move || sig.get())))
5699                .ok_or_else(|| {
5700                    SeqError::new(
5701                        file,
5702                        line,
5703                        1,
5704                        format!("unknown env.{name} or wrong type (here you need: Bool)"),
5705                    )
5706                }),
5707        },
5708        SeqValue::ItemField(field) => match res {
5709            Resolver::Static => Err(SeqError::new(
5710                file,
5711                line,
5712                1,
5713                format!("'{what}' z wiersza wymaga hosta"),
5714            )),
5715            Resolver::Host(_) => match row_value(file, line, row, field)? {
5716                RowVal::Sig(FieldSignal::Bool(sig)) => Ok(BoolSrc::Dyn(Rc::new(move || sig.get()))),
5717                _ => Err(SeqError::new(
5718                    file,
5719                    line,
5720                    1,
5721                    format!("pole wiersza '{field}' to nie Bool"),
5722                )),
5723            },
5724        },
5725        _ => Err(SeqError::new(
5726            file,
5727            line,
5728            1,
5729            format!("'{what}' to true/false albo dynamic(nazwa)"),
5730        )),
5731    }
5732}
5733
5734fn pt_src(
5735    file: &str,
5736    line: usize,
5737    res: &Resolver<'_>,
5738    row: Option<&SeqRowData>,
5739    value: &SeqValue,
5740    what: &str,
5741) -> Result<PtSrc, SeqError> {
5742    match value {
5743        SeqValue::Tuple(items) if items.len() == 2 => Ok(PtSrc {
5744            x: dyn_f32(file, line, res, row, &items[0], what)?,
5745            y: dyn_f32(file, line, res, row, &items[1], what)?,
5746        }),
5747        _ => Err(SeqError::new(
5748            file,
5749            line,
5750            1,
5751            format!("'{what}' to krotka (x, y)"),
5752        )),
5753    }
5754}
5755
5756fn rect_src(
5757    file: &str,
5758    line: usize,
5759    res: &Resolver<'_>,
5760    row: Option<&SeqRowData>,
5761    value: &SeqValue,
5762    what: &str,
5763) -> Result<RectSrc, SeqError> {
5764    match value {
5765        SeqValue::Tuple(items) if items.len() == 4 => Ok(RectSrc {
5766            x: dyn_f32(file, line, res, row, &items[0], what)?,
5767            y: dyn_f32(file, line, res, row, &items[1], what)?,
5768            w: dyn_f32(file, line, res, row, &items[2], what)?,
5769            h: dyn_f32(file, line, res, row, &items[3], what)?,
5770        }),
5771        _ => Err(SeqError::new(
5772            file,
5773            line,
5774            1,
5775            format!("'{what}' to krotka (x, y, w, h)"),
5776        )),
5777    }
5778}
5779
5780fn points_src(
5781    file: &str,
5782    line: usize,
5783    res: &Resolver<'_>,
5784    row: Option<&SeqRowData>,
5785    value: &SeqValue,
5786    what: &str,
5787) -> Result<Vec<PtSrc>, SeqError> {
5788    match value {
5789        SeqValue::Array(items) if !items.is_empty() => items
5790            .iter()
5791            .map(|it| pt_src(file, line, res, row, it, what))
5792            .collect(),
5793        _ => Err(SeqError::new(
5794            file,
5795            line,
5796            1,
5797            format!("'{what}' to [(x, y), ...] (min. 1 punkt)"),
5798        )),
5799    }
5800}
5801
5802fn uv_src_of(
5803    file: &str,
5804    res: &Resolver<'_>,
5805    row: Option<&SeqRowData>,
5806    node: &SeqNode,
5807) -> Result<[NumSrc; 4], SeqError> {
5808    match find_arg(node, "uv") {
5809        None => Ok([
5810            NumSrc::Lit(0.0),
5811            NumSrc::Lit(0.0),
5812            NumSrc::Lit(1.0),
5813            NumSrc::Lit(1.0),
5814        ]),
5815        Some(arg) => match &arg.value {
5816            SeqValue::Tuple(items) if items.len() == 4 => Ok([
5817                dyn_f32(file, arg.line, res, row, &items[0], "uv")?,
5818                dyn_f32(file, arg.line, res, row, &items[1], "uv")?,
5819                dyn_f32(file, arg.line, res, row, &items[2], "uv")?,
5820                dyn_f32(file, arg.line, res, row, &items[3], "uv")?,
5821            ]),
5822            _ => Err(SeqError::new(
5823                file,
5824                arg.line,
5825                1,
5826                "'uv' to krotka (u0, v0, u1, v1)",
5827            )),
5828        },
5829    }
5830}
5831
5832fn shape_color(file: &str, theme: &Theme, node: &SeqNode) -> Result<Color, SeqError> {
5833    for name in ["fill", "color"] {
5834        if let Some(arg) = find_arg(node, name) {
5835            return color(file, theme, arg);
5836        }
5837    }
5838    Err(SeqError::new(
5839        file,
5840        node.line,
5841        1,
5842        format!("{} wymaga fill: albo color:", node.kind),
5843    ))
5844}
5845
5846fn shape_visible(
5847    file: &str,
5848    res: &Resolver<'_>,
5849    row: Option<&SeqRowData>,
5850    node: &SeqNode,
5851) -> Result<BoolSrc, SeqError> {
5852    match find_arg(node, "visible") {
5853        None => Ok(BoolSrc::Lit(true)),
5854        Some(arg) => dyn_bool(file, arg.line, res, row, &arg.value, "visible"),
5855    }
5856}
5857
5858fn shape_name(
5859    file: &str,
5860    node: &SeqNode,
5861    res: &Resolver<'_>,
5862    row: Option<&SeqRowData>,
5863) -> Result<Option<String>, SeqError> {
5864    match find_arg(node, "name") {
5865        None => Ok(None),
5866        Some(arg) => match &arg.value {
5867            SeqValue::Str(s) => Ok(Some(s.clone())),
5868            SeqValue::ItemField(field) => match res {
5869                Resolver::Static => Err(SeqError::new(
5870                    file,
5871                    arg.line,
5872                    1,
5873                    "name: z wiersza wymaga hosta",
5874                )),
5875                Resolver::Host(_) => match row_value(file, arg.line, row, field)? {
5876                    RowVal::Id(id) => Ok(Some(id.to_string())),
5877                    RowVal::Sig(FieldSignal::Str(sig)) => Ok(Some(sig.get_untracked())),
5878                    RowVal::Sig(FieldSignal::Int(sig)) => Ok(Some(sig.get_untracked().to_string())),
5879                    RowVal::Sig(FieldSignal::Float(sig)) => {
5880                        Ok(Some(sig.get_untracked().to_string()))
5881                    }
5882                    RowVal::Sig(FieldSignal::Bool(sig)) => {
5883                        Ok(Some(sig.get_untracked().to_string()))
5884                    }
5885                },
5886            },
5887            _ => Err(SeqError::new(
5888                file,
5889                arg.line,
5890                1,
5891                "name: is a string in quotation marks or item.field",
5892            )),
5893        },
5894    }
5895}
5896
5897fn shape_num(
5898    file: &str,
5899    res: &Resolver<'_>,
5900    row: Option<&SeqRowData>,
5901    node: &SeqNode,
5902    name: &str,
5903) -> Result<NumSrc, SeqError> {
5904    match find_arg(node, name) {
5905        None => Err(SeqError::new(
5906            file,
5907            node.line,
5908            1,
5909            format!("{} wymaga {name}:", node.kind),
5910        )),
5911        Some(arg) => dyn_f32(file, arg.line, res, row, &arg.value, name),
5912    }
5913}
5914
5915fn shape_pt(
5916    file: &str,
5917    res: &Resolver<'_>,
5918    row: Option<&SeqRowData>,
5919    node: &SeqNode,
5920    name: &str,
5921) -> Result<PtSrc, SeqError> {
5922    match find_arg(node, name) {
5923        None => Err(SeqError::new(
5924            file,
5925            node.line,
5926            1,
5927            format!("{} wymaga {name}: (x, y)", node.kind),
5928        )),
5929        Some(arg) => pt_src(file, arg.line, res, row, &arg.value, name),
5930    }
5931}
5932
5933fn rect_src_of(
5934    file: &str,
5935    res: &Resolver<'_>,
5936    row: Option<&SeqRowData>,
5937    node: &SeqNode,
5938) -> Result<RectSrc, SeqError> {
5939    if let Some(arg) = find_arg(node, "rect") {
5940        return rect_src(file, arg.line, res, row, &arg.value, "rect");
5941    }
5942    Ok(RectSrc {
5943        x: shape_num(file, res, row, node, "x")?,
5944        y: shape_num(file, res, row, node, "y")?,
5945        w: shape_num(file, res, row, node, "w")?,
5946        h: shape_num(file, res, row, node, "h")?,
5947    })
5948}
5949
5950fn opt_num(
5951    file: &str,
5952    res: &Resolver<'_>,
5953    row: Option<&SeqRowData>,
5954    node: &SeqNode,
5955    name: &str,
5956) -> Result<NumSrc, SeqError> {
5957    match find_arg(node, name) {
5958        None => Ok(NumSrc::Lit(0.0)),
5959        Some(arg) => dyn_f32(file, arg.line, res, row, &arg.value, name),
5960    }
5961}
5962
5963fn opt_pt(
5964    file: &str,
5965    res: &Resolver<'_>,
5966    row: Option<&SeqRowData>,
5967    node: &SeqNode,
5968    name: &str,
5969) -> Result<Option<PtSrc>, SeqError> {
5970    match find_arg(node, name) {
5971        None => Ok(None),
5972        Some(arg) => pt_src(file, arg.line, res, row, &arg.value, name).map(Some),
5973    }
5974}
5975
5976fn shape_str(file: &str, node: &SeqNode, name: &str) -> Result<String, SeqError> {
5977    match find_arg(node, name) {
5978        Some(arg) => match &arg.value {
5979            SeqValue::Str(s) => Ok(s.clone()),
5980            _ => Err(SeqError::new(
5981                file,
5982                arg.line,
5983                1,
5984                format!("{} wymaga {name}: \"...\"", node.kind),
5985            )),
5986        },
5987        None => Err(SeqError::new(
5988            file,
5989            node.line,
5990            1,
5991            format!("{} wymaga {name}: \"...\"", node.kind),
5992        )),
5993    }
5994}
5995
5996fn points_src_of(
5997    file: &str,
5998    res: &Resolver<'_>,
5999    row: Option<&SeqRowData>,
6000    node: &SeqNode,
6001) -> Result<Vec<PtSrc>, SeqError> {
6002    match find_arg(node, "points") {
6003        None => Err(SeqError::new(
6004            file,
6005            node.line,
6006            1,
6007            format!("{} wymaga points: [(x, y), ...]", node.kind),
6008        )),
6009        Some(arg) => points_src(file, arg.line, res, row, &arg.value, "points"),
6010    }
6011}
6012fn touches(parts: &[Rc<dyn Fn()>]) -> Rc<dyn Fn()> {
6013    let parts = parts.to_vec();
6014    Rc::new(move || {
6015        for p in &parts {
6016            p();
6017        }
6018    })
6019}
6020
6021trait Touch {
6022    fn touch_rc(&self) -> Rc<dyn Fn()>;
6023}
6024
6025impl Touch for NumSrc {
6026    fn touch_rc(&self) -> Rc<dyn Fn()> {
6027        NumSrc::touch_rc(self)
6028    }
6029}
6030
6031impl Touch for PtSrc {
6032    fn touch_rc(&self) -> Rc<dyn Fn()> {
6033        PtSrc::touch_rc(self)
6034    }
6035}
6036
6037impl Touch for RectSrc {
6038    fn touch_rc(&self) -> Rc<dyn Fn()> {
6039        RectSrc::touch_rc(self)
6040    }
6041}
6042
6043impl Touch for Vec<PtSrc> {
6044    fn touch_rc(&self) -> Rc<dyn Fn()> {
6045        let this = self.clone();
6046        Rc::new(move || {
6047            for p in &this {
6048                p.touch();
6049            }
6050        })
6051    }
6052}
6053
6054impl Touch for Rc<dyn Fn()> {
6055    fn touch_rc(&self) -> Rc<dyn Fn()> {
6056        self.clone()
6057    }
6058}
6059
6060fn touch2(a: &impl Touch, b: &impl Touch) -> Rc<dyn Fn()> {
6061    touches(&[a.touch_rc(), b.touch_rc()])
6062}
6063
6064fn touch3(a: &impl Touch, b: &impl Touch, c: &impl Touch) -> Rc<dyn Fn()> {
6065    touches(&[a.touch_rc(), b.touch_rc(), c.touch_rc()])
6066}
6067
6068fn touch4(a: &impl Touch, b: &impl Touch, c: &impl Touch, d: &impl Touch) -> Rc<dyn Fn()> {
6069    touches(&[a.touch_rc(), b.touch_rc(), c.touch_rc(), d.touch_rc()])
6070}
6071
6072fn touch4e(a: &impl Touch, b: &impl Touch, c: &impl Touch, d: &impl Touch) -> Rc<dyn Fn()> {
6073    touch4(a, b, c, d)
6074}
6075
6076#[allow(clippy::too_many_arguments)]
6077fn touch5(
6078    a: &impl Touch,
6079    b: &impl Touch,
6080    c: &impl Touch,
6081    d: &impl Touch,
6082    e: &impl Touch,
6083) -> Rc<dyn Fn()> {
6084    touches(&[
6085        a.touch_rc(),
6086        b.touch_rc(),
6087        c.touch_rc(),
6088        d.touch_rc(),
6089        e.touch_rc(),
6090    ])
6091}
6092
6093fn touch_pts(pts: &Vec<PtSrc>, vis: &Rc<dyn Fn()>) -> Rc<dyn Fn()> {
6094    touches(&[pts.touch_rc(), vis.clone()])
6095}
6096
6097fn touch_pts_w(pts: &Vec<PtSrc>, w: &NumSrc, vis: &Rc<dyn Fn()>) -> Rc<dyn Fn()> {
6098    touches(&[pts.touch_rc(), w.touch_rc(), vis.clone()])
6099}
6100
6101fn only_color(file: &str, theme: &Theme, node: &SeqNode) -> Result<Color, SeqError> {
6102    shape_color(file, theme, node)
6103}
6104
6105fn dist_polyline(p: Point, pts: &[Point]) -> f32 {
6106    if pts.is_empty() {
6107        return f32::INFINITY;
6108    }
6109    if pts.len() == 1 {
6110        let dx = p.x - pts[0].x;
6111        let dy = p.y - pts[0].y;
6112        return (dx * dx + dy * dy).sqrt();
6113    }
6114    pts.windows(2)
6115        .map(|w| dist_seg(p, w[0], w[1]))
6116        .fold(f32::INFINITY, f32::min)
6117}
6118
6119#[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)]
6120fn sample_cubic(pts: &[Point]) -> Vec<Point> {
6121    const N: u32 = 16;
6122    let (p0, c0, c1, p1) = (pts[0], pts[1], pts[2], pts[3]);
6123    (0..=N)
6124        .map(|i| cubic(p0, c0, c1, p1, i as f32 / N as f32))
6125        .collect()
6126}
6127
6128#[derive(Clone)]
6129enum GestureWire {
6130    Null(Rc<dyn Fn()>),
6131
6132    Pt(Rc<dyn Fn(f64, f64)>),
6133
6134    PtNamed(Rc<dyn Fn(f64, f64, String)>),
6135
6136    Wheel(crate::bindings::WheelAction),
6137}
6138
6139fn fire_gesture(wire: &GestureWire, x: f64, y: f64, hit: Option<&str>) {
6140    match wire {
6141        GestureWire::Null(f) => f(),
6142        GestureWire::Pt(f) => f(x, y),
6143        GestureWire::PtNamed(f) => f(x, y, hit.unwrap_or("").to_string()),
6144        GestureWire::Wheel(f) => f(x, y, 0.0),
6145    }
6146}
6147
6148fn wire_gesture(
6149    file: &str,
6150    line: usize,
6151    b: &HostBindings,
6152    value: &SeqValue,
6153) -> Result<GestureWire, SeqError> {
6154    let (action, args) = match value {
6155        SeqValue::Callback { action, args } => (action.as_str(), args.as_slice()),
6156        SeqValue::CallSeq { name, args } => {
6157            return match args.len() {
6158                0 => seq_fire(file, line, b, name, Vec::new()).map(GestureWire::Null),
6159                1 => seq_fire_name(file, line, b, name).map(GestureWire::PtNamed),
6160                2 => seq_fire_pt(file, line, b, name).map(GestureWire::Pt),
6161                3 => {
6162                    let third = match &args[2] {
6163                        SeqValue::Ident(n) => n.as_str(),
6164                        _ => {
6165                            return Err(SeqError::new(
6166                                file,
6167                                line,
6168                                1,
6169                                format!("'{name}': trzeci argument gestu to `name` albo `d`"),
6170                            ));
6171                        }
6172                    };
6173                    if third == "d" {
6174                        seq_fire_wheel(file, line, b, name).map(GestureWire::Wheel)
6175                    } else {
6176                        seq_fire_pt_named(file, line, b, name).map(GestureWire::PtNamed)
6177                    }
6178                }
6179                _ => Err(SeqError::new(
6180                    file,
6181                    line,
6182                    1,
6183                    format!("'{name}': gest to fn(), fn(name) albo fn(x, y[, name|d])"),
6184                )),
6185            };
6186        }
6187        _ => {
6188            return Err(SeqError::new(
6189                file,
6190                line,
6191                1,
6192                "gest: { akcja(x, y) } albo { fn(x, y) }",
6193            ));
6194        }
6195    };
6196    let ident_args = args
6197        .iter()
6198        .map(|a| match a {
6199            SeqValue::Ident(name) => Ok(name.clone()),
6200            _ => Err(SeqError::new(
6201                file,
6202                line,
6203                1,
6204                format!("'{action}': argumenty gestu to nazwy (x, y[, name])"),
6205            )),
6206        })
6207        .collect::<Result<Vec<_>, _>>()?;
6208    match ident_args.len() {
6209        0 => b
6210            .get_action0(action)
6211            .map(GestureWire::Null)
6212            .ok_or_else(|| unknown_gesture(file, line, action)),
6213        2 => b
6214            .get_action_point(action)
6215            .map(GestureWire::Pt)
6216            .ok_or_else(|| unknown_gesture(file, line, action)),
6217        3 => {
6218            if let Some(f) = b.get_action_point_named(action) {
6219                Ok(GestureWire::PtNamed(f))
6220            } else if let Some(f) = b.get_action_wheel(action) {
6221                Ok(GestureWire::Wheel(f))
6222            } else {
6223                Err(unknown_gesture(file, line, action))
6224            }
6225        }
6226        _ => Err(SeqError::new(
6227            file,
6228            line,
6229            1,
6230            format!("'{action}': gest to 0, 2 albo 3 argumenty (x, y[, name|d])"),
6231        )),
6232    }
6233}
6234
6235fn unknown_gesture(file: &str, line: usize, action: &str) -> SeqError {
6236    SeqError::new(
6237        file,
6238        line,
6239        1,
6240        format!(
6241            "unknown callback '{action}' (missing on host: on/on_point/on_point_named/on_wheel)"
6242        ),
6243    )
6244}
6245
6246fn canvas_cursor(file: &str, line: usize, name: &str) -> Result<CursorIcon, SeqError> {
6247    match name {
6248        "default" => Ok(CursorIcon::Default),
6249        "pointer" => Ok(CursorIcon::Pointer),
6250        "crosshair" => Ok(CursorIcon::Crosshair),
6251        "grab" => Ok(CursorIcon::Grab),
6252        "grabbing" => Ok(CursorIcon::Grabbing),
6253        "text" => Ok(CursorIcon::Text),
6254        other => Err(SeqError::new(
6255            file,
6256            line,
6257            1,
6258            format!("cursor to default/pointer/crosshair/grab/grabbing/text, nie '{other}'"),
6259        )),
6260    }
6261}
6262
6263fn lower_split(
6264    file: &str,
6265    theme: &Theme,
6266    styles: &BuiltStyles,
6267    node: &SeqNode,
6268    res: &Resolver<'_>,
6269    row: Option<&SeqRowData>,
6270) -> Result<AnyView, SeqError> {
6271    let value_arg = find_arg(node, "value").ok_or_else(|| {
6272        SeqError::new(
6273            file,
6274            node.line,
6275            1,
6276            "SplitPanel wymaga value: prop".to_string(),
6277        )
6278    })?;
6279    let sig = bridge_f32(file, value_arg.line, res, row, &value_arg.value, "value")?;
6280    let mut v = SplitPanel::new(sig);
6281    for arg in &node.args {
6282        match arg.name.as_str() {
6283            "value" => {}
6284            "axis" => {
6285                v = match ident(file, arg)?.as_str() {
6286                    "horizontal" => v.horizontal(),
6287                    "vertical" => v.vertical(),
6288                    other => {
6289                        return Err(SeqError::new(
6290                            file,
6291                            arg.line,
6292                            1,
6293                            format!("axis to horizontal/vertical, nie '{other}'"),
6294                        ));
6295                    }
6296                };
6297            }
6298            "handle" => v = v.handle_width(num(file, arg, res, row)?),
6299            "min_first" => v = v.min_first(num(file, arg, res, row)?),
6300            "min_second" => v = v.min_second(num(file, arg, res, row)?),
6301            "handle_color" => v = v.handle_color(color(file, theme, arg)?),
6302            "width" => v = v.width(len(file, arg, res, row)?),
6303            "height" => v = v.height(len(file, arg, res, row)?),
6304            "padding" => match padding_val(file, arg, res, row)? {
6305                PaddingVal::All(n) => v = v.padding(n),
6306                PaddingVal::Xy(x, y) => v = v.padding_xy(x, y),
6307            },
6308            "padding_x" => v = v.padding_x(num(file, arg, res, row)?),
6309            "padding_y" => v = v.padding_y(num(file, arg, res, row)?),
6310            "background" => v = v.background(color(file, theme, arg)?),
6311            "flex" => v = v.flex(num(file, arg, res, row)?),
6312            "min_width" => v = v.min_width(num(file, arg, res, row)?),
6313            "max_width" => v = v.max_width(num(file, arg, res, row)?),
6314            "min_height" => v = v.min_height(num(file, arg, res, row)?),
6315            "max_height" => v = v.max_height(num(file, arg, res, row)?),
6316            "margin" => match margin_val(file, arg, res, row)? {
6317                MarginVal::All(n) => v = v.margin(n),
6318                MarginVal::Xy(x, y) => v = v.margin_xy(x, y),
6319            },
6320            "margin_x" => v = v.margin_x(num(file, arg, res, row)?),
6321            "margin_y" => v = v.margin_y(num(file, arg, res, row)?),
6322            "margin_left" => v = v.margin_left(num(file, arg, res, row)?),
6323            "margin_right" => v = v.margin_right(num(file, arg, res, row)?),
6324            "margin_top" => v = v.margin_top(num(file, arg, res, row)?),
6325            "margin_bottom" => v = v.margin_bottom(num(file, arg, res, row)?),
6326            "overflow" => v = v.overflow(overflow_val(file, arg)?),
6327            "hit" => v = v.hit_testable(flag(file, arg, res, row)?),
6328            "indent" | "indent_x" | "indent_y" | "indent_left" | "indent_right" | "indent_top"
6329            | "indent_bottom" => {}
6330            _ => unreachable_checked(file, node, arg)?,
6331        }
6332    }
6333    if node.children.len() != 2 {
6334        return Err(SeqError::new(
6335            file,
6336            node.line,
6337            1,
6338            "SplitPanel requires exactly 2 children: first { } and second { }",
6339        ));
6340    }
6341
6342    for c in &node.children {
6343        let _ = child_flex_weight(file, res, row, c)?;
6344    }
6345    let first = lower_node(file, theme, styles, &node.children[0], res, row)?;
6346    let second = lower_node(file, theme, styles, &node.children[1], res, row)?;
6347    v = v.first(first);
6348    v = v.second(second);
6349    Ok(AnyView::new(v))
6350}
6351
6352const DRAG_THRESHOLD_SQ: f32 = 64.0;
6353
6354#[allow(clippy::too_many_lines)]
6355fn lower_canvas(
6356    file: &str,
6357    theme: &Theme,
6358    node: &SeqNode,
6359    res: &Resolver<'_>,
6360    row: Option<&SeqRowData>,
6361) -> Result<AnyView, SeqError> {
6362    let mut v = Canvas::new();
6363    let mut click: Option<GestureWire> = None;
6364    let mut drag: Option<GestureWire> = None;
6365    let mut wheel: Option<crate::bindings::WheelAction> = None;
6366    let mut pinch: Option<crate::bindings::PinchAction> = None;
6367    let mut press: Option<GestureWire> = None;
6368
6369    let tick_name: Option<String> = match find_arg(node, "tick") {
6370        None => {
6371            if find_arg(node, "running").is_some() || find_arg(node, "speed").is_some() {
6372                return Err(SeqError::new(
6373                    file,
6374                    node.line,
6375                    1,
6376                    "running:/speed: require tick: name".to_string(),
6377                ));
6378            }
6379            None
6380        }
6381        Some(arg) => match &arg.value {
6382            SeqValue::Ident(name) => Some(name.clone()),
6383            _ => {
6384                return Err(SeqError::new(
6385                    file,
6386                    arg.line,
6387                    1,
6388                    "tick: to nazwa (tick: kat)".to_string(),
6389                ));
6390            }
6391        },
6392    };
6393    let mut owned_bindings: Option<HostBindings> = None;
6394    let mut tick_state: Option<TickState> = None;
6395    if let Some(name) = tick_name {
6396        match res {
6397            Resolver::Static => {
6398                return Err(SeqError::new(
6399                    file,
6400                    node.line,
6401                    1,
6402                    "tick: wymaga hosta Rust".to_string(),
6403                ));
6404            }
6405            Resolver::Host(b) => {
6406                let angle = Rc::new(Cell::new(0.0f32));
6407                let mut cloned = (*b).clone();
6408                cloned = cloned.tick_angle(&name, angle.clone());
6409                let running = match find_arg(node, "running") {
6410                    None => BoolSrc::Lit(true),
6411                    Some(arg) => dyn_bool(file, arg.line, res, row, &arg.value, "running")?,
6412                };
6413                let speed = match find_arg(node, "speed") {
6414                    None => NumSrc::Lit(45.0),
6415                    Some(arg) => dyn_f32(file, arg.line, res, row, &arg.value, "speed")?,
6416                };
6417                owned_bindings = Some(cloned);
6418                tick_state = Some(TickState {
6419                    angle,
6420                    last: Rc::new(std::cell::RefCell::new(None)),
6421                    running,
6422                    speed,
6423                });
6424            }
6425        }
6426    }
6427
6428    let tick_res;
6429    let res: &Resolver<'_> = match owned_bindings.as_ref() {
6430        Some(b) => {
6431            tick_res = Resolver::Host(b);
6432            &tick_res
6433        }
6434        None => res,
6435    };
6436    for arg in &node.args {
6437        match arg.name.as_str() {
6438            "width" => v = v.width(len(file, arg, res, row)?),
6439            "height" => v = v.height(len(file, arg, res, row)?),
6440            "flex" => v = v.flex(num(file, arg, res, row)?),
6441            "min_width" => v = v.min_width(num(file, arg, res, row)?),
6442            "max_width" => v = v.max_width(num(file, arg, res, row)?),
6443            "min_height" => v = v.min_height(num(file, arg, res, row)?),
6444            "max_height" => v = v.max_height(num(file, arg, res, row)?),
6445            "background" => v = v.background(color(file, theme, arg)?),
6446            "transparent" => {
6447                if flag(file, arg, res, row)? {
6448                    v = v.no_background();
6449                }
6450            }
6451            "cursor" => v = v.cursor(canvas_cursor(file, arg.line, &ident(file, arg)?)?),
6452            "focusable" => v = v.focusable(flag(file, arg, res, row)?),
6453            "grid" => v = v.grid(num(file, arg, res, row)?),
6454            "grid_color" => {
6455                let step = match find_arg(node, "grid") {
6456                    Some(g) => num(file, g, res, row)?,
6457                    None => {
6458                        return Err(SeqError::new(file, arg.line, 1, "grid_color wymaga grid:"));
6459                    }
6460                };
6461                v = v.grid_colored(step, color(file, theme, arg)?);
6462            }
6463            "axes" => {
6464                if flag(file, arg, res, row)? {
6465                    v = v.axes();
6466                }
6467            }
6468            "axes_color" => v = v.axes_colored(color(file, theme, arg)?),
6469            "clip" => v = v.clip(flag(file, arg, res, row)?),
6470            "allow_overflow" => v = v.allow_overflow(num(file, arg, res, row)?),
6471            "camera" => match &arg.value {
6472                SeqValue::Tuple(items) if items.len() == 2 => {
6473                    let pos = pt_src(file, arg.line, res, row, &items[0], "camera.pos")?;
6474                    let zoom = dyn_f32(file, arg.line, res, row, &items[1], "camera.zoom")?;
6475
6476                    match (&pos.x, &pos.y, &zoom) {
6477                        (NumSrc::Lit(x), NumSrc::Lit(y), NumSrc::Lit(z)) => {
6478                            v = v.camera(Vec2 { x: *x, y: *y }, *z);
6479                        }
6480                        _ => {
6481                            return Err(SeqError::new(
6482                                file,
6483                                arg.line,
6484                                1,
6485                                "dynamic camera later (only numbers today)",
6486                            ));
6487                        }
6488                    }
6489                }
6490                _ => {
6491                    return Err(SeqError::new(file, arg.line, 1, "camera: ((x, y), zoom)"));
6492                }
6493            },
6494            "capture" => v = v.capture(flag(file, arg, res, row)?),
6495
6496            "tick" | "speed" | "running" => {}
6497            "on_click_at" | "on_drag" | "on_wheel" | "on_press" | "on_pinch" => match res {
6498                Resolver::Static => {
6499                    return Err(SeqError::new(
6500                        file,
6501                        arg.line,
6502                        1,
6503                        format!("{} wymaga hosta Rust", arg.name),
6504                    ));
6505                }
6506                Resolver::Host(b) => match &arg.value {
6507                    SeqValue::Callback { action, args } => {
6508                        if arg.name == "on_wheel" {
6509                            let idents = args
6510                                .iter()
6511                                .map(|a| match a {
6512                                    SeqValue::Ident(n) => Ok(n.clone()),
6513                                    _ => Err(SeqError::new(
6514                                        file,
6515                                        arg.line,
6516                                        1,
6517                                        "on_wheel: { akcja(x, y, d) }",
6518                                    )),
6519                                })
6520                                .collect::<Result<Vec<_>, _>>()?;
6521                            if idents.len() != 3 {
6522                                return Err(SeqError::new(
6523                                    file,
6524                                    arg.line,
6525                                    1,
6526                                    "on_wheel: { akcja(x, y, d) }",
6527                                ));
6528                            }
6529                            wheel = Some(b.get_action_wheel(action).ok_or_else(|| {
6530                                SeqError::new(
6531                                    file,
6532                                    arg.line,
6533                                    1,
6534                                    format!(
6535                                        "unknown callback '{action}' (missing on host: on_wheel)"
6536                                    ),
6537                                )
6538                            })?);
6539                        } else if arg.name == "on_pinch" {
6540                            let idents = args
6541                                .iter()
6542                                .map(|a| match a {
6543                                    SeqValue::Ident(n) => Ok(n.clone()),
6544                                    _ => Err(SeqError::new(
6545                                        file,
6546                                        arg.line,
6547                                        1,
6548                                        "on_pinch: { akcja(x, y, s) }",
6549                                    )),
6550                                })
6551                                .collect::<Result<Vec<_>, _>>()?;
6552                            if idents.len() != 3 {
6553                                return Err(SeqError::new(
6554                                    file,
6555                                    arg.line,
6556                                    1,
6557                                    "on_pinch: { akcja(x, y, s) }",
6558                                ));
6559                            }
6560                            pinch = Some(b.get_action_pinch(action).ok_or_else(|| {
6561                                SeqError::new(
6562                                    file,
6563                                    arg.line,
6564                                    1,
6565                                    format!(
6566                                        "unknown callback '{action}' (missing on host: on_pinch)"
6567                                    ),
6568                                )
6569                            })?);
6570                        } else {
6571                            let w = wire_gesture(file, arg.line, b, &arg.value)?;
6572                            if arg.name == "on_click_at" {
6573                                click = Some(w);
6574                            } else if arg.name == "on_press" {
6575                                press = Some(w);
6576                            } else {
6577                                drag = Some(w);
6578                            }
6579                        }
6580                    }
6581                    SeqValue::CallSeq { name, args } => {
6582                        if !args.iter().all(|a| matches!(a, SeqValue::Ident(_))) {
6583                            return Err(SeqError::new(
6584                                file,
6585                                arg.line,
6586                                1,
6587                                format!("{}: {{ fn(x, y[, name|d]) }}", arg.name),
6588                            ));
6589                        }
6590                        match arg.name.as_str() {
6591                            "on_wheel" => match args.len() {
6592                                0 => {
6593                                    let f = seq_fire(file, arg.line, b, name, Vec::new())?;
6594                                    wheel = Some(Rc::new(move |_x, _y, _d| f()));
6595                                }
6596                                3 => wheel = Some(seq_fire_wheel(file, arg.line, b, name)?),
6597                                _ => {
6598                                    return Err(SeqError::new(
6599                                        file,
6600                                        arg.line,
6601                                        1,
6602                                        format!("on_wheel: {{ {name}(x, y, d) }}"),
6603                                    ));
6604                                }
6605                            },
6606                            "on_pinch" => match args.len() {
6607                                0 => {
6608                                    let f = seq_fire(file, arg.line, b, name, Vec::new())?;
6609                                    pinch = Some(Rc::new(move |_x, _y, _s| f()));
6610                                }
6611                                3 => pinch = Some(seq_fire_wheel(file, arg.line, b, name)?),
6612                                _ => {
6613                                    return Err(SeqError::new(
6614                                        file,
6615                                        arg.line,
6616                                        1,
6617                                        format!("on_pinch: {{ {name}(x, y, s) }}"),
6618                                    ));
6619                                }
6620                            },
6621                            _ => {
6622                                let w = wire_gesture(file, arg.line, b, &arg.value)?;
6623                                if arg.name == "on_click_at" {
6624                                    click = Some(w);
6625                                } else if arg.name == "on_press" {
6626                                    press = Some(w);
6627                                } else {
6628                                    drag = Some(w);
6629                                }
6630                            }
6631                        }
6632                    }
6633                    _ => {
6634                        return Err(SeqError::new(
6635                            file,
6636                            arg.line,
6637                            1,
6638                            format!("{}: {{ akcja(x, y[, name]) }}", arg.name),
6639                        ));
6640                    }
6641                },
6642            },
6643            "indent" | "indent_x" | "indent_y" | "indent_left" | "indent_right" | "indent_top"
6644            | "indent_bottom" => {}
6645            _ => unreachable_checked(file, node, arg)?,
6646        }
6647    }
6648
6649    let mut layers: Vec<DrawLayer> = Vec::new();
6650    for c in &node.children {
6651        layers.push(lower_layer(file, theme, c, res, row)?);
6652    }
6653    let draw_layers = layers.clone();
6654
6655    let track_tick = tick_state
6656        .as_ref()
6657        .map(|t| touches(&[t.running.touch_rc(), t.speed.touch_rc()]));
6658    v = v.on_draw(move |g: &mut Painter| {
6659        if let Some(t) = tick_state.as_ref() {
6660            let now = std::time::Instant::now();
6661            if t.running.val() {
6662                let prev = t.last.borrow_mut().replace(now);
6663                let dt = prev
6664                    .map(|p| now.duration_since(p).as_secs_f32())
6665                    .unwrap_or(0.0)
6666                    .clamp(0.0, MAX_ANIM_DT);
6667                t.angle.set((t.angle.get() + dt * t.speed.val()) % 360.0);
6668                g.ctx().request_frame();
6669            } else {
6670                *t.last.borrow_mut() = None;
6671            }
6672        }
6673        DrawLayer::draw_all(&draw_layers, g);
6674    });
6675    {
6676        let track_layers = layers.clone();
6677        v = v.track(move || {
6678            DrawLayer::touch_all(&track_layers);
6679            if let Some(touch) = track_tick.as_ref() {
6680                touch();
6681            }
6682        });
6683    }
6684    if click.is_some() || drag.is_some() || wheel.is_some() || press.is_some() || pinch.is_some() {
6685        let pick_layers = layers.clone();
6686        let down: Rc<Cell<Option<(f32, f32)>>> = Rc::new(Cell::new(None));
6687        let dragged = Rc::new(Cell::new(false));
6688
6689        let down_name: Rc<std::cell::RefCell<Option<String>>> =
6690            Rc::new(std::cell::RefCell::new(None));
6691        v = v.on_pointer(move |at: Point, ev: &PointerEvent, _ctx: &mut EventCtx| {
6692            match ev {
6693                PointerEvent::Down {
6694                    button: PointerButton::Left,
6695                    pos,
6696                    ..
6697                } => {
6698                    down.set(Some((pos.x, pos.y)));
6699                    dragged.set(false);
6700                    let picked = DrawLayer::pick(&pick_layers, *pos);
6701                    *down_name.borrow_mut() = picked.clone();
6702                    if let Some(p) = press.as_ref() {
6703                        fire_gesture(p, f64::from(pos.x), f64::from(pos.y), picked.as_deref());
6704                    }
6705                }
6706                PointerEvent::Move { pos, .. } => {
6707                    if let Some((x0, y0)) = down.get() {
6708                        let dx = pos.x - x0;
6709                        let dy = pos.y - y0;
6710                        if dx * dx + dy * dy > DRAG_THRESHOLD_SQ {
6711                            dragged.set(true);
6712                            if let Some(d) = drag.as_ref() {
6713                                fire_gesture(
6714                                    d,
6715                                    f64::from(pos.x),
6716                                    f64::from(pos.y),
6717                                    down_name.borrow().as_deref(),
6718                                );
6719                            }
6720                        }
6721                    }
6722                }
6723                PointerEvent::Up {
6724                    button: PointerButton::Left,
6725                    pos,
6726                    ..
6727                } => {
6728                    let name = down_name.take();
6729                    if down.take().is_some()
6730                        && !dragged.replace(false)
6731                        && let Some(c) = click.as_ref()
6732                    {
6733                        fire_gesture(c, f64::from(pos.x), f64::from(pos.y), name.as_deref());
6734                    }
6735                }
6736                PointerEvent::Wheel { delta, pos, .. } => {
6737                    if let Some(w) = wheel.as_ref() {
6738                        w(f64::from(pos.x), f64::from(pos.y), f64::from(delta.y));
6739                    }
6740                }
6741                PointerEvent::Pinch { delta, pos, .. } => {
6742                    if let Some(p) = pinch.as_ref() {
6743                        p(f64::from(pos.x), f64::from(pos.y), f64::from(*delta));
6744                    }
6745                }
6746
6747                _ => {}
6748            }
6749            let _ = at;
6750        });
6751    }
6752    Ok(AnyView::new(v))
6753}
6754
6755#[derive(Clone)]
6756enum DrawLayer {
6757    Static(ShapeDraw),
6758    Dyn(DynShapes),
6759    Group {
6760        origin: PtSrc,
6761        rot: NumSrc,
6762        has_rot: bool,
6763        scale: NumSrc,
6764        pivot: Option<PtSrc>,
6765        touch_extra: Rc<dyn Fn()>,
6766        layers: Vec<DrawLayer>,
6767    },
6768    Clip {
6769        rect: RectSrc,
6770        radius: NumSrc,
6771        has_radius: bool,
6772        visible: BoolSrc,
6773        touch_extra: Rc<dyn Fn()>,
6774        layers: Vec<DrawLayer>,
6775    },
6776}
6777
6778type RowTemplate = Rc<dyn Fn(&SeqRowData) -> Vec<DrawLayer>>;
6779
6780#[derive(Clone)]
6781struct DynShapes {
6782    rows: crate::bindings::RowsFn,
6783    render: RowTemplate,
6784}
6785
6786impl DrawLayer {
6787    fn draw_all(layers: &[DrawLayer], g: &mut Painter) {
6788        for layer in layers {
6789            match layer {
6790                DrawLayer::Static(s) => (s.draw)(g),
6791                DrawLayer::Dyn(d) => {
6792                    let mut all = Vec::new();
6793                    for row in (d.rows)() {
6794                        all.extend((d.render)(&row));
6795                    }
6796                    Self::draw_all(&all, g);
6797                }
6798                DrawLayer::Group {
6799                    origin,
6800                    rot,
6801                    has_rot,
6802                    scale,
6803                    pivot,
6804                    layers,
6805                    ..
6806                } => {
6807                    let o = origin.val();
6808
6809                    let mut painter_tr = sequoia_core::Transform::translate(o.x, o.y);
6810                    let z = scale.val();
6811                    if z != 1.0 {
6812                        painter_tr = painter_tr.then(sequoia_core::Transform::scale(z, z));
6813                    }
6814
6815                    let list_local: Option<sequoia_core::Transform> = if *has_rot
6816                        && rot.val() != 0.0
6817                    {
6818                        let base = sequoia_core::Transform::translate(o.x, o.y);
6819                        let rotated = match pivot {
6820                            Some(pv) => {
6821                                sequoia_core::Transform::rotate_around_degrees(rot.val(), pv.val())
6822                                    .then(base)
6823                            }
6824                            None => base.then(sequoia_core::Transform::rotate_degrees(rot.val())),
6825                        };
6826                        Some(if z != 1.0 {
6827                            rotated.then(sequoia_core::Transform::scale(z, z))
6828                        } else {
6829                            rotated
6830                        })
6831                    } else {
6832                        None
6833                    };
6834                    g.with_transform(painter_tr, |g| {
6835                        if let Some(lt) = list_local {
6836                            let og = g.origin_global();
6837                            let global = sequoia_core::Transform::translate(-og.x, -og.y)
6838                                .then(lt)
6839                                .then(sequoia_core::Transform::translate(og.x, og.y));
6840                            g.push_list_transform(global);
6841                            Self::draw_all(layers, g);
6842                            g.pop_list_transform();
6843                        } else {
6844                            Self::draw_all(layers, g);
6845                        }
6846                    });
6847                }
6848                DrawLayer::Clip {
6849                    rect,
6850                    radius,
6851                    has_radius,
6852                    visible,
6853                    layers,
6854                    ..
6855                } => {
6856                    if !visible.val() {
6857                        continue;
6858                    }
6859                    if *has_radius {
6860                        let rr = radius.val();
6861                        g.clipped_rounded(rect.val(), rr, |g| {
6862                            Self::draw_all(layers, g);
6863                        });
6864                    } else {
6865                        g.clipped(rect.val(), |g| {
6866                            Self::draw_all(layers, g);
6867                        });
6868                    }
6869                }
6870            }
6871        }
6872    }
6873
6874    fn touch_all(layers: &[DrawLayer]) {
6875        for layer in layers {
6876            match layer {
6877                DrawLayer::Static(s) => (s.touch)(),
6878                DrawLayer::Dyn(d) => {
6879                    let mut all = Vec::new();
6880                    for row in (d.rows)() {
6881                        all.extend((d.render)(&row));
6882                    }
6883                    Self::touch_all(&all);
6884                }
6885                DrawLayer::Group {
6886                    touch_extra,
6887                    layers,
6888                    ..
6889                }
6890                | DrawLayer::Clip {
6891                    touch_extra,
6892                    layers,
6893                    ..
6894                } => {
6895                    (touch_extra)();
6896                    Self::touch_all(layers);
6897                }
6898            }
6899        }
6900    }
6901
6902    fn pick(layers: &[DrawLayer], p: Point) -> Option<String> {
6903        Self::pick_in(layers, p, 0.0, 0.0, 1.0)
6904    }
6905
6906    fn pick_in(layers: &[DrawLayer], p: Point, ax: f32, ay: f32, az: f32) -> Option<String> {
6907        if az == 0.0 {
6908            return None;
6909        }
6910        let local = Point::new((p.x - ax) / az, (p.y - ay) / az);
6911        for layer in layers.iter().rev() {
6912            match layer {
6913                DrawLayer::Static(s) => {
6914                    for (name, test) in s.hits.iter().rev() {
6915                        if test(local) {
6916                            return Some(name.clone());
6917                        }
6918                    }
6919                }
6920                DrawLayer::Dyn(d) => {
6921                    let rows = (d.rows)();
6922                    let mut all = Vec::new();
6923                    for row in rows.iter().rev() {
6924                        all.extend((d.render)(row));
6925                    }
6926                    if let Some(hit) = Self::pick_in(&all, p, ax, ay, az) {
6927                        return Some(hit);
6928                    }
6929                }
6930                DrawLayer::Group {
6931                    origin,
6932                    scale,
6933                    layers,
6934                    ..
6935                } => {
6936                    let o = origin.val();
6937                    let z = scale.val();
6938                    if let Some(hit) =
6939                        Self::pick_in(layers, p, ax + o.x * az, ay + o.y * az, az * z)
6940                    {
6941                        return Some(hit);
6942                    }
6943                }
6944                DrawLayer::Clip { layers, .. } => {
6945                    if let Some(hit) = Self::pick_in(layers, p, ax, ay, az) {
6946                        return Some(hit);
6947                    }
6948                }
6949            }
6950        }
6951        None
6952    }
6953}
6954
6955fn lower_layer(
6956    file: &str,
6957    theme: &Theme,
6958    node: &SeqNode,
6959    res: &Resolver<'_>,
6960    row: Option<&SeqRowData>,
6961) -> Result<DrawLayer, SeqError> {
6962    match node.kind.as_str() {
6963        "List" => lower_shape_list(file, theme, node, res),
6964        "Group" => lower_group(file, theme, node, res, row),
6965        "Clip" => lower_clip(file, theme, node, res, row),
6966        _ => Ok(DrawLayer::Static(lower_shape(file, theme, node, res, row)?)),
6967    }
6968}
6969
6970fn lower_group(
6971    file: &str,
6972    theme: &Theme,
6973    node: &SeqNode,
6974    res: &Resolver<'_>,
6975    row: Option<&SeqRowData>,
6976) -> Result<DrawLayer, SeqError> {
6977    let origin = opt_pt(file, res, row, node, "translate")?.unwrap_or(PtSrc {
6978        x: NumSrc::Lit(0.0),
6979        y: NumSrc::Lit(0.0),
6980    });
6981    let rot = opt_num(file, res, row, node, "rotate")?;
6982    let has_rot = find_arg(node, "rotate").is_some();
6983    let scale = match find_arg(node, "scale") {
6984        None => NumSrc::Lit(1.0),
6985        Some(arg) => dyn_f32(file, arg.line, res, row, &arg.value, "scale")?,
6986    };
6987
6988    let pivot = opt_pt(file, res, row, node, "pivot")?;
6989    let mut layers = Vec::new();
6990    let mut touch_list = vec![origin.touch_rc(), rot.touch_rc(), scale.touch_rc()];
6991    if let Some(pv) = &pivot {
6992        touch_list.push(pv.touch_rc());
6993    }
6994    for c in &node.children {
6995        layers.push(lower_layer(file, theme, c, res, row)?);
6996    }
6997    Ok(DrawLayer::Group {
6998        origin,
6999        rot,
7000        has_rot,
7001        scale,
7002        pivot,
7003        touch_extra: touches(&touch_list),
7004        layers,
7005    })
7006}
7007fn lower_shape_list(
7008    file: &str,
7009    theme: &Theme,
7010    node: &SeqNode,
7011    res: &Resolver<'_>,
7012) -> Result<DrawLayer, SeqError> {
7013    let items = find_arg(node, "items").ok_or_else(|| {
7014        SeqError::new(
7015            file,
7016            node.line,
7017            1,
7018            "List wymaga items: nazwa_propertu".to_string(),
7019        )
7020    })?;
7021    let items_prop = match &items.value {
7022        SeqValue::Ident(name) => name.clone(),
7023        _ => {
7024            return Err(SeqError::new(
7025                file,
7026                items.line,
7027                1,
7028                "items: to nazwa propertu z hosta (items: wezly)",
7029            ));
7030        }
7031    };
7032    if let Some(key) = find_arg(node, "key") {
7033        match &key.value {
7034            SeqValue::Ident(name) if name == "id" => {}
7035            _ => {
7036                return Err(SeqError::new(
7037                    file,
7038                    key.line,
7039                    1,
7040                    "key: tylko id w inkrementcie 3",
7041                ));
7042            }
7043        }
7044    }
7045    let bindings = match res {
7046        Resolver::Static => {
7047            return Err(SeqError::new(
7048                file,
7049                node.line,
7050                1,
7051                "List(items:) wymaga hosta Rust (HostBindings::rows())",
7052            ));
7053        }
7054        Resolver::Host(b) => (*b).clone(),
7055    };
7056    let get_rows = bindings.get_rows(&items_prop).ok_or_else(|| {
7057        SeqError::new(
7058            file,
7059            items.line,
7060            1,
7061            format!("nieznany prop '{items_prop}' (HostBindings::rows())"),
7062        )
7063    })?;
7064    let template = node.children.clone();
7065    let file_owned = file.to_string();
7066    let theme_owned = *theme;
7067
7068    if let Some(first) = get_rows().first() {
7069        let res = Resolver::Host(&bindings);
7070        for n in &template {
7071            lower_layer(&file_owned, &theme_owned, n, &res, Some(first))?;
7072        }
7073    }
7074    let render = Rc::new(move |row: &SeqRowData| {
7075        let res = Resolver::Host(&bindings);
7076        template
7077            .iter()
7078            .filter_map(|n| lower_layer(&file_owned, &theme_owned, n, &res, Some(row)).ok())
7079            .collect::<Vec<_>>()
7080    });
7081    Ok(DrawLayer::Dyn(DynShapes {
7082        rows: get_rows,
7083        render,
7084    }))
7085}
7086
7087#[derive(Clone)]
7088enum TextSrc {
7089    Lit(String),
7090    Dyn(Rc<dyn Fn() -> String>),
7091}
7092
7093impl TextSrc {
7094    fn val(&self) -> String {
7095        match self {
7096            Self::Lit(s) => s.clone(),
7097            Self::Dyn(f) => f(),
7098        }
7099    }
7100
7101    fn touch_rc(&self) -> Rc<dyn Fn()> {
7102        let this = self.clone();
7103        Rc::new(move || {
7104            this.val();
7105        })
7106    }
7107}
7108
7109struct TickState {
7110    angle: Rc<Cell<f32>>,
7111    last: Rc<std::cell::RefCell<Option<std::time::Instant>>>,
7112    running: BoolSrc,
7113    speed: NumSrc,
7114}
7115
7116#[allow(clippy::too_many_lines, clippy::cast_possible_truncation)]
7117fn lower_shape(
7118    file: &str,
7119    theme: &Theme,
7120    node: &SeqNode,
7121    res: &Resolver<'_>,
7122    row: Option<&SeqRowData>,
7123) -> Result<ShapeDraw, SeqError> {
7124    let visible = shape_visible(file, res, row, node)?;
7125    let named = shape_name(file, node, res, row)?;
7126    let touch_visible: Rc<dyn Fn()> = {
7127        let visible = visible.clone();
7128        Rc::new(move || visible.touch())
7129    };
7130
7131    let gate = move |draw: Rc<dyn Fn(&mut Painter)>| {
7132        let visible = visible.clone();
7133        Rc::new(move |g: &mut Painter| {
7134            if visible.val() {
7135                draw(g);
7136            }
7137        })
7138    };
7139
7140    let mk_hits = |test: Rc<dyn Fn(Point) -> bool>| -> Vec<HitTest> {
7141        named.clone().map(|name| (name, test)).into_iter().collect()
7142    };
7143    match node.kind.as_str() {
7144        "Grid" => {
7145            let step = shape_num(file, res, row, node, "step")?;
7146            let color = shape_color(file, theme, node).unwrap_or(theme.border);
7147            let t = touches(&[step.touch_rc(), touch_visible.clone()]);
7148            Ok(ShapeDraw {
7149                draw: gate(Rc::new(move |g: &mut Painter| {
7150                    g.grid(step.val(), color);
7151                })),
7152                touch: t,
7153                hits: Vec::new(),
7154            })
7155        }
7156        "Rect" => {
7157            let r = rect_src_of(file, res, row, node)?;
7158            let radius = opt_num(file, res, row, node, "radius")?;
7159            let color = shape_color(file, theme, node)?;
7160            let t = touches(&[r.touch_rc(), radius.touch_rc(), touch_visible.clone()]);
7161            let hr = r.clone();
7162            Ok(ShapeDraw {
7163                draw: gate(Rc::new(move |g: &mut Painter| {
7164                    g.rounded(r.val(), radius.val(), Fill::solid(color));
7165                })),
7166                touch: t,
7167                hits: mk_hits(Rc::new(move |p: Point| hr.val().contains(p))),
7168            })
7169        }
7170        "Circle" => {
7171            let c = shape_pt(file, res, row, node, "c")?;
7172            let r = shape_num(file, res, row, node, "r")?;
7173            let color = shape_color(file, theme, node)?;
7174
7175            let pr = match find_arg(node, "pick_r") {
7176                None => r.clone(),
7177                Some(arg) => dyn_f32(file, arg.line, res, row, &arg.value, "pick_r")?,
7178            };
7179            let t = touches(&[
7180                c.touch_rc(),
7181                r.touch_rc(),
7182                pr.touch_rc(),
7183                touch_visible.clone(),
7184            ]);
7185            let (hc, hr, hp) = (c.clone(), r.clone(), pr.clone());
7186            Ok(ShapeDraw {
7187                draw: gate(Rc::new(move |g: &mut Painter| {
7188                    g.circle(c.val(), r.val(), Fill::solid(color));
7189                })),
7190                touch: t,
7191                hits: mk_hits(Rc::new(move |p: Point| {
7192                    let c = hc.val();
7193                    let dx = p.x - c.x;
7194                    let dy = p.y - c.y;
7195                    let rr = hp.val().max(hr.val());
7196                    dx * dx + dy * dy <= rr * rr
7197                })),
7198            })
7199        }
7200        "Ellipse" => {
7201            let c = shape_pt(file, res, row, node, "c")?;
7202            let rx = shape_num(file, res, row, node, "rx")?;
7203            let ry = shape_num(file, res, row, node, "ry")?;
7204            let color = shape_color(file, theme, node)?;
7205            let t = touch4e(&c, &rx, &ry, &touch_visible);
7206            let (hc, hx, hy) = (c.clone(), rx.clone(), ry.clone());
7207            Ok(ShapeDraw {
7208                draw: gate(Rc::new(move |g: &mut Painter| {
7209                    g.ellipse(c.val(), rx.val(), ry.val(), color);
7210                })),
7211                touch: t,
7212                hits: mk_hits(Rc::new(move |p: Point| {
7213                    let c = hc.val();
7214                    let (rx, ry) = (hx.val(), hy.val());
7215                    if rx <= 0.0 || ry <= 0.0 {
7216                        return false;
7217                    }
7218                    let dx = (p.x - c.x) / rx;
7219                    let dy = (p.y - c.y) / ry;
7220                    dx * dx + dy * dy <= 1.0
7221                })),
7222            })
7223        }
7224        "Ring" => {
7225            let c = shape_pt(file, res, row, node, "c")?;
7226            let r = shape_num(file, res, row, node, "r")?;
7227            let w = shape_num(file, res, row, node, "width")?;
7228            let color = only_color(file, theme, node)?;
7229            let t = touch4e(&c, &r, &w, &touch_visible);
7230            let (hc, hr, hw) = (c.clone(), r.clone(), w.clone());
7231            Ok(ShapeDraw {
7232                draw: gate(Rc::new(move |g: &mut Painter| {
7233                    g.ring(c.val(), r.val(), w.val(), color);
7234                })),
7235                touch: t,
7236                hits: mk_hits(Rc::new(move |p: Point| {
7237                    let c = hc.val();
7238                    let dx = p.x - c.x;
7239                    let dy = p.y - c.y;
7240                    ((dx * dx + dy * dy).sqrt() - hr.val()).abs() <= hw.val()
7241                })),
7242            })
7243        }
7244        "Line" => {
7245            let a = shape_pt(file, res, row, node, "a")?;
7246            let b = shape_pt(file, res, row, node, "b")?;
7247            let w = shape_num(file, res, row, node, "width")?;
7248            let color = only_color(file, theme, node)?;
7249            let t = touch4(&a, &b, &w, &touch_visible);
7250            let (ha, hb, hw) = (a.clone(), b.clone(), w.clone());
7251            Ok(ShapeDraw {
7252                draw: gate(Rc::new(move |g: &mut Painter| {
7253                    g.line(
7254                        a.val(),
7255                        b.val(),
7256                        sequoia_core::BrushStroke::new(w.val()),
7257                        color,
7258                    );
7259                })),
7260                touch: t,
7261                hits: mk_hits(Rc::new(move |p: Point| {
7262                    dist_seg(p, ha.val(), hb.val()) <= (hw.val() / 2.0).max(3.0)
7263                })),
7264            })
7265        }
7266        "Arrow" => {
7267            let a = shape_pt(file, res, row, node, "a")?;
7268            let b = shape_pt(file, res, row, node, "b")?;
7269            let w = shape_num(file, res, row, node, "width")?;
7270            let head = shape_num(file, res, row, node, "head")?;
7271            let color = only_color(file, theme, node)?;
7272            let t = touch5(&a, &b, &w, &head, &touch_visible);
7273            let (ha, hb, hw) = (a.clone(), b.clone(), w.clone());
7274            Ok(ShapeDraw {
7275                draw: gate(Rc::new(move |g: &mut Painter| {
7276                    let h = head.val();
7277                    g.arrow(a.val(), b.val(), w.val(), h, h, color);
7278                })),
7279                touch: t,
7280                hits: mk_hits(Rc::new(move |p: Point| {
7281                    dist_seg(p, ha.val(), hb.val()) <= (hw.val() / 2.0).max(3.0)
7282                })),
7283            })
7284        }
7285        "Capsule" => {
7286            let a = shape_pt(file, res, row, node, "a")?;
7287            let b = shape_pt(file, res, row, node, "b")?;
7288            let r = shape_num(file, res, row, node, "r")?;
7289            let color = shape_color(file, theme, node)?;
7290            let t = touch4e(&a, &b, &r, &touch_visible);
7291            let (ha, hb, hr) = (a.clone(), b.clone(), r.clone());
7292            Ok(ShapeDraw {
7293                draw: gate(Rc::new(move |g: &mut Painter| {
7294                    g.capsule(a.val(), b.val(), r.val(), color);
7295                })),
7296                touch: t,
7297                hits: mk_hits(Rc::new(move |p: Point| {
7298                    dist_seg(p, ha.val(), hb.val()) <= hr.val()
7299                })),
7300            })
7301        }
7302        "Polygon" => {
7303            let pts = points_src_of(file, res, row, node)?;
7304            let color = shape_color(file, theme, node)?;
7305            let t = touch_pts(&pts, &touch_visible);
7306            let hpts = pts.clone();
7307            Ok(ShapeDraw {
7308                draw: gate(Rc::new(move |g: &mut Painter| {
7309                    let v: Vec<Point> = pts.iter().map(PtSrc::val).collect();
7310                    g.polygon(&v, color);
7311                })),
7312                touch: t,
7313                hits: mk_hits(Rc::new(move |p: Point| {
7314                    let v: Vec<Point> = hpts.iter().map(PtSrc::val).collect();
7315                    point_in_poly(p, &v)
7316                })),
7317            })
7318        }
7319        "Polyline" => {
7320            let pts = points_src_of(file, res, row, node)?;
7321            let w = shape_num(file, res, row, node, "width")?;
7322            let color = only_color(file, theme, node)?;
7323            let t = touch_pts_w(&pts, &w, &touch_visible);
7324            let (hpts, hw) = (pts.clone(), w.clone());
7325            Ok(ShapeDraw {
7326                draw: gate(Rc::new(move |g: &mut Painter| {
7327                    let v: Vec<Point> = pts.iter().map(PtSrc::val).collect();
7328                    g.polyline(&v, sequoia_core::BrushStroke::new(w.val()), color);
7329                })),
7330                touch: t,
7331                hits: mk_hits(Rc::new(move |p: Point| {
7332                    let v: Vec<Point> = hpts.iter().map(PtSrc::val).collect();
7333                    dist_polyline(p, &v) <= (hw.val() / 2.0).max(3.0)
7334                })),
7335            })
7336        }
7337        "Spline" => {
7338            let pts = points_src_of(file, res, row, node)?;
7339            if pts.len() != 4 {
7340                return Err(SeqError::new(
7341                    file,
7342                    node.line,
7343                    1,
7344                    "A spline is exactly 4 points (p0, c0, c1, p1)",
7345                ));
7346            }
7347            let w = shape_num(file, res, row, node, "width")?;
7348            let color = only_color(file, theme, node)?;
7349            let t = touch_pts_w(&pts, &w, &touch_visible);
7350            let (hpts, hw) = (pts.clone(), w.clone());
7351            Ok(ShapeDraw {
7352                draw: gate(Rc::new(move |g: &mut Painter| {
7353                    let v: Vec<Point> = pts.iter().map(PtSrc::val).collect();
7354                    g.polyline(
7355                        &sample_cubic(&v),
7356                        sequoia_core::BrushStroke::new(w.val()),
7357                        color,
7358                    );
7359                })),
7360                touch: t,
7361                hits: mk_hits(Rc::new(move |p: Point| {
7362                    let v: Vec<Point> = hpts.iter().map(PtSrc::val).collect();
7363                    dist_polyline(p, &sample_cubic(&v)) <= (hw.val() / 2.0).max(3.0)
7364                })),
7365            })
7366        }
7367        "Star" => {
7368            let c = shape_pt(file, res, row, node, "c")?;
7369            let outer = shape_num(file, res, row, node, "outer")?;
7370            let inner = shape_num(file, res, row, node, "inner")?;
7371            let n = shape_num(file, res, row, node, "n")?;
7372            let rot = opt_num(file, res, row, node, "rotation")?;
7373            let color = only_color(file, theme, node)?;
7374            let t = touch5(&c, &outer, &inner, &n, &touch_visible);
7375            let (hc, ho) = (c.clone(), outer.clone());
7376            Ok(ShapeDraw {
7377                draw: gate(Rc::new(move |g: &mut Painter| {
7378                    g.star(
7379                        c.val(),
7380                        outer.val(),
7381                        inner.val(),
7382                        n.val() as u32,
7383                        rot.val(),
7384                        color,
7385                    );
7386                })),
7387                touch: t,
7388                hits: mk_hits(Rc::new(move |p: Point| {
7389                    let c = hc.val();
7390                    let dx = p.x - c.x;
7391                    let dy = p.y - c.y;
7392                    dx * dx + dy * dy <= ho.val() * ho.val()
7393                })),
7394            })
7395        }
7396        "Ngon" => {
7397            let c = shape_pt(file, res, row, node, "c")?;
7398            let r = shape_num(file, res, row, node, "r")?;
7399            let sides = shape_num(file, res, row, node, "sides")?;
7400            let rot = opt_num(file, res, row, node, "rotation")?;
7401            let color = only_color(file, theme, node)?;
7402            let t = touch5(&c, &r, &sides, &rot, &touch_visible);
7403            let (hc, hr) = (c.clone(), r.clone());
7404            Ok(ShapeDraw {
7405                draw: gate(Rc::new(move |g: &mut Painter| {
7406                    g.regular_polygon(c.val(), r.val(), sides.val() as u32, rot.val(), color);
7407                })),
7408                touch: t,
7409                hits: mk_hits(Rc::new(move |p: Point| {
7410                    let c = hc.val();
7411                    let dx = p.x - c.x;
7412                    let dy = p.y - c.y;
7413                    dx * dx + dy * dy <= hr.val() * hr.val()
7414                })),
7415            })
7416        }
7417        "Cross" => {
7418            let c = shape_pt(file, res, row, node, "c")?;
7419            let half = shape_num(file, res, row, node, "half")?;
7420            let w = shape_num(file, res, row, node, "width")?;
7421            let color = only_color(file, theme, node)?;
7422            let t = touch4e(&c, &half, &w, &touch_visible);
7423            let (hc, hh, hw) = (c.clone(), half.clone(), w.clone());
7424            Ok(ShapeDraw {
7425                draw: gate(Rc::new(move |g: &mut Painter| {
7426                    g.cross(c.val(), half.val(), w.val(), color);
7427                })),
7428                touch: t,
7429                hits: mk_hits(Rc::new(move |p: Point| {
7430                    let c = hc.val();
7431                    let (dx, dy) = ((p.x - c.x).abs(), (p.y - c.y).abs());
7432                    let (h, w) = (hh.val(), hw.val() / 2.0);
7433                    (dx <= h && dy <= w) || (dy <= h && dx <= w)
7434                })),
7435            })
7436        }
7437        "Text2d" => {
7438            let color = only_color(file, theme, node)?;
7439            let at = shape_pt(file, res, row, node, "at")?;
7440            let size = opt_num(file, res, row, node, "size")?;
7441            let has_size = find_arg(node, "size").is_some();
7442            let t = touch3(&at, &size, &touch_visible);
7443
7444            let text: TextSrc = if let Some(t_arg) = find_arg(node, "t") {
7445                match &t_arg.value {
7446                    SeqValue::Str(key) => match res {
7447                        Resolver::Static => {
7448                            return Err(SeqError::new(
7449                                file,
7450                                t_arg.line,
7451                                1,
7452                                "t: wymaga hosta Rust (HostBindings::i18n)",
7453                            ));
7454                        }
7455                        Resolver::Host(b) => {
7456                            let i18n = b.get_i18n().ok_or_else(|| {
7457                                SeqError::new(file, t_arg.line, 1, "t: wymaga HostBindings::i18n()")
7458                            })?;
7459                            let key = key.clone();
7460                            TextSrc::Dyn(Rc::new(move || i18n.t(&key)))
7461                        }
7462                    },
7463                    _ => {
7464                        return Err(SeqError::new(
7465                            file,
7466                            t_arg.line,
7467                            1,
7468                            "t: is the key in quotation marks (t: \"save\")",
7469                        ));
7470                    }
7471                }
7472            } else {
7473                match find_arg(node, "str") {
7474                    None => {
7475                        return Err(SeqError::new(
7476                            file,
7477                            node.line,
7478                            1,
7479                            "Text2d wymaga str: \"...\"",
7480                        ));
7481                    }
7482                    Some(arg) => match &arg.value {
7483                        SeqValue::Str(s) => TextSrc::Lit(s.clone()),
7484                        SeqValue::Ident(prop) | SeqValue::Dynamic(prop) => match res {
7485                            Resolver::Static => {
7486                                return Err(SeqError::new(
7487                                    file,
7488                                    arg.line,
7489                                    1,
7490                                    "str: z propertu wymaga hosta",
7491                                ));
7492                            }
7493                            Resolver::Host(b) => TextSrc::Dyn(display_of(file, arg.line, b, prop)?),
7494                        },
7495                        SeqValue::Env(name) => match res {
7496                            Resolver::Static => {
7497                                return Err(SeqError::new(
7498                                    file,
7499                                    arg.line,
7500                                    1,
7501                                    "str: from environment requires host",
7502                                ));
7503                            }
7504                            Resolver::Host(b) => {
7505                                TextSrc::Dyn(display_env_of(file, arg.line, b, name)?)
7506                            }
7507                        },
7508                        SeqValue::ItemField(field) => match res {
7509                            Resolver::Static => {
7510                                return Err(SeqError::new(
7511                                    file,
7512                                    arg.line,
7513                                    1,
7514                                    "str: z wiersza wymaga hosta",
7515                                ));
7516                            }
7517                            Resolver::Host(_) => {
7518                                TextSrc::Dyn(display_field(file, arg.line, row, field)?)
7519                            }
7520                        },
7521                        _ => {
7522                            return Err(SeqError::new(
7523                                file,
7524                                arg.line,
7525                                1,
7526                                "str: is a string in quotation marks or dynamic(...)",
7527                            ));
7528                        }
7529                    },
7530                }
7531            };
7532            let t = touches(&[t, text.touch_rc()]);
7533            Ok(ShapeDraw {
7534                draw: gate(Rc::new(move |g: &mut Painter| {
7535                    g.text(
7536                        &text.val(),
7537                        at.val(),
7538                        if has_size { size.val() } else { 13.0 },
7539                        color,
7540                    );
7541                })),
7542                touch: t,
7543                hits: Vec::new(),
7544            })
7545        }
7546        "Svg" => {
7547            let d = shape_str(file, node, "d")?;
7548            let color = shape_color(file, theme, node)?;
7549            Ok(ShapeDraw {
7550                draw: gate(Rc::new(move |g: &mut Painter| {
7551                    g.ctx().draw_svg(&d, None, color);
7552                })),
7553                touch: touch_visible,
7554                hits: Vec::new(),
7555            })
7556        }
7557        "Dots" => {
7558            let step = shape_num(file, res, row, node, "step")?;
7559            let color = shape_color(file, theme, node).unwrap_or(theme.border);
7560            let radius = opt_num(file, res, row, node, "radius")?;
7561            let has_radius = find_arg(node, "radius").is_some();
7562            let t = touch3(&step, &radius, &touch_visible);
7563            Ok(ShapeDraw {
7564                draw: gate(Rc::new(move |g: &mut Painter| {
7565                    let st = step.val();
7566                    let r = if has_radius {
7567                        radius.val()
7568                    } else {
7569                        (st / 8.0).max(1.0)
7570                    };
7571                    g.dots(st, Vec2::ZERO, r, color);
7572                })),
7573                touch: t,
7574                hits: Vec::new(),
7575            })
7576        }
7577        "Axes" => {
7578            let color = shape_color(file, theme, node).unwrap_or(theme.border);
7579            let w = opt_num(file, res, row, node, "width")?;
7580            let has_w = find_arg(node, "width").is_some();
7581            let at = opt_pt(file, res, row, node, "at")?.unwrap_or(PtSrc {
7582                x: NumSrc::Lit(0.0),
7583                y: NumSrc::Lit(0.0),
7584            });
7585            let t = touch3(&w, &at, &touch_visible);
7586            Ok(ShapeDraw {
7587                draw: gate(Rc::new(move |g: &mut Painter| {
7588                    g.axes(at.val(), color, if has_w { w.val() } else { 1.0 });
7589                })),
7590                touch: t,
7591                hits: Vec::new(),
7592            })
7593        }
7594        "Frame" => {
7595            let r = rect_src_of(file, res, row, node)?;
7596            let w = shape_num(file, res, row, node, "width")?;
7597            let color = only_color(file, theme, node)?;
7598            let t = touch3(&r, &w, &touch_visible);
7599            Ok(ShapeDraw {
7600                draw: gate(Rc::new(move |g: &mut Painter| {
7601                    g.frame(r.val(), color, w.val());
7602                })),
7603                touch: t,
7604                hits: Vec::new(),
7605            })
7606        }
7607        "Marquee" => {
7608            let r = rect_src_of(file, res, row, node)?;
7609            let color = only_color(file, theme, node)?;
7610            let t = touch2(&r, &touch_visible);
7611            let hr = r.clone();
7612            Ok(ShapeDraw {
7613                draw: gate(Rc::new(move |g: &mut Painter| {
7614                    let q = r.val();
7615                    g.marquee(
7616                        Point::new(q.left(), q.top()),
7617                        Point::new(q.right(), q.bottom()),
7618                        Fill::solid(Color::TRANSPARENT),
7619                        color,
7620                    );
7621                })),
7622                touch: t,
7623                hits: mk_hits(Rc::new(move |p: Point| hr.val().contains(p))),
7624            })
7625        }
7626        "Handle" => {
7627            let at = shape_pt(file, res, row, node, "at")?;
7628            let r = opt_num(file, res, row, node, "r")?;
7629            let has_r = find_arg(node, "r").is_some();
7630            let color = only_color(file, theme, node)?;
7631            let t = touch3(&at, &r, &touch_visible);
7632            let hat = at.clone();
7633            let hr2 = r.clone();
7634            Ok(ShapeDraw {
7635                draw: gate(Rc::new(move |g: &mut Painter| {
7636                    g.knob(at.val(), if has_r { r.val() } else { 4.0 }, color);
7637                })),
7638                touch: t,
7639                hits: mk_hits(Rc::new(move |p: Point| {
7640                    let c = hat.val();
7641                    let rr = if has_r { hr2.val() } else { 4.0 };
7642                    let dx = p.x - c.x;
7643                    let dy = p.y - c.y;
7644                    dx * dx + dy * dy <= rr.max(6.0) * rr.max(6.0)
7645                })),
7646            })
7647        }
7648        "Image2d" => {
7649            let src: TextSrc = match find_arg(node, "src") {
7650                None => {
7651                    return Err(SeqError::new(
7652                        file,
7653                        node.line,
7654                        1,
7655                        "Image2d wymaga src: \"...\"",
7656                    ));
7657                }
7658                Some(arg) => match &arg.value {
7659                    SeqValue::Str(s) => TextSrc::Lit(s.clone()),
7660                    SeqValue::Ident(prop) | SeqValue::Dynamic(prop) => match res {
7661                        Resolver::Static => {
7662                            return Err(SeqError::new(
7663                                file,
7664                                arg.line,
7665                                1,
7666                                "src: z propertu wymaga hosta",
7667                            ));
7668                        }
7669                        Resolver::Host(b) => TextSrc::Dyn(display_of(file, arg.line, b, prop)?),
7670                    },
7671                    SeqValue::Env(name) => match res {
7672                        Resolver::Static => {
7673                            return Err(SeqError::new(
7674                                file,
7675                                arg.line,
7676                                1,
7677                                "src: from environment requires host",
7678                            ));
7679                        }
7680                        Resolver::Host(b) => TextSrc::Dyn(display_env_of(file, arg.line, b, name)?),
7681                    },
7682                    SeqValue::ItemField(field) => match res {
7683                        Resolver::Static => {
7684                            return Err(SeqError::new(
7685                                file,
7686                                arg.line,
7687                                1,
7688                                "src: z wiersza wymaga hosta",
7689                            ));
7690                        }
7691                        Resolver::Host(_) => {
7692                            TextSrc::Dyn(display_field(file, arg.line, row, field)?)
7693                        }
7694                    },
7695                    _ => {
7696                        return Err(SeqError::new(
7697                            file,
7698                            arg.line,
7699                            1,
7700                            "src: is a string in quotation marks or dynamic(...)",
7701                        ));
7702                    }
7703                },
7704            };
7705            let r = rect_src_of(file, res, row, node)?;
7706            let uv = uv_src_of(file, res, row, node)?;
7707            let t = touches(&[
7708                r.touch_rc(),
7709                src.touch_rc(),
7710                uv[0].touch_rc(),
7711                uv[1].touch_rc(),
7712                uv[2].touch_rc(),
7713                uv[3].touch_rc(),
7714                touch_visible.clone(),
7715            ]);
7716            let hr = r.clone();
7717            Ok(ShapeDraw {
7718                draw: gate(Rc::new(move |g: &mut Painter| {
7719                    let path = src.val();
7720                    if path.is_empty() {
7721                        return;
7722                    }
7723                    g.image(
7724                        r.val(),
7725                        [uv[0].val(), uv[1].val(), uv[2].val(), uv[3].val()],
7726                        ImageSource::File(path),
7727                    );
7728                })),
7729                touch: t,
7730                hits: mk_hits(Rc::new(move |p: Point| hr.val().contains(p))),
7731            })
7732        }
7733        "Clip" | "Group" | "List" => Err(SeqError::new(
7734            file,
7735            node.line,
7736            1,
7737            format!(
7738                "'{}' is a Canvas layer, not a shape (internal error)",
7739                node.kind
7740            ),
7741        )),
7742        other => Err(SeqError::new(
7743            file,
7744            node.line,
7745            1,
7746            format!("unknown shape '{other}'"),
7747        )),
7748    }
7749}
7750
7751trait FlexBoxOps {
7752    fn set_width(self, w: Length) -> Self;
7753    fn set_height(self, h: Length) -> Self;
7754    fn set_padding(self, p: PaddingVal) -> Self;
7755    fn set_padding_x(self, x: f32) -> Self;
7756    fn set_padding_y(self, y: f32) -> Self;
7757    fn set_background(self, c: Color) -> Self;
7758    fn set_flex(self, f: f32) -> Self;
7759    fn set_gap(self, g: f32) -> Self;
7760    fn set_cross_gap(self, g: f32) -> Self;
7761    fn set_wrap(self, w: bool) -> Self;
7762    fn set_align(self, a: Align) -> Self;
7763    fn set_justify(self, j: Justify) -> Self;
7764    fn set_min_width(self, v: f32) -> Self;
7765    fn set_max_width(self, v: f32) -> Self;
7766    fn set_min_height(self, v: f32) -> Self;
7767    fn set_max_height(self, v: f32) -> Self;
7768    fn set_margin(self, m: MarginVal) -> Self;
7769    fn set_margin_x(self, x: f32) -> Self;
7770    fn set_margin_y(self, y: f32) -> Self;
7771    fn set_margin_left(self, v: f32) -> Self;
7772    fn set_margin_right(self, v: f32) -> Self;
7773    fn set_margin_top(self, v: f32) -> Self;
7774    fn set_margin_bottom(self, v: f32) -> Self;
7775    fn set_overflow(self, o: Overflow) -> Self;
7776    fn set_hit_testable(self, on: bool) -> Self;
7777    fn add_child(self, kid: AnyView) -> Self;
7778    fn add_child_flex(self, kid: AnyView, flex: f32) -> Self;
7779}
7780
7781macro_rules! impl_flex_box {
7782    ($t:ty) => {
7783        impl FlexBoxOps for $t {
7784            fn set_width(self, w: Length) -> Self {
7785                self.width(w)
7786            }
7787            fn set_height(self, h: Length) -> Self {
7788                self.height(h)
7789            }
7790            fn set_padding(self, p: PaddingVal) -> Self {
7791                match p {
7792                    PaddingVal::All(v) => self.padding(v),
7793                    PaddingVal::Xy(x, y) => self.padding_xy(x, y),
7794                }
7795            }
7796            fn set_padding_x(self, x: f32) -> Self {
7797                self.padding_x(x)
7798            }
7799            fn set_padding_y(self, y: f32) -> Self {
7800                self.padding_y(y)
7801            }
7802            fn set_background(self, c: Color) -> Self {
7803                self.background(c)
7804            }
7805            fn set_flex(self, f: f32) -> Self {
7806                self.flex(f)
7807            }
7808            fn set_gap(self, g: f32) -> Self {
7809                self.gap(g)
7810            }
7811            fn set_cross_gap(self, g: f32) -> Self {
7812                self.cross_gap(g)
7813            }
7814            fn set_wrap(self, w: bool) -> Self {
7815                self.wrap(w)
7816            }
7817            fn set_align(self, a: Align) -> Self {
7818                self.align_items(a)
7819            }
7820            fn set_justify(self, j: Justify) -> Self {
7821                self.justify_content(j)
7822            }
7823            fn set_min_width(self, v: f32) -> Self {
7824                self.min_width(v)
7825            }
7826            fn set_max_width(self, v: f32) -> Self {
7827                self.max_width(v)
7828            }
7829            fn set_min_height(self, v: f32) -> Self {
7830                self.min_height(v)
7831            }
7832            fn set_max_height(self, v: f32) -> Self {
7833                self.max_height(v)
7834            }
7835            fn set_margin(self, m: MarginVal) -> Self {
7836                match m {
7837                    MarginVal::All(v) => self.margin(v),
7838                    MarginVal::Xy(x, y) => self.margin_xy(x, y),
7839                }
7840            }
7841            fn set_margin_x(self, x: f32) -> Self {
7842                self.margin_x(x)
7843            }
7844            fn set_margin_y(self, y: f32) -> Self {
7845                self.margin_y(y)
7846            }
7847            fn set_margin_left(self, v: f32) -> Self {
7848                self.margin_left(v)
7849            }
7850            fn set_margin_right(self, v: f32) -> Self {
7851                self.margin_right(v)
7852            }
7853            fn set_margin_top(self, v: f32) -> Self {
7854                self.margin_top(v)
7855            }
7856            fn set_margin_bottom(self, v: f32) -> Self {
7857                self.margin_bottom(v)
7858            }
7859            fn set_overflow(self, o: Overflow) -> Self {
7860                self.overflow(o)
7861            }
7862            fn set_hit_testable(self, on: bool) -> Self {
7863                self.hit_testable(on)
7864            }
7865            fn add_child(self, kid: AnyView) -> Self {
7866                self.child(kid)
7867            }
7868            fn add_child_flex(self, kid: AnyView, flex: f32) -> Self {
7869                self.child_flex(kid, flex)
7870            }
7871        }
7872    };
7873}
7874
7875impl_flex_box!(Column);
7876impl_flex_box!(Row);
7877
7878fn has_click(node: &SeqNode) -> bool {
7879    find_arg(node, "on_click").is_some() || find_arg(node, "hover").is_some()
7880}
7881
7882fn is_click_boundary(node: &SeqNode) -> bool {
7883    match node.kind.as_str() {
7884        "Button" | "TextField" | "Checkbox" | "Switch" | "Slider" | "Dropdown" | "Canvas" => true,
7885        "Container" | "Row" | "Column" => has_click(node),
7886        _ => false,
7887    }
7888}
7889
7890fn can_be_hitless(kind: &str) -> bool {
7891    matches!(
7892        kind,
7893        "Column"
7894            | "Row"
7895            | "Container"
7896            | "Panel"
7897            | "Stack"
7898            | "Wrap"
7899            | "Masonry"
7900            | "Text"
7901            | "Icon"
7902            | "Spacer"
7903    )
7904}
7905
7906fn click_through(node: &SeqNode) -> SeqNode {
7907    if is_click_boundary(node) {
7908        return node.clone();
7909    }
7910    let mut out = node.clone();
7911    if can_be_hitless(&node.kind) && !out.args.iter().any(|a| a.name == "hit") {
7912        out.args.push(SeqArg {
7913            name: "hit".to_string(),
7914            line: node.line,
7915            value: SeqValue::Bool(false),
7916        });
7917    }
7918    out.children = node.children.iter().map(click_through).collect();
7919    out
7920}
7921
7922fn lower_clickable_flex<V>(
7923    file: &str,
7924    theme: &Theme,
7925    styles: &BuiltStyles,
7926    node: &SeqNode,
7927    res: &Resolver<'_>,
7928    row: Option<&SeqRowData>,
7929    make: V,
7930) -> Result<AnyView, SeqError>
7931where
7932    V: FlexBoxOps + View + 'static,
7933{
7934    let mut outer = Container::new();
7935    if let Some(s) = box_style_of(file, styles, node)? {
7936        outer = outer.box_style(s);
7937    }
7938    for arg in &node.args {
7939        match arg.name.as_str() {
7940            "width" => outer = outer.width(len(file, arg, res, row)?),
7941            "height" => outer = outer.height(len(file, arg, res, row)?),
7942            "min_width" => outer = outer.min_width(num(file, arg, res, row)?),
7943            "max_width" => outer = outer.max_width(num(file, arg, res, row)?),
7944            "min_height" => outer = outer.min_height(num(file, arg, res, row)?),
7945            "max_height" => outer = outer.max_height(num(file, arg, res, row)?),
7946            "margin" => outer = apply_container_margin(outer, file, arg, res, row)?,
7947            "margin_x" => outer = outer.margin_x(num(file, arg, res, row)?),
7948            "margin_y" => outer = outer.margin_y(num(file, arg, res, row)?),
7949            "margin_left" => outer = outer.margin_left(num(file, arg, res, row)?),
7950            "margin_right" => outer = outer.margin_right(num(file, arg, res, row)?),
7951            "margin_top" => outer = outer.margin_top(num(file, arg, res, row)?),
7952            "margin_bottom" => outer = outer.margin_bottom(num(file, arg, res, row)?),
7953            "overflow" => outer = outer.overflow(overflow_val(file, arg)?),
7954            "background" => outer = outer.background(color(file, theme, arg)?),
7955            "hover" => outer = outer.hover(color(file, theme, arg)?),
7956            _ => {}
7957        }
7958    }
7959    if let Some(arg) = find_arg(node, "on_click") {
7960        match res {
7961            Resolver::Static => {
7962                return Err(SeqError::new(
7963                    file,
7964                    arg.line,
7965                    1,
7966                    "on_click wymaga hosta Rust",
7967                ));
7968            }
7969            Resolver::Host(b) => match &arg.value {
7970                SeqValue::Callback { .. } | SeqValue::CallSeq { .. } => {
7971                    let fire = wire_action(file, arg.line, b, &arg.value, row)?;
7972                    outer = outer.on_click(move || fire());
7973                }
7974                _ => {
7975                    return Err(SeqError::new(
7976                        file,
7977                        arg.line,
7978                        1,
7979                        "on_click: { akcja(args) }",
7980                    ));
7981                }
7982            },
7983        }
7984    }
7985
7986    let mut inner_args: Vec<SeqArg> = node
7987        .args
7988        .iter()
7989        .filter(|a| {
7990            !matches!(
7991                a.name.as_str(),
7992                "min_width"
7993                    | "max_width"
7994                    | "min_height"
7995                    | "max_height"
7996                    | "margin"
7997                    | "margin_x"
7998                    | "margin_y"
7999                    | "margin_left"
8000                    | "margin_right"
8001                    | "margin_top"
8002                    | "margin_bottom"
8003                    | "overflow"
8004                    | "background"
8005                    | "style"
8006                    | "on_click"
8007                    | "hover"
8008            )
8009        })
8010        .cloned()
8011        .collect();
8012
8013    if !inner_args.iter().any(|a| a.name == "hit") {
8014        inner_args.push(SeqArg {
8015            name: "hit".to_string(),
8016            line: node.line,
8017            value: SeqValue::Bool(false),
8018        });
8019    }
8020    let inner_node = SeqNode {
8021        kind: node.kind.clone(),
8022        line: node.line,
8023        label: node.label.clone(),
8024        args: inner_args,
8025        children: node.children.iter().map(click_through).collect(),
8026    };
8027    let inner = apply_flex_box(file, theme, &inner_node, make, res, row)?;
8028    let built = boxed_children(file, theme, styles, &inner_node, inner, res, row)?;
8029    outer = outer.child(built);
8030    Ok(AnyView::new(outer))
8031}
8032
8033fn indents_of(
8034    file: &str,
8035    res: &Resolver<'_>,
8036    row: Option<&SeqRowData>,
8037    node: &SeqNode,
8038) -> Result<Option<Indents>, SeqError> {
8039    let mut ind = Indents::NONE;
8040    let mut any = false;
8041    for arg in &node.args {
8042        match arg.name.as_str() {
8043            "indent" => {
8044                any = true;
8045                let n = san_indent(num(file, arg, res, row)?);
8046                ind.top = Some(n);
8047                ind.bottom = Some(n);
8048                ind.left = Some(n);
8049                ind.right = Some(n);
8050            }
8051            "indent_x" => {
8052                any = true;
8053                let n = san_indent(num(file, arg, res, row)?);
8054                ind.left = Some(n);
8055                ind.right = Some(n);
8056            }
8057            "indent_y" => {
8058                any = true;
8059                let n = san_indent(num(file, arg, res, row)?);
8060                ind.top = Some(n);
8061                ind.bottom = Some(n);
8062            }
8063            "indent_left" => {
8064                any = true;
8065                ind.left = Some(san_indent(num(file, arg, res, row)?));
8066            }
8067            "indent_right" => {
8068                any = true;
8069                ind.right = Some(san_indent(num(file, arg, res, row)?));
8070            }
8071            "indent_top" => {
8072                any = true;
8073                ind.top = Some(san_indent(num(file, arg, res, row)?));
8074            }
8075            "indent_bottom" => {
8076                any = true;
8077                ind.bottom = Some(san_indent(num(file, arg, res, row)?));
8078            }
8079            _ => {}
8080        }
8081    }
8082    Ok(any.then_some(ind))
8083}
8084
8085fn san_indent(v: f32) -> f32 {
8086    if v.is_finite() { v.max(0.0) } else { 0.0 }
8087}
8088
8089fn maybe_indent(
8090    view: AnyView,
8091    file: &str,
8092    node: &SeqNode,
8093    res: &Resolver<'_>,
8094    row: Option<&SeqRowData>,
8095) -> Result<AnyView, SeqError> {
8096    match indents_of(file, res, row, node)? {
8097        None => Ok(view),
8098        Some(ind) => Ok(AnyView::new(Margined::with_indents(view, ind))),
8099    }
8100}
8101
8102fn boxed_children<V>(
8103    file: &str,
8104    theme: &Theme,
8105    styles: &BuiltStyles,
8106    node: &SeqNode,
8107    mut v: V,
8108    res: &Resolver<'_>,
8109    row: Option<&SeqRowData>,
8110) -> Result<AnyView, SeqError>
8111where
8112    V: FlexBoxOps + View + 'static,
8113{
8114    for c in &node.children {
8115        let kid = lower_node(file, theme, styles, c, res, row)?;
8116
8117        v = match child_flex_weight(file, res, row, c)? {
8118            Some(w) => v.add_child_flex(kid, w),
8119            None => v.add_child(kid),
8120        };
8121    }
8122    Ok(AnyView::new(v))
8123}
8124
8125fn child_flex_weight(
8126    file: &str,
8127    res: &Resolver<'_>,
8128    row: Option<&SeqRowData>,
8129    node: &SeqNode,
8130) -> Result<Option<f32>, SeqError> {
8131    match find_arg(node, "flex") {
8132        None => Ok(None),
8133        Some(arg) => match &arg.value {
8134            SeqValue::Num(n) => Ok(Some(*n)),
8135            SeqValue::Ident(_)
8136            | SeqValue::Dynamic(_)
8137            | SeqValue::ItemField(_)
8138            | SeqValue::Env(_) => Ok(Some(num(file, arg, res, row)?)),
8139            _ => Err(SeqError::new(file, arg.line, 1, "'flex' to liczba")),
8140        },
8141    }
8142}
8143
8144fn single_child(
8145    file: &str,
8146    theme: &Theme,
8147    styles: &BuiltStyles,
8148    node: &SeqNode,
8149    res: &Resolver<'_>,
8150    row: Option<&SeqRowData>,
8151) -> Result<Option<AnyView>, SeqError> {
8152    match node.children.len() {
8153        0 => Ok(None),
8154        1 => Ok(Some(lower_node(
8155            file,
8156            theme,
8157            styles,
8158            &node.children[0],
8159            res,
8160            row,
8161        )?)),
8162        _ => {
8163            let mut col = Column::new();
8164            for c in &node.children {
8165                let kid = lower_node(file, theme, styles, c, res, row)?;
8166                col = match child_flex_weight(file, res, row, c)? {
8167                    Some(w) => col.child_flex(kid, w),
8168                    None => col.child(kid),
8169                };
8170            }
8171            Ok(Some(AnyView::new(col)))
8172        }
8173    }
8174}
8175
8176fn unreachable_checked<T>(
8177    file: &str,
8178    node: &SeqNode,
8179    arg: &crate::ir::SeqArg,
8180) -> Result<T, SeqError> {
8181    Err(SeqError::new(
8182        file,
8183        arg.line,
8184        1,
8185        format!(
8186            "unknown prop '{}' in {} (call check() first)",
8187            arg.name, node.kind
8188        ),
8189    ))
8190}
8191
8192fn find_arg<'n>(node: &'n SeqNode, name: &str) -> Option<&'n crate::ir::SeqArg> {
8193    node.args.iter().find(|a| a.name == name)
8194}
8195
8196enum RowVal {
8197    Sig(FieldSignal),
8198
8199    Id(i64),
8200}
8201
8202fn row_value(
8203    file: &str,
8204    line: usize,
8205    row: Option<&SeqRowData>,
8206    field: &str,
8207) -> Result<RowVal, SeqError> {
8208    match row {
8209        None => Err(SeqError::new(
8210            file,
8211            line,
8212            1,
8213            format!("item.{field} only inside List row"),
8214        )),
8215        Some(r) if field == "id" => Ok(RowVal::Id(r.id)),
8216        Some(r) => r
8217            .fields
8218            .get(field)
8219            .cloned()
8220            .map(RowVal::Sig)
8221            .ok_or_else(|| {
8222                let mut names: Vec<&str> = r.fields.keys().map(String::as_str).collect();
8223                names.sort_unstable();
8224                SeqError::new(
8225                    file,
8226                    line,
8227                    1,
8228                    format!(
8229                        "nieznane pole wiersza '{field}' (pola: {})",
8230                        names.join(", ")
8231                    ),
8232                )
8233            }),
8234    }
8235}
8236
8237#[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)]
8238fn row_num(
8239    file: &str,
8240    line: usize,
8241    row: Option<&SeqRowData>,
8242    field: &str,
8243) -> Result<f32, SeqError> {
8244    match row_value(file, line, row, field)? {
8245        RowVal::Sig(FieldSignal::Int(sig)) => Ok(sig.get_untracked() as f32),
8246        RowVal::Sig(FieldSignal::Float(sig)) => Ok(sig.get_untracked() as f32),
8247        RowVal::Id(id) => Ok(id as f32),
8248        _ => Err(SeqError::new(
8249            file,
8250            line,
8251            1,
8252            format!("pole wiersza '{field}' to nie liczba (Int/Float/id)"),
8253        )),
8254    }
8255}
8256
8257fn opt_str(
8258    file: &str,
8259    node: &SeqNode,
8260    name: &str,
8261    res: &Resolver<'_>,
8262    row: Option<&SeqRowData>,
8263) -> Result<Option<String>, SeqError> {
8264    match find_arg(node, name) {
8265        None => Ok(None),
8266        Some(arg) => match &arg.value {
8267            SeqValue::Str(s) => Ok(Some(s.clone())),
8268            SeqValue::Ident(prop) | SeqValue::Dynamic(prop) => match res {
8269                Resolver::Static => Err(str_err(file, arg, name)),
8270                Resolver::Host(b) => Ok(Some(
8271                    b.get_str(prop)
8272                        .map(|sig| sig.get_untracked())
8273                        .or_else(|| b.get_memo_str(prop).map(|m| m.get_untracked()))
8274                        .or_else(|| b.get_env_str(prop).map(|sig| sig.get_untracked()))
8275                        .ok_or_else(|| prop_err(file, arg.line, prop, "String"))?,
8276                )),
8277            },
8278            SeqValue::Env(env_name) => match res {
8279                Resolver::Static => Err(str_err(file, arg, name)),
8280                Resolver::Host(b) => Ok(Some(
8281                    b.get_env_str(env_name)
8282                        .map(|sig| sig.get_untracked())
8283                        .ok_or_else(|| {
8284                            SeqError::new(
8285                                file,
8286                                arg.line,
8287                                1,
8288                                format!(
8289                                    "unknown env.{env_name} or wrong type (here you need: String)"
8290                                ),
8291                            )
8292                        })?,
8293                )),
8294            },
8295            SeqValue::ItemField(field) => match res {
8296                Resolver::Static => Err(str_err(file, arg, name)),
8297                Resolver::Host(_) => match row_value(file, arg.line, row, field)? {
8298                    RowVal::Sig(FieldSignal::Str(sig)) => Ok(Some(sig.get_untracked())),
8299                    RowVal::Id(id) => Ok(Some(id.to_string())),
8300                    _ => Err(SeqError::new(
8301                        file,
8302                        arg.line,
8303                        1,
8304                        format!("pole wiersza '{field}' to nie tekst (String/id)"),
8305                    )),
8306                },
8307            },
8308            _ => Err(str_err(file, arg, name)),
8309        },
8310    }
8311}
8312
8313fn str_err(file: &str, arg: &crate::ir::SeqArg, name: &str) -> SeqError {
8314    SeqError::new(
8315        file,
8316        arg.line,
8317        1,
8318        format!("'{name}' is a string in quotation marks"),
8319    )
8320}
8321
8322fn prop_err(file: &str, line: usize, prop: &str, want: &str) -> SeqError {
8323    SeqError::new(
8324        file,
8325        line,
8326        1,
8327        format!("unknown prop '{prop}' or wrong type (here you need: {want})"),
8328    )
8329}
8330
8331#[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)]
8332fn prop_num_env(file: &str, line: usize, b: &HostBindings, prop: &str) -> Result<f32, SeqError> {
8333    if let Some(sig) = b.get_float(prop) {
8334        Ok(sig.get_untracked() as f32)
8335    } else if let Some(sig) = b.get_int(prop) {
8336        Ok(sig.get_untracked() as f32)
8337    } else if let Some(m) = b.get_memo_float(prop) {
8338        Ok(m.get_untracked() as f32)
8339    } else if let Some(m) = b.get_memo_int(prop) {
8340        Ok(m.get_untracked() as f32)
8341    } else {
8342        Err(prop_err(file, line, prop, "Float/Int"))
8343    }
8344}
8345
8346#[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)]
8347fn env_num(file: &str, line: usize, b: &HostBindings, name: &str) -> Result<f32, SeqError> {
8348    if let Some(sig) = b.get_env_float(name) {
8349        Ok(sig.get_untracked() as f32)
8350    } else if let Some(sig) = b.get_env_int(name) {
8351        Ok(sig.get_untracked() as f32)
8352    } else {
8353        Err(SeqError::new(
8354            file,
8355            line,
8356            1,
8357            format!("unknown env.{name} or wrong type (here you need: Float/Int)"),
8358        ))
8359    }
8360}
8361
8362fn prop_flag(file: &str, line: usize, b: &HostBindings, prop: &str) -> Result<bool, SeqError> {
8363    if let Some(sig) = b.get_bool(prop) {
8364        Ok(sig.get_untracked())
8365    } else if let Some(m) = b.get_memo_bool(prop) {
8366        Ok(m.get_untracked())
8367    } else {
8368        Err(prop_err(file, line, prop, "Bool"))
8369    }
8370}
8371
8372fn env_flag(file: &str, line: usize, b: &HostBindings, name: &str) -> Result<bool, SeqError> {
8373    b.get_env_bool(name)
8374        .map(|sig| sig.get_untracked())
8375        .ok_or_else(|| {
8376            SeqError::new(
8377                file,
8378                line,
8379                1,
8380                format!("unknown env.{name} or wrong type (here you need: Bool)"),
8381            )
8382        })
8383}
8384
8385fn num(
8386    file: &str,
8387    arg: &crate::ir::SeqArg,
8388    res: &Resolver<'_>,
8389    row: Option<&SeqRowData>,
8390) -> Result<f32, SeqError> {
8391    match &arg.value {
8392        SeqValue::Num(n) => {
8393            if n.is_finite() {
8394                Ok(*n)
8395            } else {
8396                Err(SeqError::new(
8397                    file,
8398                    arg.line,
8399                    1,
8400                    format!("'{}' must be finished", arg.name),
8401                ))
8402            }
8403        }
8404        SeqValue::Ident(prop) | SeqValue::Dynamic(prop) => match res {
8405            Resolver::Static => Err(num_err(file, arg)),
8406            Resolver::Host(b) => prop_num_env(file, arg.line, b, prop),
8407        },
8408        SeqValue::Env(name) => match res {
8409            Resolver::Static => Err(num_err(file, arg)),
8410            Resolver::Host(b) => env_num(file, arg.line, b, name),
8411        },
8412        SeqValue::ItemField(field) => match res {
8413            Resolver::Static => Err(num_err(file, arg)),
8414            Resolver::Host(_) => row_num(file, arg.line, row, field),
8415        },
8416        _ => Err(num_err(file, arg)),
8417    }
8418}
8419
8420fn num_err(file: &str, arg: &crate::ir::SeqArg) -> SeqError {
8421    SeqError::new(file, arg.line, 1, format!("'{}' to liczba", arg.name))
8422}
8423
8424fn flag(
8425    file: &str,
8426    arg: &crate::ir::SeqArg,
8427    res: &Resolver<'_>,
8428    row: Option<&SeqRowData>,
8429) -> Result<bool, SeqError> {
8430    match &arg.value {
8431        SeqValue::Bool(b) => Ok(*b),
8432        SeqValue::Ident(prop) | SeqValue::Dynamic(prop) => match res {
8433            Resolver::Static => Err(flag_err(file, arg)),
8434            Resolver::Host(b) => prop_flag(file, arg.line, b, prop),
8435        },
8436        SeqValue::Env(name) => match res {
8437            Resolver::Static => Err(flag_err(file, arg)),
8438            Resolver::Host(b) => env_flag(file, arg.line, b, name),
8439        },
8440        SeqValue::ItemField(field) => match res {
8441            Resolver::Static => Err(flag_err(file, arg)),
8442            Resolver::Host(_) => match row_value(file, arg.line, row, field)? {
8443                RowVal::Sig(FieldSignal::Bool(sig)) => Ok(sig.get_untracked()),
8444                _ => Err(SeqError::new(
8445                    file,
8446                    arg.line,
8447                    1,
8448                    format!("pole wiersza '{field}' to nie Bool"),
8449                )),
8450            },
8451        },
8452        _ => Err(flag_err(file, arg)),
8453    }
8454}
8455
8456fn flag_err(file: &str, arg: &crate::ir::SeqArg) -> SeqError {
8457    SeqError::new(file, arg.line, 1, format!("'{}' to true/false", arg.name))
8458}
8459
8460fn ident(file: &str, arg: &crate::ir::SeqArg) -> Result<String, SeqError> {
8461    match &arg.value {
8462        SeqValue::Ident(s) => Ok(s.clone()),
8463        _ => Err(SeqError::new(
8464            file,
8465            arg.line,
8466            1,
8467            format!("'{}' is the name (without quotes)", arg.name),
8468        )),
8469    }
8470}
8471
8472fn len(
8473    file: &str,
8474    arg: &crate::ir::SeqArg,
8475    res: &Resolver<'_>,
8476    row: Option<&SeqRowData>,
8477) -> Result<Length, SeqError> {
8478    match &arg.value {
8479        SeqValue::Length(l) => match *l {
8480            LengthVal::Fill(w) => Ok(Length::Fill(w)),
8481            LengthVal::Px(v) => Ok(Length::Px(v)),
8482            LengthVal::Fit => Ok(Length::Fit),
8483            LengthVal::Percent(p) => Ok(Length::Percent(p)),
8484        },
8485        SeqValue::Ident(prop) | SeqValue::Dynamic(prop) => match res {
8486            Resolver::Static => Err(len_err(file, arg)),
8487            Resolver::Host(b) => Ok(Length::Px(prop_num_env(file, arg.line, b, prop)?)),
8488        },
8489        SeqValue::Env(name) => match res {
8490            Resolver::Static => Err(len_err(file, arg)),
8491            Resolver::Host(b) => Ok(Length::Px(env_num(file, arg.line, b, name)?)),
8492        },
8493        SeqValue::ItemField(field) => match res {
8494            Resolver::Static => Err(len_err(file, arg)),
8495            Resolver::Host(_) => Ok(Length::Px(row_num(file, arg.line, row, field)?)),
8496        },
8497        _ => Err(len_err(file, arg)),
8498    }
8499}
8500
8501fn len_err(file: &str, arg: &crate::ir::SeqArg) -> SeqError {
8502    SeqError::new(
8503        file,
8504        arg.line,
8505        1,
8506        format!("'{}' to fill()/px()/fit/percent()", arg.name),
8507    )
8508}
8509
8510enum PaddingVal {
8511    All(f32),
8512    Xy(f32, f32),
8513}
8514
8515enum MarginVal {
8516    All(f32),
8517    Xy(f32, f32),
8518}
8519
8520fn margin_val(
8521    file: &str,
8522    arg: &crate::ir::SeqArg,
8523    res: &Resolver<'_>,
8524    row: Option<&SeqRowData>,
8525) -> Result<MarginVal, SeqError> {
8526    match &arg.value {
8527        SeqValue::Num(n) => Ok(MarginVal::All(*n)),
8528        SeqValue::Ident(prop) | SeqValue::Dynamic(prop) => match res {
8529            Resolver::Static => Err(margin_err(file, arg)),
8530            Resolver::Host(b) => Ok(MarginVal::All(prop_num_env(file, arg.line, b, prop)?)),
8531        },
8532        SeqValue::Env(name) => match res {
8533            Resolver::Static => Err(margin_err(file, arg)),
8534            Resolver::Host(b) => Ok(MarginVal::All(env_num(file, arg.line, b, name)?)),
8535        },
8536        SeqValue::ItemField(field) => match res {
8537            Resolver::Static => Err(margin_err(file, arg)),
8538            Resolver::Host(_) => Ok(MarginVal::All(row_num(file, arg.line, row, field)?)),
8539        },
8540        SeqValue::Array(items) => {
8541            if items.len() == 2 {
8542                match (&items[0], &items[1]) {
8543                    (SeqValue::Num(x), SeqValue::Num(y)) => Ok(MarginVal::Xy(*x, *y)),
8544                    _ => Err(SeqError::new(
8545                        file,
8546                        arg.line,
8547                        1,
8548                        "'margin' to liczba albo [x, y]",
8549                    )),
8550                }
8551            } else {
8552                Err(SeqError::new(
8553                    file,
8554                    arg.line,
8555                    1,
8556                    "'margin' to liczba albo [x, y]",
8557                ))
8558            }
8559        }
8560        _ => Err(SeqError::new(
8561            file,
8562            arg.line,
8563            1,
8564            "'margin' to liczba albo [x, y]",
8565        )),
8566    }
8567}
8568
8569fn margin_err(file: &str, arg: &crate::ir::SeqArg) -> SeqError {
8570    SeqError::new(file, arg.line, 1, "'margin' to liczba albo [x, y]")
8571}
8572
8573fn overflow_val(file: &str, arg: &crate::ir::SeqArg) -> Result<Overflow, SeqError> {
8574    match ident(file, arg)?.as_str() {
8575        "visible" => Ok(Overflow::Visible),
8576        "hidden" => Ok(Overflow::Hidden),
8577        "scroll" => Ok(Overflow::Scroll),
8578        "auto" => Ok(Overflow::Auto),
8579        other => Err(SeqError::new(
8580            file,
8581            arg.line,
8582            1,
8583            format!("overflow to visible/hidden/scroll/auto, nie '{other}'"),
8584        )),
8585    }
8586}
8587
8588fn padding_val(
8589    file: &str,
8590    arg: &crate::ir::SeqArg,
8591    res: &Resolver<'_>,
8592    row: Option<&SeqRowData>,
8593) -> Result<PaddingVal, SeqError> {
8594    match &arg.value {
8595        SeqValue::Num(n) => Ok(PaddingVal::All(*n)),
8596        SeqValue::Ident(prop) | SeqValue::Dynamic(prop) => match res {
8597            Resolver::Static => Err(padding_err(file, arg)),
8598            Resolver::Host(b) => Ok(PaddingVal::All(prop_num_env(file, arg.line, b, prop)?)),
8599        },
8600        SeqValue::Env(name) => match res {
8601            Resolver::Static => Err(padding_err(file, arg)),
8602            Resolver::Host(b) => Ok(PaddingVal::All(env_num(file, arg.line, b, name)?)),
8603        },
8604        SeqValue::ItemField(field) => match res {
8605            Resolver::Static => Err(padding_err(file, arg)),
8606            Resolver::Host(_) => Ok(PaddingVal::All(row_num(file, arg.line, row, field)?)),
8607        },
8608        SeqValue::Array(items) => {
8609            if items.len() == 2 {
8610                match (&items[0], &items[1]) {
8611                    (SeqValue::Num(x), SeqValue::Num(y)) => Ok(PaddingVal::Xy(*x, *y)),
8612                    _ => Err(SeqError::new(
8613                        file,
8614                        arg.line,
8615                        1,
8616                        "'padding' to liczba albo [x, y]",
8617                    )),
8618                }
8619            } else {
8620                Err(SeqError::new(
8621                    file,
8622                    arg.line,
8623                    1,
8624                    "'padding' to liczba albo [x, y]",
8625                ))
8626            }
8627        }
8628        _ => Err(SeqError::new(
8629            file,
8630            arg.line,
8631            1,
8632            "'padding' to liczba albo [x, y]",
8633        )),
8634    }
8635}
8636
8637fn padding_err(file: &str, arg: &crate::ir::SeqArg) -> SeqError {
8638    SeqError::new(file, arg.line, 1, "'padding' to liczba albo [x, y]")
8639}
8640
8641fn apply_padding<V>(
8642    mut v: V,
8643    file: &str,
8644    arg: &crate::ir::SeqArg,
8645    res: &Resolver<'_>,
8646    row: Option<&SeqRowData>,
8647) -> Result<V, SeqError>
8648where
8649    V: Padded,
8650{
8651    match padding_val(file, arg, res, row)? {
8652        PaddingVal::All(n) => v = v.set_pad(n),
8653        PaddingVal::Xy(x, y) => v = v.set_pad_xy(x, y),
8654    }
8655    Ok(v)
8656}
8657
8658trait Padded {
8659    fn set_pad(self, n: f32) -> Self;
8660    fn set_pad_xy(self, x: f32, y: f32) -> Self;
8661}
8662
8663impl Padded for Container {
8664    fn set_pad(self, n: f32) -> Self {
8665        self.padding(n)
8666    }
8667    fn set_pad_xy(self, x: f32, y: f32) -> Self {
8668        self.padding_xy(x, y)
8669    }
8670}
8671
8672impl Padded for Panel {
8673    fn set_pad(self, n: f32) -> Self {
8674        self.padding(n)
8675    }
8676    fn set_pad_xy(self, x: f32, y: f32) -> Self {
8677        self.padding_xy(x, y)
8678    }
8679}
8680
8681fn apply_container_margin(
8682    v: Container,
8683    file: &str,
8684    arg: &crate::ir::SeqArg,
8685    res: &Resolver<'_>,
8686    row: Option<&SeqRowData>,
8687) -> Result<Container, SeqError> {
8688    match margin_val(file, arg, res, row)? {
8689        MarginVal::All(n) => Ok(v.margin(n)),
8690        MarginVal::Xy(x, y) => Ok(v.margin_xy(x, y)),
8691    }
8692}
8693
8694fn apply_panel_margin(
8695    v: Panel,
8696    file: &str,
8697    arg: &crate::ir::SeqArg,
8698    res: &Resolver<'_>,
8699    row: Option<&SeqRowData>,
8700) -> Result<Panel, SeqError> {
8701    match margin_val(file, arg, res, row)? {
8702        MarginVal::All(n) => Ok(v.margin(n)),
8703        MarginVal::Xy(x, y) => Ok(v.margin_xy(x, y)),
8704    }
8705}
8706
8707fn color(file: &str, theme: &Theme, arg: &crate::ir::SeqArg) -> Result<Color, SeqError> {
8708    match &arg.value {
8709        SeqValue::Color(hex) => hex_color(file, arg.line, hex),
8710        SeqValue::Theme(field) => theme_color(file, arg.line, theme, field),
8711        _ => Err(SeqError::new(
8712            file,
8713            arg.line,
8714            1,
8715            format!("'{}' to #RRGGBB albo theme.pole", arg.name),
8716        )),
8717    }
8718}
8719
8720fn hex_color(file: &str, line: usize, hex: &str) -> Result<Color, SeqError> {
8721    match u32::from_str_radix(hex, 16) {
8722        Ok(v) if hex.len() == 6 => Ok(Color::hex(v)),
8723        Ok(v) => Ok(Color::hexa(v)),
8724        Err(_) => Err(SeqError::new(file, line, 1, "wrong color hex")),
8725    }
8726}
8727
8728fn theme_color(file: &str, line: usize, theme: &Theme, field: &str) -> Result<Color, SeqError> {
8729    match field {
8730        "bg" | "background" => Ok(theme.background),
8731        "surface" => Ok(theme.surface),
8732        "border" => Ok(theme.border),
8733        "ink" | "text" => Ok(theme.text),
8734        "muted" | "text_secondary" => Ok(theme.text_secondary),
8735        "accent" => Ok(theme.accent),
8736        "disabled" => Ok(theme.disabled),
8737        _ => Err(SeqError::new(
8738            file,
8739            line,
8740            1,
8741            format!("nieznane pole theme '{field}' (bg/surface/border/ink/muted/accent/disabled)"),
8742        )),
8743    }
8744}
8745
8746fn border_pair(
8747    file: &str,
8748    theme: &Theme,
8749    arg: &crate::ir::SeqArg,
8750) -> Result<(f32, Color), SeqError> {
8751    match &arg.value {
8752        SeqValue::Array(items) if items.len() == 2 => {
8753            let w = match &items[0] {
8754                SeqValue::Num(n) => *n,
8755                _ => {
8756                    return Err(SeqError::new(
8757                        file,
8758                        arg.line,
8759                        1,
8760                        "'border' is [width, color]",
8761                    ));
8762                }
8763            };
8764            let c = match &items[1] {
8765                SeqValue::Color(hex) => hex_color(file, arg.line, hex)?,
8766                SeqValue::Theme(f) => theme_color(file, arg.line, theme, f)?,
8767                _ => {
8768                    return Err(SeqError::new(
8769                        file,
8770                        arg.line,
8771                        1,
8772                        "'border' is [width, color]",
8773                    ));
8774                }
8775            };
8776            Ok((w, c))
8777        }
8778        _ => Err(SeqError::new(
8779            file,
8780            arg.line,
8781            1,
8782            "'border' is [width, color]",
8783        )),
8784    }
8785}
8786
8787fn align_val(file: &str, arg: &crate::ir::SeqArg) -> Result<Align, SeqError> {
8788    match ident(file, arg)?.as_str() {
8789        "center" => Ok(Align::Center),
8790        "start" => Ok(Align::Start),
8791        "end" => Ok(Align::End),
8792        "stretch" => Ok(Align::Stretch),
8793        other => Err(SeqError::new(
8794            file,
8795            arg.line,
8796            1,
8797            format!("align to center/start/end/stretch, nie '{other}'"),
8798        )),
8799    }
8800}
8801
8802fn justify_val(file: &str, arg: &crate::ir::SeqArg) -> Result<Justify, SeqError> {
8803    match ident(file, arg)?.as_str() {
8804        "start" => Ok(Justify::Start),
8805        "center" => Ok(Justify::Center),
8806        "end" => Ok(Justify::End),
8807        "between" => Ok(Justify::SpaceBetween),
8808        other => Err(SeqError::new(
8809            file,
8810            arg.line,
8811            1,
8812            format!("justify to start/center/end/between, nie '{other}'"),
8813        )),
8814    }
8815}
8816
8817fn button_style_of(
8818    file: &str,
8819    styles: &BuiltStyles,
8820    node: &SeqNode,
8821) -> Result<Option<ButtonStyle>, SeqError> {
8822    match find_arg(node, "style") {
8823        None => Ok(None),
8824        Some(arg) => match &arg.value {
8825            SeqValue::Ident(name) => {
8826                if let Some(preset) = button_preset(&Some(name.clone())) {
8827                    return Ok(Some(preset));
8828                }
8829                styles.button_style(name).map(Some).ok_or_else(|| {
8830                    SeqError::new(file, arg.line, 1, format!("nieznany styl '{name}'"))
8831                })
8832            }
8833            _ => Err(SeqError::new(file, arg.line, 1, "style: to nazwa stylu")),
8834        },
8835    }
8836}