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, SceneBuilder, SizeConstraint, View, ViewContext,
  9};
 10use serde::Deserialize;
 11use std::{ops::Range, sync::Arc};
 12
 13enum ImageSource {
 14    Path(&'static str),
 15    Data(Arc<ImageData>),
 16}
 17
 18pub struct Image {
 19    source: ImageSource,
 20    style: ImageStyle,
 21}
 22
 23#[derive(Copy, Clone, Default, Deserialize)]
 24pub struct ImageStyle {
 25    #[serde(default)]
 26    pub border: Border,
 27    #[serde(default)]
 28    pub corner_radius: f32,
 29    #[serde(default)]
 30    pub height: Option<f32>,
 31    #[serde(default)]
 32    pub width: Option<f32>,
 33    #[serde(default)]
 34    pub grayscale: bool,
 35}
 36
 37impl Image {
 38    pub fn new(asset_path: &'static str) -> Self {
 39        Self {
 40            source: ImageSource::Path(asset_path),
 41            style: Default::default(),
 42        }
 43    }
 44
 45    pub fn from_data(data: Arc<ImageData>) -> Self {
 46        Self {
 47            source: ImageSource::Data(data),
 48            style: Default::default(),
 49        }
 50    }
 51
 52    pub fn with_style(mut self, style: ImageStyle) -> Self {
 53        self.style = style;
 54        self
 55    }
 56}
 57
 58impl<V: View> Element<V> for Image {
 59    type LayoutState = Option<Arc<ImageData>>;
 60    type PaintState = ();
 61
 62    fn layout(
 63        &mut self,
 64        constraint: SizeConstraint,
 65        _: &mut V,
 66        cx: &mut ViewContext<V>,
 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        scene: &mut SceneBuilder,
 94        bounds: RectF,
 95        _: RectF,
 96        layout: &mut Self::LayoutState,
 97        _: &mut V,
 98        _: &mut ViewContext<V>,
 99    ) -> Self::PaintState {
100        if let Some(data) = layout {
101            scene.push_image(scene::Image {
102                bounds,
103                border: self.style.border,
104                corner_radius: self.style.corner_radius,
105                grayscale: self.style.grayscale,
106                data: data.clone(),
107            });
108        }
109    }
110
111    fn rect_for_text_range(
112        &self,
113        _: Range<usize>,
114        _: RectF,
115        _: RectF,
116        _: &Self::LayoutState,
117        _: &Self::PaintState,
118        _: &V,
119        _: &ViewContext<V>,
120    ) -> Option<RectF> {
121        None
122    }
123
124    fn debug(
125        &self,
126        bounds: RectF,
127        _: &Self::LayoutState,
128        _: &Self::PaintState,
129        _: &V,
130        _: &ViewContext<V>,
131    ) -> serde_json::Value {
132        json!({
133            "type": "Image",
134            "bounds": bounds.to_json(),
135        })
136    }
137}