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
540        // must be NaN
541        let name = get_name(v)?;
542        if name == "pxpoint_mt" {
543            Ok(LimitPoint(DPoint {
544                rel: LuaPoint::nan().0,
545                dp: LuaPoint::nan().0,
546                px: LuaPoint::<Pixel>::from_lua(value, lua)?.0,
547            }))
548        } else if name == "abspoint_mt" {
549            Ok(LimitPoint(DPoint {
550                rel: LuaPoint::nan().0,
551                dp: LuaPoint::<Logical>::from_lua(value, lua)?.0,
552                px: LuaPoint::nan().0,
553            }))
554        } else if name == "relpoint_mt" {
555            Ok(LimitPoint(DPoint {
556                rel: LuaPoint::<Relative>::from_lua(value, lua)?.0,
557                dp: LuaPoint::nan().0,
558                px: LuaPoint::nan().0,
559            }))
560        } else if name == "coord_mt" {
561            let px = get_or::<LuaPoint<Pixel>>(v, "px", LuaPoint::nan())?.0;
562            let dp = get_or::<LuaPoint<Logical>>(v, "dp", LuaPoint::nan())?.0;
563            let rel = get_or::<LuaPoint<Relative>>(v, "rel", LuaPoint::nan())?.0;
564            Ok(LimitPoint(DPoint { dp, px, rel }))
565        } else {
566            Err(LuaError::RuntimeError(format!(
567                "Expected a point, but found {name}",
568            )))
569        }
570    }
571}
572
573impl FromLua for sRGB {
574    fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {
575        if let Some(i) = value.as_integer() {
576            Ok(sRGB32 { rgba: i as u32 }.as_f32())
577        } else if let Some(v) = value.as_table() {
578            if v.len()? == 4 {
579                Ok(sRGB::new(v.get(1)?, v.get(2)?, v.get(3)?, v.get(4)?))
580            } else {
581                Err(LuaError::RuntimeError(format!(
582                    "A color must be an array of exactly 4 numbers, declared like {{ R, G, B, A }}, but only found {}",
583                    v.len()?
584                )))
585            }
586        } else {
587            Err(LuaError::RuntimeError(format!(
588                "Expected a color but found {}",
589                value.type_name()
590            )))
591        }
592    }
593}
594
595struct LuaFontFamily(cosmic_text::FamilyOwned);
596
597impl Default for LuaFontFamily {
598    fn default() -> Self {
599        Self(cosmic_text::FamilyOwned::SansSerif)
600    }
601}
602
603impl FromLua for LuaFontFamily {
604    fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {
605        let name =
606            value
607                .as_string()
608                .and_then(|s| s.to_str().ok())
609                .ok_or(LuaError::RuntimeError(format!(
610                    "Expected a string, but found {}",
611                    value.type_name()
612                )))?;
613
614        Ok(LuaFontFamily(if name.eq_ignore_ascii_case("serif") {
615            cosmic_text::FamilyOwned::Serif
616        } else if name.eq_ignore_ascii_case("cursive") {
617            cosmic_text::FamilyOwned::Cursive
618        } else if name.eq_ignore_ascii_case("fantasy") {
619            cosmic_text::FamilyOwned::Fantasy
620        } else if name.eq_ignore_ascii_case("monospace") {
621            cosmic_text::FamilyOwned::Monospace
622        } else if name.eq_ignore_ascii_case("sansserif") || name.eq_ignore_ascii_case("sans-serif")
623        {
624            cosmic_text::FamilyOwned::SansSerif
625        } else {
626            cosmic_text::FamilyOwned::Name((*name).into())
627        }))
628    }
629}
630
631pub type ComponentBag = Box<dyn crate::component::Component<Props = PropBag>>;
632
633impl<U: ?Sized> crate::component::ComponentWrap<U> for ComponentBag
634where
635    for<'a> &'a U: std::convert::From<&'a PropBag>,
636{
637    fn layout(
638        &self,
639        manager: &mut crate::StateManager,
640        driver: &crate::graphics::Driver,
641        window: &Arc<SourceID>,
642    ) -> Box<dyn crate::layout::Layout<U>> {
643        use std::ops::Deref;
644        Box::new(Box::deref(self).layout(manager, driver, window))
645    }
646}
647
648impl StateMachineChild for ComponentBag {
649    fn id(&self) -> Arc<SourceID> {
650        use std::ops::Deref;
651        Box::deref(self).id()
652    }
653
654    fn init(
655        &self,
656        driver: &std::sync::Weak<crate::graphics::Driver>,
657    ) -> Result<Box<dyn crate::component::StateMachineWrapper>, crate::Error> {
658        use std::ops::Deref;
659        Box::deref(self).init(driver)
660    }
661
662    fn apply_children(
663        &self,
664        f: &mut dyn FnMut(&dyn StateMachineChild) -> eyre::Result<()>,
665    ) -> eyre::Result<()> {
666        use std::ops::Deref;
667        Box::deref(self).apply_children(f)
668    }
669}
670
671macro_rules! gen_from_lua {
672    ($type_name:ident) => {
673        impl mlua::FromLua for $type_name {
674            #[inline]
675            fn from_lua(value: ::mlua::Value, _: &::mlua::Lua) -> ::mlua::Result<Self> {
676                match value {
677                    ::mlua::Value::UserData(ud) => Ok(ud.borrow::<Self>()?.clone()),
678                    _ => Err(::mlua::Error::FromLuaConversionError {
679                        from: value.type_name(),
680                        to: stringify!($type_name).to_string(),
681                        message: None,
682                    }),
683                }
684            }
685        }
686    };
687}
688
689#[derive(Clone)]
690struct LuaDomain(std::sync::Arc<crate::CrossReferenceDomain>);
691
692impl UserData for LuaDomain {}
693gen_from_lua!(LuaDomain);
694
695impl UserData for Window {}
696gen_from_lua!(Window);
697
698impl UserData for LuaSourceID {}
699gen_from_lua!(LuaSourceID);
700
701impl UserData for Slot {
702    fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
703        methods.add_meta_method(mlua::MetaMethod::ToString, |_, this, ()| {
704            Ok(format!("Slot #{}: {}", this.1, this.0))
705        });
706    }
707}
708gen_from_lua!(Slot);
709
710impl UserData for ComponentBag {}
711gen_from_lua!(ComponentBag);
712
713/// This defines the "lua" app that knows how to handle a lua value that
714/// contains the expected rust objects, and hand them off for processing. This
715/// is analogous to the pure-rust [App] struct defined in lib.rs
716pub struct LuaPersist<AppData> {
717    pub window: LuaFunction, // takes a Store and an appstate and returns a Window
718    pub id_enter: LuaFunction,
719    pub init: Result<LuaFunction, AppData>,
720    phantom: PhantomData<AppData>,
721}
722
723impl<AppData: Clone> FnPersistStore for LuaPersist<AppData> {
724    type Store = AppData;
725}
726
727impl<AppData: Clone + FromLua + IntoLua>
728    FnPersist2<&AppData, ScopeID<'static>, im::HashMap<Arc<SourceID>, Option<Window>>>
729    for LuaPersist<AppData>
730{
731    fn init(&self) -> Self::Store {
732        let r = match &self.init {
733            Ok(init) => init.call::<AppData>(()),
734            Err(data) => Ok(data.clone()),
735        };
736
737        match r {
738            Err(LuaError::RuntimeError(s)) => panic!("{}", s),
739            Err(e) => panic!("{e:?}"),
740            Ok(v) => v,
741        }
742    }
743    fn call(
744        &mut self,
745        _: Self::Store,
746        appdata: &AppData,
747        mut id: ScopeID<'static>,
748    ) -> (Self::Store, im::HashMap<Arc<SourceID>, Option<Window>>) {
749        let mut h = im::HashMap::new();
750
751        let (store, w) = self
752            .id_enter
753            .call::<(AppData, crate::component::window::Window)>((
754                LuaSourceID(id.id().clone()),
755                self.window.clone(),
756                appdata.clone(),
757            ))
758            .unwrap();
759        h.insert(w.id().clone(), Some(w));
760        (store, h)
761    }
762}
763
764enum LuaDualPoint {
765    Px(Point2D<f32, Pixel>),
766    Dp(Point2D<f32, Logical>),
767}
768
769impl FromLua for LuaDualPoint {
770    fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {
771        let t = value.as_table().ok_or(LuaError::RuntimeError(format!(
772            "Expected a 2D point, but found {}",
773            value.type_name()
774        )))?;
775
776        if is_dvalue(t)? {
777            if t.contains_key("dp")? && !t.contains_key("px")? {
778                Ok(LuaDualPoint::Dp(Point2D::<f32, Logical>::splat(
779                    t.get("dp")?,
780                )))
781            } else if t.contains_key("px")? && !t.contains_key("dp")? {
782                Ok(LuaDualPoint::Px(Point2D::<f32, Pixel>::splat(t.get("px")?)))
783            } else {
784                Err(LuaError::RuntimeError("This property only accepts a point that is either abs or px, not relative or a combination.".to_string()))
785            }
786        } else if t.contains_key("dp")? && !t.contains_key("px")? {
787            Ok(LuaDualPoint::Dp(t.get::<LuaPoint<Logical>>("dp")?.0))
788        } else if t.contains_key("px")? && !t.contains_key("dp")? {
789            Ok(LuaDualPoint::Px(t.get::<LuaPoint<Pixel>>("px")?.0))
790        } else if get_kind(t)? == "px" {
791            Ok(LuaDualPoint::Px(Point2D::<f32, Pixel>::new(
792                t.get("x")?,
793                t.get("y")?,
794            )))
795        } else if get_kind(t)? == "dp" {
796            Ok(LuaDualPoint::Dp(Point2D::<f32, Logical>::new(
797                t.get("x")?,
798                t.get("y")?,
799            )))
800        } else {
801            Err(LuaError::RuntimeError("This property only accepts a point that is either abs or px, not relative or a combination.".to_string()))
802        }
803    }
804}
805
806fn load_prop<T: mlua::FromLua>(
807    f: fn(&mut PropBag, T) -> Option<T>,
808    bag: &mut PropBag,
809    props: &LuaTable,
810    name: &str,
811) -> LuaResult<()> {
812    if props.contains_key(name)? {
813        f(bag, props.get(name)?);
814    }
815    Ok(())
816}
817
818#[inline]
819fn replace_unsized<U>(rect: &mut Rect<U>) {
820    rect.v = f32x4::new(
821        rect.v
822            .to_array()
823            .map(|x| if x.is_nan() { UNSIZED_AXIS } else { x }),
824    );
825}
826
827#[inline]
828fn replace_unsized_drect(mut rect: DRect) -> DRect {
829    replace_unsized(&mut rect.dp);
830    replace_unsized(&mut rect.px);
831    replace_unsized(&mut rect.rel);
832    rect
833}
834
835#[inline]
836fn replace_limit<U>(p: &mut Point2D<f32, U>, bound: f32) {
837    if p.x.is_nan() {
838        p.x = bound
839    }
840    if p.y.is_nan() {
841        p.y = bound
842    }
843}
844
845#[inline]
846fn replace_limit_dpoint(mut p: DPoint, bound: f32) -> DPoint {
847    replace_limit(&mut p.dp, bound);
848    replace_limit(&mut p.px, bound);
849    replace_limit(&mut p.rel, bound);
850    p
851}
852
853impl FromLua for PropBag {
854    fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {
855        let props = value.as_table().ok_or(LuaError::RuntimeError(format!(
856            "Expected PropBag, got {}",
857            value.type_name(),
858        )))?;
859        let mut bag = PropBag::new();
860
861        load_prop(PropBag::set_wrap, &mut bag, props, "wrap")?;
862        load_prop(PropBag::set_zindex, &mut bag, props, "zindex")?;
863        load_prop(PropBag::set_order, &mut bag, props, "order")?;
864        load_prop(PropBag::set_grow, &mut bag, props, "grow")?;
865        load_prop(PropBag::set_shrink, &mut bag, props, "shrink")?;
866        load_prop(PropBag::set_basis, &mut bag, props, "basis")?;
867        load_prop(PropBag::set_padding, &mut bag, props, "padding")?;
868        load_prop(PropBag::set_margin, &mut bag, props, "margin")?;
869        load_prop(PropBag::set_anchor, &mut bag, props, "anchor")?;
870
871        if props.contains_key("area")? {
872            bag.set_area(replace_unsized_drect(props.get::<DRect>("area")?));
873        }
874
875        let mut limits: crate::DLimits = Default::default();
876        let mut rlimits: crate::Limits<Relative> = Default::default();
877
878        if props.contains_key("minsize")? {
879            let p = replace_limit_dpoint(props.get::<LimitPoint>("minsize")?.0, f32::NEG_INFINITY);
880            limits.dp.set_min(p.dp.to_vector().to_size());
881            limits.px.set_min(p.px.to_vector().to_size());
882            rlimits.set_min(p.rel.to_vector().to_size());
883        }
884
885        if props.contains_key("maxsize")? {
886            let p = replace_limit_dpoint(props.get::<LimitPoint>("maxsize")?.0, f32::INFINITY);
887            limits.dp.set_max(p.dp.to_vector().to_size());
888            limits.px.set_max(p.px.to_vector().to_size());
889            rlimits.set_max(p.rel.to_vector().to_size());
890        }
891
892        bag.set_limits(limits);
893        bag.set_rlimits(rlimits);
894
895        if props.contains_key("direction")? {
896            bag.set_direction(props.get::<LuaEnum<crate::RowDirection>>("direction")?.0);
897        }
898
899        if props.contains_key("justify")? {
900            bag.set_justify(props.get::<LuaEnum<flex::FlexJustify>>("justify")?.0);
901        }
902
903        if props.contains_key("align")? {
904            bag.set_align(props.get::<LuaEnum<flex::FlexJustify>>("align")?.0);
905        }
906
907        if props.contains_key("domain")? {
908            bag.set_domain(props.get::<LuaDomain>("domain")?.0);
909        }
910
911        if props.contains_key("obstacles")? {
912            bag.set_obstacles(props.get::<Vec<DAbsRect>>("obstacles")?.as_slice());
913        }
914
915        if props.contains_key("dim")? {
916            bag.set_dim(props.get::<LuaPoint<Pixel>>("dim")?.0.to_vector().to_size());
917        }
918
919        if props.contains_key("rows")? {
920            bag.set_rows(props.get::<Vec<DValue>>("rows")?.as_slice());
921        }
922
923        if props.contains_key("columns")? {
924            bag.set_columns(props.get::<Vec<DValue>>("columns")?.as_slice());
925        }
926
927        if props.contains_key("spacing")? {
928            bag.set_spacing(props.get::<DPoint>("spacing")?);
929        }
930
931        if props.contains_key("coord")? {
932            let coords = props.get::<Vec<usize>>("coord")?;
933            bag.set_coord((coords[0], coords[1]));
934        }
935
936        if props.contains_key("span")? {
937            let coords = props.get::<Vec<usize>>("span")?;
938            bag.set_span((coords[0], coords[1]));
939        }
940
941        Ok(bag)
942    }
943}
944
945fn prop_no_children(t: &LuaTable) -> LuaResult<PropBag> {
946    let count = t.len()?;
947    if count > 0 {
948        return Err(LuaError::RuntimeError(format!(
949            "Found {count} child components for a component that doesn't accept children!"
950        )));
951    }
952
953    let bag: PropBag = get_required(t, "props")?;
954    Ok(bag)
955}
956
957fn push_child<D: crate::layout::Desc + ?Sized>(
958    t: &LuaTable,
959    i: i64,
960    children: &mut im::Vector<Option<Box<ChildOf<D>>>>,
961) -> LuaResult<()>
962where
963    std::boxed::Box<dyn crate::component::Component<Props = PropBag>>:
964        crate::component::ComponentWrap<<D as crate::layout::Desc>::Child>,
965{
966    let v = t.get::<LuaValue>(i)?;
967    if v.is_nil() {
968        return Ok(());
969    } else if let Some(inner) = v.as_table()
970        && let Some(mt) = inner.metatable()
971        && let Ok(s) = mt.get::<String>("kind")
972        && s == "multicomponent_mt"
973    {
974        for j in 1..=inner.len()? {
975            push_child::<D>(inner, j, children)?;
976        }
977    } else {
978        let component: ComponentBag = t.get(i)?;
979        children.push_back(Some(Box::new(component)));
980    }
981
982    Ok(())
983}
984
985#[allow(clippy::type_complexity)]
986fn prop_children<D: crate::layout::Desc + ?Sized>(
987    t: &LuaTable,
988) -> LuaResult<(im::Vector<Option<Box<ChildOf<D>>>>, PropBag)>
989where
990    std::boxed::Box<dyn crate::component::Component<Props = PropBag>>:
991        crate::component::ComponentWrap<<D as crate::layout::Desc>::Child>,
992{
993    let mut children: im::Vector<Option<Box<ChildOf<D>>>> = im::Vector::new();
994
995    for i in 1..=t.len()? {
996        push_child::<D>(t, i, &mut children)?;
997    }
998
999    let bag: PropBag = get_required(t, "props")?;
1000    Ok((children, bag))
1001}
1002
1003fn check_args(name: &str, t: LuaTable, v: HashSet<&'static str>) -> LuaResult<()> {
1004    for pair in t.pairs() {
1005        let (k, _): (LuaValue, LuaValue) = pair?;
1006        if k.as_integer().is_none()
1007            && k.as_number().is_none()
1008            && let Some(s) = k.as_string()
1009        {
1010            let str = s.to_string_lossy();
1011            if !v.contains(str.as_str()) {
1012                return Err(LuaError::FromLuaConversionError {
1013                    from: "[attributes table]",
1014                    to: name.to_string(),
1015                    message: Some(format!("Unexpected key {str} in attributes table")),
1016                });
1017            }
1018        }
1019    }
1020
1021    Ok(())
1022}
1023
1024fn create_button(_: &Lua, (id, body): (LuaSourceID, LuaTable)) -> LuaResult<ComponentBag> {
1025    let (children, bag) = prop_children::<dyn fixed::Prop>(&body)?;
1026
1027    let mut args = HashSet::from_iter(["props"]);
1028    let onclick = get_arg_required(&mut args, &body, "onclick")?;
1029    check_args("button", body, args)?;
1030
1031    let res: LuaResult<ComponentBag> = Ok(Box::new(Button::<PropBag>::new(
1032        id.0, bag, onclick, children,
1033    )));
1034    res
1035}
1036
1037fn create_domain_line(_: &Lua, (id, body): (LuaSourceID, LuaTable)) -> LuaResult<ComponentBag> {
1038    let bag = prop_no_children(&body)?;
1039
1040    let mut args = HashSet::from_iter(["props"]);
1041    let domain: LuaDomain = get_arg_required(&mut args, &body, "domain")?;
1042    let start: LuaSourceID = get_arg_required(&mut args, &body, "start")?;
1043    let end: LuaSourceID = get_arg_required(&mut args, &body, "end")?;
1044    let fill = get_arg_default(&mut args, &body, "fill")?;
1045    check_args("domain-line", body, args)?;
1046
1047    let res: LuaResult<ComponentBag> = Ok(Box::new(DomainLine::<PropBag> {
1048        id: id.0,
1049        domain: domain.0,
1050        start: start.0,
1051        end: end.0,
1052        props: bag.into(),
1053        fill,
1054    }));
1055    res
1056}
1057
1058fn create_domain_point(_: &Lua, (id, body): (LuaSourceID, LuaTable)) -> LuaResult<ComponentBag> {
1059    let bag = prop_no_children(&body)?;
1060
1061    let args = HashSet::from_iter(["props"]);
1062    check_args("domain-point", body, args)?;
1063
1064    let res: LuaResult<ComponentBag> = Ok(Box::new(DomainPoint::<PropBag>::new(id.0, bag)));
1065    res
1066}
1067
1068fn create_flexbox(_: &Lua, (id, body): (LuaSourceID, LuaTable)) -> LuaResult<ComponentBag> {
1069    let (children, bag) = prop_children::<dyn flex::Prop>(&body)?;
1070    let args = HashSet::from_iter(["props"]);
1071    check_args("flexbox", body, args)?;
1072
1073    let res: LuaResult<ComponentBag> = Ok(Box::new(FlexBox::<PropBag>::new(id.0, bag, children)));
1074    res
1075}
1076
1077fn create_gridbox(_: &Lua, (id, body): (LuaSourceID, LuaTable)) -> LuaResult<ComponentBag> {
1078    let (children, bag) = prop_children::<dyn grid::Prop>(&body)?;
1079    let args = HashSet::from_iter(["props"]);
1080    check_args("gridbox", body, args)?;
1081
1082    let res: LuaResult<ComponentBag> = Ok(Box::new(GridBox::<PropBag>::new(id.0, bag, children)));
1083    res
1084}
1085
1086fn create_image(_: &Lua, (id, body): (LuaSourceID, LuaTable)) -> LuaResult<ComponentBag> {
1087    let bag = prop_no_children(&body)?;
1088
1089    let mut args = HashSet::from_iter(["props"]);
1090    let resource: String = get_arg_required(&mut args, &body, "resource")?;
1091    let size: DAbsPoint = get_arg_default(&mut args, &body, "size")?;
1092    let dynamic: bool = get_arg_default(&mut args, &body, "dynamic")?;
1093    check_args("image", body, args)?;
1094
1095    let location = std::path::PathBuf::from(resource);
1096
1097    let res: LuaResult<ComponentBag> = Ok(Box::new(Image::<PropBag>::new(
1098        id.0, bag, &location, size, dynamic,
1099    )));
1100    res
1101}
1102
1103fn create_line(_: &Lua, (id, body): (LuaSourceID, LuaTable)) -> LuaResult<ComponentBag> {
1104    let bag = prop_no_children(&body)?;
1105
1106    let mut args = HashSet::from_iter(["props"]);
1107    let start: LuaPoint<Pixel> = get_arg_required(&mut args, &body, "start")?;
1108    let end: LuaPoint<Pixel> = get_arg_required(&mut args, &body, "end")?;
1109    let fill = get_arg_default(&mut args, &body, "fill")?;
1110    check_args("line", body, args)?;
1111
1112    let res: LuaResult<ComponentBag> = Ok(Box::new(Line::<PropBag> {
1113        id: id.0,
1114        start: start.0,
1115        end: end.0,
1116        props: bag.into(),
1117        fill,
1118    }));
1119    res
1120}
1121
1122fn create_listbox(_: &Lua, (id, body): (LuaSourceID, LuaTable)) -> LuaResult<ComponentBag> {
1123    let (children, bag) = prop_children::<dyn list::Prop>(&body)?;
1124    let args = HashSet::from_iter(["props"]);
1125    check_args("listbox", body, args)?;
1126
1127    let res: LuaResult<ComponentBag> = Ok(Box::new(ListBox::<PropBag>::new(id.0, bag, children)));
1128    res
1129}
1130
1131fn create_mousearea(_: &Lua, (id, body): (LuaSourceID, LuaTable)) -> LuaResult<ComponentBag> {
1132    let bag = prop_no_children(&body)?;
1133
1134    let mut args = HashSet::from_iter(["props"]);
1135    let deadzone = get_arg_or(&mut args, &body, "deadzone", f32::INFINITY)?;
1136    let onclick = get_arg_default(&mut args, &body, "onclick")?;
1137    let ondblclick = get_arg_default(&mut args, &body, "ondblclick")?;
1138    let ondrag = get_arg_default(&mut args, &body, "ondrag")?;
1139    check_args("mousearea", body, args)?;
1140
1141    let res: LuaResult<ComponentBag> = Ok(Box::new(MouseArea::<PropBag>::new(
1142        id.0,
1143        bag,
1144        Some(deadzone),
1145        [onclick, ondblclick, ondrag, None, None, None],
1146    )));
1147    res
1148}
1149
1150fn create_region(_: &Lua, (id, body): (LuaSourceID, LuaTable)) -> LuaResult<ComponentBag> {
1151    let (children, bag) = prop_children::<dyn fixed::Prop>(&body)?;
1152    let mut args = HashSet::from_iter(["props"]);
1153    let color: Option<sRGB> = get_arg_default(&mut args, &body, "color")?;
1154    let rotation: Option<f32> = get_arg_default(&mut args, &body, "rotation")?;
1155    check_args("region", body, args)?;
1156
1157    Ok(Box::new(if color.is_some() || rotation.is_some() {
1158        Region::<PropBag>::new_layer(
1159            id.0,
1160            bag,
1161            color.unwrap_or(sRGB::white()).as_32bit(),
1162            rotation.unwrap_or_default(),
1163            children,
1164        )
1165    } else {
1166        Region::<PropBag>::new(id.0, bag, children)
1167    }))
1168}
1169
1170fn create_scrollarea(_: &Lua, (id, body): (LuaSourceID, LuaTable)) -> LuaResult<ComponentBag> {
1171    let (children, bag) = prop_children::<dyn fixed::Prop>(&body)?;
1172    let mut args = HashSet::from_iter(["props"]);
1173    let stepx = get_arg_default(&mut args, &body, "stepx")?;
1174    let stepy = get_arg_default(&mut args, &body, "stepy")?;
1175    let extension = get_arg_default(&mut args, &body, "extension")?;
1176    let onscroll = get_arg_default(&mut args, &body, "onscroll")?;
1177    check_args("scrollarea", body, args)?;
1178
1179    let res: LuaResult<ComponentBag> = Ok(Box::new(ScrollArea::<PropBag>::new(
1180        id.0,
1181        bag,
1182        (stepx, stepy),
1183        extension,
1184        children,
1185        [onscroll],
1186    )));
1187    res
1188}
1189
1190fn create_round_rect(lua: &Lua, (id, body): (LuaSourceID, LuaTable)) -> LuaResult<ComponentBag> {
1191    let bag = prop_no_children(&body)?;
1192
1193    let mut args: HashSet<&'static str> = HashSet::from_iter(["props"]);
1194    let border = get_arg_default(&mut args, &body, "border")?;
1195    let blur = get_arg_default(&mut args, &body, "blur")?;
1196    let fill = get_arg_default(&mut args, &body, "fill")?;
1197    let outline = get_arg_default(&mut args, &body, "outline")?;
1198    let corners = get_array_arg(&mut args, lua, &body, "corners", [0.0; 4])?;
1199    let size: DAbsPoint = get_arg_default(&mut args, &body, "size")?;
1200    check_args("round_rect", body, args)?;
1201
1202    Ok(Box::new(shape::round_rect(
1203        id.0,
1204        bag,
1205        border,
1206        blur,
1207        wide::f32x4::new(corners),
1208        fill,
1209        outline,
1210        size,
1211    )))
1212}
1213
1214fn create_arc(lua: &Lua, (id, body): (LuaSourceID, LuaTable)) -> LuaResult<ComponentBag> {
1215    let bag = prop_no_children(&body)?;
1216
1217    let mut args: HashSet<&'static str> = HashSet::from_iter(["props"]);
1218    let border = get_arg_default(&mut args, &body, "border")?;
1219    let blur = get_arg_default(&mut args, &body, "blur")?;
1220    let fill = get_arg_default(&mut args, &body, "fill")?;
1221    let outline = get_arg_default(&mut args, &body, "outline")?;
1222    let angles = get_array_arg(&mut args, lua, &body, "angles", [0.0; 2])?;
1223    let inner_radius = get_arg_default(&mut args, &body, "innerRadius")?;
1224    let size: DAbsPoint = get_arg_default(&mut args, &body, "size")?;
1225    check_args("arc", body, args)?;
1226
1227    Ok(Box::new(shape::arcs(
1228        id.0,
1229        bag,
1230        border,
1231        blur,
1232        inner_radius,
1233        angles,
1234        fill,
1235        outline,
1236        size,
1237    )))
1238}
1239
1240fn create_triangle(lua: &Lua, (id, body): (LuaSourceID, LuaTable)) -> LuaResult<ComponentBag> {
1241    let bag = prop_no_children(&body)?;
1242
1243    let mut args: HashSet<&'static str> = HashSet::from_iter(["props"]);
1244    let border = get_arg_default(&mut args, &body, "border")?;
1245    let blur = get_arg_default(&mut args, &body, "blur")?;
1246    let fill = get_arg_default(&mut args, &body, "fill")?;
1247    let outline = get_arg_default(&mut args, &body, "outline")?;
1248    let corners = get_array_arg(&mut args, lua, &body, "corners", [0.0; 3])?;
1249    let offset = get_arg_default(&mut args, &body, "offset")?;
1250    let size: DAbsPoint = get_arg_default(&mut args, &body, "size")?;
1251    check_args("triangle", body, args)?;
1252
1253    Ok(Box::new(shape::triangle(
1254        id.0, bag, border, blur, corners, offset, fill, outline, size,
1255    )))
1256}
1257
1258fn create_circle(lua: &Lua, (id, body): (LuaSourceID, LuaTable)) -> LuaResult<ComponentBag> {
1259    let bag = prop_no_children(&body)?;
1260
1261    let mut args: HashSet<&'static str> = HashSet::from_iter(["props"]);
1262    let border = get_arg_default(&mut args, &body, "border")?;
1263    let blur = get_arg_default(&mut args, &body, "blur")?;
1264    let fill = get_arg_default(&mut args, &body, "fill")?;
1265    let outline = get_arg_default(&mut args, &body, "outline")?;
1266    let radii = get_array_arg(&mut args, lua, &body, "radii", [0.0; 2])?;
1267    let size: DAbsPoint = get_arg_default(&mut args, &body, "size")?;
1268    check_args("circle", body, args)?;
1269
1270    Ok(Box::new(shape::circle(
1271        id.0, bag, border, blur, radii, fill, outline, size,
1272    )))
1273}
1274
1275// In CSS, 1.2 is usually used as the "reasonable" default line-height for a
1276// given font.
1277const DEFAULT_LINE_HEIGHT: f32 = 1.2;
1278// CSS defaults to 16 pixels, which is 12.8 points. We round up to 14 points as
1279// the next highest even number.
1280const DEFAULT_FONT_SIZE: f32 = 14.0;
1281
1282fn to_style(style: u8) -> LuaResult<cosmic_text::Style> {
1283    match style {
1284        0 => Ok(cosmic_text::Style::Normal),
1285        1 => Ok(cosmic_text::Style::Italic),
1286        2 => Ok(cosmic_text::Style::Oblique),
1287        _ => Err(LuaError::RuntimeError(format!(
1288            "{style} is not a valid style enum value!"
1289        ))),
1290    }
1291}
1292
1293fn to_wrap(wrap: u8) -> LuaResult<cosmic_text::Wrap> {
1294    match wrap {
1295        0 => Ok(cosmic_text::Wrap::None),
1296        1 => Ok(cosmic_text::Wrap::Glyph),
1297        2 => Ok(cosmic_text::Wrap::Word),
1298        3 => Ok(cosmic_text::Wrap::WordOrGlyph),
1299        _ => Err(LuaError::RuntimeError(format!(
1300            "{wrap} is not a valid wrap enum value!"
1301        ))),
1302    }
1303}
1304
1305fn to_align(align: Option<u8>) -> LuaResult<Option<cosmic_text::Align>> {
1306    if let Some(a) = align {
1307        Ok(Some(match a {
1308            0 => cosmic_text::Align::Left,
1309            1 => cosmic_text::Align::Right,
1310            2 => cosmic_text::Align::Center,
1311            3 => cosmic_text::Align::Justified,
1312            4 => cosmic_text::Align::End,
1313            _ => {
1314                return Err(LuaError::RuntimeError(format!(
1315                    "{a} is not a valid align enum value!"
1316                )));
1317            }
1318        }))
1319    } else {
1320        Ok(None)
1321    }
1322}
1323
1324fn create_text(_: &Lua, (id, body): (LuaSourceID, LuaTable)) -> LuaResult<ComponentBag> {
1325    let bag = prop_no_children(&body)?;
1326
1327    let mut args = HashSet::from_iter(["props"]);
1328    let text = get_arg_required(&mut args, &body, "text")?;
1329    let font_size = get_arg_or(&mut args, &body, "fontsize", DEFAULT_FONT_SIZE)?;
1330    let line_height = get_arg_or(
1331        &mut args,
1332        &body,
1333        "lineheight",
1334        font_size * DEFAULT_LINE_HEIGHT,
1335    )?;
1336    let font: LuaFontFamily = get_arg_default(&mut args, &body, "font")?;
1337    let color = get_arg_required(&mut args, &body, "color")?;
1338    let weight: u16 = get_arg_or(&mut args, &body, "weight", cosmic_text::Weight::NORMAL.0)?;
1339    let style: u8 = get_arg_default(&mut args, &body, "style")?;
1340    let wrap: u8 = get_arg_default(&mut args, &body, "wrap")?;
1341    let align: Option<u8> = get_arg_default(&mut args, &body, "align")?;
1342    check_args("text", body, args)?;
1343
1344    let style = to_style(style)?;
1345    let wrap = to_wrap(wrap)?;
1346    let align = to_align(align)?;
1347
1348    Ok(Box::new(Text::<PropBag>::new(
1349        id.0,
1350        bag,
1351        font_size,
1352        line_height,
1353        text,
1354        font.0,
1355        color,
1356        cosmic_text::Weight(weight),
1357        style,
1358        wrap,
1359        align,
1360    )))
1361}
1362
1363fn create_textbox(_: &Lua, (id, body): (LuaSourceID, LuaTable)) -> LuaResult<ComponentBag> {
1364    let bag = prop_no_children(&body)?;
1365
1366    let mut args = HashSet::from_iter(["props"]);
1367    let font_size = get_arg_or(&mut args, &body, "fontsize", DEFAULT_FONT_SIZE)?;
1368    let line_height = get_arg_or(
1369        &mut args,
1370        &body,
1371        "lineheight",
1372        font_size * DEFAULT_LINE_HEIGHT,
1373    )?;
1374    let font: LuaFontFamily = get_arg_default(&mut args, &body, "font")?;
1375    let color = get_arg_required(&mut args, &body, "color")?;
1376    let weight: u16 = get_arg_or(&mut args, &body, "weight", cosmic_text::Weight::NORMAL.0)?;
1377    let style: u8 = get_arg_default(&mut args, &body, "style")?;
1378    let wrap: u8 = get_arg_default(&mut args, &body, "wrap")?;
1379    let align: Option<u8> = get_arg_default(&mut args, &body, "align")?;
1380    check_args("textbox", body, args)?;
1381
1382    let style = to_style(style)?;
1383    let wrap = to_wrap(wrap)?;
1384    let align = to_align(align)?;
1385
1386    Ok(Box::new(TextBox::<PropBag>::new(
1387        id.0,
1388        bag,
1389        font_size,
1390        line_height,
1391        font.0,
1392        color,
1393        cosmic_text::Weight(weight),
1394        style,
1395        wrap,
1396        align,
1397    )))
1398}
1399
1400fn create_id(_: &Lua, (parent, v): (Option<LuaSourceID>, LuaValue)) -> LuaResult<LuaSourceID> {
1401    if let Some(n) = v.as_integer() {
1402        if let Some(parent) = parent {
1403            Ok(LuaSourceID(parent.0.child(DataID::Int(n))))
1404        } else {
1405            #[cfg(debug_assertions)]
1406            panic!("NO PARENT! This means the layout was called incorrectly!");
1407            #[cfg(not(debug_assertions))]
1408            Err(LuaError::UserDataTypeMismatch)
1409        }
1410    } else if let Some(name) = v.as_string().map(|x| x.to_string_lossy()) {
1411        if let Some(parent) = parent {
1412            Ok(LuaSourceID(parent.0.child(DataID::Owned(name))))
1413        } else {
1414            #[cfg(debug_assertions)]
1415            panic!("NO PARENT! This means the layout was called incorrectly!");
1416            #[cfg(not(debug_assertions))]
1417            Err(LuaError::UserDataTypeMismatch)
1418        }
1419    } else {
1420        Err(LuaError::RuntimeError(format!(
1421            "Expected a number or string to construct an ID, but found {}",
1422            v.type_name()
1423        )))
1424    }
1425}
1426
1427fn replace_dualpoint(p: LuaDualPoint, bound: f32) -> dpi::Size {
1428    match p {
1429        LuaDualPoint::Px(mut point2_d) => {
1430            replace_limit(&mut point2_d, bound);
1431            dpi::Size::Physical(dpi::PhysicalSize::<u32>::new(
1432                point2_d.x.ceil() as u32,
1433                point2_d.y.ceil() as u32,
1434            ))
1435        }
1436        LuaDualPoint::Dp(mut point2_d) => {
1437            replace_limit(&mut point2_d, bound);
1438            dpi::Size::Logical(dpi::LogicalSize::<f64>::new(
1439                point2_d.x as f64,
1440                point2_d.y as f64,
1441            ))
1442        }
1443    }
1444}
1445
1446fn get_required_child<T: FromLua>(t: &LuaTable, idx: usize) -> LuaResult<T> {
1447    if !t.contains_key(idx)? {
1448        Err(LuaError::RuntimeError(format!(
1449            "Expected at least {idx} children, but found {}",
1450            t.raw_len()
1451        )))
1452    } else {
1453        t.get(idx)
1454    }
1455}
1456
1457fn create_window(_: &Lua, (id, body): (LuaSourceID, LuaTable)) -> LuaResult<Window> {
1458    let title: LuaString = get_required(&body, "title")?;
1459    let child: ComponentBag = get_required_child(&body, 1)?;
1460
1461    let mut attributes = winit::window::Window::default_attributes()
1462        .with_title(title.to_string_lossy())
1463        .with_resizable(get_or(&body, "resizeable", true)?)
1464        .with_maximized(get_or(&body, "maximized", false)?)
1465        .with_visible(get_or(&body, "visible", true)?)
1466        .with_transparent(get_or(&body, "transparent", false)?)
1467        .with_blur(get_or(&body, "blur", false)?)
1468        .with_decorations(get_or(&body, "decorated", true)?)
1469        .with_content_protected(get_or(&body, "protected", false)?)
1470        .with_active(get_or(&body, "focused", false)?);
1471
1472    if body.contains_key("icon")? {
1473        attributes.window_icon = Some(
1474            crate::resource::load_icon(&std::path::PathBuf::from(body.get::<String>("icon")?))
1475                .map_err(|e| LuaError::ExternalError(Arc::new(Box::new(e))))?,
1476        );
1477    }
1478
1479    if body.contains_key("minsize")? {
1480        attributes.min_inner_size = Some(replace_dualpoint(body.get("minsize")?, f32::NEG_INFINITY))
1481    }
1482
1483    if body.contains_key("maxsize")? {
1484        attributes.max_inner_size = Some(replace_dualpoint(body.get("maxsize")?, f32::INFINITY))
1485    }
1486
1487    if body.contains_key("size")? {
1488        attributes.inner_size = Some(replace_dualpoint(body.get("size")?, 0.0));
1489    }
1490
1491    Ok(Window::new(id.0, attributes, Box::new(child)))
1492}
1493
1494#[derive(PartialEq, Clone, Debug)]
1495pub struct LuaFragment {
1496    layout: LuaFunction,
1497    id_enter: LuaFunction,
1498}
1499
1500impl FromLua for LuaFragment {
1501    fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {
1502        let t = value.as_table().ok_or(LuaError::UserDataTypeMismatch)?;
1503        Ok(Self {
1504            layout: t.get("layout")?,
1505            id_enter: t.get("id_enter")?,
1506        })
1507    }
1508}
1509
1510impl IntoLua for LuaFragment {
1511    fn into_lua(self, lua: &Lua) -> LuaResult<LuaValue> {
1512        Ok(LuaValue::Table(lua.create_table_from([
1513            ("layout", self.layout),
1514            ("id_enter", self.id_enter),
1515        ])?))
1516    }
1517}
1518
1519impl LuaFragment {
1520    pub fn call<T: IntoLuaMulti>(&self, args: T, mut id: ScopeID<'_>) -> LuaResult<ComponentBag> {
1521        self.id_enter.call::<ComponentBag>((
1522            LuaSourceID(id.id().clone()),
1523            self.layout.clone(),
1524            args,
1525        ))
1526    }
1527}
1528
1529#[derive(Clone, Debug)]
1530pub struct LuaContext {
1531    feather: LuaTable,
1532    modules: LuaTable,
1533    id_enter: LuaFunction,
1534    require: LuaFunction,
1535    load_in_sandbox: LuaFunction,
1536    lua_handlers: LuaTable,
1537    handler_slots: LuaTable,
1538    handler_count: Rc<AtomicU64>,
1539}
1540
1541impl PartialEq for LuaContext {
1542    fn eq(&self, other: &Self) -> bool {
1543        self.feather == other.feather
1544            && self.modules == other.modules
1545            && self.id_enter == other.id_enter
1546            && self.require == other.require
1547            && self.load_in_sandbox == other.load_in_sandbox
1548            && self.lua_handlers == other.lua_handlers
1549            && self.handler_slots == other.handler_slots
1550            && self.handler_count.load(Ordering::Relaxed)
1551                == other.handler_count.load(Ordering::Relaxed)
1552    }
1553}
1554
1555impl LuaContext {
1556    pub fn new(lua: &Lua) -> LuaResult<Self> {
1557        let preload = lua.create_table()?;
1558
1559        preload.set("create_id", lua.create_function(create_id)?)?;
1560        preload.set("create_button", lua.create_function(create_button)?)?;
1561        preload.set(
1562            "create_domain_line",
1563            lua.create_function(create_domain_line)?,
1564        )?;
1565        preload.set(
1566            "create_domain_point",
1567            lua.create_function(create_domain_point)?,
1568        )?;
1569        preload.set("create_flexbox", lua.create_function(create_flexbox)?)?;
1570        preload.set("create_gridbox", lua.create_function(create_gridbox)?)?;
1571        preload.set("create_image", lua.create_function(create_image)?)?;
1572        preload.set("create_line", lua.create_function(create_line)?)?;
1573        preload.set("create_listbox", lua.create_function(create_listbox)?)?;
1574        preload.set("create_mousearea", lua.create_function(create_mousearea)?)?;
1575        //preload.set("create_paragraph", lua.create_function(create_paragraph)?)?;
1576        preload.set("create_region", lua.create_function(create_region)?)?;
1577        preload.set("create_scrollarea", lua.create_function(create_scrollarea)?)?;
1578        preload.set("create_round_rect", lua.create_function(create_round_rect)?)?;
1579        preload.set("create_arc", lua.create_function(create_arc)?)?;
1580        preload.set("create_triangle", lua.create_function(create_triangle)?)?;
1581        preload.set("create_circle", lua.create_function(create_circle)?)?;
1582        preload.set("create_text", lua.create_function(create_text)?)?;
1583        preload.set("create_textbox", lua.create_function(create_textbox)?)?;
1584        preload.set("create_window", lua.create_function(create_window)?)?;
1585
1586        let preload_mt = lua.create_table()?;
1587        preload_mt.set("__index", lua.globals())?;
1588        preload.set_metatable(Some(preload_mt))?;
1589
1590        let feather: LuaTable = lua
1591            .load(NamedChunk(FEATHER, "feather"))
1592            .set_environment(preload.clone())
1593            .eval()?;
1594
1595        let id_enter = feather.get::<LuaTable>("ID")?.get::<LuaFunction>("enter")?;
1596        let lua_handlers = lua.create_table()?;
1597        let handlers = lua_handlers.clone();
1598        let handler_slots = lua.create_table()?;
1599        let slot_clone = handler_slots.clone();
1600        let handler_count = Rc::new(AtomicU64::new(0));
1601        let handler_count_clone = handler_count.clone();
1602
1603        feather.set(
1604            "add_handler",
1605            lua.create_function(move |_, (name, func): (LuaString, LuaFunction)| {
1606                let count = handler_count_clone.fetch_add(1, Ordering::Relaxed);
1607                handlers.set(&name, func)?;
1608                let slot = Slot(APP_SOURCE_ID.into(), count);
1609                slot_clone.set(name, slot.clone())?;
1610                Ok(slot)
1611            })?,
1612        )?;
1613
1614        let modules = lua.create_table()?;
1615        modules.set("feather", feather.clone())?;
1616        preload.set("injected_dep", &modules)?;
1617
1618        lua.load(NamedChunk(SANDBOX, "sandbox")).exec()?;
1619
1620        let require: LuaFunction = lua
1621            .load(
1622                r#"
1623package = {preload = {}, loaded = injected_dep}
1624return function(name) -- require stub for inside sandbox
1625  if not package.loaded[name] then
1626    if not package.preload[name] then
1627      error("Couldn't find package.preload for " .. name)
1628    end
1629    package.loaded[name] = package.preload[name]()
1630  end
1631  return package.loaded[name]
1632end
1633        "#,
1634            )
1635            .set_environment(preload)
1636            .eval()?;
1637
1638        lua.load(
1639            r#"
1640        jit.opt.start("maxtrace=10000")
1641        jit.opt.start("maxmcode=4096")
1642        jit.opt.start("recunroll=5")
1643        jit.opt.start("loopunroll=60")
1644        
1645        --local create_module = sandbox_impl(true)
1646        create_module = function(bytes, name, interface) 
1647            return load(bytes, name, "t", setmetatable(interface, {__index=_G}))
1648         end 
1649
1650
1651        function load_in_sandbox(bytes, name, additional_interface)
1652          local r, err = create_module(bytes, name, additional_interface)
1653          if r == nil then
1654            error(err)
1655          end
1656          
1657          return r()
1658        end
1659                "#,
1660        )
1661        .exec()?;
1662
1663        let load_in_sandbox: LuaFunction = lua.load("load_in_sandbox").eval()?;
1664
1665        Ok(Self {
1666            feather,
1667            id_enter,
1668            load_in_sandbox,
1669            require,
1670            modules,
1671            lua_handlers,
1672            handler_slots,
1673            handler_count,
1674        })
1675    }
1676
1677    pub fn add_module(&self, lua: &Lua, name: &str, module: &[u8]) -> LuaResult<()> {
1678        let interface = lua.create_table()?;
1679        interface.set("require", self.require.clone())?;
1680
1681        self.modules.set(
1682            name,
1683            self.load_in_sandbox
1684                .call::<LuaValue>((lua.create_string(module)?, name, interface))?,
1685        )
1686    }
1687
1688    pub fn add_function<A: FromLuaMulti, R: IntoLuaMulti>(
1689        &self,
1690        lua: &Lua,
1691        name: &str,
1692        f: impl Fn(&Lua, A) -> LuaResult<R> + mlua::MaybeSend + 'static,
1693    ) -> LuaResult<()> {
1694        self.modules.set(name, lua.create_function(f)?)
1695    }
1696
1697    pub fn add_function_mut<F, A: FromLuaMulti, R: IntoLuaMulti>(
1698        &self,
1699        lua: &Lua,
1700        name: &str,
1701        f: impl FnMut(&Lua, A) -> LuaResult<R> + mlua::MaybeSend + 'static,
1702    ) -> LuaResult<()> {
1703        self.modules.set(name, lua.create_function_mut(f)?)
1704    }
1705
1706    pub fn get_module(&self, name: &str) -> Option<LuaValue> {
1707        self.modules.get(name).ok().and_then(|x| match x {
1708            Some(LuaNil) => None,
1709            x => x,
1710        })
1711    }
1712
1713    pub fn reserve_handler(&self, name: &str) -> LuaResult<u64> {
1714        let index = self.handler_count.fetch_add(1, Ordering::Relaxed);
1715        self.handler_slots
1716            .set(name, Slot(APP_SOURCE_ID.into(), index))?;
1717        Ok(index)
1718    }
1719
1720    fn load_layout<R: FromLuaMulti>(&self, lua: &Lua, layout: &[u8]) -> LuaResult<R> {
1721        let interface = lua.create_table()?;
1722        interface.set("handlers", self.handler_slots.clone())?;
1723        interface.set("require", self.require.clone())?;
1724
1725        self.load_in_sandbox
1726            .call((lua.create_string(layout)?, "layout", interface))
1727    }
1728
1729    pub fn load_fragment(&self, lua: &Lua, layout: &[u8]) -> LuaResult<LuaFragment> {
1730        Ok(LuaFragment {
1731            layout: self.load_layout(lua, layout)?,
1732            id_enter: self.id_enter.clone(),
1733        })
1734    }
1735
1736    pub fn get_handlers<AppData: FromLua + IntoLua + Clone>(
1737        &self,
1738        mut reserved: HashMap<u64, crate::AppEvent<AppData>>,
1739    ) -> LuaResult<Vec<crate::AppEvent<AppData>>> {
1740        use std::mem::MaybeUninit;
1741
1742        let count = self.handler_count.load(Ordering::Relaxed) as usize;
1743        let mut handlers: Vec<crate::AppEvent<AppData>> = Vec::with_capacity(count);
1744        let spare = handlers.spare_capacity_mut();
1745
1746        for v in self.handler_slots.pairs::<LuaString, Slot>() {
1747            let (key, slot) = v?;
1748            if self.lua_handlers.contains_key(&key)? {
1749                let func: LuaFunction = self.lua_handlers.get(&key)?;
1750                spare[slot.1 as usize] = MaybeUninit::new(Box::new(
1751                    move |pair: crate::DispatchPair, mut state: AccessCell<AppData>| {
1752                        let (appdata, v): (AppData, LuaValue) =
1753                            match func.call((pair.0, state.value.clone())) {
1754                                Ok(v) => v,
1755                                Err(e) => return InputResult::Error(e.into()),
1756                            };
1757                        *state = appdata;
1758                        if v.as_boolean().unwrap_or(true) {
1759                            InputResult::Consume(())
1760                        } else {
1761                            InputResult::Forward(())
1762                        }
1763                    },
1764                ));
1765            } else {
1766                spare[slot.1 as usize] = MaybeUninit::new(
1767                    reserved
1768                        .remove(&slot.1)
1769                        .expect("Reserved slot had no entry in `reserved` map!"),
1770                )
1771            }
1772        }
1773
1774        unsafe {
1775            handlers.set_len(count);
1776        }
1777
1778        Ok(handlers)
1779    }
1780
1781    pub fn update_handler<AppData: FromLua + IntoLua + Clone + 'static>(
1782        &self,
1783        lua: &Lua,
1784        sender: std::sync::mpsc::Sender<crate::EventPair<AppData>>,
1785        count: AtomicU64,
1786    ) -> LuaResult<()> {
1787        let slot_clone = self.handler_slots.clone();
1788        self.feather.set(
1789            "add_handler",
1790            lua.create_function(move |_, (name, func): (LuaString, LuaFunction)| {
1791                let count = count.fetch_add(1, Ordering::Relaxed);
1792                sender
1793                    .send((
1794                        count,
1795                        Box::new(
1796                            move |pair: crate::DispatchPair, mut state: AccessCell<AppData>| {
1797                                let (appdata, v): (AppData, LuaValue) =
1798                                    match func.call((pair.0, state.value.clone())) {
1799                                        Ok(v) => v,
1800                                        Err(e) => return InputResult::Error(e.into()),
1801                                    };
1802                                *state = appdata;
1803                                if v.as_boolean().unwrap_or(true) {
1804                                    InputResult::Consume(())
1805                                } else {
1806                                    InputResult::Forward(())
1807                                }
1808                            },
1809                        ),
1810                    ))
1811                    .unwrap();
1812                let slot = Slot(APP_SOURCE_ID.into(), count);
1813                slot_clone.set(name, slot.clone())?;
1814                Ok(slot)
1815            })?,
1816        )?;
1817
1818        Ok(())
1819    }
1820}
1821
1822pub struct LuaApp<AppData: Clone + FromLua + IntoLua, T: 'static>(
1823    crate::App<AppData, LuaPersist<AppData>, T>,
1824);
1825
1826impl<AppData: Clone + FromLua + IntoLua + PartialEq + 'static, T: 'static> LuaApp<AppData, T> {
1827    pub fn new(
1828        lua: &Lua,
1829        app_state: AppData,
1830        handlers: Vec<(String, crate::AppEvent<AppData>)>,
1831        layout: &[u8],
1832    ) -> eyre::Result<(Self, crate::EventLoop<T>)> {
1833        let ctx = LuaContext::new(lua)?;
1834
1835        let mut reserved = HashMap::new();
1836        for (k, v) in handlers.into_iter() {
1837            reserved.insert(ctx.reserve_handler(&k)?, v);
1838        }
1839
1840        let (window, init): (LuaFunction, Option<LuaFunction>) = ctx.load_layout(lua, layout)?;
1841
1842        let (app, event, sender, count) = crate::App::new(
1843            app_state.clone(),
1844            ctx.get_handlers(reserved)?,
1845            LuaPersist {
1846                window,
1847                id_enter: ctx.id_enter.clone(),
1848                init: if let Some(init) = init {
1849                    Ok(init)
1850                } else {
1851                    Err(app_state)
1852                },
1853                phantom: PhantomData,
1854            },
1855            None,
1856            None,
1857        )?;
1858
1859        // Change the add_handler to use the channel to send new handlers
1860        ctx.update_handler(lua, sender, count)?;
1861        Ok((Self(app), event))
1862    }
1863}
1864
1865impl<AppData: Clone + FromLua + IntoLua + PartialEq + 'static, T: 'static> ApplicationHandler<T>
1866    for LuaApp<AppData, T>
1867{
1868    fn new_events(
1869        &mut self,
1870        event_loop: &winit::event_loop::ActiveEventLoop,
1871        cause: winit::event::StartCause,
1872    ) {
1873        <crate::App<AppData, LuaPersist<AppData>, T> as ApplicationHandler<T>>::new_events(
1874            &mut self.0,
1875            event_loop,
1876            cause,
1877        );
1878    }
1879
1880    fn user_event(&mut self, event_loop: &winit::event_loop::ActiveEventLoop, event: T) {
1881        self.0.user_event(event_loop, event);
1882    }
1883
1884    fn device_event(
1885        &mut self,
1886        event_loop: &winit::event_loop::ActiveEventLoop,
1887        device_id: winit::event::DeviceId,
1888        event: winit::event::DeviceEvent,
1889    ) {
1890        <crate::App<AppData, LuaPersist<AppData>, T> as ApplicationHandler<T>>::device_event(
1891            &mut self.0,
1892            event_loop,
1893            device_id,
1894            event,
1895        );
1896    }
1897
1898    fn about_to_wait(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
1899        <crate::App<AppData, LuaPersist<AppData>, T> as ApplicationHandler<T>>::about_to_wait(
1900            &mut self.0,
1901            event_loop,
1902        );
1903    }
1904
1905    fn suspended(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
1906        <crate::App<AppData, LuaPersist<AppData>, T> as ApplicationHandler<T>>::suspended(
1907            &mut self.0,
1908            event_loop,
1909        );
1910    }
1911
1912    fn exiting(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
1913        <crate::App<AppData, LuaPersist<AppData>, T> as ApplicationHandler<T>>::exiting(
1914            &mut self.0,
1915            event_loop,
1916        );
1917    }
1918
1919    fn memory_warning(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
1920        <crate::App<AppData, LuaPersist<AppData>, T> as ApplicationHandler<T>>::memory_warning(
1921            &mut self.0,
1922            event_loop,
1923        );
1924    }
1925
1926    fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
1927        <crate::App<AppData, LuaPersist<AppData>, T> as ApplicationHandler<T>>::resumed(
1928            &mut self.0,
1929            event_loop,
1930        );
1931    }
1932
1933    fn window_event(
1934        &mut self,
1935        event_loop: &winit::event_loop::ActiveEventLoop,
1936        window_id: winit::window::WindowId,
1937        event: winit::event::WindowEvent,
1938    ) {
1939        <crate::App<AppData, LuaPersist<AppData>, T> as ApplicationHandler<T>>::window_event(
1940            &mut self.0,
1941            event_loop,
1942            window_id,
1943            event,
1944        )
1945    }
1946}