cog_task/action/core/
image.rs

1use crate::action::{Action, Props, StatefulAction, INFINITE, VISUAL};
2use crate::comm::QWriter;
3use crate::resource::{Color, IoManager, ResourceAddr, ResourceManager, ResourceValue};
4use crate::server::{AsyncSignal, Config, State, SyncSignal};
5use eframe::egui;
6use eframe::egui::{CentralPanel, Color32, CursorIcon, Frame, TextureId, Vec2};
7use eyre::{eyre, Result};
8use serde::{Deserialize, Serialize};
9use std::path::PathBuf;
10
11#[derive(Debug, Deserialize, Serialize)]
12#[serde(deny_unknown_fields)]
13pub struct Image {
14    src: PathBuf,
15    #[serde(default)]
16    width: Option<f32>,
17    #[serde(default)]
18    background: Color,
19}
20
21stateful!(Image {
22    handle: TextureId,
23    size: Vec2,
24    width: Option<f32>,
25    background: Color32,
26});
27
28impl Image {
29    #[inline(always)]
30    pub fn new(src: PathBuf, width: Option<f32>, background: Color) -> Self {
31        Self {
32            src,
33            width,
34            background,
35        }
36    }
37}
38
39impl Action for Image {
40    #[inline(always)]
41    fn resources(&self, _config: &Config) -> Vec<ResourceAddr> {
42        vec![ResourceAddr::Image(self.src.to_owned())]
43    }
44
45    fn stateful(
46        &self,
47        _io: &IoManager,
48        res: &ResourceManager,
49        _config: &Config,
50        _sync_writer: &QWriter<SyncSignal>,
51        _async_writer: &QWriter<AsyncSignal>,
52    ) -> Result<Box<dyn StatefulAction>> {
53        let src = ResourceAddr::Image(self.src.clone());
54        let (texture, size) = {
55            if let ResourceValue::Image(texture, size) = res.fetch(&src)? {
56                (texture, size)
57            } else {
58                return Err(eyre!("Resource value and address types don't match."));
59            }
60        };
61
62        Ok(Box::new(StatefulImage {
63            done: false,
64            handle: texture,
65            size,
66            width: self.width,
67            background: self.background.into(),
68        }))
69    }
70}
71
72impl StatefulAction for StatefulImage {
73    impl_stateful!();
74
75    #[inline]
76    fn props(&self) -> Props {
77        (INFINITE | VISUAL).into()
78    }
79
80    fn show(
81        &mut self,
82        ui: &mut egui::Ui,
83        _sync_writer: &mut QWriter<SyncSignal>,
84        _async_writer: &mut QWriter<AsyncSignal>,
85        _state: &State,
86    ) -> Result<()> {
87        ui.output().cursor_icon = CursorIcon::None;
88
89        CentralPanel::default()
90            .frame(Frame::default().fill(self.background))
91            .show_inside(ui, |ui| {
92                ui.centered_and_justified(|ui| {
93                    if let Some(width) = self.width {
94                        let scale = width / self.size.x;
95                        ui.image(self.handle, self.size * scale);
96                    } else {
97                        ui.image(self.handle, self.size);
98                    }
99                });
100            });
101
102        Ok(())
103    }
104
105    fn debug(&self) -> Vec<(&str, String)> {
106        <dyn StatefulAction>::debug(self)
107            .into_iter()
108            .chain([
109                ("texture_id", format!("{:?}", self.handle)),
110                ("size", format!("{:?}", self.size)),
111            ])
112            .collect()
113    }
114}