Introduce selector flag and decouple iced_widget from iced_runtime

This commit is contained in:
Héctor Ramón Jiménez 2025-08-23 05:15:57 +02:00
parent 34a42b5ad4
commit 81d1eda7fe
No known key found for this signature in database
GPG key ID: 7CC46565708259A7
22 changed files with 118 additions and 67 deletions

View file

@ -12,10 +12,10 @@
pub mod clipboard;
pub mod font;
pub mod keyboard;
pub mod overlay;
pub mod system;
pub mod task;
pub mod user_interface;
pub mod widget;
pub mod window;
pub use iced_core as core;
@ -25,7 +25,6 @@ pub use iced_futures as futures;
pub use task::Task;
pub use user_interface::UserInterface;
use crate::core::widget;
use crate::futures::futures::channel::oneshot;
use std::borrow::Cow;
@ -45,7 +44,7 @@ pub enum Action<T> {
},
/// Run a widget operation.
Widget(Box<dyn widget::Operation>),
Widget(Box<dyn core::widget::Operation>),
/// Run a clipboard action.
Clipboard(clipboard::Action),
@ -67,8 +66,8 @@ pub enum Action<T> {
}
impl<T> Action<T> {
/// Creates a new [`Action::Widget`] with the given [`widget::Operation`].
pub fn widget(operation: impl widget::Operation + 'static) -> Self {
/// Creates a new [`Action::Widget`] with the given [`widget::Operation`](core::widget::Operation).
pub fn widget(operation: impl core::widget::Operation + 'static) -> Self {
Self::Widget(Box::new(operation))
}

View file

@ -1,4 +0,0 @@
//! Overlays for user interfaces.
mod nested;
pub use nested::Nested;

View file

@ -1,292 +0,0 @@
use crate::core::event;
use crate::core::layout;
use crate::core::mouse;
use crate::core::overlay;
use crate::core::renderer;
use crate::core::widget;
use crate::core::{Clipboard, Event, Layout, Shell, Size};
/// An overlay container that displays nested overlays
#[allow(missing_debug_implementations)]
pub struct Nested<'a, Message, Theme, Renderer> {
overlay: overlay::Element<'a, Message, Theme, Renderer>,
}
impl<'a, Message, Theme, Renderer> Nested<'a, Message, Theme, Renderer>
where
Renderer: renderer::Renderer,
{
/// Creates a nested overlay from the provided [`overlay::Element`]
pub fn new(
element: overlay::Element<'a, Message, Theme, Renderer>,
) -> Self {
Self { overlay: element }
}
/// Returns the layout [`Node`] of the [`Nested`] overlay.
///
/// [`Node`]: layout::Node
pub fn layout(
&mut self,
renderer: &Renderer,
bounds: Size,
) -> layout::Node {
fn recurse<Message, Theme, Renderer>(
element: &mut overlay::Element<'_, Message, Theme, Renderer>,
renderer: &Renderer,
bounds: Size,
) -> layout::Node
where
Renderer: renderer::Renderer,
{
let overlay = element.as_overlay_mut();
let node = overlay.layout(renderer, bounds);
let nested_node = overlay
.overlay(Layout::new(&node), renderer)
.as_mut()
.map(|nested| recurse(nested, renderer, bounds));
if let Some(nested_node) = nested_node {
layout::Node::with_children(
node.size(),
vec![node, nested_node],
)
} else {
layout::Node::with_children(node.size(), vec![node])
}
}
recurse(&mut self.overlay, renderer, bounds)
}
/// Draws the [`Nested`] overlay using the associated `Renderer`.
pub fn draw(
&mut self,
renderer: &mut Renderer,
theme: &Theme,
style: &renderer::Style,
layout: Layout<'_>,
cursor: mouse::Cursor,
) {
fn recurse<Message, Theme, Renderer>(
element: &mut overlay::Element<'_, Message, Theme, Renderer>,
layout: Layout<'_>,
renderer: &mut Renderer,
theme: &Theme,
style: &renderer::Style,
cursor: mouse::Cursor,
) where
Renderer: renderer::Renderer,
{
let mut layouts = layout.children();
if let Some(layout) = layouts.next() {
let nested_layout = layouts.next();
let overlay = element.as_overlay_mut();
let is_over = cursor
.position()
.zip(nested_layout)
.and_then(|(cursor_position, nested_layout)| {
overlay.overlay(layout, renderer).map(|nested| {
nested.as_overlay().mouse_interaction(
nested_layout.children().next().unwrap(),
mouse::Cursor::Available(cursor_position),
renderer,
) != mouse::Interaction::None
})
})
.unwrap_or_default();
renderer.with_layer(layout.bounds(), |renderer| {
overlay.draw(
renderer,
theme,
style,
layout,
if is_over {
mouse::Cursor::Unavailable
} else {
cursor
},
);
});
if let Some((mut nested, nested_layout)) =
overlay.overlay(layout, renderer).zip(nested_layout)
{
recurse(
&mut nested,
nested_layout,
renderer,
theme,
style,
cursor,
);
}
}
}
recurse(&mut self.overlay, layout, renderer, theme, style, cursor);
}
/// Applies a [`widget::Operation`] to the [`Nested`] overlay.
pub fn operate(
&mut self,
layout: Layout<'_>,
renderer: &Renderer,
operation: &mut dyn widget::Operation,
) {
fn recurse<Message, Theme, Renderer>(
element: &mut overlay::Element<'_, Message, Theme, Renderer>,
layout: Layout<'_>,
renderer: &Renderer,
operation: &mut dyn widget::Operation,
) where
Renderer: renderer::Renderer,
{
let mut layouts = layout.children();
if let Some(layout) = layouts.next() {
let overlay = element.as_overlay_mut();
overlay.operate(layout, renderer, operation);
if let Some((mut nested, nested_layout)) =
overlay.overlay(layout, renderer).zip(layouts.next())
{
recurse(&mut nested, nested_layout, renderer, operation);
}
}
}
recurse(&mut self.overlay, layout, renderer, operation);
}
/// Processes a runtime [`Event`].
pub fn update(
&mut self,
event: &Event,
layout: Layout<'_>,
cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) {
fn recurse<Message, Theme, Renderer>(
element: &mut overlay::Element<'_, Message, Theme, Renderer>,
layout: Layout<'_>,
event: &Event,
cursor: mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn Clipboard,
shell: &mut Shell<'_, Message>,
) -> bool
where
Renderer: renderer::Renderer,
{
let mut layouts = layout.children();
if let Some(layout) = layouts.next() {
let overlay = element.as_overlay_mut();
let nested_is_over = if let Some((mut nested, nested_layout)) =
overlay.overlay(layout, renderer).zip(layouts.next())
{
recurse(
&mut nested,
nested_layout,
event,
cursor,
renderer,
clipboard,
shell,
)
} else {
false
};
if shell.event_status() == event::Status::Ignored {
let is_over = nested_is_over
|| cursor
.position()
.map(|cursor_position| {
overlay.mouse_interaction(
layout,
mouse::Cursor::Available(cursor_position),
renderer,
) != mouse::Interaction::None
})
.unwrap_or_default();
overlay.update(
event,
layout,
if nested_is_over {
mouse::Cursor::Unavailable
} else {
cursor
},
renderer,
clipboard,
shell,
);
is_over
} else {
nested_is_over
}
} else {
false
}
}
let _ = recurse(
&mut self.overlay,
layout,
event,
cursor,
renderer,
clipboard,
shell,
);
}
/// Returns the current [`mouse::Interaction`] of the [`Nested`] overlay.
pub fn mouse_interaction(
&mut self,
layout: Layout<'_>,
cursor: mouse::Cursor,
renderer: &Renderer,
) -> mouse::Interaction {
fn recurse<Message, Theme, Renderer>(
element: &mut overlay::Element<'_, Message, Theme, Renderer>,
layout: Layout<'_>,
cursor: mouse::Cursor,
renderer: &Renderer,
) -> Option<mouse::Interaction>
where
Renderer: renderer::Renderer,
{
let mut layouts = layout.children();
let layout = layouts.next()?;
let overlay = element.as_overlay_mut();
Some(
overlay
.overlay(layout, renderer)
.zip(layouts.next())
.and_then(|(mut overlay, layout)| {
recurse(&mut overlay, layout, cursor, renderer)
})
.unwrap_or_else(|| {
overlay.mouse_interaction(layout, cursor, renderer)
}),
)
}
recurse(&mut self.overlay, layout, cursor, renderer).unwrap_or_default()
}
}

View file

@ -2,13 +2,13 @@
use crate::core::event::{self, Event};
use crate::core::layout;
use crate::core::mouse;
use crate::core::overlay;
use crate::core::renderer;
use crate::core::widget;
use crate::core::window;
use crate::core::{
Clipboard, Element, InputMethod, Layout, Rectangle, Shell, Size, Vector,
};
use crate::overlay;
/// A set of interactive graphical elements with a specific [`Layout`].
///

5
runtime/src/widget.rs Normal file
View file

@ -0,0 +1,5 @@
//! Operate on widgets and query them at runtime.
pub mod operation;
#[cfg(feature = "selector")]
pub mod selector;

View file

@ -0,0 +1,88 @@
//! Change internal widget state.
use crate::core::widget::Id;
use crate::core::widget::operation;
use crate::task;
use crate::{Action, Task};
pub use crate::core::widget::operation::scrollable::{
AbsoluteOffset, RelativeOffset,
};
/// Snaps the scrollable with the given [`Id`] to the provided [`RelativeOffset`].
pub fn snap_to<T>(id: impl Into<Id>, offset: RelativeOffset) -> Task<T> {
task::effect(Action::widget(operation::scrollable::snap_to(
id.into(),
offset,
)))
}
/// Snaps the scrollable with the given [`Id`] to the [`RelativeOffset::END`].
pub fn snap_to_end<T>(id: impl Into<Id>) -> Task<T> {
task::effect(Action::widget(operation::scrollable::snap_to(
id.into(),
RelativeOffset::END,
)))
}
/// Scrolls the scrollable with the given [`Id`] to the provided [`AbsoluteOffset`].
pub fn scroll_to<T>(id: impl Into<Id>, offset: AbsoluteOffset) -> Task<T> {
task::effect(Action::widget(operation::scrollable::scroll_to(
id.into(),
offset,
)))
}
/// Scrolls the scrollable with the given [`Id`] by the provided [`AbsoluteOffset`].
pub fn scroll_by<T>(id: impl Into<Id>, offset: AbsoluteOffset) -> Task<T> {
task::effect(Action::widget(operation::scrollable::scroll_by(
id.into(),
offset,
)))
}
/// Focuses the previous focusable widget.
pub fn focus_previous<T>() -> Task<T> {
task::effect(Action::widget(operation::focusable::focus_previous()))
}
/// Focuses the next focusable widget.
pub fn focus_next<T>() -> Task<T> {
task::effect(Action::widget(operation::focusable::focus_next()))
}
/// Returns whether the widget with the given [`Id`] is focused or not.
pub fn is_focused(id: impl Into<Id>) -> Task<bool> {
task::widget(operation::focusable::is_focused(id.into()))
}
/// Focuses the widget with the given [`Id`].
pub fn focus<T>(id: impl Into<Id>) -> Task<T> {
task::effect(Action::widget(operation::focusable::focus(id.into())))
}
/// Moves the cursor of the widget with the given [`Id`] to the end.
pub fn move_cursor_to_end<T>(id: impl Into<Id>) -> Task<T> {
task::effect(Action::widget(operation::text_input::move_cursor_to_end(
id.into(),
)))
}
/// Moves the cursor of the widget with the given [`Id`] to the front.
pub fn move_cursor_to_front<T>(id: impl Into<Id>) -> Task<T> {
task::effect(Action::widget(operation::text_input::move_cursor_to_front(
id.into(),
)))
}
/// Moves the cursor of the widget with the given [`Id`] to the provided position.
pub fn move_cursor_to<T>(id: impl Into<Id>, position: usize) -> Task<T> {
task::effect(Action::widget(operation::text_input::move_cursor_to(
id.into(),
position,
)))
}
/// Selects all the content of the widget with the given [`Id`].
pub fn select_all<T>(id: impl Into<Id>) -> Task<T> {
task::effect(Action::widget(operation::text_input::select_all(id.into())))
}

View file

@ -0,0 +1,18 @@
//! Find and query widgets in your applications.
pub use iced_selector::Selector;
pub use iced_selector::target::{Bounded, Match, Target, Text};
use crate::Task;
use crate::core::widget;
use crate::task;
/// Finds a widget by the given [`widget::Id`].
pub fn find_by_id(id: impl Into<widget::Id>) -> Task<Option<Match>> {
task::widget(id.into().find())
}
/// Finds a widget that contains the given text.
pub fn find_by_text(text: impl Into<String>) -> Task<Option<Text>> {
task::widget(Selector::find(text.into()))
}