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