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