use iced::advanced::layout::{self, Layout};
use iced::advanced::widget::{self, Widget};
use iced::advanced::{renderer, Clipboard, Shell};
use iced::{advanced::mouse, border::Border, Shadow};
use iced::{Color, Element, Event, Length, Padding, Point, Rectangle, Size, Vector};
const GRAB_TOLERANCE: f32 = 3.0;
#[derive(Debug, Clone, Copy, Default)]
struct State {
drag_origin: Option<Point>,
hovered: bool,
last_click: Option<mouse::Click>,
}
#[allow(missing_debug_implementations)]
pub struct ResizeHandle<'a, Message, Theme, Renderer> {
content: Element<'a, Message, Theme, Renderer>,
handle_width: f32,
on_drag: Box<dyn Fn(f32) -> Message + 'a>,
on_release: Message,
on_auto_size: Option<Message>,
}
impl<'a, Message, Theme, Renderer> ResizeHandle<'a, Message, Theme, Renderer>
where
Renderer: renderer::Renderer,
{
pub fn new(
content: impl Into<Element<'a, Message, Theme, Renderer>>,
handle_width: f32,
on_drag: impl Fn(f32) -> Message + 'a,
on_release: Message,
) -> Self {
Self {
content: content.into(),
handle_width,
on_drag: Box::new(on_drag),
on_release,
on_auto_size: None,
}
}
#[must_use]
pub fn on_auto_size(mut self, message: Message) -> Self {
self.on_auto_size = Some(message);
self
}
fn handle_bounds(&self, bounds: Rectangle) -> Rectangle {
Rectangle {
x: bounds.x + bounds.width - self.handle_width,
width: self.handle_width,
..bounds
}
}
fn grab_bounds(&self, bounds: Rectangle) -> Rectangle {
let mut bounds = self.handle_bounds(bounds);
bounds.x -= GRAB_TOLERANCE;
bounds.width += GRAB_TOLERANCE * 2.0;
bounds
}
}
impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer>
for ResizeHandle<'_, Message, Theme, Renderer>
where
Message: Clone,
Renderer: renderer::Renderer,
{
fn tag(&self) -> widget::tree::Tag {
widget::tree::Tag::of::<State>()
}
fn state(&self) -> widget::tree::State {
widget::tree::State::new(State::default())
}
fn children(&self) -> Vec<widget::Tree> {
vec![widget::Tree::new(&self.content)]
}
fn diff(&self, tree: &mut widget::Tree) {
tree.diff_children(std::slice::from_ref(&self.content));
}
fn size(&self) -> Size<Length> {
self.content.as_widget().size()
}
fn layout(
&mut self,
tree: &mut widget::Tree,
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
let padding = Padding {
top: 0.0,
right: self.handle_width,
bottom: 0.0,
left: 0.0,
};
layout::padded(limits, Length::Fill, Length::Shrink, padding, |limits| {
self.content
.as_widget_mut()
.layout(&mut tree.children[0], renderer, limits)
})
}
fn update(
&mut self,
tree: &mut widget::Tree,
event: &Event,
layout: Layout<'_>,
cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
viewport: &Rectangle,
) {
let grab = self.grab_bounds(layout.bounds());
let state = tree.state.downcast_mut::<State>();
state.hovered = cursor.is_over(grab);
if let Event::Mouse(mouse_event) = event {
match mouse_event {
mouse::Event::ButtonPressed(mouse::Button::Left) => {
if let Some(origin) = cursor.position_over(grab) {
let click =
mouse::Click::new(origin, mouse::Button::Left, state.last_click);
state.last_click = Some(click);
if matches!(click.kind(), mouse::click::Kind::Double) {
if let Some(auto_size) = self.on_auto_size.clone() {
state.drag_origin = None;
shell.publish(auto_size);
shell.capture_event();
return;
}
}
state.drag_origin = Some(origin);
shell.capture_event();
return;
}
}
mouse::Event::ButtonReleased(mouse::Button::Left) => {
if state.drag_origin.take().is_some() {
shell.publish(self.on_release.clone());
shell.capture_event();
return;
}
}
mouse::Event::CursorMoved { .. } => {
if let (Some(position), Some(origin)) = (cursor.position(), state.drag_origin) {
shell.publish((self.on_drag)((position - origin).x));
shell.capture_event();
return;
}
}
_ => {}
}
}
self.content.as_widget_mut().update(
&mut tree.children[0],
event,
layout.children().next().unwrap(),
cursor,
renderer,
clipboard,
shell,
viewport,
);
}
fn mouse_interaction(
&self,
tree: &widget::Tree,
layout: Layout<'_>,
cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
let state = tree.state.downcast_ref::<State>();
if state.hovered || state.drag_origin.is_some() {
mouse::Interaction::ResizingHorizontally
} else {
self.content.as_widget().mouse_interaction(
&tree.children[0],
layout.children().next().unwrap(),
cursor,
viewport,
renderer,
)
}
}
fn draw(
&self,
tree: &widget::Tree,
renderer: &mut Renderer,
theme: &Theme,
style: &renderer::Style,
layout: Layout<'_>,
cursor: mouse::Cursor,
viewport: &Rectangle,
) {
self.content.as_widget().draw(
&tree.children[0],
renderer,
theme,
style,
layout.children().next().unwrap(),
cursor,
viewport,
);
let bounds = layout.bounds();
let inset = (bounds.height * 0.25).min(8.0);
renderer.fill_quad(
renderer::Quad {
bounds: Rectangle {
x: (bounds.x + bounds.width - 1.0).floor(),
y: bounds.y + inset,
width: 1.0,
height: (bounds.height - inset * 2.0).max(0.0),
},
border: Border::default(),
shadow: Shadow::default(),
snap: true,
},
Color {
a: 0.25,
..style.text_color
},
);
}
fn operate(
&mut self,
tree: &mut widget::Tree,
layout: Layout<'_>,
renderer: &Renderer,
operation: &mut dyn widget::Operation,
) {
self.content.as_widget_mut().operate(
&mut tree.children[0],
layout.children().next().unwrap(),
renderer,
operation,
);
}
fn overlay<'b>(
&'b mut self,
tree: &'b mut widget::Tree,
layout: Layout<'b>,
renderer: &Renderer,
viewport: &Rectangle,
translation: Vector,
) -> Option<iced::advanced::overlay::Element<'b, Message, Theme, Renderer>> {
self.content.as_widget_mut().overlay(
&mut tree.children[0],
layout.children().next().unwrap(),
renderer,
viewport,
translation,
)
}
}
impl<'a, Message, Theme, Renderer> From<ResizeHandle<'a, Message, Theme, Renderer>>
for Element<'a, Message, Theme, Renderer>
where
Message: Clone + 'a,
Theme: 'a,
Renderer: renderer::Renderer + 'a,
{
fn from(handle: ResizeHandle<'a, Message, Theme, Renderer>) -> Self {
Element::new(handle)
}
}