moosicbox_app_native_renderer/
lib.rs

1#![cfg_attr(feature = "fail-on-warnings", deny(warnings))]
2#![warn(clippy::all, clippy::pedantic, clippy::nursery, clippy::cargo)]
3
4use std::{
5    collections::HashMap,
6    pin::Pin,
7    str::FromStr as _,
8    sync::{atomic::AtomicI32, Arc, LazyLock, Mutex, RwLock},
9};
10
11use fltk::{
12    app::{self, App},
13    enums::{self, Event},
14    frame::{self, Frame},
15    group,
16    image::{RgbImage, SharedImage},
17    prelude::*,
18    window::{DoubleWindow, Window},
19};
20use flume::{Receiver, Sender};
21use futures::Future;
22use gigachad_transformer::{
23    calc::{calc_number, Calc as _},
24    ContainerElement, Element, ElementList, HeaderSize, LayoutDirection, LayoutOverflow,
25};
26use thiserror::Error;
27
28type RouteFunc = Arc<
29    Box<
30        dyn (Fn() -> Pin<
31                Box<dyn Future<Output = Result<ElementList, Box<dyn std::error::Error>>> + Send>,
32            >) + Send
33            + Sync,
34    >,
35>;
36
37#[derive(Debug, Clone, PartialEq, Eq)]
38pub enum RoutePath {
39    Literal(String),
40    Literals(Vec<String>),
41}
42
43impl RoutePath {
44    #[must_use]
45    pub fn matches(&self, path: &str) -> bool {
46        match self {
47            Self::Literal(route_path) => route_path == path,
48            Self::Literals(route_paths) => route_paths.iter().any(|x| x == path),
49        }
50    }
51}
52
53impl From<&str> for RoutePath {
54    fn from(value: &str) -> Self {
55        Self::Literal(value.to_owned())
56    }
57}
58
59impl From<&[&str; 1]> for RoutePath {
60    fn from(value: &[&str; 1]) -> Self {
61        Self::Literals(value.iter().map(ToString::to_string).collect())
62    }
63}
64
65impl From<&[&str; 2]> for RoutePath {
66    fn from(value: &[&str; 2]) -> Self {
67        Self::Literals(value.iter().map(ToString::to_string).collect())
68    }
69}
70
71impl From<&[&str; 3]> for RoutePath {
72    fn from(value: &[&str; 3]) -> Self {
73        Self::Literals(value.iter().map(ToString::to_string).collect())
74    }
75}
76
77impl From<&[&str; 4]> for RoutePath {
78    fn from(value: &[&str; 4]) -> Self {
79        Self::Literals(value.iter().map(ToString::to_string).collect())
80    }
81}
82
83impl From<&[&str; 5]> for RoutePath {
84    fn from(value: &[&str; 5]) -> Self {
85        Self::Literals(value.iter().map(ToString::to_string).collect())
86    }
87}
88
89impl From<&[&str; 6]> for RoutePath {
90    fn from(value: &[&str; 6]) -> Self {
91        Self::Literals(value.iter().map(ToString::to_string).collect())
92    }
93}
94
95impl From<&[&str; 7]> for RoutePath {
96    fn from(value: &[&str; 7]) -> Self {
97        Self::Literals(value.iter().map(ToString::to_string).collect())
98    }
99}
100
101impl From<&[&str; 8]> for RoutePath {
102    fn from(value: &[&str; 8]) -> Self {
103        Self::Literals(value.iter().map(ToString::to_string).collect())
104    }
105}
106
107impl From<&[&str; 9]> for RoutePath {
108    fn from(value: &[&str; 9]) -> Self {
109        Self::Literals(value.iter().map(ToString::to_string).collect())
110    }
111}
112
113impl From<&[&str; 10]> for RoutePath {
114    fn from(value: &[&str; 10]) -> Self {
115        Self::Literals(value.iter().map(ToString::to_string).collect())
116    }
117}
118
119impl From<&[&str]> for RoutePath {
120    fn from(value: &[&str]) -> Self {
121        Self::Literals(value.iter().map(ToString::to_string).collect())
122    }
123}
124
125impl From<Vec<&str>> for RoutePath {
126    fn from(value: Vec<&str>) -> Self {
127        Self::Literals(value.into_iter().map(ToString::to_string).collect())
128    }
129}
130
131impl From<String> for RoutePath {
132    fn from(value: String) -> Self {
133        Self::Literal(value)
134    }
135}
136
137impl From<&[String]> for RoutePath {
138    fn from(value: &[String]) -> Self {
139        Self::Literals(value.iter().map(ToString::to_string).collect())
140    }
141}
142
143impl From<&[&String]> for RoutePath {
144    fn from(value: &[&String]) -> Self {
145        Self::Literals(value.iter().map(ToString::to_string).collect())
146    }
147}
148
149impl From<Vec<String>> for RoutePath {
150    fn from(value: Vec<String>) -> Self {
151        Self::Literals(value)
152    }
153}
154
155#[derive(Debug, Error)]
156pub enum LoadImageError {
157    #[error(transparent)]
158    Reqwest(#[from] reqwest::Error),
159    #[error(transparent)]
160    Image(#[from] image::ImageError),
161    #[error(transparent)]
162    Fltk(#[from] FltkError),
163}
164
165#[derive(Debug, Clone)]
166pub enum AppEvent {
167    Navigate {
168        href: String,
169    },
170    LoadImage {
171        source: String,
172        width: Option<f32>,
173        height: Option<f32>,
174        frame: Frame,
175    },
176}
177
178#[derive(Clone)]
179pub struct Renderer {
180    app: Option<App>,
181    window: Option<DoubleWindow>,
182    elements: Arc<Mutex<ElementList>>,
183    root: Arc<RwLock<Option<group::Flex>>>,
184    routes: Arc<RwLock<Vec<(RoutePath, RouteFunc)>>>,
185    width: Arc<AtomicI32>,
186    height: Arc<AtomicI32>,
187    event_sender: Option<Sender<AppEvent>>,
188    event_receiver: Option<Receiver<AppEvent>>,
189}
190
191impl Default for Renderer {
192    fn default() -> Self {
193        Self::new()
194    }
195}
196
197impl Renderer {
198    #[must_use]
199    pub fn new() -> Self {
200        Self {
201            app: None,
202            window: None,
203            elements: Arc::new(Mutex::new(ElementList::default())),
204            root: Arc::new(RwLock::new(None)),
205            routes: Arc::new(RwLock::new(vec![])),
206            width: Arc::new(AtomicI32::new(0)),
207            height: Arc::new(AtomicI32::new(0)),
208            event_sender: None,
209            event_receiver: None,
210        }
211    }
212
213    /// # Panics
214    ///
215    /// Will panic if elements `Mutex` is poisoned.
216    ///
217    /// # Errors
218    ///
219    /// Will error if FLTK app fails to start
220    pub fn start(mut self, width: u16, height: u16) -> Result<Self, FltkError> {
221        let app = app::App::default();
222        self.app.replace(app);
223
224        let mut window = Window::default()
225            .with_size(i32::from(width), i32::from(height))
226            .with_label("MoosicBox");
227
228        self.window.replace(window.clone());
229        self.width
230            .store(i32::from(width), std::sync::atomic::Ordering::SeqCst);
231        self.height
232            .store(i32::from(height), std::sync::atomic::Ordering::SeqCst);
233
234        app::set_background_color(24, 26, 27);
235        app::set_foreground_color(255, 255, 255);
236
237        let (tx, rx) = flume::unbounded();
238        self.event_sender.replace(tx);
239        self.event_receiver.replace(rx);
240
241        window.handle({
242            let renderer = self.clone();
243            move |window, ev| match ev {
244                Event::Resize => {
245                    if renderer.width.load(std::sync::atomic::Ordering::SeqCst) != window.width()
246                        || renderer.height.load(std::sync::atomic::Ordering::SeqCst)
247                            != window.height()
248                    {
249                        renderer
250                            .width
251                            .store(window.width(), std::sync::atomic::Ordering::SeqCst);
252                        renderer
253                            .height
254                            .store(window.height(), std::sync::atomic::Ordering::SeqCst);
255                        log::debug!(
256                            "event resize: width={} height={}",
257                            window.width(),
258                            window.height()
259                        );
260
261                        #[allow(clippy::cast_precision_loss)]
262                        {
263                            renderer
264                                .elements
265                                .lock()
266                                .unwrap()
267                                .calc(window.width() as f32, window.height() as f32);
268                        }
269
270                        if let Err(e) = renderer.perform_render() {
271                            log::error!("Failed to draw elements: {e:?}");
272                        }
273                    }
274                    true
275                }
276                _ => false,
277            }
278        });
279
280        window.set_callback(|_| {
281            if fltk::app::event() == fltk::enums::Event::Close {
282                app::quit();
283            }
284        });
285
286        window = window.center_screen();
287        window.end();
288        window.make_resizable(true);
289        window.show();
290
291        Ok(self)
292    }
293
294    /// # Panics
295    ///
296    /// Will panic if routes `RwLock` is poisoned.
297    #[must_use]
298    pub fn with_route<
299        F: Future<Output = Result<ElementList, E>> + Send + 'static,
300        E: Into<Box<dyn std::error::Error>>,
301    >(
302        self,
303        route: impl Into<RoutePath>,
304        handler: impl Fn() -> F + Send + Sync + Clone + 'static,
305    ) -> Self {
306        self.routes.write().unwrap().push((
307            route.into(),
308            Arc::new(Box::new(move || {
309                Box::pin({
310                    let handler = handler.clone();
311                    async move { handler().await.map_err(Into::into) }
312                })
313            })),
314        ));
315        self
316    }
317
318    async fn load_image(
319        source: String,
320        width: Option<f32>,
321        height: Option<f32>,
322        mut frame: Frame,
323    ) -> Result<(), LoadImageError> {
324        type ImageCache = LazyLock<Arc<tokio::sync::RwLock<HashMap<String, SharedImage>>>>;
325        static IMAGE_CACHE: ImageCache =
326            LazyLock::new(|| Arc::new(tokio::sync::RwLock::new(HashMap::new())));
327
328        let key = format!("{source}:{width:?}:{height:?}");
329
330        let cached_image = { IMAGE_CACHE.read().await.get(&key).cloned() };
331
332        let image = if let Some(image) = cached_image {
333            image
334        } else {
335            let data = reqwest::get(source).await?.bytes().await?;
336            let image = image::load_from_memory_with_format(&data, image::ImageFormat::WebP)?;
337            let image = RgbImage::new(
338                image.as_bytes(),
339                image.width().try_into().unwrap(),
340                image.height().try_into().unwrap(),
341                enums::ColorDepth::Rgb8,
342            )?;
343            let image = SharedImage::from_image(image)?;
344            IMAGE_CACHE.write().await.insert(key, image.clone());
345
346            image
347        };
348
349        if width.is_some() || height.is_some() {
350            #[allow(clippy::cast_possible_truncation)]
351            #[allow(clippy::cast_precision_loss)]
352            let width = width.unwrap_or(image.width() as f32).round() as i32;
353            #[allow(clippy::cast_possible_truncation)]
354            #[allow(clippy::cast_precision_loss)]
355            let height = height.unwrap_or(image.height() as f32).round() as i32;
356
357            frame.set_size(width, height);
358        }
359
360        frame.set_image_scaled(Some(image));
361        frame.set_damage(true);
362        app::awake();
363
364        Ok(())
365    }
366
367    pub async fn listen(&self) {
368        let Some(rx) = self.event_receiver.clone() else {
369            moosicbox_assert::die_or_panic!("Cannot listen before app is started");
370        };
371        let mut renderer = self.clone();
372        while let Ok(event) = rx.recv_async().await {
373            log::debug!("received event {event:?}");
374            match event {
375                AppEvent::Navigate { href } => {
376                    if let Err(e) = renderer.navigate(&href).await {
377                        log::error!("Failed to navigate: {e:?}");
378                    }
379                }
380                AppEvent::LoadImage {
381                    source,
382                    width,
383                    height,
384                    frame,
385                } => {
386                    moosicbox_task::spawn("renderer: load_image", async move {
387                        Self::load_image(source, width, height, frame).await
388                    });
389                }
390            }
391        }
392    }
393
394    /// # Errors
395    ///
396    /// Will error if FLTK fails to render the navigation result.
397    ///
398    /// # Panics
399    ///
400    /// Will panic if routes `RwLock` is poisoned.
401    pub async fn navigate(&mut self, path: &str) -> Result<(), FltkError> {
402        let handler = {
403            self.routes
404                .read()
405                .unwrap()
406                .iter()
407                .find(|(route, _)| route.matches(path))
408                .cloned()
409                .map(|(_, handler)| handler)
410        };
411        if let Some(handler) = handler {
412            match handler().await {
413                Ok(elements) => {
414                    self.render(elements)?;
415                }
416                Err(e) => {
417                    log::error!("Failed to fetch route elements: {e:?}");
418                }
419            }
420        } else {
421            log::warn!("Invalid navigation path={path:?}");
422        }
423
424        Ok(())
425    }
426
427    fn perform_render(&self) -> Result<(), FltkError> {
428        let (Some(mut window), Some(tx)) = (self.window.clone(), self.event_sender.clone()) else {
429            moosicbox_assert::die_or_panic!("Cannot perform_render before app is started");
430        };
431        log::debug!("perform_render: started");
432        {
433            let mut root = self.root.write().unwrap();
434            if let Some(root) = root.take() {
435                window.remove(&root);
436                log::debug!("perform_render: removed root");
437            }
438            window.begin();
439            log::debug!("perform_render: begin");
440            let elements: &[Element] = &self.elements.lock().unwrap();
441            root.replace(draw_elements(
442                elements,
443                #[allow(clippy::cast_precision_loss)]
444                Context::new(window.width() as f32, window.height() as f32),
445                tx,
446            )?);
447        }
448        window.end();
449        window.flush();
450        app::awake();
451        log::debug!("perform_render: finished");
452        Ok(())
453    }
454
455    /// # Errors
456    ///
457    /// Will error if FLTK fails to render the elements.
458    ///
459    /// # Panics
460    ///
461    /// Will panic if elements `Mutex` is poisoned.
462    pub fn render(&mut self, mut elements: ElementList) -> Result<(), FltkError> {
463        log::debug!("render: {elements:?}");
464
465        #[allow(clippy::cast_precision_loss)]
466        {
467            elements.calc(
468                self.width.load(std::sync::atomic::Ordering::SeqCst) as f32,
469                self.height.load(std::sync::atomic::Ordering::SeqCst) as f32,
470            );
471
472            *self.elements.lock().unwrap() = elements;
473        }
474
475        self.perform_render()?;
476
477        Ok(())
478    }
479
480    /// # Errors
481    ///
482    /// Will error if FLTK fails to run the event loop.
483    pub fn run(self) -> Result<(), FltkError> {
484        let Some(app) = self.app else {
485            moosicbox_assert::die_or_panic!("Cannot listen before app is started");
486        };
487        app.run()
488    }
489}
490
491#[derive(Clone)]
492struct Context {
493    size: u16,
494    direction: LayoutDirection,
495    overflow: LayoutOverflow,
496    width: f32,
497    height: f32,
498}
499
500impl Context {
501    fn new(width: f32, height: f32) -> Self {
502        Self {
503            size: 12,
504            direction: LayoutDirection::default(),
505            overflow: LayoutOverflow::default(),
506            width,
507            height,
508        }
509    }
510
511    fn with_container(mut self, container: &ContainerElement) -> Self {
512        self.direction = container.direction;
513        self.overflow = container.overflow;
514        self.width = container
515            .calculated_width
516            .or_else(|| container.width.map(|x| calc_number(x, self.width)))
517            .unwrap_or(self.width);
518        self.height = container
519            .calculated_height
520            .or_else(|| container.height.map(|x| calc_number(x, self.height)))
521            .unwrap_or(self.height);
522        self
523    }
524}
525
526fn draw_elements(
527    elements: &[Element],
528    context: Context,
529    event_sender: Sender<AppEvent>,
530) -> Result<group::Flex, FltkError> {
531    log::debug!("draw_elements: elements={elements:?}");
532
533    let outer_flex = match context.overflow {
534        LayoutOverflow::Scroll | LayoutOverflow::Squash => None,
535        LayoutOverflow::Wrap => Some(match context.direction {
536            LayoutDirection::Row => group::Flex::default_fill().column(),
537            LayoutDirection::Column => group::Flex::default_fill().row(),
538        }),
539    };
540
541    let flex = group::Flex::default_fill();
542    let mut flex = match context.direction {
543        LayoutDirection::Row => flex.row(),
544        LayoutDirection::Column => flex.column(),
545    };
546
547    let Some(first) = elements.first() else {
548        flex.end();
549        if let Some(outer) = outer_flex {
550            outer.end();
551            return Ok(outer);
552        }
553        return Ok(flex);
554    };
555
556    let (mut row, mut col) = first
557        .container_element()
558        .and_then(|x| {
559            x.calculated_position.as_ref().and_then(|x| match x {
560                gigachad_transformer::LayoutPosition::Wrap { row, col } => Some((*row, *col)),
561                gigachad_transformer::LayoutPosition::Default => None,
562            })
563        })
564        .unwrap_or((0, 0));
565
566    for (i, element) in elements.iter().enumerate() {
567        let (current_row, current_col) = element
568            .container_element()
569            .and_then(|x| {
570                x.calculated_position.as_ref().and_then(|x| match x {
571                    gigachad_transformer::LayoutPosition::Wrap { row, col } => Some((*row, *col)),
572                    gigachad_transformer::LayoutPosition::Default => None,
573                })
574            })
575            .unwrap_or((row, col));
576
577        if context.direction == LayoutDirection::Row && row != current_row
578            || context.direction == LayoutDirection::Column && col != current_col
579        {
580            flex.end();
581
582            flex = match context.direction {
583                LayoutDirection::Row => group::Flex::default_fill().row(),
584                LayoutDirection::Column => group::Flex::default_fill().column(),
585            };
586        }
587
588        row = current_row;
589        col = current_col;
590
591        if i == elements.len() - 1 {
592            draw_element(element, context, &mut flex, event_sender)?;
593            break;
594        }
595        draw_element(element, context.clone(), &mut flex, event_sender.clone())?;
596    }
597
598    flex.end();
599    if let Some(outer) = outer_flex {
600        outer.end();
601        return Ok(outer);
602    }
603    Ok(flex)
604}
605
606#[allow(clippy::too_many_lines)]
607#[allow(clippy::cognitive_complexity)]
608fn draw_element(
609    element: &Element,
610    mut context: Context,
611    flex: &mut group::Flex,
612    event_sender: Sender<AppEvent>,
613) -> Result<Option<Box<dyn WidgetExt>>, FltkError> {
614    log::debug!(
615        "draw_element: element={element:?} flex_width={} flex_height={} bounds={:?}",
616        flex.width(),
617        flex.height(),
618        flex.bounds()
619    );
620
621    let direction = context.direction;
622    let mut width = None;
623    let mut height = None;
624    let mut flex_element = None;
625    let mut other_element: Option<Box<dyn WidgetExt>> = None;
626
627    match element {
628        Element::Raw { value } => {
629            app::set_font_size(context.size);
630            let frame = frame::Frame::default()
631                .with_label(value)
632                .with_align(enums::Align::Inside | enums::Align::Left);
633
634            other_element = Some(Box::new(frame));
635        }
636        Element::Div { element } => {
637            context = context.with_container(element);
638            width = element.calculated_width;
639            height = element.calculated_height;
640            flex_element = Some(draw_elements(&element.elements, context, event_sender)?);
641        }
642        Element::Aside { element } => {
643            context = context.with_container(element);
644            width = element.calculated_width;
645            height = element.calculated_height;
646            flex_element = Some(draw_elements(&element.elements, context, event_sender)?);
647        }
648        Element::Header { element } => {
649            context = context.with_container(element);
650            width = element.calculated_width;
651            height = element.calculated_height;
652            flex_element = Some(draw_elements(&element.elements, context, event_sender)?);
653        }
654        Element::Footer { element } => {
655            context = context.with_container(element);
656            width = element.calculated_width;
657            height = element.calculated_height;
658            flex_element = Some(draw_elements(&element.elements, context, event_sender)?);
659        }
660        Element::Main { element } => {
661            context = context.with_container(element);
662            width = element.calculated_width;
663            height = element.calculated_height;
664            flex_element = Some(draw_elements(&element.elements, context, event_sender)?);
665        }
666        Element::Section { element } => {
667            context = context.with_container(element);
668            width = element.calculated_width;
669            height = element.calculated_height;
670            flex_element = Some(draw_elements(&element.elements, context, event_sender)?);
671        }
672        Element::Form { element } => {
673            context = context.with_container(element);
674            width = element.calculated_width;
675            height = element.calculated_height;
676            flex_element = Some(draw_elements(&element.elements, context, event_sender)?);
677        }
678        Element::Span { element } => {
679            context = context.with_container(element);
680            width = element.calculated_width;
681            height = element.calculated_height;
682            flex_element = Some(draw_elements(&element.elements, context, event_sender)?);
683        }
684        Element::Input(_) => {}
685        Element::Button { element } => {
686            context = context.with_container(element);
687            width = element.calculated_width;
688            height = element.calculated_height;
689            flex_element = Some(draw_elements(&element.elements, context, event_sender)?);
690        }
691        Element::Image { source, element } => {
692            context = context.with_container(element);
693            width = element.calculated_width;
694            height = element.calculated_height;
695            let mut frame = Frame::default_fill();
696
697            if let Some(source) = source {
698                if source.starts_with("http") {
699                    if let Err(e) = event_sender.send(AppEvent::LoadImage {
700                        source: source.to_owned(),
701                        width: element.width.map(|_| width.unwrap()),
702                        height: element.height.map(|_| height.unwrap()),
703                        frame: frame.clone(),
704                    }) {
705                        log::error!("Failed to send LoadImage event with source={source}: {e:?}");
706                    }
707                } else if let Ok(manifest_path) = std::env::var("CARGO_MANIFEST_DIR") {
708                    if let Ok(path) = std::path::PathBuf::from_str(&manifest_path) {
709                        let source = source
710                            .chars()
711                            .skip_while(|x| *x == '/' || *x == '\\')
712                            .collect::<String>();
713
714                        if let Some(path) = path
715                            .parent()
716                            .and_then(|x| x.parent())
717                            .map(|x| x.join("app-website").join("public").join(source))
718                        {
719                            if let Ok(path) = path.canonicalize() {
720                                if path.is_file() {
721                                    let image = SharedImage::load(path)?;
722
723                                    if width.is_some() || height.is_some() {
724                                        #[allow(clippy::cast_possible_truncation)]
725                                        let width = calc_number(
726                                            element.width.unwrap_or_default(),
727                                            context.width,
728                                        )
729                                        .round()
730                                            as i32;
731                                        #[allow(clippy::cast_possible_truncation)]
732                                        let height = calc_number(
733                                            element.height.unwrap_or_default(),
734                                            context.height,
735                                        )
736                                        .round()
737                                            as i32;
738
739                                        frame.set_size(width, height);
740                                    }
741
742                                    frame.set_image_scaled(Some(image));
743                                }
744                            }
745                        }
746                    }
747                }
748            }
749
750            other_element = Some(Box::new(frame));
751        }
752        Element::Anchor { element, href } => {
753            context = context.with_container(element);
754            width = element.calculated_width;
755            height = element.calculated_height;
756            let mut elements = draw_elements(&element.elements, context, event_sender.clone())?;
757            if let Some(href) = href.to_owned() {
758                elements.handle(move |_, ev| match ev {
759                    Event::Push => true,
760                    Event::Released => {
761                        if let Err(e) = event_sender.send(AppEvent::Navigate { href: href.clone() })
762                        {
763                            log::error!("Failed to navigate to href={href}: {e:?}");
764                        }
765                        true
766                    }
767                    _ => false,
768                });
769            }
770            flex_element = Some(elements);
771        }
772        Element::Heading { element, size } => {
773            context = context.with_container(element);
774            context.size = match size {
775                HeaderSize::H1 => 36,
776                HeaderSize::H2 => 30,
777                HeaderSize::H3 => 24,
778                HeaderSize::H4 => 20,
779                HeaderSize::H5 => 16,
780                HeaderSize::H6 => 12,
781            };
782            width = element.calculated_width;
783            height = element.calculated_height;
784            flex_element = Some(draw_elements(&element.elements, context, event_sender)?);
785        }
786        Element::OrderedList { element } | Element::UnorderedList { element } => {
787            context = context.with_container(element);
788            width = element.calculated_width;
789            height = element.calculated_height;
790            flex_element = Some(draw_elements(&element.elements, context, event_sender)?);
791        }
792        Element::ListItem { element } => {
793            context = context.with_container(element);
794            width = element.calculated_width;
795            height = element.calculated_height;
796            flex_element = Some(draw_elements(&element.elements, context, event_sender)?);
797        }
798    }
799
800    if let Some(flex_element) = &flex_element {
801        match direction {
802            LayoutDirection::Row => {
803                if let Some(width) = width {
804                    #[allow(clippy::cast_possible_truncation)]
805                    flex.fixed(flex_element, width.round() as i32);
806                    log::debug!("draw_element: setting fixed width={width}");
807                } else {
808                    log::debug!(
809                        "draw_element: not setting fixed width size width={width:?} height={height:?}"
810                    );
811                }
812            }
813            LayoutDirection::Column => {
814                if let Some(height) = height {
815                    #[allow(clippy::cast_possible_truncation)]
816                    flex.fixed(flex_element, height.round() as i32);
817                    log::debug!("draw_element: setting fixed height={height}");
818                } else {
819                    log::debug!(
820                        "draw_element: not setting fixed height size width={width:?} height={height:?}"
821                    );
822                }
823            }
824        }
825    }
826
827    Ok(flex_element
828        .map(|x| Box::new(x) as Box<dyn WidgetExt>)
829        .or(other_element))
830}