use iced::widget::Image;
use iced::{Element, Radians, Rotation, Theme};
use serde_json::Value;
use crate::PlushieRenderer;
use crate::a11y::A11yOverrides;
use crate::iced_convert;
use crate::message::Message;
use crate::protocol::TreeNode;
use crate::registry::PlushieWidget;
use crate::render_ctx::RenderCtx;
use crate::widget::helpers::*;
use plushie_core::types::{A11y, ContentFit, FilterMethod, Length, PlushieType};
struct ImageProps {
width: Option<Length>,
height: Option<Length>,
content_fit: Option<ContentFit>,
filter_method: Option<FilterMethod>,
alt: Option<String>,
description: Option<String>,
}
impl ImageProps {
fn from_node(node: &TreeNode) -> Self {
let p = &node.props;
Self {
width: Length::extract(p, "width"),
height: Length::extract(p, "height"),
content_fit: ContentFit::extract(p, "content_fit"),
filter_method: FilterMethod::extract(p, "filter_method"),
alt: String::extract(p, "alt"),
description: String::extract(p, "description"),
}
}
}
pub(crate) struct ImageWidget;
impl<R: PlushieRenderer> PlushieWidget<R> for ImageWidget {
fn type_names(&self) -> &[&str] {
&["image"]
}
fn render<'a>(
&'a self,
node: &'a TreeNode,
ctx: &RenderCtx<'a, R>,
) -> Element<'a, Message, Theme, R> {
let ip = ImageProps::from_node(node);
let props = &node.props;
let width = ip
.width
.as_ref()
.map(iced_convert::length)
.unwrap_or(iced::Length::Shrink);
let height = ip
.height
.as_ref()
.map(iced_convert::length)
.unwrap_or(iced::Length::Shrink);
let source_val = props.get_value("source");
if source_val.is_none() {
log::warn!("[id={}] image: no 'source' prop specified", node.id);
}
let handle: iced::widget::image::Handle = match source_val {
Some(Value::Object(obj)) => {
if let Some(name) = obj.get("handle").and_then(|v| v.as_str()) {
match ctx.images.get(name) {
Some(h) => h,
None => {
log::warn!("[id={}] image: unknown registry handle: {name}", node.id);
iced::widget::image::Handle::from_bytes(vec![])
}
}
} else {
iced::widget::image::Handle::from_bytes(vec![])
}
}
_ => {
let path = prop_str(props, "source").unwrap_or_default();
iced::widget::image::Handle::from_path(path)
}
};
let mut img = Image::new(handle).width(width).height(height);
if let Some(cf) = ip.content_fit {
img = img.content_fit(iced_convert::content_fit(cf));
}
if let Some(r) =
prop_animated_f32(&ctx.caches.interpolated_props, &node.id, props, "rotation")
{
img = img.rotation(Rotation::from(Radians(r.to_radians())));
}
if let Some(o) =
prop_animated_f32(&ctx.caches.interpolated_props, &node.id, props, "opacity")
{
img = img.opacity(o);
}
if let Some(br) = prop_animated_f32(
&ctx.caches.interpolated_props,
&node.id,
props,
"border_radius",
) {
img = img.border_radius(br);
}
if let Some(fm) = ip.filter_method {
img = img.filter_method(iced_convert::filter_method(fm));
}
if let Some(expand) = prop_bool(props, "expand") {
img = img.expand(expand);
}
if let Some(scale) =
prop_animated_f32(&ctx.caches.interpolated_props, &node.id, props, "scale")
{
img = img.scale(scale);
}
if let Some(alt) = ip.alt {
img = img.alt(alt);
}
if let Some(desc) = ip.description {
img = img.description(desc);
}
if prop_bool_default(props, "decorative", false) {
img = img.decorative();
}
let crop_val = props.get_value("crop");
if let Some(crop_obj) = crop_val.as_ref().and_then(|v| v.as_object()) {
let cx = crop_obj.get("x").and_then(|v| v.as_u64()).unwrap_or(0) as u32;
let cy = crop_obj.get("y").and_then(|v| v.as_u64()).unwrap_or(0) as u32;
let cw = crop_obj.get("width").and_then(|v| v.as_u64()).unwrap_or(0) as u32;
let ch = crop_obj.get("height").and_then(|v| v.as_u64()).unwrap_or(0) as u32;
img = img.crop(iced::Rectangle {
x: cx,
y: cy,
width: cw,
height: ch,
});
}
img.into()
}
fn infer_a11y(&self, node: &TreeNode) -> Option<A11yOverrides> {
if prop_bool_default(&node.props, "decorative", false) {
return Some(A11yOverrides::from_core(&A11y::new().hidden(true)));
}
None
}
fn fresh_for_session(&self) -> Box<dyn PlushieWidget<R>> {
Box::new(ImageWidget)
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
fn infer(props: serde_json::Value) -> Option<A11yOverrides> {
let node = crate::testing::node_with_props("img", "image", props);
<ImageWidget as PlushieWidget<iced::Renderer>>::infer_a11y(&ImageWidget, &node)
}
#[test]
fn decorative_infers_hidden() {
let overrides = infer(json!({"decorative": true})).expect("decorative should infer");
assert_eq!(overrides.core().hidden, Some(true));
}
#[test]
fn no_decorative_returns_none() {
assert!(infer(json!({"alt": "logo"})).is_none());
}
#[test]
fn decorative_false_returns_none() {
assert!(infer(json!({"decorative": false})).is_none());
}
}