use day_core::*;
use day_spec::kinds;
use crate::Decorated;
use day_spec::props::*;
pub struct Image {
source: crate::Reactive<day_spec::ImageSource>,
content_mode: ContentMode,
aspect_ratio: Option<f64>,
decorative: bool,
template: bool,
tint: Option<day_spec::Color>,
}
pub fn image<M>(source: impl IntoImageSource<M>) -> Image {
Image {
source: source.into_image_source(),
content_mode: ContentMode::default(),
aspect_ratio: None,
decorative: false,
template: false,
tint: None,
}
}
pub trait IntoImageSource<M> {
fn into_image_source(self) -> crate::Reactive<day_spec::ImageSource>;
}
pub struct ImageValueMark;
pub struct ImageSignalMark;
pub struct ImageFnMark;
macro_rules! image_source_from_value {
($($t:ty),* $(,)?) => {
$(impl IntoImageSource<ImageValueMark> for $t {
fn into_image_source(self) -> crate::Reactive<day_spec::ImageSource> {
crate::Reactive::Const(self.into())
}
})*
};
}
image_source_from_value!(
day_spec::ImageSource,
day_spec::ImageName,
day_spec::VectorName,
day_spec::BitmapId,
String,
Vec<u8>,
std::sync::Arc<Vec<u8>>,
);
impl IntoImageSource<ImageValueMark> for &day_core::Bitmap {
fn into_image_source(self) -> crate::Reactive<day_spec::ImageSource> {
let keep = self.clone();
crate::Reactive::Dyn(std::rc::Rc::new(move || {
day_spec::ImageSource::Decoded(keep.id())
}))
}
}
impl IntoImageSource<ImageSignalMark> for day_reactive::Signal<day_spec::ImageSource> {
fn into_image_source(self) -> crate::Reactive<day_spec::ImageSource> {
crate::Reactive::Dyn(std::rc::Rc::new(move || self.get()))
}
}
impl<F: Fn() -> day_spec::ImageSource + 'static> IntoImageSource<ImageFnMark> for F {
fn into_image_source(self) -> crate::Reactive<day_spec::ImageSource> {
crate::Reactive::Dyn(std::rc::Rc::new(self))
}
}
#[day_macros::builder(ImageBuilder)]
impl Image {
pub fn content_mode(mut self, m: ContentMode) -> Self {
self.content_mode = m;
self
}
pub fn fit(self) -> Self {
self.content_mode(ContentMode::Fit)
}
pub fn fill(self) -> Self {
self.content_mode(ContentMode::Fill)
}
pub fn stretch(self) -> Self {
self.content_mode(ContentMode::Stretch)
}
pub fn aspect_ratio(mut self, ratio: f64) -> Self {
if ratio > 0.0 {
self.aspect_ratio = Some(ratio);
}
self
}
pub fn decorative(mut self) -> Self {
self.decorative = true;
self
}
pub fn template(mut self) -> Self {
self.template = true;
self
}
pub(crate) fn initial_tint(mut self, tint: Option<day_spec::Color>) -> Self {
self.tint = tint;
self
}
}
impl Piece for Image {
fn build(self, cx: &mut BuildCx) -> day_core::RNode {
let source = self.source;
let seed = source.get_untracked();
let props = ImageProps {
source: seed.clone(),
decorative: self.decorative,
content_mode: self.content_mode,
aspect_ratio: self.aspect_ratio,
tint: self.tint,
template: self.template,
};
let node = match self.aspect_ratio {
Some(ratio) => cx.native(
kinds::IMAGE,
&props,
std::rc::Rc::new(AspectRatioLayout { ratio }),
Flex::default(),
day_core::Boundary::No,
),
None => cx.leaf(kinds::IMAGE, &props, Flex::default()),
};
day_reactive::bind_seeded(
seed,
move || source.get(),
move |s: &day_spec::ImageSource| {
day_core::with_tree(|t| {
t.patch(node, Box::new(ImagePatch::Source(s.clone())), true)
});
},
);
node
}
}
pub struct Vector {
source: crate::Reactive<day_spec::VectorName>,
tint: Option<crate::Reactive<day_spec::Color>>,
weight: VectorWeight,
decorative: bool,
template: bool,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum VectorWeight {
Light,
#[default]
Regular,
Bold,
}
pub fn vector<M>(name: impl crate::IntoReactive<day_spec::VectorName, M>) -> Vector {
Vector {
source: name.into_reactive(),
tint: None,
weight: VectorWeight::Regular,
decorative: false,
template: false,
}
}
#[day_macros::builder(VectorBuilder)]
impl Vector {
pub fn tint<M>(mut self, color: impl crate::IntoReactive<day_spec::Color, M>) -> Self {
self.tint = Some(color.into_reactive());
self
}
pub fn weight(mut self, w: VectorWeight) -> Self {
self.weight = w;
self
}
pub fn decorative(mut self) -> Self {
self.decorative = true;
self
}
pub fn template(mut self) -> Self {
self.template = true;
self
}
}
impl Piece for Vector {
fn build(self, cx: &mut BuildCx) -> day_core::RNode {
let weight = self.weight;
let resolved = move |name: day_spec::VectorName| {
let name = name.as_str();
day_spec::ImageSource::Named(match weight {
VectorWeight::Regular => name.to_owned(),
VectorWeight::Light => format!("{name}__light"),
VectorWeight::Bold => format!("{name}__bold"),
})
};
let source = self.source;
let source_seed = resolved(source.get_untracked());
let tint = self.tint;
let seed = tint.as_ref().map(|t| t.get_untracked());
let props = ImageProps {
source: source_seed.clone(),
decorative: self.decorative,
content_mode: ContentMode::Fit,
aspect_ratio: None,
tint: seed,
template: self.template,
};
let node = cx.leaf(kinds::IMAGE, &props, Flex::default());
if let crate::Reactive::Dyn(_) = source {
day_reactive::bind_seeded(
source_seed,
move || resolved(source.get()),
move |s: &day_spec::ImageSource| {
day_core::with_tree(|t| {
t.patch(node, Box::new(ImagePatch::Source(s.clone())), true)
});
},
);
}
if let Some(tint) = tint
&& let Some(seed) = seed
{
day_reactive::bind_seeded(
seed,
move || tint.get(),
move |c: &day_spec::Color| {
day_core::with_tree(|t| {
t.patch(
node,
Box::new(day_spec::props::ImagePatch::Tint(Some(*c))),
false,
)
});
},
);
}
node
}
}
#[cfg(feature = "conformance")]
pub(crate) mod conformance {
use day_core::conformance::{Case, Drive};
use day_spec::{Cap, kinds};
use crate::*;
pub(crate) const RED_BLUE: [u8; 76] = [
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44,
0x52, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 0x08, 0x02, 0x00, 0x00, 0x00, 0xf0,
0xca, 0xea, 0x34, 0x00, 0x00, 0x00, 0x13, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0xf8,
0xcf, 0xc0, 0x00, 0x44, 0x60, 0xe2, 0x3f, 0x03, 0x32, 0x07, 0x00, 0x67, 0xb2, 0x07, 0xf9,
0xf7, 0x92, 0x56, 0x82, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60,
0x82,
];
#[day_macros::test(day_core)]
fn image_bytes() -> Case {
Case::new()
.proves(kinds::IMAGE)
.proves_cap(Cap::ImageDecode)
.proves_modifier("frame")
.page(|| {
image(RED_BLUE.to_vec())
.stretch()
.id(day_core::id!(image))
.frame(120.0, 60.0)
})
.shot("default")
.drive(|d: Drive| async move {
d.assert_size("image", 120.0, 60.0).await?;
d.sample_pixel("image", 0.2, 0.5, "#ff0000").await?;
d.sample_pixel("image", 0.8, 0.5, "#0000ff").await
})
}
#[day_macros::test(day_core)]
fn image_fit() -> Case {
Case::new()
.proves(kinds::IMAGE)
.page(|| {
image(RED_BLUE.to_vec())
.fit()
.id(day_core::id!(image))
.frame(100.0, 100.0)
})
.drive(|d: Drive| async move {
d.assert_size("image", 100.0, 100.0).await?;
d.sample_pixel("image", 0.25, 0.5, "#ff0000").await?;
d.sample_pixel("image", 0.75, 0.5, "#0000ff").await
})
}
#[day_macros::test(day_core)]
fn image_a11y() -> Case {
Case::new()
.proves(kinds::IMAGE)
.page(|| {
image(RED_BLUE.to_vec())
.a11y(|a| a.label("Red and blue"))
.id(day_core::id!(image))
.frame(40.0, 20.0)
})
.drive(|d: Drive| async move { d.a11y_audit(Some("image")).await })
}
}