1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
use crate::action::{Action, Props, StatefulAction, INFINITE, VISUAL};
use crate::comm::QWriter;
use crate::resource::{Color, IoManager, ResourceAddr, ResourceManager, ResourceValue};
use crate::server::{AsyncSignal, Config, State, SyncSignal};
use eframe::egui;
use eframe::egui::{CentralPanel, Color32, CursorIcon, Frame, TextureId, Vec2};
use eyre::{eyre, Result};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;

#[derive(Debug, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct Image {
    src: PathBuf,
    #[serde(default)]
    width: Option<f32>,
    #[serde(default)]
    background: Color,
}

stateful!(Image {
    handle: TextureId,
    size: Vec2,
    width: Option<f32>,
    background: Color32,
});

impl Image {
    #[inline(always)]
    pub fn new(src: PathBuf, width: Option<f32>, background: Color) -> Self {
        Self {
            src,
            width,
            background,
        }
    }
}

impl Action for Image {
    #[inline(always)]
    fn resources(&self, _config: &Config) -> Vec<ResourceAddr> {
        vec![ResourceAddr::Image(self.src.to_owned())]
    }

    fn stateful(
        &self,
        _io: &IoManager,
        res: &ResourceManager,
        _config: &Config,
        _sync_writer: &QWriter<SyncSignal>,
        _async_writer: &QWriter<AsyncSignal>,
    ) -> Result<Box<dyn StatefulAction>> {
        let src = ResourceAddr::Image(self.src.clone());
        let (texture, size) = {
            if let ResourceValue::Image(texture, size) = res.fetch(&src)? {
                (texture, size)
            } else {
                return Err(eyre!("Resource value and address types don't match."));
            }
        };

        Ok(Box::new(StatefulImage {
            done: false,
            handle: texture,
            size,
            width: self.width,
            background: self.background.into(),
        }))
    }
}

impl StatefulAction for StatefulImage {
    impl_stateful!();

    #[inline]
    fn props(&self) -> Props {
        (INFINITE | VISUAL).into()
    }

    fn show(
        &mut self,
        ui: &mut egui::Ui,
        _sync_writer: &mut QWriter<SyncSignal>,
        _async_writer: &mut QWriter<AsyncSignal>,
        _state: &State,
    ) -> Result<()> {
        ui.output().cursor_icon = CursorIcon::None;

        CentralPanel::default()
            .frame(Frame::default().fill(self.background))
            .show_inside(ui, |ui| {
                ui.centered_and_justified(|ui| {
                    if let Some(width) = self.width {
                        let scale = width / self.size.x;
                        ui.image(self.handle, self.size * scale);
                    } else {
                        ui.image(self.handle, self.size);
                    }
                });
            });

        Ok(())
    }

    fn debug(&self) -> Vec<(&str, String)> {
        <dyn StatefulAction>::debug(self)
            .into_iter()
            .chain([
                ("texture_id", format!("{:?}", self.handle)),
                ("size", format!("{:?}", self.size)),
            ])
            .collect()
    }
}