use iced::advanced::layout::{self, Layout}; use iced::advanced::{renderer, Clipboard}; use iced::advanced::widget::{self, Widget}; use iced::mouse; use iced::{Element, Event, Length, Rectangle, Size}; // We need the image renderer trait use iced::advanced::image as img; // ---------- State ---------- /// Internal state tracking whether the button is currently pressed. #[derive(Default)] pub struct State { is_pressed: bool, } // ---------- Widget struct ---------- /// A button that displays one image when idle and another when pressed. pub struct ImageButton { normal: Handle, pressed: Handle, width: Length, height: Length, on_press: Option, is_pressed: bool, } pub fn image_button(normal: impl Into, pressed: impl Into, is_pressed: bool) -> ImageButton { ImageButton::new(normal, pressed, is_pressed) } impl ImageButton { /// Create a new [`ImageButton`]. /// /// * `normal` – image shown in the default / hover state /// * `pressed` – image shown while the left mouse button is held pub fn new(normal: impl Into, pressed: impl Into, is_pressed: bool) -> Self { Self { normal: normal.into(), pressed: pressed.into(), width: Length::Shrink, height: Length::Shrink, on_press: None, is_pressed, } } /// The message to emit when the button is clicked (press + release). pub fn on_press(mut self, message: Message) -> Self { self.on_press = Some(message); self } /// Override the widget width. pub fn width(mut self, width: impl Into) -> Self { self.width = width.into(); self } /// Override the widget height. pub fn height(mut self, height: impl Into) -> Self { self.height = height.into(); self } } // ---------- Widget impl ---------- impl Widget for ImageButton where Renderer: img::Renderer, Handle: Clone, Message: Clone, { // --- Tree (internal state) --- fn tag(&self) -> widget::tree::Tag { widget::tree::Tag::of::() } fn state(&self) -> widget::tree::State { widget::tree::State::new(State::default()) } // --- Size --- fn size(&self) -> Size { Size { width: self.width, height: self.height, } } // --- Layout --- fn layout( &mut self, _tree: &mut widget::Tree, renderer: &Renderer, limits: &layout::Limits, ) -> layout::Node { // Use the natural dimensions of the normal image if the renderer // can measure it; otherwise fall back to the limits maximum. let size = renderer .measure_image(&self.normal) .map(|s| Size::new(s.width as f32, s.height as f32)) .unwrap_or_else(|| limits.max()); let size = limits.resolve(self.width, self.height, size); layout::Node::new(size) } // --- Events --- fn update( &mut self, tree: &mut widget::Tree, event: &Event, layout: Layout<'_>, cursor: mouse::Cursor, _renderer: &Renderer, _clipboard: &mut dyn Clipboard, shell: &mut iced::advanced::Shell<'_, Message>, _viewport: &Rectangle, ) { let state = tree.state.downcast_mut::(); let bounds = layout.bounds(); match event { Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => { if cursor.is_over(bounds) { shell.capture_event(); shell.request_redraw(); if let Some(on_press) = self.on_press.clone() { shell.publish(on_press); } } } _ => {} } } // --- Cursor --- fn mouse_interaction( &self, _tree: &widget::Tree, layout: Layout<'_>, cursor: mouse::Cursor, _viewport: &Rectangle, _renderer: &Renderer, ) -> mouse::Interaction { if cursor.is_over(layout.bounds()) && self.on_press.is_some() { mouse::Interaction::Pointer } else { mouse::Interaction::None } } // --- Draw --- fn draw( &self, tree: &widget::Tree, renderer: &mut Renderer, _theme: &Theme, _style: &renderer::Style, layout: Layout<'_>, _cursor: mouse::Cursor, _viewport: &Rectangle, ) { let state = tree.state.downcast_ref::(); let bounds = layout.bounds(); // Pick the correct image handle. let handle = if self.is_pressed { &self.pressed } else { &self.normal }; renderer.draw_image( img::Image { handle: handle.clone(), border_radius: 0.0.into(), filter_method: img::FilterMethod::Linear, rotation: iced::Radians(0.0), opacity: 1.0, snap: false, }, bounds, // drawing bounds bounds, // clip bounds ); } } // ---------- Into ---------- impl<'a, Message, Theme, Renderer, Handle> From> for Element<'a, Message, Theme, Renderer> where Renderer: img::Renderer + 'a, Handle: Clone + 'a, Message: Clone + 'a, Theme: 'a, { fn from(widget: ImageButton) -> Self { Self::new(widget) } }