image.rs

  1use super::constrain_size_preserving_aspect_ratio;
  2use crate::{
  3    geometry::{
  4        rect::RectF,
  5        vector::{vec2f, Vector2F},
  6    },
  7    json::{json, ToJson},
  8    scene, Border, Element, ImageData, LayoutContext, PaintContext, SceneBuilder, SizeConstraint,
  9    ViewContext,
 10};
 11use schemars::JsonSchema;
 12use serde::Deserialize;
 13use std::{ops::Range, sync::Arc};
 14
 15enum ImageSource {
 16    Path(&'static str),
 17    Data(Arc<ImageData>),
 18}
 19
 20pub struct Image {
 21    source: ImageSource,
 22    style: ImageStyle,
 23}
 24
 25#[derive(Copy, Clone, Default, Deserialize, JsonSchema)]
 26pub struct ImageStyle {
 27    #[serde(default)]
 28    pub border: Border,
 29    #[serde(default)]
 30    pub corner_radius: f32,
 31    #[serde(default)]
 32    pub height: Option<f32>,
 33    #[serde(default)]
 34    pub width: Option<f32>,
 35    #[serde(default)]
 36    pub grayscale: bool,
 37}
 38
 39impl Image {
 40    pub fn new(asset_path: &'static str) -> Self {
 41        Self {
 42            source: ImageSource::Path(asset_path),
 43            style: Default::default(),
 44        }
 45    }
 46
 47    pub fn from_data(data: Arc<ImageData>) -> Self {
 48        Self {
 49            source: ImageSource::Data(data),
 50            style: Default::default(),
 51        }
 52    }
 53
 54    pub fn with_style(mut self, style: ImageStyle) -> Self {
 55        self.style = style;
 56        self
 57    }
 58}
 59
 60impl<V: 'static> Element<V> for Image {
 61    type LayoutState = Option<Arc<ImageData>>;
 62    type PaintState = ();
 63
 64    fn layout(
 65        &mut self,
 66        constraint: SizeConstraint,
 67        _: &mut V,
 68        cx: &mut LayoutContext<V>,
 69    ) -> (Vector2F, Self::LayoutState) {
 70        let data = match &self.source {
 71            ImageSource::Path(path) => match cx.asset_cache.png(path) {
 72                Ok(data) => data,
 73                Err(error) => {
 74                    log::error!("could not load image: {}", error);
 75                    return (Vector2F::zero(), None);
 76                }
 77            },
 78            ImageSource::Data(data) => data.clone(),
 79        };
 80
 81        let desired_size = vec2f(
 82            self.style.width.unwrap_or_else(|| constraint.max.x()),
 83            self.style.height.unwrap_or_else(|| constraint.max.y()),
 84        );
 85        let size = constrain_size_preserving_aspect_ratio(
 86            constraint.constrain(desired_size),
 87            data.size().to_f32(),
 88        );
 89
 90        (size, Some(data))
 91    }
 92
 93    fn paint(
 94        &mut self,
 95        scene: &mut SceneBuilder,
 96        bounds: RectF,
 97        _: RectF,
 98        layout: &mut Self::LayoutState,
 99        _: &mut V,
100        _: &mut PaintContext<V>,
101    ) -> Self::PaintState {
102        if let Some(data) = layout {
103            scene.push_image(scene::Image {
104                bounds,
105                border: self.style.border,
106                corner_radii: self.style.corner_radius.into(),
107                grayscale: self.style.grayscale,
108                data: data.clone(),
109            });
110        }
111    }
112
113    fn rect_for_text_range(
114        &self,
115        _: Range<usize>,
116        _: RectF,
117        _: RectF,
118        _: &Self::LayoutState,
119        _: &Self::PaintState,
120        _: &V,
121        _: &ViewContext<V>,
122    ) -> Option<RectF> {
123        None
124    }
125
126    fn debug(
127        &self,
128        bounds: RectF,
129        _: &Self::LayoutState,
130        _: &Self::PaintState,
131        _: &V,
132        _: &ViewContext<V>,
133    ) -> serde_json::Value {
134        json!({
135            "type": "Image",
136            "bounds": bounds.to_json(),
137        })
138    }
139}