feather_ui/
lua.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2025 Fundament Research Institute <https://fundament.institute>
3
4use crate::color::{sRGB, sRGB32};
5use crate::component::button::Button;
6use crate::component::domain_line::DomainLine;
7use crate::component::domain_point::DomainPoint;
8use crate::component::flexbox::FlexBox;
9use crate::component::gridbox::GridBox;
10use crate::component::image::Image;
11use crate::component::line::Line;
12use crate::component::listbox::ListBox;
13use crate::component::mouse_area::MouseArea;
14use crate::component::region::Region;
15use crate::component::scroll_area::ScrollArea;
16use crate::component::text::Text;
17use crate::component::textbox::TextBox;
18use crate::component::window::Window;
19use crate::component::{ChildOf, shape};
20use crate::layout::{fixed, flex, grid, list};
21use crate::persist::FnPersistStore;
22use crate::propbag::PropBag;
23use crate::{
24    APP_SOURCE_ID, AccessCell, DAbsPoint, DAbsRect, DPoint, DRect, DValue, DataID, FnPersist2,
25    InputResult, Logical, Pixel, Rect, Relative, ScopeID, Slot, SourceID, StateMachineChild,
26    UNSIZED_AXIS,
27};
28use guillotiere::euclid::Point2D;
29use mlua::UserData;
30use mlua::prelude::*;
31use std::collections::{HashMap, HashSet};
32use std::marker::PhantomData;
33use std::rc::Rc;
34use std::sync::Arc;
35use std::sync::atomic::{AtomicU64, Ordering};
36use wide::f32x4;
37use winit::application::ApplicationHandler;
38
39use winit::dpi;
40
41const SANDBOX: &[u8] = include_bytes!("./sandbox.lua");
42const FEATHER: &[u8] = include_bytes!("./feather.lua");
43
44struct NamedChunk<'a>(&'a [u8], &'a str);
45
46impl mlua::AsChunk for NamedChunk<'static> {
47    fn name(&self) -> Option<String> {
48        Some(self.1.into())
49    }
50
51    fn source<'a>(&self) -> std::io::Result<std::borrow::Cow<'a, [u8]>> {
52        Ok(std::borrow::Cow::Borrowed(self.0))
53    }
54}
55
56fn point_to_rect<T>(pt: Point2D<f32, T>) -> Rect<T> {
57    Rect::new(pt.x, pt.y, pt.x, pt.y)
58}
59
60#[derive(Clone, Debug)]
61struct LuaSourceID(Arc<SourceID>);
62
63impl std::fmt::Display for LuaSourceID {
64    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
65        self.0.fmt(f)
66    }
67}
68
69struct LuaEnum<T>(T);
70
71impl<T: TryFrom<u8>> FromLua for LuaEnum<T>
72where
73    <T as TryFrom<u8>>::Error: std::error::Error + 'static + Sync + Send,
74{
75    fn from_lua(value: LuaValue, _: &mlua::Lua) -> LuaResult<Self> {
76        T::try_from(value.as_i32().ok_or(LuaError::RuntimeError(format!(
77            "Can't convert {} to enum",
78            value.type_name()
79        )))? as u8)
80        .map_err(|e| LuaError::ExternalError(std::sync::Arc::new(e)))
81        .map(|x| LuaEnum(x))
82    }
83}
84
85impl<T: Into<u8>> IntoLua for LuaEnum<T> {
86    fn into_lua(self, _: &Lua) -> LuaResult<LuaValue> {
87        Ok(LuaValue::Integer(self.0.into().into()))
88    }
89}
90
91fn get_key<V: FromLua>(t: &LuaTable, key: &str) -> LuaResult<Option<V>> {
92    if t.contains_key(key)? {
93        Ok(Some(t.get(key)?))
94    } else {
95        Ok(None)
96    }
97}
98
99fn get_or_default<V: FromLua + Default>(t: &LuaTable, key: &str) -> LuaResult<V> {
100    Ok(get_key(t, key)?.unwrap_or_default())
101}
102
103fn get_or<V: FromLua>(t: &LuaTable, key: &str, v: V) -> LuaResult<V> {
104    Ok(get_key(t, key)?.unwrap_or(v))
105}
106
107fn get_required<V: FromLua>(t: &LuaTable, key: &str) -> LuaResult<V> {
108    if !t.contains_key(key)? {
109        Err(LuaError::RuntimeError(format!(
110            "Missing required property: {key}"
111        )))
112    } else {
113        t.get(key)
114    }
115}
116
117fn get_array_or<T: num_traits::FromPrimitive + FromLua + Clone + Copy, const N: usize>(
118    lua: &Lua,
119    t: &LuaTable,
120    key: &str,
121    d: [T; N],
122) -> LuaResult<[T; N]> {
123    Ok(if t.contains_key(key)? {
124        let v = t.get::<LuaValue>(key)?;
125        if let Some(n) = v.as_number() {
126            let num = T::from_f64(n).ok_or(LuaError::RuntimeError(format!(
127                "Failed to convert {n}, is it out of range?"
128            )))?;
129            let test: [T; N] = [num; N];
130            test
131        } else if let Some(n) = v.as_integer() {
132            let num = T::from_i64(n).ok_or(LuaError::RuntimeError(format!(
133                "Failed to convert {n}, is it out of range?"
134            )))?;
135            let test: [T; N] = [num; N];
136            test
137        } else {
138            <[T; N] as FromLua>::from_lua(v, lua)?
139        }
140    } else {
141        d
142    })
143}
144
145fn get_arg_default<V: FromLua + Default>(
146    args: &mut HashSet<&'static str>,
147    t: &LuaTable,
148    key: &'static str,
149) -> LuaResult<V> {
150    args.insert(key);
151    get_or_default(t, key)
152}
153
154fn get_arg_or<V: FromLua>(
155    args: &mut HashSet<&'static str>,
156    t: &LuaTable,
157    key: &'static str,
158    v: V,
159) -> LuaResult<V> {
160    args.insert(key);
161    get_or(t, key, v)
162}
163
164fn get_arg_required<V: FromLua>(
165    args: &mut HashSet<&'static str>,
166    t: &LuaTable,
167    key: &'static str,
168) -> LuaResult<V> {
169    args.insert(key);
170    get_required(t, key)
171}
172
173fn get_array_arg<T: num_traits::FromPrimitive + FromLua + Clone + Copy, const N: usize>(
174    args: &mut HashSet<&'static str>,
175    lua: &Lua,
176    t: &LuaTable,
177    key: &'static str,
178    d: [T; N],
179) -> LuaResult<[T; N]> {
180    args.insert(key);
181    get_array_or(lua, t, key, d)
182}
183
184fn is_dvalue(t: &LuaTable) -> LuaResult<bool> {
185    if let Some(mt) = t.metatable() {
186        Ok(mt.get::<String>("name")? == "value_mt")
187    } else {
188        Ok(false)
189    }
190}
191
192#[allow(dead_code)]
193fn dump_value(k: LuaValue, v: LuaValue, i: usize) -> LuaResult<()> {
194    if let Some(t) = v.as_table() {
195        println!("{:i$}{k:?} -", "");
196        if let Some(mt) = t.metatable() {
197            println!("{:i$}  mt - {}", "", mt.get::<String>("name")?);
198            mt.for_each(|k, v| dump_value(k, v, i + 4))?;
199        }
200
201        t.for_each(|k, v| dump_value(k, v, i + 2))?;
202    } else {
203        println!("{:i$}{k:?} : {v:?}", "");
204    }
205    Ok(())
206}
207
208fn get_kind(t: &LuaTable) -> LuaResult<String> {
209    if let Some(mt) = t.metatable() {
210        if !mt.contains_key("kind")? {
211            Err(LuaError::RuntimeError("Unknown metatable".into()))
212        } else {
213            mt.get("kind")
214        }
215    } else {
216        Err(LuaError::RuntimeError(
217            "Expected type to have metatable, but no metatable was found.".into(),
218        ))
219    }
220}
221
222fn get_name(t: &LuaTable) -> LuaResult<String> {
223    if let Some(mt) = t.metatable() {
224        if !mt.contains_key("name")? {
225            Err(LuaError::RuntimeError("nameless metatable".into()))
226        } else {
227            mt.get("name")
228        }
229    } else {
230        Err(LuaError::RuntimeError(
231            "Expected type to have metatable, but no metatable was found.".into(),
232        ))
233    }
234}
235
236fn expect_name(t: &LuaTable, expect: &str) -> LuaResult<()> {
237    let name = match get_name(t) {
238        Ok(s) => s,
239        Err(_) => "[unknown table type]".into(),
240    };
241
242    if name != expect {
243        Err(LuaError::RuntimeError(format!(
244            "Expected {expect} type, but found {name}",
245        )))
246    } else {
247        Ok(())
248    }
249}
250
251impl FromLua for DValue {
252    fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {
253        let t = value.as_table().ok_or(LuaError::RuntimeError(format!(
254            "Expected DValue, found {}",
255            value.type_name(),
256        )))?;
257        if !is_dvalue(t)? {
258            return Err(LuaError::RuntimeError(format!(
259                "Expected DValue, found {}",
260                get_name(t)?,
261            )))?;
262        }
263
264        Ok(DValue {
265            dp: get_or_default(t, "dp")?,
266            px: get_or_default(t, "px")?,
267            rel: get_or_default(t, "rel")?,
268        })
269    }
270}
271
272#[derive(Copy, Clone, Debug, Default, PartialEq)]
273struct LuaPoint<U>(Point2D<f32, U>);
274
275impl<U> LuaPoint<U> {
276    pub const fn nan() -> Self {
277        Self(Point2D::new(f32::NAN, f32::NAN))
278    }
279}
280
281impl<U> From<LuaPoint<U>> for Point2D<f32, U> {
282    fn from(val: LuaPoint<U>) -> Self {
283        val.0
284    }
285}
286
287trait LuaKind {
288    const KIND: &str;
289}
290impl LuaKind for crate::Pixel {
291    const KIND: &str = "px";
292}
293impl LuaKind for crate::Logical {
294    const KIND: &str = "dp";
295}
296impl LuaKind for crate::Relative {
297    const KIND: &str = "rel";
298}
299
300impl<U: LuaKind> FromLua for LuaPoint<U> {
301    fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {
302        if let Some(v) = value.as_number() {
303            return Ok(LuaPoint(Point2D::<f32, U>::splat(v as f32)));
304        }
305        if let Some(v) = value.as_integer() {
306            return Ok(LuaPoint(Point2D::<f32, U>::splat(v as f32)));
307        }
308
309        let t = value.as_table().ok_or(LuaError::RuntimeError(format!(
310            "Expected a number or an object, but found {}",
311            value.type_name()
312        )))?;
313        if is_dvalue(t)? {
314            const TYPES: [&str; 3] = ["dp", "px", "rel"];
315
316            for ty in TYPES {
317                if t.contains_key(ty)? != (U::KIND == ty) {
318                    return Err(LuaError::RuntimeError(format!(
319                        "Tried to build a {}point but found a value of kind {ty}",
320                        U::KIND
321                    )));
322                }
323            }
324            Ok(LuaPoint(Point2D::<f32, U>::splat(t.get(U::KIND)?)))
325        } else if let Some(mt) = t.metatable() {
326            expect_name(&mt, "point_mt")?;
327
328            if get_kind(t)? == U::KIND {
329                Ok(LuaPoint(Point2D::<f32, U>::new(t.get("x")?, t.get("y")?)))
330            } else {
331                Err(LuaError::RuntimeError(format!(
332                    "Tried to build a {}point but found kind {}",
333                    U::KIND,
334                    get_kind(t)?
335                )))
336            }
337        } else {
338            Err(LuaError::RuntimeError(format!(
339                "Found unknown table kind {} while looking for a {}point",
340                get_kind(t)?,
341                U::KIND,
342            )))
343        }
344    }
345}
346
347impl<U: LuaKind> FromLua for Rect<U> {
348    fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {
349        let v = value.as_table().ok_or(LuaError::RuntimeError(format!(
350            "Expected a rect object, but found {}",
351            value.type_name()
352        )))?;
353
354        if get_kind(v)? != U::KIND {
355            return Err(LuaError::RuntimeError(format!(
356                "Trying to build a {}rect but found kind {}",
357                U::KIND,
358                get_kind(v)?
359            )));
360        }
361
362        Ok(Rect::<U> {
363            v: f32x4::new(if v.contains_key("x")? || v.contains_key("y")? {
364                let x = get_or_default(v, "x")?;
365                let y = get_or_default(v, "y")?;
366                [x, y, x, y]
367            } else {
368                [
369                    get_or_default(v, "left")?,
370                    get_or_default(v, "top")?,
371                    get_or_default(v, "right")?,
372                    get_or_default(v, "bottom")?,
373                ]
374            }),
375            _unit: PhantomData,
376        })
377    }
378}
379
380impl FromLua for DAbsRect {
381    fn from_lua(value: LuaValue, lua: &Lua) -> LuaResult<Self> {
382        let v = value.as_table().ok_or(LuaError::RuntimeError(format!(
383            "Expected a rect, but found {}",
384            value.type_name()
385        )))?;
386        let name = get_name(v)?;
387        if name == "value_mt" {
388            if v.contains_key("rel")? {
389                return Err(LuaError::RuntimeError(
390                    "DAbsRect cannot have a relative component!".to_string(),
391                ));
392            }
393            let v = DValue::from_lua(value, lua)?;
394            let px = Rect::splat(v.px);
395            let dp = Rect::splat(v.dp);
396            Ok(DAbsRect { dp, px })
397        } else if name == "pxrect_mt" {
398            Ok(Rect::<Pixel>::from_lua(value, lua)?.into())
399        } else if name == "dprect_mt" {
400            Ok(Rect::<Logical>::from_lua(value, lua)?.into())
401        } else if name == "pxpoint_mt" {
402            Ok(point_to_rect(LuaPoint::<Pixel>::from_lua(value, lua)?.0).into())
403        } else if name == "abspoint_mt" {
404            Ok(point_to_rect(LuaPoint::<Logical>::from_lua(value, lua)?.0).into())
405        } else if name == "area_mt" {
406            let px = get_or_default(v, "px")?;
407            let dp = get_or_default(v, "dp")?;
408            if v.contains_key("rel")? {
409                return Err(LuaError::RuntimeError(
410                    "DAbsRect cannot have a relative component!".to_string(),
411                ));
412            }
413            Ok(DAbsRect { dp, px })
414        } else {
415            Err(LuaError::RuntimeError(format!(
416                "Expected an AbsRect or PxRect, but found {name}",
417            )))
418        }
419    }
420}
421
422impl FromLua for DRect {
423    fn from_lua(value: LuaValue, lua: &Lua) -> LuaResult<Self> {
424        let v = value.as_table().ok_or(LuaError::RuntimeError(format!(
425            "Expected a rect, but found {}",
426            value.type_name()
427        )))?;
428        let name = get_name(v)?;
429        if name == "value_mt" {
430            let v = DValue::from_lua(value, lua)?;
431            let px = Rect::splat(v.px);
432            let dp = Rect::splat(v.dp);
433            let rel = Rect::splat(v.rel);
434            Ok(DRect { dp, px, rel })
435        } else if name == "pxrect_mt" {
436            Ok(Rect::<Pixel>::from_lua(value, lua)?.into())
437        } else if name == "dprect_mt" {
438            Ok(Rect::<Logical>::from_lua(value, lua)?.into())
439        } else if name == "relrect_mt" {
440            Ok(Rect::<Relative>::from_lua(value, lua)?.into())
441        } else if name == "pxpoint_mt" {
442            Ok(point_to_rect(LuaPoint::<Pixel>::from_lua(value, lua)?.0).into())
443        } else if name == "abspoint_mt" {
444            Ok(point_to_rect(LuaPoint::<Logical>::from_lua(value, lua)?.0).into())
445        } else if name == "relpoint_mt" {
446            Ok(point_to_rect(LuaPoint::<Relative>::from_lua(value, lua)?.0).into())
447        } else if name == "area_mt" {
448            let px = get_or_default(v, "px")?;
449            let dp = get_or_default(v, "dp")?;
450            let rel = get_or_default(v, "rel")?;
451            Ok(DRect { dp, px, rel })
452        } else {
453            Err(LuaError::RuntimeError(format!(
454                "Expected a rect, but found {name}",
455            )))
456        }
457    }
458}
459
460impl FromLua for DAbsPoint {
461    fn from_lua(value: LuaValue, lua: &Lua) -> LuaResult<Self> {
462        let v = value.as_table().ok_or(LuaError::RuntimeError(format!(
463            "Expected a point, but found {}",
464            value.type_name()
465        )))?;
466        let name = get_name(v)?;
467        if name == "value_mt" {
468            if v.contains_key("rel")? {
469                return Err(LuaError::RuntimeError(
470                    "DAbsPoint cannot have a relative component!".to_string(),
471                ));
472            }
473            let v = DValue::from_lua(value, lua)?;
474            let px = Point2D::splat(v.px);
475            let dp = Point2D::splat(v.dp);
476            Ok(DAbsPoint { dp, px })
477        } else if name == "pxpoint_mt" {
478            Ok(LuaPoint::<Pixel>::from_lua(value, lua)?.0.into())
479        } else if name == "abspoint_mt" {
480            Ok(LuaPoint::<Logical>::from_lua(value, lua)?.0.into())
481        } else if name == "coord_mt" {
482            let px = get_or_default::<LuaPoint<Pixel>>(v, "px")?.0;
483            let dp = get_or_default::<LuaPoint<Logical>>(v, "dp")?.0;
484            if v.contains_key("rel")? {
485                return Err(LuaError::RuntimeError(
486                    "DAbsPoint cannot have a relative component!".to_string(),
487                ));
488            }
489            Ok(DAbsPoint { dp, px })
490        } else {
491            Err(LuaError::RuntimeError(format!(
492                "Expected either an abs or a px point, but found {name}",
493            )))
494        }
495    }
496}
497
498impl FromLua for DPoint {
499    fn from_lua(value: LuaValue, lua: &Lua) -> LuaResult<Self> {
500        let v = value.as_table().ok_or(LuaError::RuntimeError(format!(
501            "Expected a point, but found {}",
502            value.type_name()
503        )))?;
504        let name = get_name(v)?;
505        if name == "value_mt" {
506            let v = DValue::from_lua(value, lua)?;
507            let px = Point2D::splat(v.px);
508            let dp = Point2D::splat(v.dp);
509            let rel = Point2D::splat(v.rel);
510            Ok(DPoint { dp, px, rel })
511        } else if name == "pxpoint_mt" {
512            Ok(LuaPoint::<Pixel>::from_lua(value, lua)?.0.into())
513        } else if name == "abspoint_mt" {
514            Ok(LuaPoint::<Logical>::from_lua(value, lua)?.0.into())
515        } else if name == "relpoint_mt" {
516            Ok(LuaPoint::<Relative>::from_lua(value, lua)?.0.into())
517        } else if name == "coord_mt" {
518            let px = get_or_default::<LuaPoint<Pixel>>(v, "px")?.0;
519            let dp = get_or_default::<LuaPoint<Logical>>(v, "dp")?.0;
520            let rel = get_or_default::<LuaPoint<Relative>>(v, "rel")?.0;
521            Ok(DPoint { dp, px, rel })
522        } else {
523            Err(LuaError::RuntimeError(format!(
524                "Expected a point, but found {name}",
525            )))
526        }
527    }
528}
529
530struct LimitPoint(DPoint);
531
532impl FromLua for LimitPoint {
533    fn from_lua(value: LuaValue, lua: &Lua) -> LuaResult<Self> {
534        let v = value.as_table().ok_or(LuaError::RuntimeError(format!(
535            "Expected a point, but found {}",
536            value.type_name()
537        )))?;
538
539        // We can't use the default .into() method here because all unassigned values must be NaN
540        let name = get_name(v)?;
541        if name == "pxpoint_mt" {
542            Ok(LimitPoint(DPoint {
543                rel: LuaPoint::nan().0,
544                dp: LuaPoint::nan().0,
545                px: LuaPoint::<Pixel>::from_lua(value, lua)?.0,
546            }))
547        } else if name == "abspoint_mt" {
548            Ok(LimitPoint(DPoint {
549                rel: LuaPoint::nan().0,
550                dp: LuaPoint::<Logical>::from_lua(value, lua)?.0,
551                px: LuaPoint::nan().0,
552            }))
553        } else if name == "relpoint_mt" {
554            Ok(LimitPoint(DPoint {
555                rel: LuaPoint::<Relative>::from_lua(value, lua)?.0,
556                dp: LuaPoint::nan().0,
557                px: LuaPoint::nan().0,
558            }))
559        } else if name == "coord_mt" {
560            let px = get_or::<LuaPoint<Pixel>>(v, "px", LuaPoint::nan())?.0;
561            let dp = get_or::<LuaPoint<Logical>>(v, "dp", LuaPoint::nan())?.0;
562            let rel = get_or::<LuaPoint<Relative>>(v, "rel", LuaPoint::nan())?.0;
563            Ok(LimitPoint(DPoint { dp, px, rel }))
564        } else {
565            Err(LuaError::RuntimeError(format!(
566                "Expected a point, but found {name}",
567            )))
568        }
569    }
570}
571
572impl FromLua for sRGB {
573    fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {
574        if let Some(i) = value.as_integer() {
575            Ok(sRGB32 { rgba: i as u32 }.as_f32())
576        } else if let Some(v) = value.as_table() {
577            if v.len()? == 4 {
578                Ok(sRGB::new(v.get(1)?, v.get(2)?, v.get(3)?, v.get(4)?))
579            } else {
580                Err(LuaError::RuntimeError(format!(
581                    "A color must be an array of exactly 4 numbers, declared like {{ R, G, B, A }}, but only found {}",
582                    v.len()?
583                )))
584            }
585        } else {
586            Err(LuaError::RuntimeError(format!(
587                "Expected a color but found {}",
588                value.type_name()
589            )))
590        }
591    }
592}
593
594struct LuaFontFamily(cosmic_text::FamilyOwned);
595
596impl Default for LuaFontFamily {
597    fn default() -> Self {
598        Self(cosmic_text::FamilyOwned::SansSerif)
599    }
600}
601
602impl FromLua for LuaFontFamily {
603    fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {
604        let name =
605            value
606                .as_string()
607                .and_then(|s| s.to_str().ok())
608                .ok_or(LuaError::RuntimeError(format!(
609                    "Expected a string, but found {}",
610                    value.type_name()
611                )))?;
612
613        Ok(LuaFontFamily(if name.eq_ignore_ascii_case("serif") {
614            cosmic_text::FamilyOwned::Serif
615        } else if name.eq_ignore_ascii_case("cursive") {
616            cosmic_text::FamilyOwned::Cursive
617        } else if name.eq_ignore_ascii_case("fantasy") {
618            cosmic_text::FamilyOwned::Fantasy
619        } else if name.eq_ignore_ascii_case("monospace") {
620            cosmic_text::FamilyOwned::Monospace
621        } else if name.eq_ignore_ascii_case("sansserif") || name.eq_ignore_ascii_case("sans-serif")
622        {
623            cosmic_text::FamilyOwned::SansSerif
624        } else {
625            cosmic_text::FamilyOwned::Name((*name).into())
626        }))
627    }
628}
629
630pub type ComponentBag = Box<dyn crate::component::Component<Props = PropBag>>;
631
632impl<U: ?Sized> crate::component::ComponentWrap<U> for ComponentBag
633where
634    for<'a> &'a U: std::convert::From<&'a PropBag>,
635{
636    fn layout(
637        &self,
638        manager: &mut crate::StateManager,
639        driver: &crate::graphics::Driver,
640        window: &Arc<SourceID>,
641    ) -> Box<dyn crate::layout::Layout<U> + 'static> {
642        use std::ops::Deref;
643        Box::new(Box::deref(self).layout(manager, driver, window))
644    }
645}
646
647impl StateMachineChild for ComponentBag {
648    fn id(&self) -> Arc<SourceID> {
649        use std::ops::Deref;
650        Box::deref(self).id()
651    }
652
653    fn init(
654        &self,
655        driver: &std::sync::Weak<crate::graphics::Driver>,
656    ) -> Result<Box<dyn crate::component::StateMachineWrapper>, crate::Error> {
657        use std::ops::Deref;
658        Box::deref(self).init(driver)
659    }
660
661    fn apply_children(
662        &self,
663        f: &mut dyn FnMut(&dyn StateMachineChild) -> eyre::Result<()>,
664    ) -> eyre::Result<()> {
665        use std::ops::Deref;
666        Box::deref(self).apply_children(f)
667    }
668}
669
670macro_rules! gen_from_lua {
671    ($type_name:ident) => {
672        impl mlua::FromLua for $type_name {
673            #[inline]
674            fn from_lua(value: ::mlua::Value, _: &::mlua::Lua) -> ::mlua::Result<Self> {
675                match value {
676                    ::mlua::Value::UserData(ud) => Ok(ud.borrow::<Self>()?.clone()),
677                    _ => Err(::mlua::Error::FromLuaConversionError {
678                        from: value.type_name(),
679                        to: stringify!($type_name).to_string(),
680                        message: None,
681                    }),
682                }
683            }
684        }
685    };
686}
687
688#[derive(Clone)]
689struct LuaDomain(std::sync::Arc<crate::CrossReferenceDomain>);
690
691impl UserData for LuaDomain {}
692gen_from_lua!(LuaDomain);
693
694impl UserData for Window {}
695gen_from_lua!(Window);
696
697impl UserData for LuaSourceID {}
698gen_from_lua!(LuaSourceID);
699
700impl UserData for Slot {
701    fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
702        methods.add_meta_method(mlua::MetaMethod::ToString, |_, this, ()| {
703            Ok(format!("Slot #{}: {}", this.1, this.0))
704        });
705    }
706}
707gen_from_lua!(Slot);
708
709impl UserData for ComponentBag {}
710gen_from_lua!(ComponentBag);
711
712/// This defines the "lua" app that knows how to handle a lua value that contains the
713/// expected rust objects, and hand them off for processing. This is analogous to the
714/// pure-rust [App] struct defined in lib.rs
715pub struct LuaPersist<AppData> {
716    pub window: LuaFunction, // takes a Store and an appstate and returns a Window
717    pub id_enter: LuaFunction,
718    pub init: Result<LuaFunction, AppData>,
719    phantom: PhantomData<AppData>,
720}
721
722impl<AppData: Clone> FnPersistStore for LuaPersist<AppData> {
723    type Store = AppData;
724}
725
726impl<AppData: Clone + FromLua + IntoLua>
727    FnPersist2<AppData, ScopeID<'static>, im::HashMap<Arc<SourceID>, Option<Window>>>
728    for LuaPersist<AppData>
729{
730    fn init(&self) -> Self::Store {
731        let r = match &self.init {
732            Ok(init) => init.call::<AppData>(()),
733            Err(data) => Ok(data.clone()),
734        };
735
736        match r {
737            Err(LuaError::RuntimeError(s)) => panic!("{}", s),
738            Err(e) => panic!("{e:?}"),
739            Ok(v) => v,
740        }
741    }
742    fn call(
743        &mut self,
744        _: Self::Store,
745        appdata: AppData,
746        mut id: ScopeID<'static>,
747    ) -> (Self::Store, im::HashMap<Arc<SourceID>, Option<Window>>) {
748        let mut h = im::HashMap::new();
749
750        let (store, w) = self
751            .id_enter
752            .call::<(AppData, crate::component::window::Window)>((
753                LuaSourceID(id.id().clone()),
754                self.window.clone(),
755                appdata.clone(),
756            ))
757            .unwrap();
758        h.insert(w.id().clone(), Some(w));
759        (store, h)
760    }
761}
762
763enum LuaDualPoint {
764    Px(Point2D<f32, Pixel>),
765    Dp(Point2D<f32, Logical>),
766}
767
768impl FromLua for LuaDualPoint {
769    fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {
770        let t = value.as_table().ok_or(LuaError::RuntimeError(format!(
771            "Expected a 2D point, but found {}",
772            value.type_name()
773        )))?;
774
775        if is_dvalue(t)? {
776            if t.contains_key("dp")? && !t.contains_key("px")? {
777                Ok(LuaDualPoint::Dp(Point2D::<f32, Logical>::splat(
778                    t.get("dp")?,
779                )))
780            } else if t.contains_key("px")? && !t.contains_key("dp")? {
781                Ok(LuaDualPoint::Px(Point2D::<f32, Pixel>::splat(t.get("px")?)))
782            } else {
783                Err(LuaError::RuntimeError("This property only accepts a point that is either abs or px, not relative or a combination.".to_string()))
784            }
785        } else if t.contains_key("dp")? && !t.contains_key("px")? {
786            Ok(LuaDualPoint::Dp(t.get::<LuaPoint<Logical>>("dp")?.0))
787        } else if t.contains_key("px")? && !t.contains_key("dp")? {
788            Ok(LuaDualPoint::Px(t.get::<LuaPoint<Pixel>>("px")?.0))
789        } else if get_kind(t)? == "px" {
790            Ok(LuaDualPoint::Px(Point2D::<f32, Pixel>::new(
791                t.get("x")?,
792                t.get("y")?,
793            )))
794        } else if get_kind(t)? == "dp" {
795            Ok(LuaDualPoint::Dp(Point2D::<f32, Logical>::new(
796                t.get("x")?,
797                t.get("y")?,
798            )))
799        } else {
800            Err(LuaError::RuntimeError("This property only accepts a point that is either abs or px, not relative or a combination.".to_string()))
801        }
802    }
803}
804
805fn load_prop<T: mlua::FromLua>(
806    f: fn(&mut PropBag, T) -> Option<T>,
807    bag: &mut PropBag,
808    props: &LuaTable,
809    name: &str,
810) -> LuaResult<()> {
811    if props.contains_key(name)? {
812        f(bag, props.get(name)?);
813    }
814    Ok(())
815}
816
817#[inline]
818fn replace_unsized<U>(rect: &mut Rect<U>) {
819    rect.v = f32x4::new(
820        rect.v
821            .to_array()
822            .map(|x| if x.is_nan() { UNSIZED_AXIS } else { x }),
823    );
824}
825
826#[inline]
827fn replace_unsized_drect(mut rect: DRect) -> DRect {
828    replace_unsized(&mut rect.dp);
829    replace_unsized(&mut rect.px);
830    replace_unsized(&mut rect.rel);
831    rect
832}
833
834#[inline]
835fn replace_limit<U>(p: &mut Point2D<f32, U>, bound: f32) {
836    if p.x.is_nan() {
837        p.x = bound
838    }
839    if p.y.is_nan() {
840        p.y = bound
841    }
842}
843
844#[inline]
845fn replace_limit_dpoint(mut p: DPoint, bound: f32) -> DPoint {
846    replace_limit(&mut p.dp, bound);
847    replace_limit(&mut p.px, bound);
848    replace_limit(&mut p.rel, bound);
849    p
850}
851
852impl FromLua for PropBag {
853    fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {
854        let props = value.as_table().ok_or(LuaError::RuntimeError(format!(
855            "Expected PropBag, got {}",
856            value.type_name(),
857        )))?;
858        let mut bag = PropBag::new();
859
860        load_prop(PropBag::set_wrap, &mut bag, props, "wrap")?;
861        load_prop(PropBag::set_zindex, &mut bag, props, "zindex")?;
862        load_prop(PropBag::set_order, &mut bag, props, "order")?;
863        load_prop(PropBag::set_grow, &mut bag, props, "grow")?;
864        load_prop(PropBag::set_shrink, &mut bag, props, "shrink")?;
865        load_prop(PropBag::set_basis, &mut bag, props, "basis")?;
866        load_prop(PropBag::set_padding, &mut bag, props, "padding")?;
867        load_prop(PropBag::set_margin, &mut bag, props, "margin")?;
868        load_prop(PropBag::set_anchor, &mut bag, props, "anchor")?;
869
870        if props.contains_key("area")? {
871            bag.set_area(replace_unsized_drect(props.get::<DRect>("area")?));
872        }
873
874        let mut limits: crate::DLimits = Default::default();
875        let mut rlimits: crate::Limits<Relative> = Default::default();
876
877        if props.contains_key("minsize")? {
878            let p = replace_limit_dpoint(props.get::<LimitPoint>("minsize")?.0, f32::NEG_INFINITY);
879            limits.dp.set_min(p.dp.to_vector().to_size());
880            limits.px.set_min(p.px.to_vector().to_size());
881            rlimits.set_min(p.rel.to_vector().to_size());
882        }
883
884        if props.contains_key("maxsize")? {
885            let p = replace_limit_dpoint(props.get::<LimitPoint>("maxsize")?.0, f32::INFINITY);
886            limits.dp.set_max(p.dp.to_vector().to_size());
887            limits.px.set_max(p.px.to_vector().to_size());
888            rlimits.set_max(p.rel.to_vector().to_size());
889        }
890
891        bag.set_limits(limits);
892        bag.set_rlimits(rlimits);
893
894        if props.contains_key("direction")? {
895            bag.set_direction(props.get::<LuaEnum<crate::RowDirection>>("direction")?.0);
896        }
897
898        if props.contains_key("justify")? {
899            bag.set_justify(props.get::<LuaEnum<flex::FlexJustify>>("justify")?.0);
900        }
901
902        if props.contains_key("align")? {
903            bag.set_align(props.get::<LuaEnum<flex::FlexJustify>>("align")?.0);
904        }
905
906        if props.contains_key("domain")? {
907            bag.set_domain(props.get::<LuaDomain>("domain")?.0);
908        }
909
910        if props.contains_key("obstacles")? {
911            bag.set_obstacles(props.get::<Vec<DAbsRect>>("obstacles")?.as_slice());
912        }
913
914        if props.contains_key("dim")? {
915            bag.set_dim(props.get::<LuaPoint<Pixel>>("dim")?.0.to_vector().to_size());
916        }
917
918        if props.contains_key("rows")? {
919            bag.set_rows(props.get::<Vec<DValue>>("rows")?.as_slice());
920        }
921
922        if props.contains_key("columns")? {
923            bag.set_columns(props.get::<Vec<DValue>>("columns")?.as_slice());
924        }
925
926        if props.contains_key("spacing")? {
927            bag.set_spacing(props.get::<DPoint>("spacing")?);
928        }
929
930        if props.contains_key("coord")? {
931            let coords = props.get::<Vec<usize>>("coord")?;
932            bag.set_coord((coords[0], coords[1]));
933        }
934
935        if props.contains_key("span")? {
936            let coords = props.get::<Vec<usize>>("span")?;
937            bag.set_span((coords[0], coords[1]));
938        }
939
940        Ok(bag)
941    }
942}
943
944fn prop_no_children(t: &LuaTable) -> LuaResult<PropBag> {
945    let count = t.len()?;
946    if count > 0 {
947        return Err(LuaError::RuntimeError(format!(
948            "Found {count} child components for a component that doesn't accept children!"
949        )));
950    }
951
952    let bag: PropBag = get_required(t, "props")?;
953    Ok(bag)
954}
955
956fn push_child<D: crate::layout::Desc + ?Sized>(
957    t: &LuaTable,
958    i: i64,
959    children: &mut im::Vector<Option<Box<ChildOf<D>>>>,
960) -> LuaResult<()>
961where
962    std::boxed::Box<dyn crate::component::Component<Props = PropBag>>:
963        crate::component::ComponentWrap<<D as crate::layout::Desc>::Child>,
964{
965    let v = t.get::<LuaValue>(i)?;
966    if v.is_nil() {
967        return Ok(());
968    } else if let Some(inner) = v.as_table()
969        && let Some(mt) = inner.metatable()
970        && let Ok(s) = mt.get::<String>("kind")
971        && s == "multicomponent_mt"
972    {
973        for j in 1..=inner.len()? {
974            push_child::<D>(inner, j, children)?;
975        }
976    } else {
977        let component: ComponentBag = t.get(i)?;
978        children.push_back(Some(Box::new(component)));
979    }
980
981    Ok(())
982}
983
984#[allow(clippy::type_complexity)]
985fn prop_children<D: crate::layout::Desc + ?Sized>(
986    t: &LuaTable,
987) -> LuaResult<(im::Vector<Option<Box<ChildOf<D>>>>, PropBag)>
988where
989    std::boxed::Box<dyn crate::component::Component<Props = PropBag>>:
990        crate::component::ComponentWrap<<D as crate::layout::Desc>::Child>,
991{
992    let mut children: im::Vector<Option<Box<ChildOf<D>>>> = im::Vector::new();
993
994    for i in 1..=t.len()? {
995        push_child::<D>(t, i, &mut children)?;
996    }
997
998    let bag: PropBag = get_required(t, "props")?;
999    Ok((children, bag))
1000}
1001
1002fn check_args(name: &str, t: LuaTable, v: HashSet<&'static str>) -> LuaResult<()> {
1003    for pair in t.pairs() {
1004        let (k, _): (LuaValue, LuaValue) = pair?;
1005        if k.as_integer().is_none()
1006            && k.as_number().is_none()
1007            && let Some(s) = k.as_string()
1008        {
1009            let str = s.to_string_lossy();
1010            if !v.contains(str.as_str()) {
1011                return Err(LuaError::FromLuaConversionError {
1012                    from: "[attributes table]",
1013                    to: name.to_string(),
1014                    message: Some(format!("Unexpected key {str} in attributes table")),
1015                });
1016            }
1017        }
1018    }
1019
1020    Ok(())
1021}
1022
1023fn create_button(_: &Lua, (id, body): (LuaSourceID, LuaTable)) -> LuaResult<ComponentBag> {
1024    let (children, bag) = prop_children::<dyn fixed::Prop>(&body)?;
1025
1026    let mut args = HashSet::from_iter(["props"]);
1027    let onclick = get_arg_required(&mut args, &body, "onclick")?;
1028    check_args("button", body, args)?;
1029
1030    let res: LuaResult<ComponentBag> = Ok(Box::new(Button::<PropBag>::new(
1031        id.0, bag, onclick, children,
1032    )));
1033    res
1034}
1035
1036fn create_domain_line(_: &Lua, (id, body): (LuaSourceID, LuaTable)) -> LuaResult<ComponentBag> {
1037    let bag = prop_no_children(&body)?;
1038
1039    let mut args = HashSet::from_iter(["props"]);
1040    let domain: LuaDomain = get_arg_required(&mut args, &body, "domain")?;
1041    let start: LuaSourceID = get_arg_required(&mut args, &body, "start")?;
1042    let end: LuaSourceID = get_arg_required(&mut args, &body, "end")?;
1043    let fill = get_arg_default(&mut args, &body, "fill")?;
1044    check_args("domain-line", body, args)?;
1045
1046    let res: LuaResult<ComponentBag> = Ok(Box::new(DomainLine::<PropBag> {
1047        id: id.0,
1048        domain: domain.0,
1049        start: start.0,
1050        end: end.0,
1051        props: bag.into(),
1052        fill,
1053    }));
1054    res
1055}
1056
1057fn create_domain_point(_: &Lua, (id, body): (LuaSourceID, LuaTable)) -> LuaResult<ComponentBag> {
1058    let bag = prop_no_children(&body)?;
1059
1060    let args = HashSet::from_iter(["props"]);
1061    check_args("domain-point", body, args)?;
1062
1063    let res: LuaResult<ComponentBag> = Ok(Box::new(DomainPoint::<PropBag>::new(id.0, bag)));
1064    res
1065}
1066
1067fn create_flexbox(_: &Lua, (id, body): (LuaSourceID, LuaTable)) -> LuaResult<ComponentBag> {
1068    let (children, bag) = prop_children::<dyn flex::Prop>(&body)?;
1069    let args = HashSet::from_iter(["props"]);
1070    check_args("flexbox", body, args)?;
1071
1072    let res: LuaResult<ComponentBag> = Ok(Box::new(FlexBox::<PropBag>::new(id.0, bag, children)));
1073    res
1074}
1075
1076fn create_gridbox(_: &Lua, (id, body): (LuaSourceID, LuaTable)) -> LuaResult<ComponentBag> {
1077    let (children, bag) = prop_children::<dyn grid::Prop>(&body)?;
1078    let args = HashSet::from_iter(["props"]);
1079    check_args("gridbox", body, args)?;
1080
1081    let res: LuaResult<ComponentBag> = Ok(Box::new(GridBox::<PropBag>::new(id.0, bag, children)));
1082    res
1083}
1084
1085fn create_image(_: &Lua, (id, body): (LuaSourceID, LuaTable)) -> LuaResult<ComponentBag> {
1086    let bag = prop_no_children(&body)?;
1087
1088    let mut args = HashSet::from_iter(["props"]);
1089    let resource: String = get_arg_required(&mut args, &body, "resource")?;
1090    let size: DAbsPoint = get_arg_default(&mut args, &body, "size")?;
1091    let dynamic: bool = get_arg_default(&mut args, &body, "dynamic")?;
1092    check_args("image", body, args)?;
1093
1094    let location = std::path::PathBuf::from(resource);
1095
1096    let res: LuaResult<ComponentBag> = Ok(Box::new(Image::<PropBag>::new(
1097        id.0, bag, &location, size, dynamic,
1098    )));
1099    res
1100}
1101
1102fn create_line(_: &Lua, (id, body): (LuaSourceID, LuaTable)) -> LuaResult<ComponentBag> {
1103    let bag = prop_no_children(&body)?;
1104
1105    let mut args = HashSet::from_iter(["props"]);
1106    let start: LuaPoint<Pixel> = get_arg_required(&mut args, &body, "start")?;
1107    let end: LuaPoint<Pixel> = get_arg_required(&mut args, &body, "end")?;
1108    let fill = get_arg_default(&mut args, &body, "fill")?;
1109    check_args("line", body, args)?;
1110
1111    let res: LuaResult<ComponentBag> = Ok(Box::new(Line::<PropBag> {
1112        id: id.0,
1113        start: start.0,
1114        end: end.0,
1115        props: bag.into(),
1116        fill,
1117    }));
1118    res
1119}
1120
1121fn create_listbox(_: &Lua, (id, body): (LuaSourceID, LuaTable)) -> LuaResult<ComponentBag> {
1122    let (children, bag) = prop_children::<dyn list::Prop>(&body)?;
1123    let args = HashSet::from_iter(["props"]);
1124    check_args("listbox", body, args)?;
1125
1126    let res: LuaResult<ComponentBag> = Ok(Box::new(ListBox::<PropBag>::new(id.0, bag, children)));
1127    res
1128}
1129
1130fn create_mousearea(_: &Lua, (id, body): (LuaSourceID, LuaTable)) -> LuaResult<ComponentBag> {
1131    let bag = prop_no_children(&body)?;
1132
1133    let mut args = HashSet::from_iter(["props"]);
1134    let deadzone = get_arg_or(&mut args, &body, "deadzone", f32::INFINITY)?;
1135    let onclick = get_arg_default(&mut args, &body, "onclick")?;
1136    let ondblclick = get_arg_default(&mut args, &body, "ondblclick")?;
1137    let ondrag = get_arg_default(&mut args, &body, "ondrag")?;
1138    check_args("mousearea", body, args)?;
1139
1140    let res: LuaResult<ComponentBag> = Ok(Box::new(MouseArea::<PropBag>::new(
1141        id.0,
1142        bag,
1143        Some(deadzone),
1144        [onclick, ondblclick, ondrag, None, None, None],
1145    )));
1146    res
1147}
1148
1149fn create_region(_: &Lua, (id, body): (LuaSourceID, LuaTable)) -> LuaResult<ComponentBag> {
1150    let (children, bag) = prop_children::<dyn fixed::Prop>(&body)?;
1151    let mut args = HashSet::from_iter(["props"]);
1152    let color: Option<sRGB> = get_arg_default(&mut args, &body, "color")?;
1153    let rotation: Option<f32> = get_arg_default(&mut args, &body, "rotation")?;
1154    check_args("region", body, args)?;
1155
1156    Ok(Box::new(if color.is_some() || rotation.is_some() {
1157        Region::<PropBag>::new_layer(
1158            id.0,
1159            bag,
1160            color.unwrap_or(sRGB::white()).as_32bit(),
1161            rotation.unwrap_or_default(),
1162            children,
1163        )
1164    } else {
1165        Region::<PropBag>::new(id.0, bag, children)
1166    }))
1167}
1168
1169fn create_scrollarea(_: &Lua, (id, body): (LuaSourceID, LuaTable)) -> LuaResult<ComponentBag> {
1170    let (children, bag) = prop_children::<dyn fixed::Prop>(&body)?;
1171    let mut args = HashSet::from_iter(["props"]);
1172    let stepx = get_arg_default(&mut args, &body, "stepx")?;
1173    let stepy = get_arg_default(&mut args, &body, "stepy")?;
1174    let extension = get_arg_default(&mut args, &body, "extension")?;
1175    let onscroll = get_arg_default(&mut args, &body, "onscroll")?;
1176    check_args("scrollarea", body, args)?;
1177
1178    let res: LuaResult<ComponentBag> = Ok(Box::new(ScrollArea::<PropBag>::new(
1179        id.0,
1180        bag,
1181        (stepx, stepy),
1182        extension,
1183        children,
1184        [onscroll],
1185    )));
1186    res
1187}
1188
1189fn create_round_rect(lua: &Lua, (id, body): (LuaSourceID, LuaTable)) -> LuaResult<ComponentBag> {
1190    let bag = prop_no_children(&body)?;
1191
1192    let mut args: HashSet<&'static str> = HashSet::from_iter(["props"]);
1193    let border = get_arg_default(&mut args, &body, "border")?;
1194    let blur = get_arg_default(&mut args, &body, "blur")?;
1195    let fill = get_arg_default(&mut args, &body, "fill")?;
1196    let outline = get_arg_default(&mut args, &body, "outline")?;
1197    let corners = get_array_arg(&mut args, lua, &body, "corners", [0.0; 4])?;
1198    let size: DAbsPoint = get_arg_default(&mut args, &body, "size")?;
1199    check_args("round_rect", body, args)?;
1200
1201    Ok(Box::new(shape::round_rect(
1202        id.0,
1203        bag,
1204        border,
1205        blur,
1206        wide::f32x4::new(corners),
1207        fill,
1208        outline,
1209        size,
1210    )))
1211}
1212
1213fn create_arc(lua: &Lua, (id, body): (LuaSourceID, LuaTable)) -> LuaResult<ComponentBag> {
1214    let bag = prop_no_children(&body)?;
1215
1216    let mut args: HashSet<&'static str> = HashSet::from_iter(["props"]);
1217    let border = get_arg_default(&mut args, &body, "border")?;
1218    let blur = get_arg_default(&mut args, &body, "blur")?;
1219    let fill = get_arg_default(&mut args, &body, "fill")?;
1220    let outline = get_arg_default(&mut args, &body, "outline")?;
1221    let angles = get_array_arg(&mut args, lua, &body, "angles", [0.0; 2])?;
1222    let inner_radius = get_arg_default(&mut args, &body, "innerRadius")?;
1223    let size: DAbsPoint = get_arg_default(&mut args, &body, "size")?;
1224    check_args("arc", body, args)?;
1225
1226    Ok(Box::new(shape::arcs(
1227        id.0,
1228        bag,
1229        border,
1230        blur,
1231        inner_radius,
1232        angles,
1233        fill,
1234        outline,
1235        size,
1236    )))
1237}
1238
1239fn create_triangle(lua: &Lua, (id, body): (LuaSourceID, LuaTable)) -> LuaResult<ComponentBag> {
1240    let bag = prop_no_children(&body)?;
1241
1242    let mut args: HashSet<&'static str> = HashSet::from_iter(["props"]);
1243    let border = get_arg_default(&mut args, &body, "border")?;
1244    let blur = get_arg_default(&mut args, &body, "blur")?;
1245    let fill = get_arg_default(&mut args, &body, "fill")?;
1246    let outline = get_arg_default(&mut args, &body, "outline")?;
1247    let corners = get_array_arg(&mut args, lua, &body, "corners", [0.0; 3])?;
1248    let offset = get_arg_default(&mut args, &body, "offset")?;
1249    let size: DAbsPoint = get_arg_default(&mut args, &body, "size")?;
1250    check_args("triangle", body, args)?;
1251
1252    Ok(Box::new(shape::triangle(
1253        id.0, bag, border, blur, corners, offset, fill, outline, size,
1254    )))
1255}
1256
1257fn create_circle(lua: &Lua, (id, body): (LuaSourceID, LuaTable)) -> LuaResult<ComponentBag> {
1258    let bag = prop_no_children(&body)?;
1259
1260    let mut args: HashSet<&'static str> = HashSet::from_iter(["props"]);
1261    let border = get_arg_default(&mut args, &body, "border")?;
1262    let blur = get_arg_default(&mut args, &body, "blur")?;
1263    let fill = get_arg_default(&mut args, &body, "fill")?;
1264    let outline = get_arg_default(&mut args, &body, "outline")?;
1265    let radii = get_array_arg(&mut args, lua, &body, "radii", [0.0; 2])?;
1266    let size: DAbsPoint = get_arg_default(&mut args, &body, "size")?;
1267    check_args("circle", body, args)?;
1268
1269    Ok(Box::new(shape::circle(
1270        id.0, bag, border, blur, radii, fill, outline, size,
1271    )))
1272}
1273
1274// In CSS, 1.2 is usually used as the "reasonable" default line-height for a given font.
1275const DEFAULT_LINE_HEIGHT: f32 = 1.2;
1276// CSS defaults to 16 pixels, which is 12.8 points. We round up to 14 points as the next highest even number.
1277const DEFAULT_FONT_SIZE: f32 = 14.0;
1278
1279fn to_style(style: u8) -> LuaResult<cosmic_text::Style> {
1280    match style {
1281        0 => Ok(cosmic_text::Style::Normal),
1282        1 => Ok(cosmic_text::Style::Italic),
1283        2 => Ok(cosmic_text::Style::Oblique),
1284        _ => Err(LuaError::RuntimeError(format!(
1285            "{style} is not a valid style enum value!"
1286        ))),
1287    }
1288}
1289
1290fn to_wrap(wrap: u8) -> LuaResult<cosmic_text::Wrap> {
1291    match wrap {
1292        0 => Ok(cosmic_text::Wrap::None),
1293        1 => Ok(cosmic_text::Wrap::Glyph),
1294        2 => Ok(cosmic_text::Wrap::Word),
1295        3 => Ok(cosmic_text::Wrap::WordOrGlyph),
1296        _ => Err(LuaError::RuntimeError(format!(
1297            "{wrap} is not a valid wrap enum value!"
1298        ))),
1299    }
1300}
1301
1302fn to_align(align: Option<u8>) -> LuaResult<Option<cosmic_text::Align>> {
1303    if let Some(a) = align {
1304        Ok(Some(match a {
1305            0 => cosmic_text::Align::Left,
1306            1 => cosmic_text::Align::Right,
1307            2 => cosmic_text::Align::Center,
1308            3 => cosmic_text::Align::Justified,
1309            4 => cosmic_text::Align::End,
1310            _ => {
1311                return Err(LuaError::RuntimeError(format!(
1312                    "{a} is not a valid align enum value!"
1313                )));
1314            }
1315        }))
1316    } else {
1317        Ok(None)
1318    }
1319}
1320
1321fn create_text(_: &Lua, (id, body): (LuaSourceID, LuaTable)) -> LuaResult<ComponentBag> {
1322    let bag = prop_no_children(&body)?;
1323
1324    let mut args = HashSet::from_iter(["props"]);
1325    let text = get_arg_required(&mut args, &body, "text")?;
1326    let font_size = get_arg_or(&mut args, &body, "fontsize", DEFAULT_FONT_SIZE)?;
1327    let line_height = get_arg_or(
1328        &mut args,
1329        &body,
1330        "lineheight",
1331        font_size * DEFAULT_LINE_HEIGHT,
1332    )?;
1333    let font: LuaFontFamily = get_arg_default(&mut args, &body, "font")?;
1334    let color = get_arg_required(&mut args, &body, "color")?;
1335    let weight: u16 = get_arg_or(&mut args, &body, "weight", cosmic_text::Weight::NORMAL.0)?;
1336    let style: u8 = get_arg_default(&mut args, &body, "style")?;
1337    let wrap: u8 = get_arg_default(&mut args, &body, "wrap")?;
1338    let align: Option<u8> = get_arg_default(&mut args, &body, "align")?;
1339    check_args("text", body, args)?;
1340
1341    let style = to_style(style)?;
1342    let wrap = to_wrap(wrap)?;
1343    let align = to_align(align)?;
1344
1345    Ok(Box::new(Text::<PropBag>::new(
1346        id.0,
1347        bag,
1348        font_size,
1349        line_height,
1350        text,
1351        font.0,
1352        color,
1353        cosmic_text::Weight(weight),
1354        style,
1355        wrap,
1356        align,
1357    )))
1358}
1359
1360fn create_textbox(_: &Lua, (id, body): (LuaSourceID, LuaTable)) -> LuaResult<ComponentBag> {
1361    let bag = prop_no_children(&body)?;
1362
1363    let mut args = HashSet::from_iter(["props"]);
1364    let font_size = get_arg_or(&mut args, &body, "fontsize", DEFAULT_FONT_SIZE)?;
1365    let line_height = get_arg_or(
1366        &mut args,
1367        &body,
1368        "lineheight",
1369        font_size * DEFAULT_LINE_HEIGHT,
1370    )?;
1371    let font: LuaFontFamily = get_arg_default(&mut args, &body, "font")?;
1372    let color = get_arg_required(&mut args, &body, "color")?;
1373    let weight: u16 = get_arg_or(&mut args, &body, "weight", cosmic_text::Weight::NORMAL.0)?;
1374    let style: u8 = get_arg_default(&mut args, &body, "style")?;
1375    let wrap: u8 = get_arg_default(&mut args, &body, "wrap")?;
1376    let align: Option<u8> = get_arg_default(&mut args, &body, "align")?;
1377    check_args("textbox", body, args)?;
1378
1379    let style = to_style(style)?;
1380    let wrap = to_wrap(wrap)?;
1381    let align = to_align(align)?;
1382
1383    Ok(Box::new(TextBox::<PropBag>::new(
1384        id.0,
1385        bag,
1386        font_size,
1387        line_height,
1388        font.0,
1389        color,
1390        cosmic_text::Weight(weight),
1391        style,
1392        wrap,
1393        align,
1394    )))
1395}
1396
1397fn create_id(_: &Lua, (parent, v): (Option<LuaSourceID>, LuaValue)) -> LuaResult<LuaSourceID> {
1398    if let Some(n) = v.as_integer() {
1399        if let Some(parent) = parent {
1400            Ok(LuaSourceID(parent.0.child(DataID::Int(n))))
1401        } else {
1402            #[cfg(debug_assertions)]
1403            panic!("NO PARENT! This means the layout was called incorrectly!");
1404            #[cfg(not(debug_assertions))]
1405            Err(LuaError::UserDataTypeMismatch)
1406        }
1407    } else if let Some(name) = v.as_string().map(|x| x.to_string_lossy()) {
1408        if let Some(parent) = parent {
1409            Ok(LuaSourceID(parent.0.child(DataID::Owned(name))))
1410        } else {
1411            #[cfg(debug_assertions)]
1412            panic!("NO PARENT! This means the layout was called incorrectly!");
1413            #[cfg(not(debug_assertions))]
1414            Err(LuaError::UserDataTypeMismatch)
1415        }
1416    } else {
1417        Err(LuaError::RuntimeError(format!(
1418            "Expected a number or string to construct an ID, but found {}",
1419            v.type_name()
1420        )))
1421    }
1422}
1423
1424fn replace_dualpoint(p: LuaDualPoint, bound: f32) -> dpi::Size {
1425    match p {
1426        LuaDualPoint::Px(mut point2_d) => {
1427            replace_limit(&mut point2_d, bound);
1428            dpi::Size::Physical(dpi::PhysicalSize::<u32>::new(
1429                point2_d.x.ceil() as u32,
1430                point2_d.y.ceil() as u32,
1431            ))
1432        }
1433        LuaDualPoint::Dp(mut point2_d) => {
1434            replace_limit(&mut point2_d, bound);
1435            dpi::Size::Logical(dpi::LogicalSize::<f64>::new(
1436                point2_d.x as f64,
1437                point2_d.y as f64,
1438            ))
1439        }
1440    }
1441}
1442
1443fn get_required_child<T: FromLua>(t: &LuaTable, idx: usize) -> LuaResult<T> {
1444    if !t.contains_key(idx)? {
1445        Err(LuaError::RuntimeError(format!(
1446            "Expected at least {idx} children, but found {}",
1447            t.raw_len()
1448        )))
1449    } else {
1450        t.get(idx)
1451    }
1452}
1453
1454fn create_window(_: &Lua, (id, body): (LuaSourceID, LuaTable)) -> LuaResult<Window> {
1455    let title: LuaString = get_required(&body, "title")?;
1456    let child: ComponentBag = get_required_child(&body, 1)?;
1457
1458    let mut attributes = winit::window::Window::default_attributes()
1459        .with_title(title.to_string_lossy())
1460        .with_resizable(get_or(&body, "resizeable", true)?)
1461        .with_maximized(get_or(&body, "maximized", false)?)
1462        .with_visible(get_or(&body, "visible", true)?)
1463        .with_transparent(get_or(&body, "transparent", false)?)
1464        .with_blur(get_or(&body, "blur", false)?)
1465        .with_decorations(get_or(&body, "decorated", true)?)
1466        .with_content_protected(get_or(&body, "protected", false)?)
1467        .with_active(get_or(&body, "focused", false)?);
1468
1469    if body.contains_key("icon")? {
1470        attributes.window_icon = Some(
1471            crate::resource::load_icon(&std::path::PathBuf::from(body.get::<String>("icon")?))
1472                .map_err(|e| LuaError::ExternalError(Arc::new(Box::new(e))))?,
1473        );
1474    }
1475
1476    if body.contains_key("minsize")? {
1477        attributes.min_inner_size = Some(replace_dualpoint(body.get("minsize")?, f32::NEG_INFINITY))
1478    }
1479
1480    if body.contains_key("maxsize")? {
1481        attributes.max_inner_size = Some(replace_dualpoint(body.get("maxsize")?, f32::INFINITY))
1482    }
1483
1484    if body.contains_key("size")? {
1485        attributes.inner_size = Some(replace_dualpoint(body.get("size")?, 0.0));
1486    }
1487
1488    Ok(Window::new(id.0, attributes, Box::new(child)))
1489}
1490
1491#[derive(PartialEq, Clone, Debug)]
1492pub struct LuaFragment {
1493    layout: LuaFunction,
1494    id_enter: LuaFunction,
1495}
1496
1497impl FromLua for LuaFragment {
1498    fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {
1499        let t = value.as_table().ok_or(LuaError::UserDataTypeMismatch)?;
1500        Ok(Self {
1501            layout: t.get("layout")?,
1502            id_enter: t.get("id_enter")?,
1503        })
1504    }
1505}
1506
1507impl IntoLua for LuaFragment {
1508    fn into_lua(self, lua: &Lua) -> LuaResult<LuaValue> {
1509        Ok(LuaValue::Table(lua.create_table_from([
1510            ("layout", self.layout),
1511            ("id_enter", self.id_enter),
1512        ])?))
1513    }
1514}
1515
1516impl LuaFragment {
1517    pub fn call<T: IntoLuaMulti>(&self, args: T, mut id: ScopeID<'_>) -> LuaResult<ComponentBag> {
1518        self.id_enter.call::<ComponentBag>((
1519            LuaSourceID(id.id().clone()),
1520            self.layout.clone(),
1521            args,
1522        ))
1523    }
1524}
1525
1526#[derive(Clone, Debug)]
1527pub struct LuaContext {
1528    feather: LuaTable,
1529    modules: LuaTable,
1530    id_enter: LuaFunction,
1531    require: LuaFunction,
1532    load_in_sandbox: LuaFunction,
1533    lua_handlers: LuaTable,
1534    handler_slots: LuaTable,
1535    handler_count: Rc<AtomicU64>,
1536}
1537
1538impl PartialEq for LuaContext {
1539    fn eq(&self, other: &Self) -> bool {
1540        self.feather == other.feather
1541            && self.modules == other.modules
1542            && self.id_enter == other.id_enter
1543            && self.require == other.require
1544            && self.load_in_sandbox == other.load_in_sandbox
1545            && self.lua_handlers == other.lua_handlers
1546            && self.handler_slots == other.handler_slots
1547            && self.handler_count.load(Ordering::Relaxed)
1548                == other.handler_count.load(Ordering::Relaxed)
1549    }
1550}
1551
1552impl LuaContext {
1553    pub fn new(lua: &Lua) -> LuaResult<Self> {
1554        let preload = lua.create_table()?;
1555
1556        preload.set("create_id", lua.create_function(create_id)?)?;
1557        preload.set("create_button", lua.create_function(create_button)?)?;
1558        preload.set(
1559            "create_domain_line",
1560            lua.create_function(create_domain_line)?,
1561        )?;
1562        preload.set(
1563            "create_domain_point",
1564            lua.create_function(create_domain_point)?,
1565        )?;
1566        preload.set("create_flexbox", lua.create_function(create_flexbox)?)?;
1567        preload.set("create_gridbox", lua.create_function(create_gridbox)?)?;
1568        preload.set("create_image", lua.create_function(create_image)?)?;
1569        preload.set("create_line", lua.create_function(create_line)?)?;
1570        preload.set("create_listbox", lua.create_function(create_listbox)?)?;
1571        preload.set("create_mousearea", lua.create_function(create_mousearea)?)?;
1572        //preload.set("create_paragraph", lua.create_function(create_paragraph)?)?;
1573        preload.set("create_region", lua.create_function(create_region)?)?;
1574        preload.set("create_scrollarea", lua.create_function(create_scrollarea)?)?;
1575        preload.set("create_round_rect", lua.create_function(create_round_rect)?)?;
1576        preload.set("create_arc", lua.create_function(create_arc)?)?;
1577        preload.set("create_triangle", lua.create_function(create_triangle)?)?;
1578        preload.set("create_circle", lua.create_function(create_circle)?)?;
1579        preload.set("create_text", lua.create_function(create_text)?)?;
1580        preload.set("create_textbox", lua.create_function(create_textbox)?)?;
1581        preload.set("create_window", lua.create_function(create_window)?)?;
1582
1583        let preload_mt = lua.create_table()?;
1584        preload_mt.set("__index", lua.globals())?;
1585        preload.set_metatable(Some(preload_mt))?;
1586
1587        let feather: LuaTable = lua
1588            .load(NamedChunk(FEATHER, "feather"))
1589            .set_environment(preload.clone())
1590            .eval()?;
1591
1592        let id_enter = feather.get::<LuaTable>("ID")?.get::<LuaFunction>("enter")?;
1593        let lua_handlers = lua.create_table()?;
1594        let handlers = lua_handlers.clone();
1595        let handler_slots = lua.create_table()?;
1596        let slot_clone = handler_slots.clone();
1597        let handler_count = Rc::new(AtomicU64::new(0));
1598        let handler_count_clone = handler_count.clone();
1599
1600        feather.set(
1601            "add_handler",
1602            lua.create_function(move |_, (name, func): (LuaString, LuaFunction)| {
1603                let count = handler_count_clone.fetch_add(1, Ordering::Relaxed);
1604                handlers.set(&name, func)?;
1605                slot_clone.set(name, Slot(APP_SOURCE_ID.into(), count))?;
1606                Ok(LuaNil)
1607            })?,
1608        )?;
1609
1610        let modules = lua.create_table()?;
1611        preload.set("injected_dep", &modules)?;
1612
1613        lua.load(NamedChunk(SANDBOX, "sandbox")).exec()?;
1614
1615        let require: LuaFunction = lua
1616            .load(
1617                r#"
1618package = {preload = {}, loaded = injected_dep}
1619return function(name) -- require stub for inside sandbox
1620  if not package.loaded[name] then
1621    if not package.preload[name] then
1622      error("Couldn't find package.preload for " .. name)
1623    end
1624    package.loaded[name] = package.preload[name]()
1625  end
1626  return package.loaded[name]
1627end
1628        "#,
1629            )
1630            .set_environment(preload)
1631            .eval()?;
1632
1633        lua.load(
1634            r#"
1635        jit.opt.start("maxtrace=10000")
1636        jit.opt.start("maxmcode=4096")
1637        jit.opt.start("recunroll=5")
1638        jit.opt.start("loopunroll=60")
1639        
1640        --local create_module = sandbox_impl(true)
1641        create_module = function(bytes, name, interface) 
1642            return load(bytes, name, "t", setmetatable(interface, {__index=_G}))
1643         end 
1644
1645
1646        function load_in_sandbox(bytes, name, additional_interface)
1647          local r, err = create_module(bytes, name, additional_interface)
1648          if r == nil then
1649            error(err)
1650          end
1651          
1652          return r()
1653        end
1654                "#,
1655        )
1656        .exec()?;
1657
1658        let load_in_sandbox: LuaFunction = lua.load("load_in_sandbox").eval()?;
1659
1660        Ok(Self {
1661            feather,
1662            id_enter,
1663            load_in_sandbox,
1664            require,
1665            modules,
1666            lua_handlers,
1667            handler_slots,
1668            handler_count,
1669        })
1670    }
1671
1672    pub fn add_module(&self, lua: &Lua, name: &str, module: &[u8]) -> LuaResult<()> {
1673        let interface = lua.create_table()?;
1674        interface.set("f", self.feather.clone())?;
1675        interface.set("require", self.require.clone())?;
1676
1677        self.modules.set(
1678            name,
1679            self.load_in_sandbox
1680                .call::<LuaValue>((lua.create_string(module)?, name, interface))?,
1681        )
1682    }
1683
1684    pub fn get_module(&self, name: &str) -> Option<LuaValue> {
1685        self.modules.get(name).ok().and_then(|x| match x {
1686            Some(LuaNil) => None,
1687            x => x,
1688        })
1689    }
1690
1691    pub fn reserve_handler(&self, name: &str) -> LuaResult<u64> {
1692        let index = self.handler_count.fetch_add(1, Ordering::Relaxed);
1693        self.handler_slots
1694            .set(name, Slot(APP_SOURCE_ID.into(), index))?;
1695        Ok(index)
1696    }
1697
1698    fn load_layout<R: FromLuaMulti>(&self, lua: &Lua, layout: &[u8]) -> LuaResult<R> {
1699        let interface = lua.create_table()?;
1700        interface.set("handlers", self.handler_slots.clone())?;
1701        interface.set("f", self.feather.clone())?;
1702        interface.set("require", self.require.clone())?;
1703
1704        self.load_in_sandbox
1705            .call((lua.create_string(layout)?, "layout", interface))
1706    }
1707
1708    pub fn load_fragment(&self, lua: &Lua, layout: &[u8]) -> LuaResult<LuaFragment> {
1709        Ok(LuaFragment {
1710            layout: self.load_layout(lua, layout)?,
1711            id_enter: self.id_enter.clone(),
1712        })
1713    }
1714
1715    pub fn get_handlers<AppData: FromLua + IntoLua + Clone>(
1716        &self,
1717        mut reserved: HashMap<u64, crate::AppEvent<AppData>>,
1718    ) -> LuaResult<Vec<crate::AppEvent<AppData>>> {
1719        use std::mem::MaybeUninit;
1720
1721        let count = self.handler_count.load(Ordering::Relaxed) as usize;
1722        let mut handlers: Vec<crate::AppEvent<AppData>> = Vec::with_capacity(count);
1723        let spare = handlers.spare_capacity_mut();
1724
1725        for v in self.handler_slots.pairs::<LuaString, Slot>() {
1726            let (key, slot) = v?;
1727            if self.lua_handlers.contains_key(&key)? {
1728                let func: LuaFunction = self.lua_handlers.get(&key)?;
1729                spare[slot.1 as usize] = MaybeUninit::new(Box::new(
1730                    move |pair: crate::DispatchPair, mut state: AccessCell<AppData>| {
1731                        let (appdata, v): (AppData, LuaValue) =
1732                            match func.call((pair.0, state.value.clone())) {
1733                                Ok(v) => v,
1734                                Err(e) => return InputResult::Error(e.into()),
1735                            };
1736                        *state = appdata;
1737                        if v.as_boolean().unwrap_or(true) {
1738                            InputResult::Consume(())
1739                        } else {
1740                            InputResult::Forward(())
1741                        }
1742                    },
1743                ));
1744            } else {
1745                spare[slot.1 as usize] = MaybeUninit::new(
1746                    reserved
1747                        .remove(&slot.1)
1748                        .expect("Reserved slot had no entry in `reserved` map!"),
1749                )
1750            }
1751        }
1752
1753        unsafe {
1754            handlers.set_len(count);
1755        }
1756
1757        Ok(handlers)
1758    }
1759
1760    pub fn update_handler<AppData: FromLua + IntoLua + Clone + 'static>(
1761        &self,
1762        lua: &Lua,
1763        sender: std::sync::mpsc::Sender<crate::EventPair<AppData>>,
1764        count: AtomicU64,
1765    ) -> LuaResult<()> {
1766        let slot_clone = self.handler_slots.clone();
1767        self.feather.set(
1768            "add_handler",
1769            lua.create_function(move |_, (name, func): (LuaString, LuaFunction)| {
1770                let count = count.fetch_add(1, Ordering::Relaxed);
1771                sender
1772                    .send((
1773                        count,
1774                        Box::new(
1775                            move |pair: crate::DispatchPair, mut state: AccessCell<AppData>| {
1776                                let (appdata, v): (AppData, LuaValue) =
1777                                    match func.call((pair.0, state.value.clone())) {
1778                                        Ok(v) => v,
1779                                        Err(e) => return InputResult::Error(e.into()),
1780                                    };
1781                                *state = appdata;
1782                                if v.as_boolean().unwrap_or(true) {
1783                                    InputResult::Consume(())
1784                                } else {
1785                                    InputResult::Forward(())
1786                                }
1787                            },
1788                        ),
1789                    ))
1790                    .unwrap();
1791                let slot = Slot(APP_SOURCE_ID.into(), count);
1792                slot_clone.set(name, slot.clone())?;
1793                Ok(slot)
1794            })?,
1795        )?;
1796
1797        Ok(())
1798    }
1799}
1800
1801pub struct LuaApp<AppData: Clone + FromLua + IntoLua>(crate::App<AppData, LuaPersist<AppData>>);
1802
1803impl<AppData: Clone + FromLua + IntoLua + PartialEq + 'static> LuaApp<AppData> {
1804    pub fn new<T>(
1805        lua: &Lua,
1806        app_state: AppData,
1807        handlers: Vec<(String, crate::AppEvent<AppData>)>,
1808        layout: &[u8],
1809    ) -> eyre::Result<(Self, crate::EventLoop<T>)> {
1810        let ctx = LuaContext::new(lua)?;
1811
1812        let mut reserved = HashMap::new();
1813        for (k, v) in handlers.into_iter() {
1814            reserved.insert(ctx.reserve_handler(&k)?, v);
1815        }
1816
1817        let (window, init): (LuaFunction, Option<LuaFunction>) = ctx.load_layout(lua, layout)?;
1818
1819        let (app, event, sender, count) = crate::App::new(
1820            app_state.clone(),
1821            ctx.get_handlers(reserved)?,
1822            LuaPersist {
1823                window,
1824                id_enter: ctx.id_enter.clone(),
1825                init: if let Some(init) = init {
1826                    Ok(init)
1827                } else {
1828                    Err(app_state)
1829                },
1830                phantom: PhantomData,
1831            },
1832            |_| (),
1833        )?;
1834
1835        // Change the add_handler to use the channel to send new handlers
1836        ctx.update_handler(lua, sender, count)?;
1837        Ok((Self(app), event))
1838    }
1839}
1840
1841impl<AppData: Clone + FromLua + IntoLua + PartialEq + 'static, T: 'static> ApplicationHandler<T>
1842    for LuaApp<AppData>
1843{
1844    fn new_events(
1845        &mut self,
1846        event_loop: &winit::event_loop::ActiveEventLoop,
1847        cause: winit::event::StartCause,
1848    ) {
1849        <crate::App<AppData, LuaPersist<AppData>> as ApplicationHandler<T>>::new_events(
1850            &mut self.0,
1851            event_loop,
1852            cause,
1853        );
1854    }
1855
1856    fn user_event(&mut self, event_loop: &winit::event_loop::ActiveEventLoop, event: T) {
1857        self.0.user_event(event_loop, event);
1858    }
1859
1860    fn device_event(
1861        &mut self,
1862        event_loop: &winit::event_loop::ActiveEventLoop,
1863        device_id: winit::event::DeviceId,
1864        event: winit::event::DeviceEvent,
1865    ) {
1866        <crate::App<AppData, LuaPersist<AppData>> as ApplicationHandler<T>>::device_event(
1867            &mut self.0,
1868            event_loop,
1869            device_id,
1870            event,
1871        );
1872    }
1873
1874    fn about_to_wait(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
1875        <crate::App<AppData, LuaPersist<AppData>> as ApplicationHandler<T>>::about_to_wait(
1876            &mut self.0,
1877            event_loop,
1878        );
1879    }
1880
1881    fn suspended(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
1882        <crate::App<AppData, LuaPersist<AppData>> as ApplicationHandler<T>>::suspended(
1883            &mut self.0,
1884            event_loop,
1885        );
1886    }
1887
1888    fn exiting(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
1889        <crate::App<AppData, LuaPersist<AppData>> as ApplicationHandler<T>>::exiting(
1890            &mut self.0,
1891            event_loop,
1892        );
1893    }
1894
1895    fn memory_warning(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
1896        <crate::App<AppData, LuaPersist<AppData>> as ApplicationHandler<T>>::memory_warning(
1897            &mut self.0,
1898            event_loop,
1899        );
1900    }
1901
1902    fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
1903        <crate::App<AppData, LuaPersist<AppData>> as ApplicationHandler<T>>::resumed(
1904            &mut self.0,
1905            event_loop,
1906        );
1907    }
1908
1909    fn window_event(
1910        &mut self,
1911        event_loop: &winit::event_loop::ActiveEventLoop,
1912        window_id: winit::window::WindowId,
1913        event: winit::event::WindowEvent,
1914    ) {
1915        <crate::App<AppData, LuaPersist<AppData>> as ApplicationHandler<T>>::window_event(
1916            &mut self.0,
1917            event_loop,
1918            window_id,
1919            event,
1920        )
1921    }
1922}