use crate::{
Color, ContentMode, CornerRounding, DrawWithTooltips, EdgeInsets, EventCtx, GeomBatch,
JustDraw, RewriteColor, ScreenDims, ScreenPt, Text, Widget,
};
use geom::{Bounds, Polygon, Pt2D};
use std::borrow::Cow;
#[derive(Clone, Debug, Default)]
pub struct Image<'a, 'c> {
source: Option<Cow<'c, ImageSource<'a>>>,
tooltip: Option<Text>,
color: Option<RewriteColor>,
content_mode: Option<ContentMode>,
corner_rounding: Option<CornerRounding>,
padding: Option<EdgeInsets>,
bg_color: Option<Color>,
dims: Option<ScreenDims>,
}
#[derive(Clone, Debug)]
pub enum ImageSource<'a> {
Path(&'a str),
Bytes { bytes: &'a [u8], cache_key: &'a str },
GeomBatch(GeomBatch, geom::Bounds),
}
impl ImageSource<'_> {
pub fn load(&self, prerender: &crate::Prerender) -> (GeomBatch, geom::Bounds) {
use crate::svg;
match self {
ImageSource::Path(image_path) => svg::load_svg(prerender, image_path),
ImageSource::Bytes { bytes, cache_key } => {
svg::load_svg_bytes(prerender, cache_key, bytes).expect(&format!(
"Failed to load svg from bytes. cache_key: {}",
cache_key
))
}
ImageSource::GeomBatch(geom_batch, bounds) => (geom_batch.clone(), *bounds),
}
}
}
impl<'a, 'c> Image<'a, 'c> {
pub fn empty() -> Self {
Self {
..Default::default()
}
}
pub fn from_path(filename: &'a str) -> Self {
Self {
source: Some(Cow::Owned(ImageSource::Path(filename))),
..Default::default()
}
}
pub fn untinted(filename: &'a str) -> Self {
Self::from_path(filename).color(RewriteColor::NoOp)
}
pub fn from_bytes(labeled_bytes: (&'a str, &'a [u8])) -> Self {
Self {
source: Some(Cow::Owned(ImageSource::Bytes {
cache_key: labeled_bytes.0,
bytes: labeled_bytes.1,
})),
..Default::default()
}
}
pub fn from_batch(batch: GeomBatch, bounds: Bounds) -> Self {
Self {
source: Some(Cow::Owned(ImageSource::GeomBatch(batch, bounds))),
..Default::default()
}
}
pub fn source(mut self, source: ImageSource<'a>) -> Self {
self.source = Some(Cow::Owned(source));
self
}
pub fn source_path(self, path: &'a str) -> Self {
self.source(ImageSource::Path(path))
}
pub fn source_bytes(self, labeled_bytes: (&'a str, &'a [u8])) -> Self {
let (label, bytes) = labeled_bytes;
self.source(ImageSource::Bytes {
bytes,
cache_key: label,
})
}
pub fn source_batch(self, batch: GeomBatch, bounds: geom::Bounds) -> Self {
self.source(ImageSource::GeomBatch(batch, bounds))
}
pub fn tooltip(mut self, tooltip: impl Into<Text>) -> Self {
self.tooltip = Some(tooltip.into());
self
}
pub fn merged_image_style(&'c self, other: &'c Self) -> Self {
let source_cow: Option<&Cow<'c, ImageSource>> =
other.source.as_ref().or(self.source.as_ref());
let source: Option<Cow<'c, ImageSource>> = source_cow.map(|source: &Cow<ImageSource>| {
let source: &ImageSource = *&source;
Cow::Borrowed(source)
});
Self {
source,
tooltip: other.tooltip.clone().or(self.tooltip.clone()),
color: other.color.or(self.color),
content_mode: other.content_mode.or(self.content_mode),
corner_rounding: other.corner_rounding.or(self.corner_rounding),
padding: other.padding.or(self.padding),
bg_color: other.bg_color.or(self.bg_color),
dims: other.dims.or(self.dims),
}
}
pub fn color<RWC: Into<RewriteColor>>(mut self, value: RWC) -> Self {
self.color = Some(value.into());
self
}
pub fn bg_color(mut self, value: Color) -> Self {
self.bg_color = Some(value);
self
}
pub fn dims<D: Into<ScreenDims>>(mut self, dims: D) -> Self {
self.dims = Some(dims.into());
self
}
pub fn content_mode(mut self, value: ContentMode) -> Self {
self.content_mode = Some(value);
self
}
pub fn corner_rounding<R: Into<CornerRounding>>(mut self, value: R) -> Self {
self.corner_rounding = Some(value.into());
self
}
pub fn padding<EI: Into<EdgeInsets>>(mut self, value: EI) -> Self {
self.padding = Some(value.into());
self
}
pub fn build_batch(&self, ctx: &EventCtx) -> Option<(GeomBatch, Bounds)> {
self.source.as_ref().map(|source| {
let (mut image_batch, image_bounds) = source.load(ctx.prerender);
image_batch = image_batch.color(
self.color
.unwrap_or(RewriteColor::ChangeAll(ctx.style().icon_fg)),
);
match self.dims {
None => {
image_batch.push(Color::CLEAR, image_bounds.get_rectangle());
(image_batch, image_bounds)
}
Some(image_dims) => {
if image_bounds.width() != 0.0 && image_bounds.height() != 0.0 {
let (x_factor, y_factor) = (
image_dims.width / image_bounds.width(),
image_dims.height / image_bounds.height(),
);
image_batch = match self.content_mode.unwrap_or_default() {
ContentMode::ScaleToFill => image_batch.scale_xy(x_factor, y_factor),
ContentMode::ScaleAspectFit => {
image_batch.scale(x_factor.min(y_factor))
}
ContentMode::ScaleAspectFill => {
image_batch.scale(x_factor.max(y_factor))
}
};
}
let image_corners = self.corner_rounding.unwrap_or_default();
let padding = self.padding.unwrap_or_default();
let mut container_batch = GeomBatch::new();
let container_bounds = Bounds {
min_x: 0.0,
min_y: 0.0,
max_x: image_dims.width + padding.left + padding.right,
max_y: image_dims.height + padding.top + padding.bottom,
};
let container = match image_corners {
CornerRounding::FullyRounded => {
Polygon::pill(container_bounds.width(), container_bounds.height())
}
CornerRounding::CornerRadii(image_corners) => Polygon::rounded_rectangle(
container_bounds.width(),
container_bounds.height(),
image_corners,
),
};
let image_bg = self.bg_color.unwrap_or(Color::CLEAR);
container_batch.push(image_bg, container);
let center = Pt2D::new(
image_dims.width / 2.0 + padding.left,
image_dims.height / 2.0 + padding.top,
);
image_batch = image_batch.autocrop().centered_on(center);
container_batch.append(image_batch);
(container_batch, container_bounds)
}
}
})
}
pub fn into_widget(self, ctx: &EventCtx) -> Widget {
match self.build_batch(ctx) {
None => Widget::nothing(),
Some((batch, bounds)) => {
if let Some(tooltip) = self.tooltip {
DrawWithTooltips::new(
ctx,
batch,
vec![(bounds.get_rectangle(), tooltip)],
Box::new(|_| GeomBatch::new()),
)
} else {
Widget::new(Box::new(JustDraw {
dims: ScreenDims::new(bounds.width(), bounds.height()),
draw: ctx.upload(batch),
top_left: ScreenPt::new(0.0, 0.0),
}))
}
}
}
}
}