use iced::{ advanced::{ image::{Data, Renderer as ImageRenderer}, layout::{Limits, Node}, overlay, renderer::{Quad, Style}, widget::Tree, Clipboard, Layout, Renderer as IRenderer, Shell, Widget, }, event::Status, font::{Stretch, Weight}, gradient::Linear, mouse, mouse::{Button, Cursor}, theme::Text, touch, widget::{image, image::FilterMethod, text}, Alignment, Background, Color, ContentFit, Degrees, Element, Event, Font, Gradient, Length, Point, Rectangle, Renderer, Size, Theme, Vector, }; pub fn image_card<'a, M: 'a>(handle: impl Into, caption: &str) -> ImageCard<'a, M> { let image_handle = handle.into(); ImageCard { image_handle: image_handle.clone(), text: text(caption) .size(14) .font(Font { weight: Weight::Bold, stretch: Stretch::Condensed, ..Font::with_name("Helvetica Neue") }) .style(Text::Color(Color::WHITE)) .into(), on_press: None, width: Length::FillPortion(1), height: Length::Fixed(128.0), } } pub struct ImageCard<'a, M> { image_handle: image::Handle, text: Element<'a, M, Renderer>, on_press: Option, width: Length, height: Length, } impl<'a, M> ImageCard<'a, M> { pub fn on_press(mut self, msg: M) -> Self { self.on_press = Some(msg); self } } impl<'a, M: Clone> Widget for ImageCard<'a, M> { fn size(&self) -> Size { Size::new(self.width, self.height) } fn layout(&self, _tree: &mut Tree, _renderer: &Renderer, limits: &Limits) -> Node { let limits = limits.width(self.width).height(self.height); let size = limits.resolve(self.width, self.height, Size::ZERO); Node::new(size) } fn draw( &self, _state: &Tree, renderer: &mut Renderer, _theme: &Theme, _style: &Style, layout: Layout<'_>, _cursor: Cursor, _viewport: &Rectangle, ) { let bounds = layout.bounds(); // The raw w/h of the underlying image, renderer.dimensions is _really_ // slow so enforce the use of preparsed images from `theme`. #[allow(clippy::cast_precision_loss)] let image_size = match self.image_handle.data() { Data::Rgba { width, height, .. } => Size::new(*width as f32, *height as f32), Data::Path(_) | Data::Bytes(_) => panic!("only parsed images are supported"), }; let adjusted_fit = ContentFit::Cover.fit(image_size, bounds.size()); renderer.with_layer(bounds, |renderer| { let offset = Vector::new( (bounds.width - adjusted_fit.width).min(0.0) / 1.5, (bounds.height - adjusted_fit.height).min(0.0) / 1.5, ); let drawing_bounds = Rectangle { width: adjusted_fit.width, height: adjusted_fit.height, ..bounds }; renderer.draw( self.image_handle.clone(), FilterMethod::Linear, drawing_bounds + offset, ); }); } fn children(&self) -> Vec { vec![Tree::new(&self.text)] } fn diff(&self, tree: &mut Tree) { tree.diff_children(&[&self.text]); } fn overlay<'b>( &'b mut self, state: &'b mut Tree, layout: Layout<'_>, _renderer: &Renderer, ) -> Option> { Some( overlay::Group::with_children(vec![ overlay::Element::new( layout.position(), Box::new(super::forced_rounded::Overlay { size: layout.bounds().size(), position: None, }), ), overlay::Element::new( layout.position(), Box::new(Overlay { text: &mut self.text, tree: &mut state.children[0], size: layout.bounds().size(), on_press: self.on_press.as_ref(), }), ), ]) .overlay(), ) } } struct Overlay<'a, 'b, M> { text: &'b mut Element<'a, M, Renderer>, tree: &'b mut Tree, size: Size, on_press: Option<&'b M>, } impl<'a, 'b, M: Clone> overlay::Overlay for Overlay<'a, 'b, M> { fn layout( &mut self, renderer: &Renderer, _bounds: Size, position: Point, _translation: Vector, ) -> Node { let limits = Limits::new(Size::ZERO, self.size).shrink([0, 10]); let child = self .text .as_widget() .layout(self.tree, renderer, &limits) .align(Alignment::Center, Alignment::End, limits.max()); Node::with_children(self.size, vec![child]).move_to(position) } fn draw( &self, renderer: &mut Renderer, theme: &Theme, style: &Style, layout: Layout<'_>, cursor: Cursor, ) { renderer.fill_quad( Quad { bounds: Rectangle { height: self.size.height + 12., width: self.size.width + 12., x: layout.bounds().x - 6., y: layout.bounds().y - 6., }, border_radius: [20., 20., 20., 20.].into(), border_width: 10., border_color: Color::WHITE, }, Background::Gradient(Gradient::Linear( Linear::new(Degrees(270.)) .add_stop(0.0, Color::from_rgba8(0, 0, 0, 0.0)) .add_stop(0.4, Color::from_rgba8(0, 0, 0, 0.0)) .add_stop(1.0, Color::from_rgba8(0, 0, 0, 0.8)), )), ); self.text.as_widget().draw( self.tree, renderer, theme, style, layout.children().next().unwrap(), cursor, &layout.bounds(), ); } fn on_event( &mut self, event: Event, layout: Layout<'_>, cursor: Cursor, _renderer: &Renderer, _clipboard: &mut dyn Clipboard, shell: &mut Shell<'_, M>, ) -> Status { if !cursor.is_over(Rectangle { height: self.size.height, width: self.size.width, ..layout.bounds() }) { return Status::Ignored; } if let Some(on_press) = self.on_press { if let Event::Mouse(mouse::Event::ButtonPressed(Button::Left)) | Event::Touch(touch::Event::FingerPressed { .. }) = &event { shell.publish(on_press.clone()); return Status::Captured; } } Status::Ignored } } impl<'a, M> From> for Element<'a, M> where M: 'a + Clone, { fn from(modal: ImageCard<'a, M>) -> Self { Element::new(modal) } }