fixes: various fixes and cleanup

This commit is contained in:
Ashley Wulber 2026-02-19 17:01:13 -05:00
parent b4346ff3ca
commit c1edb81084
No known key found for this signature in database
GPG key ID: 5216D4F46A90A820
27 changed files with 676 additions and 1455 deletions

View file

@ -373,7 +373,6 @@ where
}
.build(),
));
shell.invalidate_layout();
shell.request_redraw();
}

View file

@ -1,522 +0,0 @@
use crate::core::event::{self, Event};
use crate::core::layout::{self, Layout};
use crate::core::mouse;
use crate::core::overlay;
use crate::core::renderer;
use crate::core::widget::tree::{self, Tree};
use crate::core::{
self, Clipboard, Element, Length, Point, Rectangle, Shell, Size, Vector,
Widget,
};
use crate::horizontal_space;
use crate::runtime::overlay::Nested;
use iced_renderer::core::widget::Operation;
use ouroboros::self_referencing;
use std::cell::{RefCell, RefMut};
use std::marker::PhantomData;
use std::ops::Deref;
/// A widget that is aware of its dimensions.
///
/// A [`Responsive`] widget will always try to fill all the available space of
/// its parent.
#[cfg(feature = "lazy")]
#[allow(missing_debug_implementations)]
pub struct Responsive<
'a,
Message,
Theme = crate::Theme,
Renderer = crate::Renderer,
> {
view: Box<dyn Fn(Size) -> Element<'a, Message, Theme, Renderer> + 'a>,
content: RefCell<Content<'a, Message, Theme, Renderer>>,
}
impl<'a, Message, Theme, Renderer> Responsive<'a, Message, Theme, Renderer>
where
Renderer: core::Renderer,
{
/// Creates a new [`Responsive`] widget with a closure that produces its
/// contents.
///
/// The `view` closure will be provided with the current [`Size`] of
/// the [`Responsive`] widget and, therefore, can be used to build the
/// contents of the widget in a responsive way.
pub fn new(
view: impl Fn(Size) -> Element<'a, Message, Theme, Renderer> + 'a,
) -> Self {
Self {
view: Box::new(view),
content: RefCell::new(Content {
size: Size::ZERO,
layout: None,
element: Element::new(horizontal_space().width(0)),
}),
}
}
}
struct Content<'a, Message, Theme, Renderer> {
size: Size,
layout: Option<layout::Node>,
element: Element<'a, Message, Theme, Renderer>,
}
impl<'a, Message, Theme, Renderer> Content<'a, Message, Theme, Renderer>
where
Renderer: core::Renderer,
{
fn layout(&mut self, tree: &mut Tree, renderer: &Renderer) {
if self.layout.is_none() {
self.layout = Some(self.element.as_widget().layout(
tree,
renderer,
&layout::Limits::new(Size::ZERO, self.size),
));
}
}
fn update(
&mut self,
tree: &mut Tree,
new_size: Size,
view: &dyn Fn(Size) -> Element<'a, Message, Theme, Renderer>,
) {
if self.size == new_size {
return;
}
self.element = view(new_size);
self.size = new_size;
self.layout = None;
tree.diff(&mut self.element);
}
fn resolve<R, T>(
&mut self,
tree: &mut Tree,
renderer: R,
layout: Layout<'_>,
view: &dyn Fn(Size) -> Element<'a, Message, Theme, Renderer>,
f: impl FnOnce(
&mut Tree,
R,
Layout<'_>,
&mut Element<'a, Message, Theme, Renderer>,
) -> T,
) -> T
where
R: Deref<Target = Renderer>,
{
self.update(tree, layout.bounds().size(), view);
self.layout(tree, renderer.deref());
let content_layout = Layout::with_offset(
layout.position() - Point::ORIGIN,
self.layout.as_ref().unwrap(),
);
f(tree, renderer, content_layout, &mut self.element)
}
}
struct State {
tree: RefCell<Tree>,
}
impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer>
for Responsive<'a, Message, Theme, Renderer>
where
Renderer: core::Renderer,
{
fn tag(&self) -> tree::Tag {
tree::Tag::of::<State>()
}
fn state(&self) -> tree::State {
tree::State::new(State {
tree: RefCell::new(Tree::empty()),
})
}
fn size(&self) -> Size<Length> {
Size {
width: Length::Fill,
height: Length::Fill,
}
}
fn layout(
&self,
_tree: &mut Tree,
_renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
layout::Node::new(limits.max())
}
fn operate(
&self,
tree: &mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
operation: &mut dyn crate::core::widget::Operation,
) {
let state = tree.state.downcast_mut::<State>();
let mut content = self.content.borrow_mut();
content.resolve(
&mut state.tree.borrow_mut(),
renderer,
layout,
&self.view,
|tree, renderer, layout, element| {
element
.as_widget()
.operate(tree, layout, renderer, operation);
},
);
}
fn update(
&mut self,
tree: &mut Tree,
event: Event,
layout: Layout<'_>,
cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
viewport: &Rectangle,
) -> event::Status {
let state = tree.state.downcast_mut::<State>();
let mut content = self.content.borrow_mut();
let mut local_messages = vec![];
let mut local_shell = Shell::new(&mut local_messages);
let status = content.resolve(
&mut state.tree.borrow_mut(),
renderer,
layout,
&self.view,
|tree, renderer, layout, element| {
element.as_widget_mut().on_event(
tree,
event,
layout,
cursor,
renderer,
clipboard,
&mut local_shell,
viewport,
)
},
);
if local_shell.is_layout_invalid() {
content.layout = None;
}
shell.merge(local_shell, std::convert::identity);
status
}
fn draw(
&self,
tree: &Tree,
renderer: &mut Renderer,
theme: &Theme,
style: &renderer::Style,
layout: Layout<'_>,
cursor: mouse::Cursor,
viewport: &Rectangle,
) {
let state = tree.state.downcast_ref::<State>();
let mut content = self.content.borrow_mut();
content.resolve(
&mut state.tree.borrow_mut(),
renderer,
layout,
&self.view,
|tree, renderer, layout, element| {
element.as_widget().draw(
tree, renderer, theme, style, layout, cursor, viewport,
);
},
);
}
fn mouse_interaction(
&self,
tree: &Tree,
layout: Layout<'_>,
cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
let state = tree.state.downcast_ref::<State>();
let mut content = self.content.borrow_mut();
content.resolve(
&mut state.tree.borrow_mut(),
renderer,
layout,
&self.view,
|tree, renderer, layout, element| {
element
.as_widget()
.mouse_interaction(tree, layout, cursor, viewport, renderer)
},
)
}
fn overlay<'b>(
&'b mut self,
tree: &'b mut Tree,
layout: Layout<'_>,
renderer: &Renderer,
translation: Vector,
) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
use std::ops::DerefMut;
let state = tree.state.downcast_ref::<State>();
let overlay = OverlayBuilder {
content: self.content.borrow_mut(),
tree: state.tree.borrow_mut(),
types: PhantomData,
overlay_builder: |content: &mut RefMut<
'_,
Content<'_, _, _, _>,
>,
tree| {
content.update(tree, layout.bounds().size(), &self.view);
content.layout(tree, renderer);
let Content {
element,
layout: content_layout_node,
..
} = content.deref_mut();
let content_layout = Layout::with_offset(
layout.bounds().position() - Point::ORIGIN,
content_layout_node.as_ref().unwrap(),
);
(
element
.as_widget_mut()
.overlay(tree, content_layout, renderer, translation)
.map(|overlay| RefCell::new(Nested::new(overlay))),
content_layout_node,
)
},
}
.build();
if overlay.with_overlay(|(overlay, _layout)| overlay.is_some()) {
Some(overlay::Element::new(Box::new(overlay)))
} else {
None
}
}
#[cfg(feature = "a11y")]
fn a11y_nodes(
&self,
layout: Layout<'_>,
tree: &Tree,
cursor_position: mouse::Cursor,
) -> iced_accessibility::A11yTree {
let state = tree.state.downcast_ref::<State>().tree.borrow();
self.content.borrow().element.as_widget().a11y_nodes(
layout,
&*state,
cursor_position,
)
}
fn id(&self) -> Option<core::widget::Id> {
self.content.borrow().element.as_widget().id()
}
fn set_id(&mut self, _id: iced_runtime::core::id::Id) {
self.content
.borrow_mut()
.element
.as_widget_mut()
.set_id(_id);
}
fn drag_destinations(
&self,
state: &Tree,
layout: Layout<'_>,
renderer: &Renderer,
dnd_rectangles: &mut core::clipboard::DndDestinationRectangles,
) {
let ret = self.content.borrow_mut().resolve(
&mut state.state.downcast_ref::<State>().tree.borrow_mut(),
renderer,
layout,
&self.view,
|tree, r, layout, element| {
element.as_widget().drag_destinations(
tree,
layout,
r,
dnd_rectangles,
);
},
);
ret
}
}
impl<'a, Message, Theme, Renderer>
From<Responsive<'a, Message, Theme, Renderer>>
for Element<'a, Message, Theme, Renderer>
where
Message: 'a,
Theme: 'a,
Renderer: core::Renderer + 'a,
{
fn from(responsive: Responsive<'a, Message, Theme, Renderer>) -> Self {
Self::new(responsive)
}
}
#[self_referencing]
struct Overlay<'a, 'b, Message, Theme, Renderer> {
content: RefMut<'a, Content<'b, Message, Theme, Renderer>>,
tree: RefMut<'a, Tree>,
types: PhantomData<Message>,
#[borrows(mut content, mut tree)]
#[not_covariant]
overlay: (
Option<RefCell<Nested<'this, Message, Theme, Renderer>>>,
&'this mut Option<layout::Node>,
),
}
impl<'a, 'b, Message, Theme, Renderer>
Overlay<'a, 'b, Message, Theme, Renderer>
{
fn with_overlay_maybe<T>(
&self,
f: impl FnOnce(&mut Nested<'_, Message, Theme, Renderer>) -> T,
) -> Option<T> {
self.with_overlay(|(overlay, _layout)| {
overlay.as_ref().map(|nested| (f)(&mut nested.borrow_mut()))
})
}
fn with_overlay_mut_maybe<T>(
&mut self,
f: impl FnOnce(&mut Nested<'_, Message, Theme, Renderer>) -> T,
) -> Option<T> {
self.with_overlay_mut(|(overlay, _layout)| {
overlay.as_mut().map(|nested| (f)(nested.get_mut()))
})
}
}
impl<'a, 'b, Message, Theme, Renderer>
overlay::Overlay<Message, Theme, Renderer>
for Overlay<'a, 'b, Message, Theme, Renderer>
where
Renderer: core::Renderer,
{
fn layout(&mut self, renderer: &Renderer, bounds: Size) -> layout::Node {
self.with_overlay_maybe(|overlay| overlay.layout(renderer, bounds))
.unwrap_or_default()
}
fn draw(
&self,
renderer: &mut Renderer,
theme: &Theme,
style: &renderer::Style,
layout: Layout<'_>,
cursor: mouse::Cursor,
) {
let _ = self.with_overlay_maybe(|overlay| {
overlay.draw(renderer, theme, style, layout, cursor);
});
}
fn mouse_interaction(
&self,
layout: Layout<'_>,
cursor: mouse::Cursor,
viewport: &Rectangle,
renderer: &Renderer,
) -> mouse::Interaction {
self.with_overlay_maybe(|overlay| {
overlay.mouse_interaction(layout, cursor, viewport, renderer)
})
.unwrap_or_default()
}
fn update(
&mut self,
event: Event,
layout: Layout<'_>,
cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> event::Status {
let mut is_layout_invalid = false;
let event_status = self
.with_overlay_mut_maybe(|overlay| {
let event_status = overlay.on_event(
event, layout, cursor, renderer, clipboard, shell,
);
is_layout_invalid = shell.is_layout_invalid();
event_status
})
.unwrap_or(event::Status::Ignored);
if is_layout_invalid {
self.with_overlay_mut(|(_overlay, layout)| {
**layout = None;
});
}
event_status
}
fn is_over(
&self,
layout: Layout<'_>,
renderer: &Renderer,
cursor_position: Point,
) -> bool {
self.with_overlay_maybe(|overlay| {
overlay.is_over(layout, renderer, cursor_position)
})
.unwrap_or_default()
}
fn operate(
&mut self,
layout: Layout<'_>,
renderer: &Renderer,
operation: &mut dyn Operation,
) {
let _ = self.with_overlay_mut_maybe(|overlay| {
overlay.operate(layout, renderer, operation);
});
}
}

View file

@ -158,7 +158,7 @@ struct State {
// TODO: Support on_enter and on_exit
drag_initiated: Option<Point>,
is_out_of_bounds: bool,
last_click: Option<Click>,
last_press: Option<Click>,
}
impl Default for State {
fn default() -> Self {
@ -166,7 +166,7 @@ impl Default for State {
is_hovered: Default::default(),
drag_initiated: None,
is_out_of_bounds: true,
last_click: None,
last_press: None,
cursor_position: None,
bounds: Rectangle::default(),
previous_click: None,
@ -456,44 +456,46 @@ fn update<Message: Clone, Theme, Renderer>(
return;
}
if let Some(message) = widget.on_double_press.as_ref() {
if let Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) =
event
{
if let Some(cursor_position) = cursor.position() {
let click = mouse::Click::new(
cursor_position,
mouse::Button::Left,
state.last_click,
);
state.last_click = Some(click);
if let mouse::click::Kind::Double = click.kind() {
state.drag_initiated = None;
shell.publish(message.clone());
shell.capture_event();
return;
}
}
}
}
if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
| Event::Touch(touch::Event::FingerPressed { .. }) = event
{
let mut captured = false;
if let Some(position) = cursor_position {
let new_click = mouse::Click::new(
position,
mouse::Button::Left,
state.last_press,
);
if new_click.kind() == mouse::click::Kind::Double
&& let Some(double_press) = widget.on_double_press.as_ref()
{
shell.publish(double_press.clone());
} else if let Some(on_press_message) = widget.on_press.as_ref() {
shell.publish(on_press_message.clone());
}
if let Some(position) = cursor_position
&& let Some(message) = widget.on_double_click.as_ref()
{
state.last_press = Some(new_click);
// Even if this is not a double click, but the press is nevertheless
// processed by us and should not be popup to parent widgets.
shell.capture_event();
}
}
if let Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
| Event::Touch(touch::Event::FingerLifted { .. }) = event
{
if let Some(position) = cursor_position {
let new_click = mouse::Click::new(
position,
mouse::Button::Left,
state.previous_click,
);
if new_click.kind() == mouse::click::Kind::Double {
shell.publish(message.clone());
if new_click.kind() == mouse::click::Kind::Double
&& let Some(double_press) = widget.on_double_click.as_ref()
{
shell.publish(double_press.clone());
} else if let Some(on_press_message) = widget.on_release.as_ref() {
shell.publish(on_press_message.clone());
}
state.previous_click = Some(new_click);
@ -505,15 +507,6 @@ fn update<Message: Clone, Theme, Renderer>(
}
match event {
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
| Event::Touch(touch::Event::FingerLifted { .. }) => {
state.drag_initiated = None;
if let Some(message) = widget.on_release.as_ref() {
shell.publish(message.clone());
}
shell.capture_event();
return;
}
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Right)) => {
if let Some(message) = widget.on_right_release.as_ref() {
shell.publish(message.clone());

View file

@ -400,13 +400,13 @@ where
retain
});
// let ids = self.contents.iter().map(|_| None).collect(); // TODO
// tree.diff_children_custom(
// &mut self.contents,
// ids,
// |state, (_, content): (_, _)| content.diff(state),
// |(_, content): (_, _)| content.state(),
// );
let ids = self.contents.iter().map(|_| None).collect(); // TODO
tree.diff_children_custom(
&mut self.contents,
ids,
|state, content: _| content.diff(state),
|content: _| content.state(),
);
let Memory { order, .. } = tree.state.downcast_mut();
order.clone_from(&self.panes);

View file

@ -1188,7 +1188,6 @@ where
if delta.y.abs() < AUTOSCROLL_DEADZONE {
delta.y = 0.0;
}
if delta.x != 0.0 || delta.y != 0.0 {
let time_delta =
if let Some(last_frame) = last_frame {
@ -2007,7 +2006,6 @@ fn notify_viewport<Message>(
}
state.last_notified = Some(viewport);
if let Some(on_scroll) = on_scroll {
shell.publish(on_scroll(viewport));
}