198 lines
5.2 KiB
Rust
198 lines
5.2 KiB
Rust
use iced::advanced::layout::{self, Layout};
|
||
use iced::advanced::widget::{self, Widget};
|
||
use iced::advanced::{Clipboard, renderer};
|
||
use iced::mouse;
|
||
use iced::{Element, Event, Length, Rectangle, Size};
|
||
|
||
// We need the image renderer trait
|
||
use iced::advanced::image as img;
|
||
|
||
// ---------- Widget struct ----------
|
||
|
||
/// A button that displays one image when idle and another when pressed.
|
||
pub struct ImageButton<Handle, Message> {
|
||
normal: Handle,
|
||
pressed: Handle,
|
||
width: Length,
|
||
height: Length,
|
||
on_press: Option<Message>,
|
||
is_pressed: bool,
|
||
}
|
||
|
||
pub fn image_button<Handle, Message>(
|
||
normal: impl Into<Handle>,
|
||
pressed: impl Into<Handle>,
|
||
is_pressed: bool,
|
||
) -> ImageButton<Handle, Message> {
|
||
ImageButton::new(normal, pressed, is_pressed)
|
||
}
|
||
|
||
impl<Handle, Message> ImageButton<Handle, Message> {
|
||
/// 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<Handle>, pressed: impl Into<Handle>, 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<Length>) -> Self {
|
||
self.width = width.into();
|
||
self
|
||
}
|
||
|
||
/// Override the widget height.
|
||
pub fn height(mut self, height: impl Into<Length>) -> Self {
|
||
self.height = height.into();
|
||
self
|
||
}
|
||
}
|
||
|
||
// ---------- Widget impl ----------
|
||
|
||
impl<Message, Theme, Renderer, Handle> Widget<Message, Theme, Renderer>
|
||
for ImageButton<Handle, Message>
|
||
where
|
||
Renderer: img::Renderer<Handle = Handle>,
|
||
Handle: Clone,
|
||
Message: Clone,
|
||
{
|
||
// --- Size ---
|
||
|
||
fn size(&self) -> Size<Length> {
|
||
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 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 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<Element> ----------
|
||
|
||
impl<'a, Message, Theme, Renderer, Handle> From<ImageButton<Handle, Message>>
|
||
for Element<'a, Message, Theme, Renderer>
|
||
where
|
||
Renderer: img::Renderer<Handle = Handle> + 'a,
|
||
Handle: Clone + 'a,
|
||
Message: Clone + 'a,
|
||
Theme: 'a,
|
||
{
|
||
fn from(widget: ImageButton<Handle, Message>) -> Self {
|
||
Self::new(widget)
|
||
}
|
||
}
|