Merge pull request #1393 from iced-rs/deprecate-stateful-widgets
Replace stateful widgets with the new `iced_pure` API
This commit is contained in:
commit
1923dbf7f0
160 changed files with 4612 additions and 14928 deletions
|
|
@ -3,6 +3,8 @@ mod action;
|
|||
|
||||
pub use action::Action;
|
||||
|
||||
use crate::widget;
|
||||
|
||||
use iced_futures::MaybeSend;
|
||||
|
||||
use std::fmt;
|
||||
|
|
@ -24,6 +26,13 @@ impl<T> Command<T> {
|
|||
Self(iced_futures::Command::single(action))
|
||||
}
|
||||
|
||||
/// Creates a [`Command`] that performs a [`widget::Operation`].
|
||||
pub fn widget(operation: impl widget::Operation<T> + 'static) -> Self {
|
||||
Self(iced_futures::Command::single(Action::Widget(
|
||||
widget::Action::new(operation),
|
||||
)))
|
||||
}
|
||||
|
||||
/// Creates a [`Command`] that performs the action of the given future.
|
||||
pub fn perform<A>(
|
||||
future: impl Future<Output = T> + 'static + MaybeSend,
|
||||
|
|
@ -51,6 +60,7 @@ impl<T> Command<T> {
|
|||
) -> Command<A>
|
||||
where
|
||||
T: 'static,
|
||||
A: 'static,
|
||||
{
|
||||
let Command(command) = self;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use crate::clipboard;
|
||||
use crate::system;
|
||||
use crate::widget;
|
||||
use crate::window;
|
||||
|
||||
use iced_futures::MaybeSend;
|
||||
|
|
@ -23,6 +24,9 @@ pub enum Action<T> {
|
|||
|
||||
/// Run a system action.
|
||||
System(system::Action<T>),
|
||||
|
||||
/// Run a widget action.
|
||||
Widget(widget::Action<T>),
|
||||
}
|
||||
|
||||
impl<T> Action<T> {
|
||||
|
|
@ -34,6 +38,7 @@ impl<T> Action<T> {
|
|||
f: impl Fn(T) -> A + 'static + MaybeSend + Sync,
|
||||
) -> Action<A>
|
||||
where
|
||||
A: 'static,
|
||||
T: 'static,
|
||||
{
|
||||
use iced_futures::futures::FutureExt;
|
||||
|
|
@ -43,6 +48,7 @@ impl<T> Action<T> {
|
|||
Self::Clipboard(action) => Action::Clipboard(action.map(f)),
|
||||
Self::Window(window) => Action::Window(window),
|
||||
Self::System(system) => Action::System(system.map(f)),
|
||||
Self::Widget(widget) => Action::Widget(widget.map(f)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -56,6 +62,7 @@ impl<T> fmt::Debug for Action<T> {
|
|||
}
|
||||
Self::Window(action) => write!(f, "Action::Window({:?})", action),
|
||||
Self::System(action) => write!(f, "Action::System({:?})", action),
|
||||
Self::Widget(_action) => write!(f, "Action::Widget"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,9 +3,11 @@ use crate::layout;
|
|||
use crate::mouse;
|
||||
use crate::overlay;
|
||||
use crate::renderer;
|
||||
use crate::{
|
||||
Clipboard, Color, Layout, Length, Point, Rectangle, Shell, Widget,
|
||||
};
|
||||
use crate::widget;
|
||||
use crate::widget::tree::{self, Tree};
|
||||
use crate::{Clipboard, Layout, Length, Point, Rectangle, Shell, Widget};
|
||||
|
||||
use std::borrow::Borrow;
|
||||
|
||||
/// A generic [`Widget`].
|
||||
///
|
||||
|
|
@ -15,25 +17,33 @@ use crate::{
|
|||
/// If you have a [built-in widget], you should be able to use `Into<Element>`
|
||||
/// to turn it into an [`Element`].
|
||||
///
|
||||
/// [built-in widget]: widget/index.html#built-in-widgets
|
||||
/// [built-in widget]: crate::widget
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Element<'a, Message, Renderer> {
|
||||
pub(crate) widget: Box<dyn Widget<Message, Renderer> + 'a>,
|
||||
widget: Box<dyn Widget<Message, Renderer> + 'a>,
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> Element<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: crate::Renderer,
|
||||
{
|
||||
impl<'a, Message, Renderer> Element<'a, Message, Renderer> {
|
||||
/// Creates a new [`Element`] containing the given [`Widget`].
|
||||
pub fn new(
|
||||
widget: impl Widget<Message, Renderer> + 'a,
|
||||
) -> Element<'a, Message, Renderer> {
|
||||
Element {
|
||||
pub fn new(widget: impl Widget<Message, Renderer> + 'a) -> Self
|
||||
where
|
||||
Renderer: crate::Renderer,
|
||||
{
|
||||
Self {
|
||||
widget: Box::new(widget),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a reference to the [`Widget`] of the [`Element`],
|
||||
pub fn as_widget(&self) -> &dyn Widget<Message, Renderer> {
|
||||
self.widget.as_ref()
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the [`Widget`] of the [`Element`],
|
||||
pub fn as_widget_mut(&mut self) -> &mut dyn Widget<Message, Renderer> {
|
||||
self.widget.as_mut()
|
||||
}
|
||||
|
||||
/// Applies a transformation to the produced message of the [`Element`].
|
||||
///
|
||||
/// This method is useful when you want to decouple different parts of your
|
||||
|
|
@ -168,127 +178,22 @@ where
|
|||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn map<F, B>(self, f: F) -> Element<'a, B, Renderer>
|
||||
where
|
||||
Message: 'static,
|
||||
Renderer: 'a,
|
||||
B: 'static,
|
||||
F: 'static + Fn(Message) -> B,
|
||||
{
|
||||
Element {
|
||||
widget: Box::new(Map::new(self.widget, f)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Marks the [`Element`] as _to-be-explained_.
|
||||
///
|
||||
/// The [`Renderer`] will explain the layout of the [`Element`] graphically.
|
||||
/// This can be very useful for debugging your layout!
|
||||
///
|
||||
/// [`Renderer`]: crate::Renderer
|
||||
pub fn explain<C: Into<Color>>(
|
||||
pub fn map<B>(
|
||||
self,
|
||||
color: C,
|
||||
) -> Element<'a, Message, Renderer>
|
||||
f: impl Fn(Message) -> B + 'a,
|
||||
) -> Element<'a, B, Renderer>
|
||||
where
|
||||
Message: 'static,
|
||||
Renderer: 'a,
|
||||
Message: 'a,
|
||||
Renderer: crate::Renderer + 'a,
|
||||
B: 'a,
|
||||
{
|
||||
Element {
|
||||
widget: Box::new(Explain::new(self, color.into())),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the width of the [`Element`].
|
||||
pub fn width(&self) -> Length {
|
||||
self.widget.width()
|
||||
}
|
||||
|
||||
/// Returns the height of the [`Element`].
|
||||
pub fn height(&self) -> Length {
|
||||
self.widget.height()
|
||||
}
|
||||
|
||||
/// Computes the layout of the [`Element`] in the given [`Limits`].
|
||||
///
|
||||
/// [`Limits`]: layout::Limits
|
||||
pub fn layout(
|
||||
&self,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
self.widget.layout(renderer, limits)
|
||||
}
|
||||
|
||||
/// Processes a runtime [`Event`].
|
||||
pub fn on_event(
|
||||
&mut self,
|
||||
event: Event,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
renderer: &Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
) -> event::Status {
|
||||
self.widget.on_event(
|
||||
event,
|
||||
layout,
|
||||
cursor_position,
|
||||
renderer,
|
||||
clipboard,
|
||||
shell,
|
||||
)
|
||||
}
|
||||
|
||||
/// Draws the [`Element`] and its children using the given [`Layout`].
|
||||
pub fn draw(
|
||||
&self,
|
||||
renderer: &mut Renderer,
|
||||
theme: &Renderer::Theme,
|
||||
style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
) {
|
||||
self.widget.draw(
|
||||
renderer,
|
||||
theme,
|
||||
style,
|
||||
layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns the current [`mouse::Interaction`] of the [`Element`].
|
||||
pub fn mouse_interaction(
|
||||
&self,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
renderer: &Renderer,
|
||||
) -> mouse::Interaction {
|
||||
self.widget.mouse_interaction(
|
||||
layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
renderer,
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns the overlay of the [`Element`], if there is any.
|
||||
pub fn overlay<'b>(
|
||||
&'b mut self,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
) -> Option<overlay::Element<'b, Message, Renderer>> {
|
||||
self.widget.overlay(layout, renderer)
|
||||
Element::new(Map::new(self.widget, f))
|
||||
}
|
||||
}
|
||||
|
||||
struct Map<'a, A, B, Renderer> {
|
||||
widget: Box<dyn Widget<A, Renderer> + 'a>,
|
||||
mapper: Box<dyn Fn(A) -> B>,
|
||||
mapper: Box<dyn Fn(A) -> B + 'a>,
|
||||
}
|
||||
|
||||
impl<'a, A, B, Renderer> Map<'a, A, B, Renderer> {
|
||||
|
|
@ -297,7 +202,7 @@ impl<'a, A, B, Renderer> Map<'a, A, B, Renderer> {
|
|||
mapper: F,
|
||||
) -> Map<'a, A, B, Renderer>
|
||||
where
|
||||
F: 'static + Fn(A) -> B,
|
||||
F: 'a + Fn(A) -> B,
|
||||
{
|
||||
Map {
|
||||
widget,
|
||||
|
|
@ -309,9 +214,25 @@ impl<'a, A, B, Renderer> Map<'a, A, B, Renderer> {
|
|||
impl<'a, A, B, Renderer> Widget<B, Renderer> for Map<'a, A, B, Renderer>
|
||||
where
|
||||
Renderer: crate::Renderer + 'a,
|
||||
A: 'static,
|
||||
B: 'static,
|
||||
A: 'a,
|
||||
B: 'a,
|
||||
{
|
||||
fn tag(&self) -> tree::Tag {
|
||||
self.widget.tag()
|
||||
}
|
||||
|
||||
fn state(&self) -> tree::State {
|
||||
self.widget.state()
|
||||
}
|
||||
|
||||
fn children(&self) -> Vec<Tree> {
|
||||
self.widget.children()
|
||||
}
|
||||
|
||||
fn diff(&self, tree: &mut Tree) {
|
||||
self.widget.diff(tree)
|
||||
}
|
||||
|
||||
fn width(&self) -> Length {
|
||||
self.widget.width()
|
||||
}
|
||||
|
|
@ -328,8 +249,45 @@ where
|
|||
self.widget.layout(renderer, limits)
|
||||
}
|
||||
|
||||
fn operate(
|
||||
&self,
|
||||
tree: &mut Tree,
|
||||
layout: Layout<'_>,
|
||||
operation: &mut dyn widget::Operation<B>,
|
||||
) {
|
||||
struct MapOperation<'a, B> {
|
||||
operation: &'a mut dyn widget::Operation<B>,
|
||||
}
|
||||
|
||||
impl<'a, T, B> widget::Operation<T> for MapOperation<'a, B> {
|
||||
fn container(
|
||||
&mut self,
|
||||
id: Option<&widget::Id>,
|
||||
operate_on_children: &mut dyn FnMut(
|
||||
&mut dyn widget::Operation<T>,
|
||||
),
|
||||
) {
|
||||
self.operation.container(id, &mut |operation| {
|
||||
operate_on_children(&mut MapOperation { operation });
|
||||
});
|
||||
}
|
||||
|
||||
fn focusable(
|
||||
&mut self,
|
||||
state: &mut dyn widget::operation::Focusable,
|
||||
id: Option<&widget::Id>,
|
||||
) {
|
||||
self.operation.focusable(state, id);
|
||||
}
|
||||
}
|
||||
|
||||
self.widget
|
||||
.operate(tree, layout, &mut MapOperation { operation });
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
event: Event,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
|
|
@ -341,6 +299,7 @@ where
|
|||
let mut local_shell = Shell::new(&mut local_messages);
|
||||
|
||||
let status = self.widget.on_event(
|
||||
tree,
|
||||
event,
|
||||
layout,
|
||||
cursor_position,
|
||||
|
|
@ -356,6 +315,7 @@ where
|
|||
|
||||
fn draw(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
renderer: &mut Renderer,
|
||||
theme: &Renderer::Theme,
|
||||
style: &renderer::Style,
|
||||
|
|
@ -364,6 +324,7 @@ where
|
|||
viewport: &Rectangle,
|
||||
) {
|
||||
self.widget.draw(
|
||||
tree,
|
||||
renderer,
|
||||
theme,
|
||||
style,
|
||||
|
|
@ -375,12 +336,14 @@ where
|
|||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
renderer: &Renderer,
|
||||
) -> mouse::Interaction {
|
||||
self.widget.mouse_interaction(
|
||||
tree,
|
||||
layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
|
|
@ -388,134 +351,32 @@ where
|
|||
)
|
||||
}
|
||||
|
||||
fn overlay(
|
||||
&mut self,
|
||||
fn overlay<'b>(
|
||||
&'b self,
|
||||
tree: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
) -> Option<overlay::Element<'_, B, Renderer>> {
|
||||
) -> Option<overlay::Element<'b, B, Renderer>> {
|
||||
let mapper = &self.mapper;
|
||||
|
||||
self.widget
|
||||
.overlay(layout, renderer)
|
||||
.overlay(tree, layout, renderer)
|
||||
.map(move |overlay| overlay.map(mapper))
|
||||
}
|
||||
}
|
||||
|
||||
struct Explain<'a, Message, Renderer: crate::Renderer> {
|
||||
element: Element<'a, Message, Renderer>,
|
||||
color: Color,
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> Explain<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: crate::Renderer,
|
||||
impl<'a, Message, Renderer> Borrow<dyn Widget<Message, Renderer> + 'a>
|
||||
for Element<'a, Message, Renderer>
|
||||
{
|
||||
fn new(element: Element<'a, Message, Renderer>, color: Color) -> Self {
|
||||
Explain { element, color }
|
||||
fn borrow(&self) -> &(dyn Widget<Message, Renderer> + 'a) {
|
||||
self.widget.borrow()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> Widget<Message, Renderer>
|
||||
for Explain<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: crate::Renderer,
|
||||
impl<'a, Message, Renderer> Borrow<dyn Widget<Message, Renderer> + 'a>
|
||||
for &Element<'a, Message, Renderer>
|
||||
{
|
||||
fn width(&self) -> Length {
|
||||
self.element.widget.width()
|
||||
}
|
||||
|
||||
fn height(&self) -> Length {
|
||||
self.element.widget.height()
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
self.element.widget.layout(renderer, limits)
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
&mut self,
|
||||
event: Event,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
renderer: &Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
) -> event::Status {
|
||||
self.element.widget.on_event(
|
||||
event,
|
||||
layout,
|
||||
cursor_position,
|
||||
renderer,
|
||||
clipboard,
|
||||
shell,
|
||||
)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
renderer: &mut Renderer,
|
||||
theme: &Renderer::Theme,
|
||||
style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
) {
|
||||
fn explain_layout<Renderer: crate::Renderer>(
|
||||
renderer: &mut Renderer,
|
||||
color: Color,
|
||||
layout: Layout<'_>,
|
||||
) {
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds: layout.bounds(),
|
||||
border_color: color,
|
||||
border_width: 1.0,
|
||||
border_radius: 0.0,
|
||||
},
|
||||
Color::TRANSPARENT,
|
||||
);
|
||||
|
||||
for child in layout.children() {
|
||||
explain_layout(renderer, color, child);
|
||||
}
|
||||
}
|
||||
|
||||
self.element.widget.draw(
|
||||
renderer,
|
||||
theme,
|
||||
style,
|
||||
layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
);
|
||||
|
||||
explain_layout(renderer, self.color, layout);
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
renderer: &Renderer,
|
||||
) -> mouse::Interaction {
|
||||
self.element.widget.mouse_interaction(
|
||||
layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
renderer,
|
||||
)
|
||||
}
|
||||
|
||||
fn overlay(
|
||||
&mut self,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
) -> Option<overlay::Element<'_, Message, Renderer>> {
|
||||
self.element.overlay(layout, renderer)
|
||||
fn borrow(&self) -> &(dyn Widget<Message, Renderer> + 'a) {
|
||||
self.widget.borrow()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,8 +16,10 @@
|
|||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
use crate::Element;
|
||||
|
||||
use crate::layout::{Limits, Node};
|
||||
use crate::{Alignment, Element, Padding, Point, Size};
|
||||
use crate::{Alignment, Padding, Point, Size};
|
||||
|
||||
/// The main axis of a flex layout.
|
||||
#[derive(Debug)]
|
||||
|
|
@ -84,8 +86,8 @@ where
|
|||
|
||||
items.iter().for_each(|child| {
|
||||
let cross_fill_factor = match axis {
|
||||
Axis::Horizontal => child.height(),
|
||||
Axis::Vertical => child.width(),
|
||||
Axis::Horizontal => child.as_widget().height(),
|
||||
Axis::Vertical => child.as_widget().width(),
|
||||
}
|
||||
.fill_factor();
|
||||
|
||||
|
|
@ -95,7 +97,7 @@ where
|
|||
let child_limits =
|
||||
Limits::new(Size::ZERO, Size::new(max_width, max_height));
|
||||
|
||||
let layout = child.layout(renderer, &child_limits);
|
||||
let layout = child.as_widget().layout(renderer, &child_limits);
|
||||
let size = layout.size();
|
||||
|
||||
fill_cross = fill_cross.max(axis.cross(size));
|
||||
|
|
@ -107,8 +109,8 @@ where
|
|||
|
||||
for (i, child) in items.iter().enumerate() {
|
||||
let fill_factor = match axis {
|
||||
Axis::Horizontal => child.width(),
|
||||
Axis::Vertical => child.height(),
|
||||
Axis::Horizontal => child.as_widget().width(),
|
||||
Axis::Vertical => child.as_widget().height(),
|
||||
}
|
||||
.fill_factor();
|
||||
|
||||
|
|
@ -130,7 +132,7 @@ where
|
|||
Size::new(max_width, max_height),
|
||||
);
|
||||
|
||||
let layout = child.layout(renderer, &child_limits);
|
||||
let layout = child.as_widget().layout(renderer, &child_limits);
|
||||
let size = layout.size();
|
||||
|
||||
available -= axis.main(size);
|
||||
|
|
@ -149,8 +151,8 @@ where
|
|||
|
||||
for (i, child) in items.iter().enumerate() {
|
||||
let fill_factor = match axis {
|
||||
Axis::Horizontal => child.width(),
|
||||
Axis::Vertical => child.height(),
|
||||
Axis::Horizontal => child.as_widget().width(),
|
||||
Axis::Vertical => child.as_widget().height(),
|
||||
}
|
||||
.fill_factor();
|
||||
|
||||
|
|
@ -179,7 +181,7 @@ where
|
|||
Size::new(max_width, max_height),
|
||||
);
|
||||
|
||||
let layout = child.layout(renderer, &child_limits);
|
||||
let layout = child.as_widget().layout(renderer, &child_limits);
|
||||
|
||||
if align_items != Alignment::Fill {
|
||||
cross = cross.max(axis.cross(layout.size()));
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ use crate::event::{self, Event};
|
|||
use crate::layout;
|
||||
use crate::mouse;
|
||||
use crate::renderer;
|
||||
use crate::widget;
|
||||
use crate::widget::tree::{self, Tree};
|
||||
use crate::{Clipboard, Layout, Point, Rectangle, Shell, Size};
|
||||
|
||||
/// An interactive component that can be displayed on top of other widgets.
|
||||
|
|
@ -40,6 +42,36 @@ where
|
|||
cursor_position: Point,
|
||||
);
|
||||
|
||||
/// Returns the [`Tag`] of the [`Widget`].
|
||||
///
|
||||
/// [`Tag`]: tree::Tag
|
||||
fn tag(&self) -> tree::Tag {
|
||||
tree::Tag::stateless()
|
||||
}
|
||||
|
||||
/// Returns the [`State`] of the [`Widget`].
|
||||
///
|
||||
/// [`State`]: tree::State
|
||||
fn state(&self) -> tree::State {
|
||||
tree::State::None
|
||||
}
|
||||
|
||||
/// Returns the state [`Tree`] of the children of the [`Widget`].
|
||||
fn children(&self) -> Vec<Tree> {
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
/// Reconciliates the [`Widget`] with the provided [`Tree`].
|
||||
fn diff(&self, _tree: &mut Tree) {}
|
||||
|
||||
/// Applies an [`Operation`] to the [`Widget`].
|
||||
fn operate(
|
||||
&self,
|
||||
_layout: Layout<'_>,
|
||||
_operation: &mut dyn widget::Operation<Message>,
|
||||
) {
|
||||
}
|
||||
|
||||
/// Processes a runtime [`Event`].
|
||||
///
|
||||
/// It receives:
|
||||
|
|
@ -77,3 +109,26 @@ where
|
|||
mouse::Interaction::Idle
|
||||
}
|
||||
}
|
||||
|
||||
/// Obtains the first overlay [`Element`] found in the given children.
|
||||
///
|
||||
/// This method will generally only be used by advanced users that are
|
||||
/// implementing the [`Widget`](crate::Widget) trait.
|
||||
pub fn from_children<'a, Message, Renderer>(
|
||||
children: &'a [crate::Element<'_, Message, Renderer>],
|
||||
tree: &'a mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
) -> Option<Element<'a, Message, Renderer>>
|
||||
where
|
||||
Renderer: crate::Renderer,
|
||||
{
|
||||
children
|
||||
.iter()
|
||||
.zip(&mut tree.children)
|
||||
.zip(layout.children())
|
||||
.filter_map(|((child, state), layout)| {
|
||||
child.as_widget().overlay(state, layout, renderer)
|
||||
})
|
||||
.next()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ use crate::event::{self, Event};
|
|||
use crate::layout;
|
||||
use crate::mouse;
|
||||
use crate::renderer;
|
||||
use crate::widget;
|
||||
use crate::{Clipboard, Layout, Point, Rectangle, Shell, Size, Vector};
|
||||
|
||||
/// A generic [`Overlay`].
|
||||
|
|
@ -102,6 +103,15 @@ where
|
|||
self.overlay
|
||||
.draw(renderer, theme, style, layout, cursor_position)
|
||||
}
|
||||
|
||||
/// Applies an [`Operation`] to the [`Element`].
|
||||
pub fn operate(
|
||||
&self,
|
||||
layout: Layout<'_>,
|
||||
operation: &mut dyn widget::Operation<Message>,
|
||||
) {
|
||||
self.overlay.operate(layout, operation);
|
||||
}
|
||||
}
|
||||
|
||||
struct Map<'a, A, B, Renderer> {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ use crate::text::{self, Text};
|
|||
use crate::touch;
|
||||
use crate::widget::container::{self, Container};
|
||||
use crate::widget::scrollable::{self, Scrollable};
|
||||
use crate::widget::tree::{self, Tree};
|
||||
use crate::{
|
||||
Clipboard, Color, Element, Layout, Length, Padding, Point, Rectangle,
|
||||
Shell, Size, Vector, Widget,
|
||||
|
|
@ -114,15 +115,23 @@ where
|
|||
}
|
||||
|
||||
/// The local state of a [`Menu`].
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[derive(Debug)]
|
||||
pub struct State {
|
||||
scrollable: scrollable::State,
|
||||
tree: Tree,
|
||||
}
|
||||
|
||||
impl State {
|
||||
/// Creates a new [`State`] for a [`Menu`].
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
Self {
|
||||
tree: Tree::empty(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for State {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -131,6 +140,7 @@ where
|
|||
Renderer: crate::Renderer,
|
||||
Renderer::Theme: StyleSheet + container::StyleSheet,
|
||||
{
|
||||
state: &'a mut Tree,
|
||||
container: Container<'a, Message, Renderer>,
|
||||
width: u16,
|
||||
target_height: f32,
|
||||
|
|
@ -161,18 +171,20 @@ where
|
|||
style,
|
||||
} = menu;
|
||||
|
||||
let container =
|
||||
Container::new(Scrollable::new(&mut state.scrollable).push(List {
|
||||
options,
|
||||
hovered_option,
|
||||
last_selection,
|
||||
font,
|
||||
text_size,
|
||||
padding,
|
||||
style,
|
||||
}));
|
||||
let container = Container::new(Scrollable::new(List {
|
||||
options,
|
||||
hovered_option,
|
||||
last_selection,
|
||||
font,
|
||||
text_size,
|
||||
padding,
|
||||
style,
|
||||
}));
|
||||
|
||||
state.tree.diff(&container as &dyn Widget<_, _>);
|
||||
|
||||
Self {
|
||||
state: &mut state.tree,
|
||||
container,
|
||||
width,
|
||||
target_height,
|
||||
|
|
@ -187,6 +199,18 @@ where
|
|||
Renderer: text::Renderer,
|
||||
Renderer::Theme: StyleSheet + container::StyleSheet,
|
||||
{
|
||||
fn tag(&self) -> tree::Tag {
|
||||
self.container.tag()
|
||||
}
|
||||
|
||||
fn state(&self) -> tree::State {
|
||||
self.container.state()
|
||||
}
|
||||
|
||||
fn children(&self) -> Vec<Tree> {
|
||||
self.container.children()
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
renderer: &Renderer,
|
||||
|
|
@ -230,6 +254,7 @@ where
|
|||
shell: &mut Shell<'_, Message>,
|
||||
) -> event::Status {
|
||||
self.container.on_event(
|
||||
self.state,
|
||||
event,
|
||||
layout,
|
||||
cursor_position,
|
||||
|
|
@ -247,6 +272,7 @@ where
|
|||
renderer: &Renderer,
|
||||
) -> mouse::Interaction {
|
||||
self.container.mouse_interaction(
|
||||
self.state,
|
||||
layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
|
|
@ -279,6 +305,7 @@ where
|
|||
);
|
||||
|
||||
self.container.draw(
|
||||
self.state,
|
||||
renderer,
|
||||
theme,
|
||||
style,
|
||||
|
|
@ -344,6 +371,7 @@ where
|
|||
|
||||
fn on_event(
|
||||
&mut self,
|
||||
_state: &mut Tree,
|
||||
event: Event,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
|
|
@ -407,6 +435,7 @@ where
|
|||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
_state: &Tree,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
|
|
@ -423,6 +452,7 @@ where
|
|||
|
||||
fn draw(
|
||||
&self,
|
||||
_state: &Tree,
|
||||
renderer: &mut Renderer,
|
||||
theme: &Renderer::Theme,
|
||||
_style: &renderer::Style,
|
||||
|
|
|
|||
|
|
@ -26,5 +26,5 @@ pub trait Program: Sized {
|
|||
/// Returns the widgets to display in the [`Program`].
|
||||
///
|
||||
/// These widgets can produce __messages__ based on user interaction.
|
||||
fn view(&mut self) -> Element<'_, Self::Message, Self::Renderer>;
|
||||
fn view(&self) -> Element<'_, Self::Message, Self::Renderer>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ pub trait Renderer: Sized {
|
|||
element: &Element<'a, Message, Self>,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
element.layout(self, limits)
|
||||
element.as_widget().layout(self, limits)
|
||||
}
|
||||
|
||||
/// Draws the primitives recorded in the given closure in a new layer.
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ use crate::event::{self, Event};
|
|||
use crate::layout;
|
||||
use crate::mouse;
|
||||
use crate::renderer;
|
||||
use crate::widget;
|
||||
use crate::{Clipboard, Element, Layout, Point, Rectangle, Shell, Size};
|
||||
|
||||
/// A set of interactive graphical elements with a specific [`Layout`].
|
||||
|
|
@ -22,6 +23,7 @@ use crate::{Clipboard, Element, Layout, Point, Rectangle, Shell, Size};
|
|||
pub struct UserInterface<'a, Message, Renderer> {
|
||||
root: Element<'a, Message, Renderer>,
|
||||
base: layout::Node,
|
||||
state: widget::Tree,
|
||||
overlay: Option<layout::Node>,
|
||||
bounds: Size,
|
||||
}
|
||||
|
|
@ -88,17 +90,21 @@ where
|
|||
pub fn build<E: Into<Element<'a, Message, Renderer>>>(
|
||||
root: E,
|
||||
bounds: Size,
|
||||
_cache: Cache,
|
||||
cache: Cache,
|
||||
renderer: &mut Renderer,
|
||||
) -> Self {
|
||||
let root = root.into();
|
||||
|
||||
let Cache { mut state } = cache;
|
||||
state.diff(root.as_widget());
|
||||
|
||||
let base =
|
||||
renderer.layout(&root, &layout::Limits::new(Size::ZERO, bounds));
|
||||
|
||||
UserInterface {
|
||||
root,
|
||||
base,
|
||||
state,
|
||||
overlay: None,
|
||||
bounds,
|
||||
}
|
||||
|
|
@ -182,9 +188,12 @@ where
|
|||
use std::mem::ManuallyDrop;
|
||||
|
||||
let mut state = State::Updated;
|
||||
let mut manual_overlay = ManuallyDrop::new(
|
||||
self.root.overlay(Layout::new(&self.base), renderer),
|
||||
);
|
||||
let mut manual_overlay =
|
||||
ManuallyDrop::new(self.root.as_widget().overlay(
|
||||
&mut self.state,
|
||||
Layout::new(&self.base),
|
||||
renderer,
|
||||
));
|
||||
|
||||
let (base_cursor, overlay_statuses) = if manual_overlay.is_some() {
|
||||
let bounds = self.bounds;
|
||||
|
|
@ -215,9 +224,12 @@ where
|
|||
&layout::Limits::new(Size::ZERO, self.bounds),
|
||||
);
|
||||
|
||||
manual_overlay = ManuallyDrop::new(
|
||||
self.root.overlay(Layout::new(&self.base), renderer),
|
||||
);
|
||||
manual_overlay =
|
||||
ManuallyDrop::new(self.root.as_widget().overlay(
|
||||
&mut self.state,
|
||||
Layout::new(&self.base),
|
||||
renderer,
|
||||
));
|
||||
|
||||
if manual_overlay.is_none() {
|
||||
break;
|
||||
|
|
@ -262,7 +274,8 @@ where
|
|||
|
||||
let mut shell = Shell::new(messages);
|
||||
|
||||
let event_status = self.root.widget.on_event(
|
||||
let event_status = self.root.as_widget_mut().on_event(
|
||||
&mut self.state,
|
||||
event,
|
||||
Layout::new(&self.base),
|
||||
base_cursor,
|
||||
|
|
@ -377,9 +390,11 @@ where
|
|||
|
||||
let viewport = Rectangle::with_size(self.bounds);
|
||||
|
||||
let base_cursor = if let Some(overlay) =
|
||||
self.root.overlay(Layout::new(&self.base), renderer)
|
||||
{
|
||||
let base_cursor = if let Some(overlay) = self.root.as_widget().overlay(
|
||||
&mut self.state,
|
||||
Layout::new(&self.base),
|
||||
renderer,
|
||||
) {
|
||||
let overlay_layout = self
|
||||
.overlay
|
||||
.take()
|
||||
|
|
@ -399,7 +414,8 @@ where
|
|||
cursor_position
|
||||
};
|
||||
|
||||
self.root.widget.draw(
|
||||
self.root.as_widget().draw(
|
||||
&self.state,
|
||||
renderer,
|
||||
theme,
|
||||
style,
|
||||
|
|
@ -408,7 +424,8 @@ where
|
|||
&viewport,
|
||||
);
|
||||
|
||||
let base_interaction = self.root.widget.mouse_interaction(
|
||||
let base_interaction = self.root.as_widget().mouse_interaction(
|
||||
&self.state,
|
||||
Layout::new(&self.base),
|
||||
cursor_position,
|
||||
&viewport,
|
||||
|
|
@ -430,52 +447,79 @@ where
|
|||
overlay
|
||||
.as_ref()
|
||||
.and_then(|layout| {
|
||||
root.overlay(Layout::new(base), renderer).map(|overlay| {
|
||||
let overlay_interaction = overlay.mouse_interaction(
|
||||
Layout::new(layout),
|
||||
cursor_position,
|
||||
&viewport,
|
||||
renderer,
|
||||
);
|
||||
|
||||
let overlay_bounds = layout.bounds();
|
||||
|
||||
renderer.with_layer(overlay_bounds, |renderer| {
|
||||
overlay.draw(
|
||||
renderer,
|
||||
theme,
|
||||
style,
|
||||
root.as_widget()
|
||||
.overlay(&mut self.state, Layout::new(base), renderer)
|
||||
.map(|overlay| {
|
||||
let overlay_interaction = overlay.mouse_interaction(
|
||||
Layout::new(layout),
|
||||
cursor_position,
|
||||
&viewport,
|
||||
renderer,
|
||||
);
|
||||
});
|
||||
|
||||
if overlay_bounds.contains(cursor_position) {
|
||||
overlay_interaction
|
||||
} else {
|
||||
base_interaction
|
||||
}
|
||||
})
|
||||
let overlay_bounds = layout.bounds();
|
||||
|
||||
renderer.with_layer(overlay_bounds, |renderer| {
|
||||
overlay.draw(
|
||||
renderer,
|
||||
theme,
|
||||
style,
|
||||
Layout::new(layout),
|
||||
cursor_position,
|
||||
);
|
||||
});
|
||||
|
||||
if overlay_bounds.contains(cursor_position) {
|
||||
overlay_interaction
|
||||
} else {
|
||||
base_interaction
|
||||
}
|
||||
})
|
||||
})
|
||||
.unwrap_or(base_interaction)
|
||||
}
|
||||
|
||||
/// Applies a [`widget::Operation`] to the [`UserInterface`].
|
||||
pub fn operate(
|
||||
&mut self,
|
||||
renderer: &Renderer,
|
||||
operation: &mut dyn widget::Operation<Message>,
|
||||
) {
|
||||
self.root.as_widget().operate(
|
||||
&mut self.state,
|
||||
Layout::new(&self.base),
|
||||
operation,
|
||||
);
|
||||
|
||||
if let Some(layout) = self.overlay.as_ref() {
|
||||
if let Some(overlay) = self.root.as_widget().overlay(
|
||||
&mut self.state,
|
||||
Layout::new(&self.base),
|
||||
renderer,
|
||||
) {
|
||||
overlay.operate(Layout::new(layout), operation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Relayouts and returns a new [`UserInterface`] using the provided
|
||||
/// bounds.
|
||||
pub fn relayout(self, bounds: Size, renderer: &mut Renderer) -> Self {
|
||||
Self::build(self.root, bounds, Cache, renderer)
|
||||
Self::build(self.root, bounds, Cache { state: self.state }, renderer)
|
||||
}
|
||||
|
||||
/// Extract the [`Cache`] of the [`UserInterface`], consuming it in the
|
||||
/// process.
|
||||
pub fn into_cache(self) -> Cache {
|
||||
Cache
|
||||
Cache { state: self.state }
|
||||
}
|
||||
}
|
||||
|
||||
/// Reusable data of a specific [`UserInterface`].
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Cache;
|
||||
#[derive(Debug)]
|
||||
pub struct Cache {
|
||||
state: widget::Tree,
|
||||
}
|
||||
|
||||
impl Cache {
|
||||
/// Creates an empty [`Cache`].
|
||||
|
|
@ -483,7 +527,9 @@ impl Cache {
|
|||
/// You should use this to initialize a [`Cache`] before building your first
|
||||
/// [`UserInterface`].
|
||||
pub fn new() -> Cache {
|
||||
Cache
|
||||
Cache {
|
||||
state: widget::Tree::empty(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,9 @@ pub mod button;
|
|||
pub mod checkbox;
|
||||
pub mod column;
|
||||
pub mod container;
|
||||
pub mod helpers;
|
||||
pub mod image;
|
||||
pub mod operation;
|
||||
pub mod pane_grid;
|
||||
pub mod pick_list;
|
||||
pub mod progress_bar;
|
||||
|
|
@ -30,6 +32,10 @@ pub mod text;
|
|||
pub mod text_input;
|
||||
pub mod toggler;
|
||||
pub mod tooltip;
|
||||
pub mod tree;
|
||||
|
||||
mod action;
|
||||
mod id;
|
||||
|
||||
#[doc(no_inline)]
|
||||
pub use button::Button;
|
||||
|
|
@ -40,6 +46,8 @@ pub use column::Column;
|
|||
#[doc(no_inline)]
|
||||
pub use container::Container;
|
||||
#[doc(no_inline)]
|
||||
pub use helpers::*;
|
||||
#[doc(no_inline)]
|
||||
pub use image::Image;
|
||||
#[doc(no_inline)]
|
||||
pub use pane_grid::PaneGrid;
|
||||
|
|
@ -69,6 +77,12 @@ pub use text_input::TextInput;
|
|||
pub use toggler::Toggler;
|
||||
#[doc(no_inline)]
|
||||
pub use tooltip::Tooltip;
|
||||
#[doc(no_inline)]
|
||||
pub use tree::Tree;
|
||||
|
||||
pub use action::Action;
|
||||
pub use id::Id;
|
||||
pub use operation::Operation;
|
||||
|
||||
use crate::event::{self, Event};
|
||||
use crate::layout;
|
||||
|
|
@ -109,12 +123,10 @@ where
|
|||
/// Returns the height of the [`Widget`].
|
||||
fn height(&self) -> Length;
|
||||
|
||||
/// Returns the [`Node`] of the [`Widget`].
|
||||
/// Returns the [`layout::Node`] of the [`Widget`].
|
||||
///
|
||||
/// This [`Node`] is used by the runtime to compute the [`Layout`] of the
|
||||
/// This [`layout::Node`] is used by the runtime to compute the [`Layout`] of the
|
||||
/// user interface.
|
||||
///
|
||||
/// [`Node`]: layout::Node
|
||||
fn layout(
|
||||
&self,
|
||||
renderer: &Renderer,
|
||||
|
|
@ -124,6 +136,7 @@ where
|
|||
/// Draws the [`Widget`] using the associated `Renderer`.
|
||||
fn draw(
|
||||
&self,
|
||||
state: &Tree,
|
||||
renderer: &mut Renderer,
|
||||
theme: &Renderer::Theme,
|
||||
style: &renderer::Style,
|
||||
|
|
@ -132,20 +145,43 @@ where
|
|||
viewport: &Rectangle,
|
||||
);
|
||||
|
||||
/// Processes a runtime [`Event`].
|
||||
/// Returns the [`Tag`] of the [`Widget`].
|
||||
///
|
||||
/// It receives:
|
||||
/// * an [`Event`] describing user interaction
|
||||
/// * the computed [`Layout`] of the [`Widget`]
|
||||
/// * the current cursor position
|
||||
/// * a mutable `Message` list, allowing the [`Widget`] to produce
|
||||
/// new messages based on user interaction.
|
||||
/// * the `Renderer`
|
||||
/// * a [`Clipboard`], if available
|
||||
/// [`Tag`]: tree::Tag
|
||||
fn tag(&self) -> tree::Tag {
|
||||
tree::Tag::stateless()
|
||||
}
|
||||
|
||||
/// Returns the [`State`] of the [`Widget`].
|
||||
///
|
||||
/// [`State`]: tree::State
|
||||
fn state(&self) -> tree::State {
|
||||
tree::State::None
|
||||
}
|
||||
|
||||
/// Returns the state [`Tree`] of the children of the [`Widget`].
|
||||
fn children(&self) -> Vec<Tree> {
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
/// Reconciliates the [`Widget`] with the provided [`Tree`].
|
||||
fn diff(&self, _tree: &mut Tree) {}
|
||||
|
||||
/// Applies an [`Operation`] to the [`Widget`].
|
||||
fn operate(
|
||||
&self,
|
||||
_state: &mut Tree,
|
||||
_layout: Layout<'_>,
|
||||
_operation: &mut dyn Operation<Message>,
|
||||
) {
|
||||
}
|
||||
|
||||
/// Processes a runtime [`Event`].
|
||||
///
|
||||
/// By default, it does nothing.
|
||||
fn on_event(
|
||||
&mut self,
|
||||
_state: &mut Tree,
|
||||
_event: Event,
|
||||
_layout: Layout<'_>,
|
||||
_cursor_position: Point,
|
||||
|
|
@ -161,6 +197,7 @@ where
|
|||
/// By default, it returns [`mouse::Interaction::Idle`].
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
_state: &Tree,
|
||||
_layout: Layout<'_>,
|
||||
_cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
|
|
@ -170,11 +207,12 @@ where
|
|||
}
|
||||
|
||||
/// Returns the overlay of the [`Widget`], if there is any.
|
||||
fn overlay(
|
||||
&mut self,
|
||||
fn overlay<'a>(
|
||||
&'a self,
|
||||
_state: &'a mut Tree,
|
||||
_layout: Layout<'_>,
|
||||
_renderer: &Renderer,
|
||||
) -> Option<overlay::Element<'_, Message, Renderer>> {
|
||||
) -> Option<overlay::Element<'a, Message, Renderer>> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
|
|
|||
88
native/src/widget/action.rs
Normal file
88
native/src/widget/action.rs
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
use crate::widget::operation::{self, Operation};
|
||||
use crate::widget::Id;
|
||||
|
||||
use iced_futures::MaybeSend;
|
||||
|
||||
/// An operation to be performed on the widget tree.
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Action<T>(Box<dyn Operation<T>>);
|
||||
|
||||
impl<T> Action<T> {
|
||||
/// Creates a new [`Action`] with the given [`Operation`].
|
||||
pub fn new(operation: impl Operation<T> + 'static) -> Self {
|
||||
Self(Box::new(operation))
|
||||
}
|
||||
|
||||
/// Maps the output of an [`Action`] using the given function.
|
||||
pub fn map<A>(
|
||||
self,
|
||||
f: impl Fn(T) -> A + 'static + MaybeSend + Sync,
|
||||
) -> Action<A>
|
||||
where
|
||||
T: 'static,
|
||||
A: 'static,
|
||||
{
|
||||
Action(Box::new(Map {
|
||||
operation: self.0,
|
||||
f: Box::new(f),
|
||||
}))
|
||||
}
|
||||
|
||||
/// Consumes the [`Action`] and returns the internal [`Operation`].
|
||||
pub fn into_operation(self) -> Box<dyn Operation<T>> {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(missing_debug_implementations)]
|
||||
struct Map<A, B> {
|
||||
operation: Box<dyn Operation<A>>,
|
||||
f: Box<dyn Fn(A) -> B>,
|
||||
}
|
||||
|
||||
impl<A, B> Operation<B> for Map<A, B>
|
||||
where
|
||||
A: 'static,
|
||||
B: 'static,
|
||||
{
|
||||
fn container(
|
||||
&mut self,
|
||||
id: Option<&Id>,
|
||||
operate_on_children: &mut dyn FnMut(&mut dyn Operation<B>),
|
||||
) {
|
||||
struct MapRef<'a, A, B> {
|
||||
operation: &'a mut dyn Operation<A>,
|
||||
f: &'a dyn Fn(A) -> B,
|
||||
}
|
||||
|
||||
impl<'a, A, B> Operation<B> for MapRef<'a, A, B> {
|
||||
fn container(
|
||||
&mut self,
|
||||
id: Option<&Id>,
|
||||
operate_on_children: &mut dyn FnMut(&mut dyn Operation<B>),
|
||||
) {
|
||||
let Self { operation, f } = self;
|
||||
|
||||
operation.container(id, &mut |operation| {
|
||||
operate_on_children(&mut MapRef { operation, f });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let Self { operation, f } = self;
|
||||
|
||||
MapRef {
|
||||
operation: operation.as_mut(),
|
||||
f,
|
||||
}
|
||||
.container(id, operate_on_children);
|
||||
}
|
||||
|
||||
fn focusable(
|
||||
&mut self,
|
||||
state: &mut dyn operation::Focusable,
|
||||
id: Option<&Id>,
|
||||
) {
|
||||
self.operation.focusable(state, id);
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,8 @@ use crate::mouse;
|
|||
use crate::overlay;
|
||||
use crate::renderer;
|
||||
use crate::touch;
|
||||
use crate::widget::tree::{self, Tree};
|
||||
use crate::widget::Operation;
|
||||
use crate::{
|
||||
Background, Clipboard, Color, Element, Layout, Length, Padding, Point,
|
||||
Rectangle, Shell, Vector, Widget,
|
||||
|
|
@ -17,8 +19,6 @@ pub use iced_style::button::{Appearance, StyleSheet};
|
|||
/// A generic widget that produces a message when pressed.
|
||||
///
|
||||
/// ```
|
||||
/// # use iced_native::widget::{button, Text};
|
||||
/// #
|
||||
/// # type Button<'a, Message> =
|
||||
/// # iced_native::widget::Button<'a, Message, iced_native::renderer::Null>;
|
||||
/// #
|
||||
|
|
@ -27,17 +27,13 @@ pub use iced_style::button::{Appearance, StyleSheet};
|
|||
/// ButtonPressed,
|
||||
/// }
|
||||
///
|
||||
/// let mut state = button::State::new();
|
||||
/// let button = Button::new(&mut state, Text::new("Press me!"))
|
||||
/// .on_press(Message::ButtonPressed);
|
||||
/// let button = Button::new("Press me!").on_press(Message::ButtonPressed);
|
||||
/// ```
|
||||
///
|
||||
/// If a [`Button::on_press`] handler is not set, the resulting [`Button`] will
|
||||
/// be disabled:
|
||||
///
|
||||
/// ```
|
||||
/// # use iced_native::widget::{button, Text};
|
||||
/// #
|
||||
/// # type Button<'a, Message> =
|
||||
/// # iced_native::widget::Button<'a, Message, iced_native::renderer::Null>;
|
||||
/// #
|
||||
|
|
@ -46,12 +42,12 @@ pub use iced_style::button::{Appearance, StyleSheet};
|
|||
/// ButtonPressed,
|
||||
/// }
|
||||
///
|
||||
/// fn disabled_button(state: &mut button::State) -> Button<'_, Message> {
|
||||
/// Button::new(state, Text::new("I'm disabled!"))
|
||||
/// fn disabled_button<'a>() -> Button<'a, Message> {
|
||||
/// Button::new("I'm disabled!")
|
||||
/// }
|
||||
///
|
||||
/// fn enabled_button(state: &mut button::State) -> Button<'_, Message> {
|
||||
/// disabled_button(state).on_press(Message::ButtonPressed)
|
||||
/// fn enabled_button<'a>() -> Button<'a, Message> {
|
||||
/// disabled_button().on_press(Message::ButtonPressed)
|
||||
/// }
|
||||
/// ```
|
||||
#[allow(missing_debug_implementations)]
|
||||
|
|
@ -60,7 +56,6 @@ where
|
|||
Renderer: crate::Renderer,
|
||||
Renderer::Theme: StyleSheet,
|
||||
{
|
||||
state: &'a mut State,
|
||||
content: Element<'a, Message, Renderer>,
|
||||
on_press: Option<Message>,
|
||||
width: Length,
|
||||
|
|
@ -71,24 +66,18 @@ where
|
|||
|
||||
impl<'a, Message, Renderer> Button<'a, Message, Renderer>
|
||||
where
|
||||
Message: Clone,
|
||||
Renderer: crate::Renderer,
|
||||
Renderer::Theme: StyleSheet,
|
||||
{
|
||||
/// Creates a new [`Button`] with some local [`State`] and the given
|
||||
/// content.
|
||||
pub fn new<E>(state: &'a mut State, content: E) -> Self
|
||||
where
|
||||
E: Into<Element<'a, Message, Renderer>>,
|
||||
{
|
||||
/// Creates a new [`Button`] with the given content.
|
||||
pub fn new(content: impl Into<Element<'a, Message, Renderer>>) -> Self {
|
||||
Button {
|
||||
state,
|
||||
content: content.into(),
|
||||
on_press: None,
|
||||
width: Length::Shrink,
|
||||
height: Length::Shrink,
|
||||
padding: Padding::new(5),
|
||||
style: Default::default(),
|
||||
style: <Renderer::Theme as StyleSheet>::Style::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -111,13 +100,14 @@ where
|
|||
}
|
||||
|
||||
/// Sets the message that will be produced when the [`Button`] is pressed.
|
||||
/// If on_press isn't set, button will be disabled.
|
||||
///
|
||||
/// Unless `on_press` is called, the [`Button`] will be disabled.
|
||||
pub fn on_press(mut self, msg: Message) -> Self {
|
||||
self.on_press = Some(msg);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the style of this [`Button`].
|
||||
/// Sets the style variant of this [`Button`].
|
||||
pub fn style(
|
||||
mut self,
|
||||
style: <Renderer::Theme as StyleSheet>::Style,
|
||||
|
|
@ -127,6 +117,174 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> Widget<Message, Renderer>
|
||||
for Button<'a, Message, Renderer>
|
||||
where
|
||||
Message: 'a + Clone,
|
||||
Renderer: 'a + crate::Renderer,
|
||||
Renderer::Theme: StyleSheet,
|
||||
{
|
||||
fn tag(&self) -> tree::Tag {
|
||||
tree::Tag::of::<State>()
|
||||
}
|
||||
|
||||
fn state(&self) -> tree::State {
|
||||
tree::State::new(State::new())
|
||||
}
|
||||
|
||||
fn children(&self) -> Vec<Tree> {
|
||||
vec![Tree::new(&self.content)]
|
||||
}
|
||||
|
||||
fn diff(&self, tree: &mut Tree) {
|
||||
tree.diff_children(std::slice::from_ref(&self.content))
|
||||
}
|
||||
|
||||
fn width(&self) -> Length {
|
||||
self.width
|
||||
}
|
||||
|
||||
fn height(&self) -> Length {
|
||||
self.height
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
layout(
|
||||
renderer,
|
||||
limits,
|
||||
self.width,
|
||||
self.height,
|
||||
self.padding,
|
||||
|renderer, limits| {
|
||||
self.content.as_widget().layout(renderer, limits)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn operate(
|
||||
&self,
|
||||
tree: &mut Tree,
|
||||
layout: Layout<'_>,
|
||||
operation: &mut dyn Operation<Message>,
|
||||
) {
|
||||
operation.container(None, &mut |operation| {
|
||||
self.content.as_widget().operate(
|
||||
&mut tree.children[0],
|
||||
layout.children().next().unwrap(),
|
||||
operation,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
event: Event,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
renderer: &Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
) -> event::Status {
|
||||
if let event::Status::Captured = self.content.as_widget_mut().on_event(
|
||||
&mut tree.children[0],
|
||||
event.clone(),
|
||||
layout.children().next().unwrap(),
|
||||
cursor_position,
|
||||
renderer,
|
||||
clipboard,
|
||||
shell,
|
||||
) {
|
||||
return event::Status::Captured;
|
||||
}
|
||||
|
||||
update(
|
||||
event,
|
||||
layout,
|
||||
cursor_position,
|
||||
shell,
|
||||
&self.on_press,
|
||||
|| tree.state.downcast_mut::<State>(),
|
||||
)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
renderer: &mut Renderer,
|
||||
theme: &Renderer::Theme,
|
||||
_style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
) {
|
||||
let bounds = layout.bounds();
|
||||
let content_layout = layout.children().next().unwrap();
|
||||
|
||||
let styling = draw(
|
||||
renderer,
|
||||
bounds,
|
||||
cursor_position,
|
||||
self.on_press.is_some(),
|
||||
theme,
|
||||
self.style,
|
||||
|| tree.state.downcast_ref::<State>(),
|
||||
);
|
||||
|
||||
self.content.as_widget().draw(
|
||||
&tree.children[0],
|
||||
renderer,
|
||||
theme,
|
||||
&renderer::Style {
|
||||
text_color: styling.text_color,
|
||||
},
|
||||
content_layout,
|
||||
cursor_position,
|
||||
&bounds,
|
||||
);
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
_tree: &Tree,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
_renderer: &Renderer,
|
||||
) -> mouse::Interaction {
|
||||
mouse_interaction(layout, cursor_position, self.on_press.is_some())
|
||||
}
|
||||
|
||||
fn overlay<'b>(
|
||||
&'b self,
|
||||
tree: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
) -> Option<overlay::Element<'b, Message, Renderer>> {
|
||||
self.content.as_widget().overlay(
|
||||
&mut tree.children[0],
|
||||
layout.children().next().unwrap(),
|
||||
renderer,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> From<Button<'a, Message, Renderer>>
|
||||
for Element<'a, Message, Renderer>
|
||||
where
|
||||
Message: Clone + 'a,
|
||||
Renderer: crate::Renderer + 'a,
|
||||
Renderer::Theme: StyleSheet,
|
||||
{
|
||||
fn from(button: Button<'a, Message, Renderer>) -> Self {
|
||||
Self::new(button)
|
||||
}
|
||||
}
|
||||
|
||||
/// The local state of a [`Button`].
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub struct State {
|
||||
|
|
@ -292,131 +450,3 @@ pub fn mouse_interaction(
|
|||
mouse::Interaction::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> Widget<Message, Renderer>
|
||||
for Button<'a, Message, Renderer>
|
||||
where
|
||||
Message: Clone,
|
||||
Renderer: crate::Renderer,
|
||||
Renderer::Theme: StyleSheet,
|
||||
{
|
||||
fn width(&self) -> Length {
|
||||
self.width
|
||||
}
|
||||
|
||||
fn height(&self) -> Length {
|
||||
self.height
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
layout(
|
||||
renderer,
|
||||
limits,
|
||||
self.width,
|
||||
self.height,
|
||||
self.padding,
|
||||
|renderer, limits| self.content.layout(renderer, limits),
|
||||
)
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
&mut self,
|
||||
event: Event,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
renderer: &Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
) -> event::Status {
|
||||
if let event::Status::Captured = self.content.on_event(
|
||||
event.clone(),
|
||||
layout.children().next().unwrap(),
|
||||
cursor_position,
|
||||
renderer,
|
||||
clipboard,
|
||||
shell,
|
||||
) {
|
||||
return event::Status::Captured;
|
||||
}
|
||||
|
||||
update(
|
||||
event,
|
||||
layout,
|
||||
cursor_position,
|
||||
shell,
|
||||
&self.on_press,
|
||||
|| &mut self.state,
|
||||
)
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
_renderer: &Renderer,
|
||||
) -> mouse::Interaction {
|
||||
mouse_interaction(layout, cursor_position, self.on_press.is_some())
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
renderer: &mut Renderer,
|
||||
theme: &Renderer::Theme,
|
||||
_style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
) {
|
||||
let bounds = layout.bounds();
|
||||
let content_layout = layout.children().next().unwrap();
|
||||
|
||||
let styling = draw(
|
||||
renderer,
|
||||
bounds,
|
||||
cursor_position,
|
||||
self.on_press.is_some(),
|
||||
theme,
|
||||
self.style,
|
||||
|| self.state,
|
||||
);
|
||||
|
||||
self.content.draw(
|
||||
renderer,
|
||||
theme,
|
||||
&renderer::Style {
|
||||
text_color: styling.text_color,
|
||||
},
|
||||
content_layout,
|
||||
cursor_position,
|
||||
&bounds,
|
||||
);
|
||||
}
|
||||
|
||||
fn overlay(
|
||||
&mut self,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
) -> Option<overlay::Element<'_, Message, Renderer>> {
|
||||
self.content
|
||||
.overlay(layout.children().next().unwrap(), renderer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> From<Button<'a, Message, Renderer>>
|
||||
for Element<'a, Message, Renderer>
|
||||
where
|
||||
Message: 'a + Clone,
|
||||
Renderer: 'a + crate::Renderer,
|
||||
Renderer::Theme: StyleSheet,
|
||||
{
|
||||
fn from(
|
||||
button: Button<'a, Message, Renderer>,
|
||||
) -> Element<'a, Message, Renderer> {
|
||||
Element::new(button)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use crate::mouse;
|
|||
use crate::renderer;
|
||||
use crate::text;
|
||||
use crate::touch;
|
||||
use crate::widget::{self, Row, Text};
|
||||
use crate::widget::{self, Row, Text, Tree};
|
||||
use crate::{
|
||||
Alignment, Clipboard, Element, Layout, Length, Point, Rectangle, Shell,
|
||||
Widget,
|
||||
|
|
@ -168,6 +168,7 @@ where
|
|||
|
||||
fn on_event(
|
||||
&mut self,
|
||||
_tree: &mut Tree,
|
||||
event: Event,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
|
|
@ -194,6 +195,7 @@ where
|
|||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
_tree: &Tree,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
|
|
@ -208,6 +210,7 @@ where
|
|||
|
||||
fn draw(
|
||||
&self,
|
||||
_tree: &Tree,
|
||||
renderer: &mut Renderer,
|
||||
theme: &Renderer::Theme,
|
||||
style: &renderer::Style,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ use crate::layout;
|
|||
use crate::mouse;
|
||||
use crate::overlay;
|
||||
use crate::renderer;
|
||||
use crate::widget::{Operation, Tree};
|
||||
use crate::{
|
||||
Alignment, Clipboard, Element, Layout, Length, Padding, Point, Rectangle,
|
||||
Shell, Widget,
|
||||
|
|
@ -19,7 +20,6 @@ pub struct Column<'a, Message, Renderer> {
|
|||
width: Length,
|
||||
height: Length,
|
||||
max_width: u32,
|
||||
max_height: u32,
|
||||
align_items: Alignment,
|
||||
children: Vec<Element<'a, Message, Renderer>>,
|
||||
}
|
||||
|
|
@ -40,7 +40,6 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> {
|
|||
width: Length::Shrink,
|
||||
height: Length::Shrink,
|
||||
max_width: u32::MAX,
|
||||
max_height: u32::MAX,
|
||||
align_items: Alignment::Start,
|
||||
children,
|
||||
}
|
||||
|
|
@ -80,12 +79,6 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> {
|
|||
self
|
||||
}
|
||||
|
||||
/// Sets the maximum height of the [`Column`] in pixels.
|
||||
pub fn max_height(mut self, max_height: u32) -> Self {
|
||||
self.max_height = max_height;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the horizontal alignment of the contents of the [`Column`] .
|
||||
pub fn align_items(mut self, align: Alignment) -> Self {
|
||||
self.align_items = align;
|
||||
|
|
@ -93,10 +86,10 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> {
|
|||
}
|
||||
|
||||
/// Adds an element to the [`Column`].
|
||||
pub fn push<E>(mut self, child: E) -> Self
|
||||
where
|
||||
E: Into<Element<'a, Message, Renderer>>,
|
||||
{
|
||||
pub fn push(
|
||||
mut self,
|
||||
child: impl Into<Element<'a, Message, Renderer>>,
|
||||
) -> Self {
|
||||
self.children.push(child.into());
|
||||
self
|
||||
}
|
||||
|
|
@ -113,6 +106,14 @@ impl<'a, Message, Renderer> Widget<Message, Renderer>
|
|||
where
|
||||
Renderer: crate::Renderer,
|
||||
{
|
||||
fn children(&self) -> Vec<Tree> {
|
||||
self.children.iter().map(Tree::new).collect()
|
||||
}
|
||||
|
||||
fn diff(&self, tree: &mut Tree) {
|
||||
tree.diff_children(&self.children);
|
||||
}
|
||||
|
||||
fn width(&self) -> Length {
|
||||
self.width
|
||||
}
|
||||
|
|
@ -128,7 +129,6 @@ where
|
|||
) -> layout::Node {
|
||||
let limits = limits
|
||||
.max_width(self.max_width)
|
||||
.max_height(self.max_height)
|
||||
.width(self.width)
|
||||
.height(self.height);
|
||||
|
||||
|
|
@ -143,8 +143,26 @@ where
|
|||
)
|
||||
}
|
||||
|
||||
fn operate(
|
||||
&self,
|
||||
tree: &mut Tree,
|
||||
layout: Layout<'_>,
|
||||
operation: &mut dyn Operation<Message>,
|
||||
) {
|
||||
operation.container(None, &mut |operation| {
|
||||
self.children
|
||||
.iter()
|
||||
.zip(&mut tree.children)
|
||||
.zip(layout.children())
|
||||
.for_each(|((child, state), layout)| {
|
||||
child.as_widget().operate(state, layout, operation);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
event: Event,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
|
|
@ -154,9 +172,11 @@ where
|
|||
) -> event::Status {
|
||||
self.children
|
||||
.iter_mut()
|
||||
.zip(&mut tree.children)
|
||||
.zip(layout.children())
|
||||
.map(|(child, layout)| {
|
||||
child.widget.on_event(
|
||||
.map(|((child, state), layout)| {
|
||||
child.as_widget_mut().on_event(
|
||||
state,
|
||||
event.clone(),
|
||||
layout,
|
||||
cursor_position,
|
||||
|
|
@ -170,6 +190,7 @@ where
|
|||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
|
|
@ -177,9 +198,11 @@ where
|
|||
) -> mouse::Interaction {
|
||||
self.children
|
||||
.iter()
|
||||
.zip(&tree.children)
|
||||
.zip(layout.children())
|
||||
.map(|(child, layout)| {
|
||||
child.widget.mouse_interaction(
|
||||
.map(|((child, state), layout)| {
|
||||
child.as_widget().mouse_interaction(
|
||||
state,
|
||||
layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
|
|
@ -192,6 +215,7 @@ where
|
|||
|
||||
fn draw(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
renderer: &mut Renderer,
|
||||
theme: &Renderer::Theme,
|
||||
style: &renderer::Style,
|
||||
|
|
@ -199,8 +223,14 @@ where
|
|||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
) {
|
||||
for (child, layout) in self.children.iter().zip(layout.children()) {
|
||||
child.draw(
|
||||
for ((child, state), layout) in self
|
||||
.children
|
||||
.iter()
|
||||
.zip(&tree.children)
|
||||
.zip(layout.children())
|
||||
{
|
||||
child.as_widget().draw(
|
||||
state,
|
||||
renderer,
|
||||
theme,
|
||||
style,
|
||||
|
|
@ -211,30 +241,23 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
fn overlay(
|
||||
&mut self,
|
||||
fn overlay<'b>(
|
||||
&'b self,
|
||||
tree: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
) -> Option<overlay::Element<'_, Message, Renderer>> {
|
||||
self.children
|
||||
.iter_mut()
|
||||
.zip(layout.children())
|
||||
.filter_map(|(child, layout)| {
|
||||
child.widget.overlay(layout, renderer)
|
||||
})
|
||||
.next()
|
||||
) -> Option<overlay::Element<'b, Message, Renderer>> {
|
||||
overlay::from_children(&self.children, tree, layout, renderer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> From<Column<'a, Message, Renderer>>
|
||||
for Element<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: 'a + crate::Renderer,
|
||||
Message: 'a,
|
||||
Renderer: crate::Renderer + 'a,
|
||||
{
|
||||
fn from(
|
||||
column: Column<'a, Message, Renderer>,
|
||||
) -> Element<'a, Message, Renderer> {
|
||||
Element::new(column)
|
||||
fn from(column: Column<'a, Message, Renderer>) -> Self {
|
||||
Self::new(column)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ use crate::layout;
|
|||
use crate::mouse;
|
||||
use crate::overlay;
|
||||
use crate::renderer;
|
||||
use crate::widget::{Operation, Tree};
|
||||
use crate::{
|
||||
Background, Clipboard, Color, Element, Layout, Length, Padding, Point,
|
||||
Rectangle, Shell, Widget,
|
||||
|
|
@ -121,6 +122,159 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> Widget<Message, Renderer>
|
||||
for Container<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: crate::Renderer,
|
||||
Renderer::Theme: StyleSheet,
|
||||
{
|
||||
fn children(&self) -> Vec<Tree> {
|
||||
vec![Tree::new(&self.content)]
|
||||
}
|
||||
|
||||
fn diff(&self, tree: &mut Tree) {
|
||||
tree.diff_children(std::slice::from_ref(&self.content))
|
||||
}
|
||||
|
||||
fn width(&self) -> Length {
|
||||
self.width
|
||||
}
|
||||
|
||||
fn height(&self) -> Length {
|
||||
self.height
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
layout(
|
||||
renderer,
|
||||
limits,
|
||||
self.width,
|
||||
self.height,
|
||||
self.max_width,
|
||||
self.max_height,
|
||||
self.padding,
|
||||
self.horizontal_alignment,
|
||||
self.vertical_alignment,
|
||||
|renderer, limits| {
|
||||
self.content.as_widget().layout(renderer, limits)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn operate(
|
||||
&self,
|
||||
tree: &mut Tree,
|
||||
layout: Layout<'_>,
|
||||
operation: &mut dyn Operation<Message>,
|
||||
) {
|
||||
operation.container(None, &mut |operation| {
|
||||
self.content.as_widget().operate(
|
||||
&mut tree.children[0],
|
||||
layout.children().next().unwrap(),
|
||||
operation,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
event: Event,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
renderer: &Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
) -> event::Status {
|
||||
self.content.as_widget_mut().on_event(
|
||||
&mut tree.children[0],
|
||||
event,
|
||||
layout.children().next().unwrap(),
|
||||
cursor_position,
|
||||
renderer,
|
||||
clipboard,
|
||||
shell,
|
||||
)
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
renderer: &Renderer,
|
||||
) -> mouse::Interaction {
|
||||
self.content.as_widget().mouse_interaction(
|
||||
&tree.children[0],
|
||||
layout.children().next().unwrap(),
|
||||
cursor_position,
|
||||
viewport,
|
||||
renderer,
|
||||
)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
renderer: &mut Renderer,
|
||||
theme: &Renderer::Theme,
|
||||
renderer_style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
) {
|
||||
let style = theme.appearance(self.style);
|
||||
|
||||
draw_background(renderer, &style, layout.bounds());
|
||||
|
||||
self.content.as_widget().draw(
|
||||
&tree.children[0],
|
||||
renderer,
|
||||
theme,
|
||||
&renderer::Style {
|
||||
text_color: style
|
||||
.text_color
|
||||
.unwrap_or(renderer_style.text_color),
|
||||
},
|
||||
layout.children().next().unwrap(),
|
||||
cursor_position,
|
||||
viewport,
|
||||
);
|
||||
}
|
||||
|
||||
fn overlay<'b>(
|
||||
&'b self,
|
||||
tree: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
) -> Option<overlay::Element<'b, Message, Renderer>> {
|
||||
self.content.as_widget().overlay(
|
||||
&mut tree.children[0],
|
||||
layout.children().next().unwrap(),
|
||||
renderer,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> From<Container<'a, Message, Renderer>>
|
||||
for Element<'a, Message, Renderer>
|
||||
where
|
||||
Message: 'a,
|
||||
Renderer: 'a + crate::Renderer,
|
||||
Renderer::Theme: StyleSheet,
|
||||
{
|
||||
fn from(
|
||||
column: Container<'a, Message, Renderer>,
|
||||
) -> Element<'a, Message, Renderer> {
|
||||
Element::new(column)
|
||||
}
|
||||
}
|
||||
|
||||
/// Computes the layout of a [`Container`].
|
||||
pub fn layout<Renderer>(
|
||||
renderer: &Renderer,
|
||||
|
|
@ -155,110 +309,6 @@ pub fn layout<Renderer>(
|
|||
layout::Node::with_children(size.pad(padding), vec![content])
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> Widget<Message, Renderer>
|
||||
for Container<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: crate::Renderer,
|
||||
Renderer::Theme: StyleSheet,
|
||||
{
|
||||
fn width(&self) -> Length {
|
||||
self.width
|
||||
}
|
||||
|
||||
fn height(&self) -> Length {
|
||||
self.height
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
layout(
|
||||
renderer,
|
||||
limits,
|
||||
self.width,
|
||||
self.height,
|
||||
self.max_width,
|
||||
self.max_height,
|
||||
self.padding,
|
||||
self.horizontal_alignment,
|
||||
self.vertical_alignment,
|
||||
|renderer, limits| self.content.layout(renderer, limits),
|
||||
)
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
&mut self,
|
||||
event: Event,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
renderer: &Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
) -> event::Status {
|
||||
self.content.widget.on_event(
|
||||
event,
|
||||
layout.children().next().unwrap(),
|
||||
cursor_position,
|
||||
renderer,
|
||||
clipboard,
|
||||
shell,
|
||||
)
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
renderer: &Renderer,
|
||||
) -> mouse::Interaction {
|
||||
self.content.widget.mouse_interaction(
|
||||
layout.children().next().unwrap(),
|
||||
cursor_position,
|
||||
viewport,
|
||||
renderer,
|
||||
)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
renderer: &mut Renderer,
|
||||
theme: &Renderer::Theme,
|
||||
renderer_style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
) {
|
||||
let style = theme.appearance(self.style);
|
||||
|
||||
draw_background(renderer, &style, layout.bounds());
|
||||
|
||||
self.content.draw(
|
||||
renderer,
|
||||
theme,
|
||||
&renderer::Style {
|
||||
text_color: style
|
||||
.text_color
|
||||
.unwrap_or(renderer_style.text_color),
|
||||
},
|
||||
layout.children().next().unwrap(),
|
||||
cursor_position,
|
||||
viewport,
|
||||
);
|
||||
}
|
||||
|
||||
fn overlay(
|
||||
&mut self,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
) -> Option<overlay::Element<'_, Message, Renderer>> {
|
||||
self.content
|
||||
.overlay(layout.children().next().unwrap(), renderer)
|
||||
}
|
||||
}
|
||||
|
||||
/// Draws the background of a [`Container`] given its [`Style`] and its `bounds`.
|
||||
pub fn draw_background<Renderer>(
|
||||
renderer: &mut Renderer,
|
||||
|
|
@ -281,17 +331,3 @@ pub fn draw_background<Renderer>(
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> From<Container<'a, Message, Renderer>>
|
||||
for Element<'a, Message, Renderer>
|
||||
where
|
||||
Message: 'a,
|
||||
Renderer: 'a + crate::Renderer,
|
||||
Renderer::Theme: StyleSheet,
|
||||
{
|
||||
fn from(
|
||||
column: Container<'a, Message, Renderer>,
|
||||
) -> Element<'a, Message, Renderer> {
|
||||
Element::new(column)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
283
native/src/widget/helpers.rs
Normal file
283
native/src/widget/helpers.rs
Normal file
|
|
@ -0,0 +1,283 @@
|
|||
//! Helper functions to create pure widgets.
|
||||
use crate::widget;
|
||||
use crate::{Element, Length};
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
/// Creates a [`Column`] with the given children.
|
||||
///
|
||||
/// [`Column`]: widget::Column
|
||||
#[macro_export]
|
||||
macro_rules! column {
|
||||
() => (
|
||||
$crate::widget::Column::new()
|
||||
);
|
||||
($($x:expr),+ $(,)?) => (
|
||||
$crate::widget::Column::with_children(vec![$($crate::Element::from($x)),+])
|
||||
);
|
||||
}
|
||||
|
||||
/// Creates a [Row`] with the given children.
|
||||
///
|
||||
/// [`Row`]: widget::Row
|
||||
#[macro_export]
|
||||
macro_rules! row {
|
||||
() => (
|
||||
$crate::widget::Row::new()
|
||||
);
|
||||
($($x:expr),+ $(,)?) => (
|
||||
$crate::widget::Row::with_children(vec![$($crate::Element::from($x)),+])
|
||||
);
|
||||
}
|
||||
|
||||
/// Creates a new [`Container`] with the provided content.
|
||||
///
|
||||
/// [`Container`]: widget::Container
|
||||
pub fn container<'a, Message, Renderer>(
|
||||
content: impl Into<Element<'a, Message, Renderer>>,
|
||||
) -> widget::Container<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: crate::Renderer,
|
||||
Renderer::Theme: widget::container::StyleSheet,
|
||||
{
|
||||
widget::Container::new(content)
|
||||
}
|
||||
|
||||
/// Creates a new [`Column`] with the given children.
|
||||
///
|
||||
/// [`Column`]: widget::Column
|
||||
pub fn column<Message, Renderer>(
|
||||
children: Vec<Element<'_, Message, Renderer>>,
|
||||
) -> widget::Column<'_, Message, Renderer> {
|
||||
widget::Column::with_children(children)
|
||||
}
|
||||
|
||||
/// Creates a new [`Row`] with the given children.
|
||||
///
|
||||
/// [`Row`]: widget::Row
|
||||
pub fn row<Message, Renderer>(
|
||||
children: Vec<Element<'_, Message, Renderer>>,
|
||||
) -> widget::Row<'_, Message, Renderer> {
|
||||
widget::Row::with_children(children)
|
||||
}
|
||||
|
||||
/// Creates a new [`Scrollable`] with the provided content.
|
||||
///
|
||||
/// [`Scrollable`]: widget::Scrollable
|
||||
pub fn scrollable<'a, Message, Renderer>(
|
||||
content: impl Into<Element<'a, Message, Renderer>>,
|
||||
) -> widget::Scrollable<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: crate::Renderer,
|
||||
Renderer::Theme: widget::scrollable::StyleSheet,
|
||||
{
|
||||
widget::Scrollable::new(content)
|
||||
}
|
||||
|
||||
/// Creates a new [`Button`] with the provided content.
|
||||
///
|
||||
/// [`Button`]: widget::Button
|
||||
pub fn button<'a, Message, Renderer>(
|
||||
content: impl Into<Element<'a, Message, Renderer>>,
|
||||
) -> widget::Button<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: crate::Renderer,
|
||||
Renderer::Theme: widget::button::StyleSheet,
|
||||
{
|
||||
widget::Button::new(content)
|
||||
}
|
||||
|
||||
/// Creates a new [`Tooltip`] with the provided content, tooltip text, and [`tooltip::Position`].
|
||||
///
|
||||
/// [`Tooltip`]: widget::Tooltip
|
||||
/// [`tooltip::Position`]: widget::tooltip::Position
|
||||
pub fn tooltip<'a, Message, Renderer>(
|
||||
content: impl Into<Element<'a, Message, Renderer>>,
|
||||
tooltip: impl ToString,
|
||||
position: widget::tooltip::Position,
|
||||
) -> widget::Tooltip<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: crate::text::Renderer,
|
||||
Renderer::Theme: widget::container::StyleSheet + widget::text::StyleSheet,
|
||||
{
|
||||
widget::Tooltip::new(content, tooltip, position)
|
||||
}
|
||||
|
||||
/// Creates a new [`Text`] widget with the provided content.
|
||||
///
|
||||
/// [`Text`]: widget::Text
|
||||
pub fn text<Renderer>(text: impl ToString) -> widget::Text<Renderer>
|
||||
where
|
||||
Renderer: crate::text::Renderer,
|
||||
Renderer::Theme: widget::text::StyleSheet,
|
||||
{
|
||||
widget::Text::new(text)
|
||||
}
|
||||
|
||||
/// Creates a new [`Checkbox`].
|
||||
///
|
||||
/// [`Checkbox`]: widget::Checkbox
|
||||
pub fn checkbox<'a, Message, Renderer>(
|
||||
label: impl Into<String>,
|
||||
is_checked: bool,
|
||||
f: impl Fn(bool) -> Message + 'a,
|
||||
) -> widget::Checkbox<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: crate::text::Renderer,
|
||||
Renderer::Theme: widget::checkbox::StyleSheet + widget::text::StyleSheet,
|
||||
{
|
||||
widget::Checkbox::new(is_checked, label, f)
|
||||
}
|
||||
|
||||
/// Creates a new [`Radio`].
|
||||
///
|
||||
/// [`Radio`]: widget::Radio
|
||||
pub fn radio<Message, Renderer, V>(
|
||||
label: impl Into<String>,
|
||||
value: V,
|
||||
selected: Option<V>,
|
||||
on_click: impl FnOnce(V) -> Message,
|
||||
) -> widget::Radio<Message, Renderer>
|
||||
where
|
||||
Message: Clone,
|
||||
Renderer: crate::text::Renderer,
|
||||
Renderer::Theme: widget::radio::StyleSheet,
|
||||
V: Copy + Eq,
|
||||
{
|
||||
widget::Radio::new(value, label, selected, on_click)
|
||||
}
|
||||
|
||||
/// Creates a new [`Toggler`].
|
||||
///
|
||||
/// [`Toggler`]: widget::Toggler
|
||||
pub fn toggler<'a, Message, Renderer>(
|
||||
label: impl Into<Option<String>>,
|
||||
is_checked: bool,
|
||||
f: impl Fn(bool) -> Message + 'a,
|
||||
) -> widget::Toggler<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: crate::text::Renderer,
|
||||
Renderer::Theme: widget::toggler::StyleSheet,
|
||||
{
|
||||
widget::Toggler::new(is_checked, label, f)
|
||||
}
|
||||
|
||||
/// Creates a new [`TextInput`].
|
||||
///
|
||||
/// [`TextInput`]: widget::TextInput
|
||||
pub fn text_input<'a, Message, Renderer>(
|
||||
placeholder: &str,
|
||||
value: &str,
|
||||
on_change: impl Fn(String) -> Message + 'a,
|
||||
) -> widget::TextInput<'a, Message, Renderer>
|
||||
where
|
||||
Message: Clone,
|
||||
Renderer: crate::text::Renderer,
|
||||
Renderer::Theme: widget::text_input::StyleSheet,
|
||||
{
|
||||
widget::TextInput::new(placeholder, value, on_change)
|
||||
}
|
||||
|
||||
/// Creates a new [`Slider`].
|
||||
///
|
||||
/// [`Slider`]: widget::Slider
|
||||
pub fn slider<'a, T, Message, Renderer>(
|
||||
range: std::ops::RangeInclusive<T>,
|
||||
value: T,
|
||||
on_change: impl Fn(T) -> Message + 'a,
|
||||
) -> widget::Slider<'a, T, Message, Renderer>
|
||||
where
|
||||
T: Copy + From<u8> + std::cmp::PartialOrd,
|
||||
Message: Clone,
|
||||
Renderer: crate::Renderer,
|
||||
Renderer::Theme: widget::slider::StyleSheet,
|
||||
{
|
||||
widget::Slider::new(range, value, on_change)
|
||||
}
|
||||
|
||||
/// Creates a new [`PickList`].
|
||||
///
|
||||
/// [`PickList`]: widget::PickList
|
||||
pub fn pick_list<'a, Message, Renderer, T>(
|
||||
options: impl Into<Cow<'a, [T]>>,
|
||||
selected: Option<T>,
|
||||
on_selected: impl Fn(T) -> Message + 'a,
|
||||
) -> widget::PickList<'a, T, Message, Renderer>
|
||||
where
|
||||
T: ToString + Eq + 'static,
|
||||
[T]: ToOwned<Owned = Vec<T>>,
|
||||
Renderer: crate::text::Renderer,
|
||||
Renderer::Theme: widget::pick_list::StyleSheet,
|
||||
{
|
||||
widget::PickList::new(options, selected, on_selected)
|
||||
}
|
||||
|
||||
/// Creates a new [`Image`].
|
||||
///
|
||||
/// [`Image`]: widget::Image
|
||||
pub fn image<Handle>(handle: impl Into<Handle>) -> widget::Image<Handle> {
|
||||
widget::Image::new(handle.into())
|
||||
}
|
||||
|
||||
/// Creates a new horizontal [`Space`] with the given [`Length`].
|
||||
///
|
||||
/// [`Space`]: widget::Space
|
||||
pub fn horizontal_space(width: Length) -> widget::Space {
|
||||
widget::Space::with_width(width)
|
||||
}
|
||||
|
||||
/// Creates a new vertical [`Space`] with the given [`Length`].
|
||||
///
|
||||
/// [`Space`]: widget::Space
|
||||
pub fn vertical_space(height: Length) -> widget::Space {
|
||||
widget::Space::with_height(height)
|
||||
}
|
||||
|
||||
/// Creates a horizontal [`Rule`] with the given height.
|
||||
///
|
||||
/// [`Rule`]: widget::Rule
|
||||
pub fn horizontal_rule<Renderer>(height: u16) -> widget::Rule<Renderer>
|
||||
where
|
||||
Renderer: crate::Renderer,
|
||||
Renderer::Theme: widget::rule::StyleSheet,
|
||||
{
|
||||
widget::Rule::horizontal(height)
|
||||
}
|
||||
|
||||
/// Creates a vertical [`Rule`] with the given width.
|
||||
///
|
||||
/// [`Rule`]: widget::Rule
|
||||
pub fn vertical_rule<Renderer>(width: u16) -> widget::Rule<Renderer>
|
||||
where
|
||||
Renderer: crate::Renderer,
|
||||
Renderer::Theme: widget::rule::StyleSheet,
|
||||
{
|
||||
widget::Rule::vertical(width)
|
||||
}
|
||||
|
||||
/// Creates a new [`ProgressBar`].
|
||||
///
|
||||
/// It expects:
|
||||
/// * an inclusive range of possible values, and
|
||||
/// * the current value of the [`ProgressBar`].
|
||||
///
|
||||
/// [`ProgressBar`]: widget::ProgressBar
|
||||
pub fn progress_bar<Renderer>(
|
||||
range: RangeInclusive<f32>,
|
||||
value: f32,
|
||||
) -> widget::ProgressBar<Renderer>
|
||||
where
|
||||
Renderer: crate::Renderer,
|
||||
Renderer::Theme: widget::progress_bar::StyleSheet,
|
||||
{
|
||||
widget::ProgressBar::new(range, value)
|
||||
}
|
||||
|
||||
/// Creates a new [`Svg`] widget from the given [`Handle`].
|
||||
///
|
||||
/// [`Svg`]: widget::Svg
|
||||
/// [`Handle`]: widget::svg::Handle
|
||||
pub fn svg(handle: impl Into<widget::svg::Handle>) -> widget::Svg {
|
||||
widget::Svg::new(handle)
|
||||
}
|
||||
43
native/src/widget/id.rs
Normal file
43
native/src/widget/id.rs
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
use std::borrow;
|
||||
use std::sync::atomic::{self, AtomicUsize};
|
||||
|
||||
static NEXT_ID: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
/// The identifier of a generic widget.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct Id(Internal);
|
||||
|
||||
impl Id {
|
||||
/// Creates a custom [`Id`].
|
||||
pub fn new(id: impl Into<borrow::Cow<'static, str>>) -> Self {
|
||||
Self(Internal::Custom(id.into()))
|
||||
}
|
||||
|
||||
/// Creates a unique [`Id`].
|
||||
///
|
||||
/// This function produces a different [`Id`] every time it is called.
|
||||
pub fn unique() -> Self {
|
||||
let id = NEXT_ID.fetch_add(1, atomic::Ordering::Relaxed);
|
||||
|
||||
Self(Internal::Unique(id))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum Internal {
|
||||
Unique(usize),
|
||||
Custom(borrow::Cow<'static, str>),
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Id;
|
||||
|
||||
#[test]
|
||||
fn unique_generates_different_ids() {
|
||||
let a = Id::unique();
|
||||
let b = Id::unique();
|
||||
|
||||
assert_ne!(a, b);
|
||||
}
|
||||
}
|
||||
|
|
@ -5,12 +5,18 @@ pub use viewer::Viewer;
|
|||
use crate::image;
|
||||
use crate::layout;
|
||||
use crate::renderer;
|
||||
use crate::widget::Tree;
|
||||
use crate::{
|
||||
ContentFit, Element, Layout, Length, Point, Rectangle, Size, Vector, Widget,
|
||||
};
|
||||
|
||||
use std::hash::Hash;
|
||||
|
||||
/// Creates a new [`Viewer`] with the given image `Handle`.
|
||||
pub fn viewer<Handle>(handle: Handle) -> Viewer<Handle> {
|
||||
Viewer::new(handle)
|
||||
}
|
||||
|
||||
/// A frame that displays an image while keeping aspect ratio.
|
||||
///
|
||||
/// # Example
|
||||
|
|
@ -135,6 +141,7 @@ where
|
|||
|
||||
fn draw(
|
||||
&self,
|
||||
_state: &Tree,
|
||||
renderer: &mut Renderer,
|
||||
_theme: &Renderer::Theme,
|
||||
_style: &renderer::Style,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ use crate::image;
|
|||
use crate::layout;
|
||||
use crate::mouse;
|
||||
use crate::renderer;
|
||||
use crate::widget::tree::{self, Tree};
|
||||
use crate::{
|
||||
Clipboard, Element, Layout, Length, Point, Rectangle, Shell, Size, Vector,
|
||||
Widget,
|
||||
|
|
@ -13,8 +14,7 @@ use std::hash::Hash;
|
|||
|
||||
/// A frame that displays an image with the ability to zoom in/out and pan.
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Viewer<'a, Handle> {
|
||||
state: &'a mut State,
|
||||
pub struct Viewer<Handle> {
|
||||
padding: u16,
|
||||
width: Length,
|
||||
height: Length,
|
||||
|
|
@ -24,11 +24,10 @@ pub struct Viewer<'a, Handle> {
|
|||
handle: Handle,
|
||||
}
|
||||
|
||||
impl<'a, Handle> Viewer<'a, Handle> {
|
||||
impl<Handle> Viewer<Handle> {
|
||||
/// Creates a new [`Viewer`] with the given [`State`].
|
||||
pub fn new(state: &'a mut State, handle: Handle) -> Self {
|
||||
pub fn new(handle: Handle) -> Self {
|
||||
Viewer {
|
||||
state,
|
||||
padding: 0,
|
||||
width: Length::Shrink,
|
||||
height: Length::Shrink,
|
||||
|
|
@ -81,43 +80,21 @@ impl<'a, Handle> Viewer<'a, Handle> {
|
|||
self.scale_step = scale_step;
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns the bounds of the underlying image, given the bounds of
|
||||
/// the [`Viewer`]. Scaling will be applied and original aspect ratio
|
||||
/// will be respected.
|
||||
fn image_size<Renderer>(&self, renderer: &Renderer, bounds: Size) -> Size
|
||||
where
|
||||
Renderer: image::Renderer<Handle = Handle>,
|
||||
{
|
||||
let (width, height) = renderer.dimensions(&self.handle);
|
||||
|
||||
let (width, height) = {
|
||||
let dimensions = (width as f32, height as f32);
|
||||
|
||||
let width_ratio = bounds.width / dimensions.0;
|
||||
let height_ratio = bounds.height / dimensions.1;
|
||||
|
||||
let ratio = width_ratio.min(height_ratio);
|
||||
|
||||
let scale = self.state.scale;
|
||||
|
||||
if ratio < 1.0 {
|
||||
(dimensions.0 * ratio * scale, dimensions.1 * ratio * scale)
|
||||
} else {
|
||||
(dimensions.0 * scale, dimensions.1 * scale)
|
||||
}
|
||||
};
|
||||
|
||||
Size::new(width, height)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer, Handle> Widget<Message, Renderer>
|
||||
for Viewer<'a, Handle>
|
||||
impl<Message, Renderer, Handle> Widget<Message, Renderer> for Viewer<Handle>
|
||||
where
|
||||
Renderer: image::Renderer<Handle = Handle>,
|
||||
Handle: Clone + Hash,
|
||||
{
|
||||
fn tag(&self) -> tree::Tag {
|
||||
tree::Tag::of::<State>()
|
||||
}
|
||||
|
||||
fn state(&self) -> tree::State {
|
||||
tree::State::new(State::new())
|
||||
}
|
||||
|
||||
fn width(&self) -> Length {
|
||||
self.width
|
||||
}
|
||||
|
|
@ -164,6 +141,7 @@ where
|
|||
|
||||
fn on_event(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
event: Event,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
|
|
@ -181,39 +159,43 @@ where
|
|||
match delta {
|
||||
mouse::ScrollDelta::Lines { y, .. }
|
||||
| mouse::ScrollDelta::Pixels { y, .. } => {
|
||||
let previous_scale = self.state.scale;
|
||||
let state = tree.state.downcast_mut::<State>();
|
||||
let previous_scale = state.scale;
|
||||
|
||||
if y < 0.0 && previous_scale > self.min_scale
|
||||
|| y > 0.0 && previous_scale < self.max_scale
|
||||
{
|
||||
self.state.scale = (if y > 0.0 {
|
||||
self.state.scale * (1.0 + self.scale_step)
|
||||
state.scale = (if y > 0.0 {
|
||||
state.scale * (1.0 + self.scale_step)
|
||||
} else {
|
||||
self.state.scale / (1.0 + self.scale_step)
|
||||
state.scale / (1.0 + self.scale_step)
|
||||
})
|
||||
.max(self.min_scale)
|
||||
.min(self.max_scale);
|
||||
|
||||
let image_size =
|
||||
self.image_size(renderer, bounds.size());
|
||||
let image_size = image_size(
|
||||
renderer,
|
||||
&self.handle,
|
||||
state,
|
||||
bounds.size(),
|
||||
);
|
||||
|
||||
let factor =
|
||||
self.state.scale / previous_scale - 1.0;
|
||||
let factor = state.scale / previous_scale - 1.0;
|
||||
|
||||
let cursor_to_center =
|
||||
cursor_position - bounds.center();
|
||||
|
||||
let adjustment = cursor_to_center * factor
|
||||
+ self.state.current_offset * factor;
|
||||
+ state.current_offset * factor;
|
||||
|
||||
self.state.current_offset = Vector::new(
|
||||
state.current_offset = Vector::new(
|
||||
if image_size.width > bounds.width {
|
||||
self.state.current_offset.x + adjustment.x
|
||||
state.current_offset.x + adjustment.x
|
||||
} else {
|
||||
0.0
|
||||
},
|
||||
if image_size.height > bounds.height {
|
||||
self.state.current_offset.y + adjustment.y
|
||||
state.current_offset.y + adjustment.y
|
||||
} else {
|
||||
0.0
|
||||
},
|
||||
|
|
@ -227,21 +209,34 @@ where
|
|||
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
|
||||
if is_mouse_over =>
|
||||
{
|
||||
self.state.cursor_grabbed_at = Some(cursor_position);
|
||||
self.state.starting_offset = self.state.current_offset;
|
||||
let state = tree.state.downcast_mut::<State>();
|
||||
|
||||
state.cursor_grabbed_at = Some(cursor_position);
|
||||
state.starting_offset = state.current_offset;
|
||||
|
||||
event::Status::Captured
|
||||
}
|
||||
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
|
||||
if self.state.cursor_grabbed_at.is_some() =>
|
||||
{
|
||||
self.state.cursor_grabbed_at = None;
|
||||
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) => {
|
||||
let state = tree.state.downcast_mut::<State>();
|
||||
|
||||
event::Status::Captured
|
||||
if state.cursor_grabbed_at.is_some() {
|
||||
state.cursor_grabbed_at = None;
|
||||
|
||||
event::Status::Captured
|
||||
} else {
|
||||
event::Status::Ignored
|
||||
}
|
||||
}
|
||||
Event::Mouse(mouse::Event::CursorMoved { position }) => {
|
||||
if let Some(origin) = self.state.cursor_grabbed_at {
|
||||
let image_size = self.image_size(renderer, bounds.size());
|
||||
let state = tree.state.downcast_mut::<State>();
|
||||
|
||||
if let Some(origin) = state.cursor_grabbed_at {
|
||||
let image_size = image_size(
|
||||
renderer,
|
||||
&self.handle,
|
||||
state,
|
||||
bounds.size(),
|
||||
);
|
||||
|
||||
let hidden_width = (image_size.width - bounds.width / 2.0)
|
||||
.max(0.0)
|
||||
|
|
@ -255,7 +250,7 @@ where
|
|||
let delta = position - origin;
|
||||
|
||||
let x = if bounds.width < image_size.width {
|
||||
(self.state.starting_offset.x - delta.x)
|
||||
(state.starting_offset.x - delta.x)
|
||||
.min(hidden_width)
|
||||
.max(-hidden_width)
|
||||
} else {
|
||||
|
|
@ -263,14 +258,14 @@ where
|
|||
};
|
||||
|
||||
let y = if bounds.height < image_size.height {
|
||||
(self.state.starting_offset.y - delta.y)
|
||||
(state.starting_offset.y - delta.y)
|
||||
.min(hidden_height)
|
||||
.max(-hidden_height)
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
self.state.current_offset = Vector::new(x, y);
|
||||
state.current_offset = Vector::new(x, y);
|
||||
|
||||
event::Status::Captured
|
||||
} else {
|
||||
|
|
@ -283,15 +278,17 @@ where
|
|||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
_renderer: &Renderer,
|
||||
) -> mouse::Interaction {
|
||||
let state = tree.state.downcast_ref::<State>();
|
||||
let bounds = layout.bounds();
|
||||
let is_mouse_over = bounds.contains(cursor_position);
|
||||
|
||||
if self.state.is_cursor_grabbed() {
|
||||
if state.is_cursor_grabbed() {
|
||||
mouse::Interaction::Grabbing
|
||||
} else if is_mouse_over {
|
||||
mouse::Interaction::Grab
|
||||
|
|
@ -302,6 +299,7 @@ where
|
|||
|
||||
fn draw(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
renderer: &mut Renderer,
|
||||
_theme: &Renderer::Theme,
|
||||
_style: &renderer::Style,
|
||||
|
|
@ -309,9 +307,11 @@ where
|
|||
_cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
) {
|
||||
let state = tree.state.downcast_ref::<State>();
|
||||
let bounds = layout.bounds();
|
||||
|
||||
let image_size = self.image_size(renderer, bounds.size());
|
||||
let image_size =
|
||||
image_size(renderer, &self.handle, state, bounds.size());
|
||||
|
||||
let translation = {
|
||||
let image_top_left = Vector::new(
|
||||
|
|
@ -319,7 +319,7 @@ where
|
|||
bounds.height / 2.0 - image_size.height / 2.0,
|
||||
);
|
||||
|
||||
image_top_left - self.state.offset(bounds, image_size)
|
||||
image_top_left - state.offset(bounds, image_size)
|
||||
};
|
||||
|
||||
renderer.with_layer(bounds, |renderer| {
|
||||
|
|
@ -385,14 +385,47 @@ impl State {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer, Handle> From<Viewer<'a, Handle>>
|
||||
impl<'a, Message, Renderer, Handle> From<Viewer<Handle>>
|
||||
for Element<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: 'a + image::Renderer<Handle = Handle>,
|
||||
Message: 'a,
|
||||
Handle: Clone + Hash + 'a,
|
||||
{
|
||||
fn from(viewer: Viewer<'a, Handle>) -> Element<'a, Message, Renderer> {
|
||||
fn from(viewer: Viewer<Handle>) -> Element<'a, Message, Renderer> {
|
||||
Element::new(viewer)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the bounds of the underlying image, given the bounds of
|
||||
/// the [`Viewer`]. Scaling will be applied and original aspect ratio
|
||||
/// will be respected.
|
||||
pub fn image_size<Renderer>(
|
||||
renderer: &Renderer,
|
||||
handle: &<Renderer as image::Renderer>::Handle,
|
||||
state: &State,
|
||||
bounds: Size,
|
||||
) -> Size
|
||||
where
|
||||
Renderer: image::Renderer,
|
||||
{
|
||||
let (width, height) = renderer.dimensions(handle);
|
||||
|
||||
let (width, height) = {
|
||||
let dimensions = (width as f32, height as f32);
|
||||
|
||||
let width_ratio = bounds.width / dimensions.0;
|
||||
let height_ratio = bounds.height / dimensions.1;
|
||||
|
||||
let ratio = width_ratio.min(height_ratio);
|
||||
let scale = state.scale;
|
||||
|
||||
if ratio < 1.0 {
|
||||
(dimensions.0 * ratio * scale, dimensions.1 * ratio * scale)
|
||||
} else {
|
||||
(dimensions.0 * scale, dimensions.1 * scale)
|
||||
}
|
||||
};
|
||||
|
||||
Size::new(width, height)
|
||||
}
|
||||
|
|
|
|||
60
native/src/widget/operation.rs
Normal file
60
native/src/widget/operation.rs
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
//! Query or update internal widget state.
|
||||
pub mod focusable;
|
||||
pub mod scrollable;
|
||||
|
||||
pub use focusable::Focusable;
|
||||
pub use scrollable::Scrollable;
|
||||
|
||||
use crate::widget::Id;
|
||||
|
||||
use std::fmt;
|
||||
|
||||
/// A piece of logic that can traverse the widget tree of an application in
|
||||
/// order to query or update some widget state.
|
||||
pub trait Operation<T> {
|
||||
/// Operates on a widget that contains other widgets.
|
||||
///
|
||||
/// The `operate_on_children` function can be called to return control to
|
||||
/// the widget tree and keep traversing it.
|
||||
fn container(
|
||||
&mut self,
|
||||
id: Option<&Id>,
|
||||
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
|
||||
);
|
||||
|
||||
/// Operates on a widget that can be focused.
|
||||
fn focusable(&mut self, _state: &mut dyn Focusable, _id: Option<&Id>) {}
|
||||
|
||||
/// Operates on a widget that can be scrolled.
|
||||
fn scrollable(&mut self, _state: &mut dyn Scrollable, _id: Option<&Id>) {}
|
||||
|
||||
/// Finishes the [`Operation`] and returns its [`Outcome`].
|
||||
fn finish(&self) -> Outcome<T> {
|
||||
Outcome::None
|
||||
}
|
||||
}
|
||||
|
||||
/// The result of an [`Operation`].
|
||||
pub enum Outcome<T> {
|
||||
/// The [`Operation`] produced no result.
|
||||
None,
|
||||
|
||||
/// The [`Operation`] produced some result.
|
||||
Some(T),
|
||||
|
||||
/// The [`Operation`] needs to be followed by another [`Operation`].
|
||||
Chain(Box<dyn Operation<T>>),
|
||||
}
|
||||
|
||||
impl<T> fmt::Debug for Outcome<T>
|
||||
where
|
||||
T: fmt::Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::None => write!(f, "Outcome::None"),
|
||||
Self::Some(output) => write!(f, "Outcome::Some({:?})", output),
|
||||
Self::Chain(_) => write!(f, "Outcome::Chain(...)"),
|
||||
}
|
||||
}
|
||||
}
|
||||
169
native/src/widget/operation/focusable.rs
Normal file
169
native/src/widget/operation/focusable.rs
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
//! Operate on widgets that can be focused.
|
||||
use crate::widget::operation::{Operation, Outcome};
|
||||
use crate::widget::Id;
|
||||
|
||||
/// The internal state of a widget that can be focused.
|
||||
pub trait Focusable {
|
||||
/// Returns whether the widget is focused or not.
|
||||
fn is_focused(&self) -> bool;
|
||||
|
||||
/// Focuses the widget.
|
||||
fn focus(&mut self);
|
||||
|
||||
/// Unfocuses the widget.
|
||||
fn unfocus(&mut self);
|
||||
}
|
||||
|
||||
/// A summary of the focusable widgets present on a widget tree.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub struct Count {
|
||||
/// The index of the current focused widget, if any.
|
||||
focused: Option<usize>,
|
||||
|
||||
/// The total amount of focusable widgets.
|
||||
total: usize,
|
||||
}
|
||||
|
||||
/// Produces an [`Operation`] that focuses the widget with the given [`Id`].
|
||||
pub fn focus<T>(target: Id) -> impl Operation<T> {
|
||||
struct Focus {
|
||||
target: Id,
|
||||
}
|
||||
|
||||
impl<T> Operation<T> for Focus {
|
||||
fn focusable(&mut self, state: &mut dyn Focusable, id: Option<&Id>) {
|
||||
match id {
|
||||
Some(id) if id == &self.target => {
|
||||
state.focus();
|
||||
}
|
||||
_ => {
|
||||
state.unfocus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn container(
|
||||
&mut self,
|
||||
_id: Option<&Id>,
|
||||
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
|
||||
) {
|
||||
operate_on_children(self)
|
||||
}
|
||||
}
|
||||
|
||||
Focus { target }
|
||||
}
|
||||
|
||||
/// Produces an [`Operation`] that generates a [`Count`] and chains it with the
|
||||
/// provided function to build a new [`Operation`].
|
||||
pub fn count<T, O>(f: fn(Count) -> O) -> impl Operation<T>
|
||||
where
|
||||
O: Operation<T> + 'static,
|
||||
{
|
||||
struct CountFocusable<O> {
|
||||
count: Count,
|
||||
next: fn(Count) -> O,
|
||||
}
|
||||
|
||||
impl<T, O> Operation<T> for CountFocusable<O>
|
||||
where
|
||||
O: Operation<T> + 'static,
|
||||
{
|
||||
fn focusable(&mut self, state: &mut dyn Focusable, _id: Option<&Id>) {
|
||||
if state.is_focused() {
|
||||
self.count.focused = Some(self.count.total);
|
||||
}
|
||||
|
||||
self.count.total += 1;
|
||||
}
|
||||
|
||||
fn container(
|
||||
&mut self,
|
||||
_id: Option<&Id>,
|
||||
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
|
||||
) {
|
||||
operate_on_children(self)
|
||||
}
|
||||
|
||||
fn finish(&self) -> Outcome<T> {
|
||||
Outcome::Chain(Box::new((self.next)(self.count)))
|
||||
}
|
||||
}
|
||||
|
||||
CountFocusable {
|
||||
count: Count::default(),
|
||||
next: f,
|
||||
}
|
||||
}
|
||||
|
||||
/// Produces an [`Operation`] that searches for the current focused widget, and
|
||||
/// - if found, focuses the previous focusable widget.
|
||||
/// - if not found, focuses the last focusable widget.
|
||||
pub fn focus_previous<T>() -> impl Operation<T> {
|
||||
struct FocusPrevious {
|
||||
count: Count,
|
||||
current: usize,
|
||||
}
|
||||
|
||||
impl<T> Operation<T> for FocusPrevious {
|
||||
fn focusable(&mut self, state: &mut dyn Focusable, _id: Option<&Id>) {
|
||||
if self.count.total == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
match self.count.focused {
|
||||
None if self.current == self.count.total - 1 => state.focus(),
|
||||
Some(0) if self.current == 0 => state.unfocus(),
|
||||
Some(0) => {}
|
||||
Some(focused) if focused == self.current => state.unfocus(),
|
||||
Some(focused) if focused - 1 == self.current => state.focus(),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
self.current += 1;
|
||||
}
|
||||
|
||||
fn container(
|
||||
&mut self,
|
||||
_id: Option<&Id>,
|
||||
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
|
||||
) {
|
||||
operate_on_children(self)
|
||||
}
|
||||
}
|
||||
|
||||
count(|count| FocusPrevious { count, current: 0 })
|
||||
}
|
||||
|
||||
/// Produces an [`Operation`] that searches for the current focused widget, and
|
||||
/// - if found, focuses the next focusable widget.
|
||||
/// - if not found, focuses the first focusable widget.
|
||||
pub fn focus_next<T>() -> impl Operation<T> {
|
||||
struct FocusNext {
|
||||
count: Count,
|
||||
current: usize,
|
||||
}
|
||||
|
||||
impl<T> Operation<T> for FocusNext {
|
||||
fn focusable(&mut self, state: &mut dyn Focusable, _id: Option<&Id>) {
|
||||
match self.count.focused {
|
||||
None if self.current == 0 => state.focus(),
|
||||
Some(focused) if focused == self.current => state.unfocus(),
|
||||
Some(focused) if focused + 1 == self.current => state.focus(),
|
||||
_ => {}
|
||||
}
|
||||
|
||||
self.current += 1;
|
||||
}
|
||||
|
||||
fn container(
|
||||
&mut self,
|
||||
_id: Option<&Id>,
|
||||
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
|
||||
) {
|
||||
operate_on_children(self)
|
||||
}
|
||||
}
|
||||
|
||||
count(|count| FocusNext { count, current: 0 })
|
||||
}
|
||||
35
native/src/widget/operation/scrollable.rs
Normal file
35
native/src/widget/operation/scrollable.rs
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
//! Operate on widgets that can be scrolled.
|
||||
use crate::widget::{Id, Operation};
|
||||
|
||||
/// The internal state of a widget that can be scrolled.
|
||||
pub trait Scrollable {
|
||||
/// Snaps the scroll of the widget to the given `percentage`.
|
||||
fn snap_to(&mut self, percentage: f32);
|
||||
}
|
||||
|
||||
/// Produces an [`Operation`] that snaps the widget with the given [`Id`] to
|
||||
/// the provided `percentage`.
|
||||
pub fn snap_to<T>(target: Id, percentage: f32) -> impl Operation<T> {
|
||||
struct SnapTo {
|
||||
target: Id,
|
||||
percentage: f32,
|
||||
}
|
||||
|
||||
impl<T> Operation<T> for SnapTo {
|
||||
fn scrollable(&mut self, state: &mut dyn Scrollable, id: Option<&Id>) {
|
||||
if Some(&self.target) == id {
|
||||
state.snap_to(self.percentage);
|
||||
}
|
||||
}
|
||||
|
||||
fn container(
|
||||
&mut self,
|
||||
_id: Option<&Id>,
|
||||
operate_on_children: &mut dyn FnMut(&mut dyn Operation<T>),
|
||||
) {
|
||||
operate_on_children(self)
|
||||
}
|
||||
}
|
||||
|
||||
SnapTo { target, percentage }
|
||||
}
|
||||
|
|
@ -30,6 +30,8 @@ pub use split::Split;
|
|||
pub use state::State;
|
||||
pub use title_bar::TitleBar;
|
||||
|
||||
pub use iced_style::pane_grid::{Line, StyleSheet};
|
||||
|
||||
use crate::event::{self, Event};
|
||||
use crate::layout;
|
||||
use crate::mouse;
|
||||
|
|
@ -37,13 +39,12 @@ use crate::overlay;
|
|||
use crate::renderer;
|
||||
use crate::touch;
|
||||
use crate::widget::container;
|
||||
use crate::widget::tree::{self, Tree};
|
||||
use crate::{
|
||||
Clipboard, Color, Element, Layout, Length, Point, Rectangle, Shell, Size,
|
||||
Vector, Widget,
|
||||
};
|
||||
|
||||
pub use iced_style::pane_grid::{Line, StyleSheet};
|
||||
|
||||
/// A collection of panes distributed using either vertical or horizontal splits
|
||||
/// to completely fill the space available.
|
||||
///
|
||||
|
|
@ -66,7 +67,7 @@ pub use iced_style::pane_grid::{Line, StyleSheet};
|
|||
/// ## Example
|
||||
///
|
||||
/// ```
|
||||
/// # use iced_native::widget::{pane_grid, Text};
|
||||
/// # use iced_native::widget::{pane_grid, text};
|
||||
/// #
|
||||
/// # type PaneGrid<'a, Message> =
|
||||
/// # iced_native::widget::PaneGrid<'a, Message, iced_native::renderer::Null>;
|
||||
|
|
@ -84,10 +85,10 @@ pub use iced_style::pane_grid::{Line, StyleSheet};
|
|||
/// let (mut state, _) = pane_grid::State::new(PaneState::SomePane);
|
||||
///
|
||||
/// let pane_grid =
|
||||
/// PaneGrid::new(&mut state, |pane, state| {
|
||||
/// PaneGrid::new(&state, |pane, state| {
|
||||
/// pane_grid::Content::new(match state {
|
||||
/// PaneState::SomePane => Text::new("This is some pane"),
|
||||
/// PaneState::AnotherKindOfPane => Text::new("This is another kind of pane"),
|
||||
/// PaneState::SomePane => text("This is some pane"),
|
||||
/// PaneState::AnotherKindOfPane => text("This is another kind of pane"),
|
||||
/// })
|
||||
/// })
|
||||
/// .on_drag(Message::PaneDragged)
|
||||
|
|
@ -99,8 +100,7 @@ where
|
|||
Renderer: crate::Renderer,
|
||||
Renderer::Theme: StyleSheet + container::StyleSheet,
|
||||
{
|
||||
state: &'a mut state::Internal,
|
||||
action: &'a mut state::Action,
|
||||
state: &'a state::Internal,
|
||||
elements: Vec<(Pane, Content<'a, Message, Renderer>)>,
|
||||
width: Length,
|
||||
height: Length,
|
||||
|
|
@ -121,21 +121,20 @@ where
|
|||
/// The view function will be called to display each [`Pane`] present in the
|
||||
/// [`State`].
|
||||
pub fn new<T>(
|
||||
state: &'a mut State<T>,
|
||||
view: impl Fn(Pane, &'a mut T) -> Content<'a, Message, Renderer>,
|
||||
state: &'a State<T>,
|
||||
view: impl Fn(Pane, &'a T) -> Content<'a, Message, Renderer>,
|
||||
) -> Self {
|
||||
let elements = {
|
||||
state
|
||||
.panes
|
||||
.iter_mut()
|
||||
.iter()
|
||||
.map(|(pane, pane_state)| (*pane, view(*pane, pane_state)))
|
||||
.collect()
|
||||
};
|
||||
|
||||
Self {
|
||||
state: &mut state.internal,
|
||||
action: &mut state.action,
|
||||
elements,
|
||||
state: &state.internal,
|
||||
width: Length::Fill,
|
||||
height: Length::Fill,
|
||||
spacing: 0,
|
||||
|
|
@ -211,6 +210,220 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> Widget<Message, Renderer>
|
||||
for PaneGrid<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: crate::Renderer,
|
||||
Renderer::Theme: StyleSheet + container::StyleSheet,
|
||||
{
|
||||
fn tag(&self) -> tree::Tag {
|
||||
tree::Tag::of::<state::Action>()
|
||||
}
|
||||
|
||||
fn state(&self) -> tree::State {
|
||||
tree::State::new(state::Action::Idle)
|
||||
}
|
||||
|
||||
fn children(&self) -> Vec<Tree> {
|
||||
self.elements
|
||||
.iter()
|
||||
.map(|(_, content)| content.state())
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn diff(&self, tree: &mut Tree) {
|
||||
tree.diff_children_custom(
|
||||
&self.elements,
|
||||
|state, (_, content)| content.diff(state),
|
||||
|(_, content)| content.state(),
|
||||
)
|
||||
}
|
||||
|
||||
fn width(&self) -> Length {
|
||||
self.width
|
||||
}
|
||||
|
||||
fn height(&self) -> Length {
|
||||
self.height
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
layout(
|
||||
renderer,
|
||||
limits,
|
||||
self.state,
|
||||
self.width,
|
||||
self.height,
|
||||
self.spacing,
|
||||
self.elements.iter().map(|(pane, content)| (*pane, content)),
|
||||
|element, renderer, limits| element.layout(renderer, limits),
|
||||
)
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
event: Event,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
renderer: &Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
) -> event::Status {
|
||||
let action = tree.state.downcast_mut::<state::Action>();
|
||||
|
||||
let event_status = update(
|
||||
action,
|
||||
self.state,
|
||||
&event,
|
||||
layout,
|
||||
cursor_position,
|
||||
shell,
|
||||
self.spacing,
|
||||
self.elements.iter().map(|(pane, content)| (*pane, content)),
|
||||
&self.on_click,
|
||||
&self.on_drag,
|
||||
&self.on_resize,
|
||||
);
|
||||
|
||||
let picked_pane = action.picked_pane().map(|(pane, _)| pane);
|
||||
|
||||
self.elements
|
||||
.iter_mut()
|
||||
.zip(&mut tree.children)
|
||||
.zip(layout.children())
|
||||
.map(|(((pane, content), tree), layout)| {
|
||||
let is_picked = picked_pane == Some(*pane);
|
||||
|
||||
content.on_event(
|
||||
tree,
|
||||
event.clone(),
|
||||
layout,
|
||||
cursor_position,
|
||||
renderer,
|
||||
clipboard,
|
||||
shell,
|
||||
is_picked,
|
||||
)
|
||||
})
|
||||
.fold(event_status, event::Status::merge)
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
renderer: &Renderer,
|
||||
) -> mouse::Interaction {
|
||||
mouse_interaction(
|
||||
tree.state.downcast_ref(),
|
||||
self.state,
|
||||
layout,
|
||||
cursor_position,
|
||||
self.spacing,
|
||||
self.on_resize.as_ref().map(|(leeway, _)| *leeway),
|
||||
)
|
||||
.unwrap_or_else(|| {
|
||||
self.elements
|
||||
.iter()
|
||||
.zip(&tree.children)
|
||||
.zip(layout.children())
|
||||
.map(|(((_pane, content), tree), layout)| {
|
||||
content.mouse_interaction(
|
||||
tree,
|
||||
layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
renderer,
|
||||
)
|
||||
})
|
||||
.max()
|
||||
.unwrap_or_default()
|
||||
})
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
renderer: &mut Renderer,
|
||||
theme: &Renderer::Theme,
|
||||
style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
) {
|
||||
draw(
|
||||
tree.state.downcast_ref(),
|
||||
self.state,
|
||||
layout,
|
||||
cursor_position,
|
||||
renderer,
|
||||
theme,
|
||||
style,
|
||||
viewport,
|
||||
self.spacing,
|
||||
self.on_resize.as_ref().map(|(leeway, _)| *leeway),
|
||||
self.style,
|
||||
self.elements
|
||||
.iter()
|
||||
.zip(&tree.children)
|
||||
.map(|((pane, content), tree)| (*pane, (content, tree))),
|
||||
|(content, tree),
|
||||
renderer,
|
||||
style,
|
||||
layout,
|
||||
cursor_position,
|
||||
rectangle| {
|
||||
content.draw(
|
||||
tree,
|
||||
renderer,
|
||||
theme,
|
||||
style,
|
||||
layout,
|
||||
cursor_position,
|
||||
rectangle,
|
||||
);
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn overlay<'b>(
|
||||
&'b self,
|
||||
tree: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
) -> Option<overlay::Element<'_, Message, Renderer>> {
|
||||
self.elements
|
||||
.iter()
|
||||
.zip(&mut tree.children)
|
||||
.zip(layout.children())
|
||||
.filter_map(|(((_, pane), tree), layout)| {
|
||||
pane.overlay(tree, layout, renderer)
|
||||
})
|
||||
.next()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> From<PaneGrid<'a, Message, Renderer>>
|
||||
for Element<'a, Message, Renderer>
|
||||
where
|
||||
Message: 'a,
|
||||
Renderer: 'a + crate::Renderer,
|
||||
Renderer::Theme: StyleSheet + container::StyleSheet,
|
||||
{
|
||||
fn from(
|
||||
pane_grid: PaneGrid<'a, Message, Renderer>,
|
||||
) -> Element<'a, Message, Renderer> {
|
||||
Element::new(pane_grid)
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculates the [`Layout`] of a [`PaneGrid`].
|
||||
pub fn layout<Renderer, T>(
|
||||
renderer: &Renderer,
|
||||
|
|
@ -656,175 +869,6 @@ pub struct ResizeEvent {
|
|||
pub ratio: f32,
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> Widget<Message, Renderer>
|
||||
for PaneGrid<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: crate::Renderer,
|
||||
Renderer::Theme: StyleSheet + container::StyleSheet,
|
||||
{
|
||||
fn width(&self) -> Length {
|
||||
self.width
|
||||
}
|
||||
|
||||
fn height(&self) -> Length {
|
||||
self.height
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
layout(
|
||||
renderer,
|
||||
limits,
|
||||
self.state,
|
||||
self.width,
|
||||
self.height,
|
||||
self.spacing,
|
||||
self.elements.iter().map(|(pane, content)| (*pane, content)),
|
||||
|element, renderer, limits| element.layout(renderer, limits),
|
||||
)
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
&mut self,
|
||||
event: Event,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
renderer: &Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
) -> event::Status {
|
||||
let event_status = update(
|
||||
self.action,
|
||||
self.state,
|
||||
&event,
|
||||
layout,
|
||||
cursor_position,
|
||||
shell,
|
||||
self.spacing,
|
||||
self.elements.iter().map(|(pane, content)| (*pane, content)),
|
||||
&self.on_click,
|
||||
&self.on_drag,
|
||||
&self.on_resize,
|
||||
);
|
||||
|
||||
let picked_pane = self.action.picked_pane().map(|(pane, _)| pane);
|
||||
|
||||
self.elements
|
||||
.iter_mut()
|
||||
.zip(layout.children())
|
||||
.map(|((pane, content), layout)| {
|
||||
let is_picked = picked_pane == Some(*pane);
|
||||
|
||||
content.on_event(
|
||||
event.clone(),
|
||||
layout,
|
||||
cursor_position,
|
||||
renderer,
|
||||
clipboard,
|
||||
shell,
|
||||
is_picked,
|
||||
)
|
||||
})
|
||||
.fold(event_status, event::Status::merge)
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
renderer: &Renderer,
|
||||
) -> mouse::Interaction {
|
||||
mouse_interaction(
|
||||
self.action,
|
||||
self.state,
|
||||
layout,
|
||||
cursor_position,
|
||||
self.spacing,
|
||||
self.on_resize.as_ref().map(|(leeway, _)| *leeway),
|
||||
)
|
||||
.unwrap_or_else(|| {
|
||||
self.elements
|
||||
.iter()
|
||||
.zip(layout.children())
|
||||
.map(|((_pane, content), layout)| {
|
||||
content.mouse_interaction(
|
||||
layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
renderer,
|
||||
)
|
||||
})
|
||||
.max()
|
||||
.unwrap_or_default()
|
||||
})
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
renderer: &mut Renderer,
|
||||
theme: &Renderer::Theme,
|
||||
style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
) {
|
||||
draw(
|
||||
self.action,
|
||||
self.state,
|
||||
layout,
|
||||
cursor_position,
|
||||
renderer,
|
||||
theme,
|
||||
style,
|
||||
viewport,
|
||||
self.spacing,
|
||||
self.on_resize.as_ref().map(|(leeway, _)| *leeway),
|
||||
self.style,
|
||||
self.elements.iter().map(|(pane, content)| (*pane, content)),
|
||||
|pane, renderer, style, layout, cursor_position, rectangle| {
|
||||
pane.draw(
|
||||
renderer,
|
||||
theme,
|
||||
style,
|
||||
layout,
|
||||
cursor_position,
|
||||
rectangle,
|
||||
);
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn overlay(
|
||||
&mut self,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
) -> Option<overlay::Element<'_, Message, Renderer>> {
|
||||
self.elements
|
||||
.iter_mut()
|
||||
.zip(layout.children())
|
||||
.filter_map(|((_, pane), layout)| pane.overlay(layout, renderer))
|
||||
.next()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> From<PaneGrid<'a, Message, Renderer>>
|
||||
for Element<'a, Message, Renderer>
|
||||
where
|
||||
Message: 'a,
|
||||
Renderer: 'a + crate::Renderer,
|
||||
Renderer::Theme: StyleSheet + container::StyleSheet,
|
||||
{
|
||||
fn from(
|
||||
pane_grid: PaneGrid<'a, Message, Renderer>,
|
||||
) -> Element<'a, Message, Renderer> {
|
||||
Element::new(pane_grid)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Helpers
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ use crate::overlay;
|
|||
use crate::renderer;
|
||||
use crate::widget::container;
|
||||
use crate::widget::pane_grid::{Draggable, TitleBar};
|
||||
use crate::widget::Tree;
|
||||
use crate::{Clipboard, Element, Layout, Point, Rectangle, Shell, Size};
|
||||
|
||||
/// The content of a [`Pane`].
|
||||
|
|
@ -59,11 +60,37 @@ where
|
|||
Renderer: crate::Renderer,
|
||||
Renderer::Theme: container::StyleSheet,
|
||||
{
|
||||
pub(super) fn state(&self) -> Tree {
|
||||
let children = if let Some(title_bar) = self.title_bar.as_ref() {
|
||||
vec![Tree::new(&self.body), title_bar.state()]
|
||||
} else {
|
||||
vec![Tree::new(&self.body), Tree::empty()]
|
||||
};
|
||||
|
||||
Tree {
|
||||
children,
|
||||
..Tree::empty()
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn diff(&self, tree: &mut Tree) {
|
||||
if tree.children.len() == 2 {
|
||||
if let Some(title_bar) = self.title_bar.as_ref() {
|
||||
title_bar.diff(&mut tree.children[1]);
|
||||
}
|
||||
|
||||
tree.children[0].diff(&self.body);
|
||||
} else {
|
||||
*tree = self.state();
|
||||
}
|
||||
}
|
||||
|
||||
/// Draws the [`Content`] with the provided [`Renderer`] and [`Layout`].
|
||||
///
|
||||
/// [`Renderer`]: crate::Renderer
|
||||
/// [`Renderer`]: iced_native::Renderer
|
||||
pub fn draw(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
renderer: &mut Renderer,
|
||||
theme: &Renderer::Theme,
|
||||
style: &renderer::Style,
|
||||
|
|
@ -89,6 +116,7 @@ where
|
|||
let show_controls = bounds.contains(cursor_position);
|
||||
|
||||
title_bar.draw(
|
||||
&tree.children[1],
|
||||
renderer,
|
||||
theme,
|
||||
style,
|
||||
|
|
@ -98,7 +126,8 @@ where
|
|||
show_controls,
|
||||
);
|
||||
|
||||
self.body.draw(
|
||||
self.body.as_widget().draw(
|
||||
&tree.children[0],
|
||||
renderer,
|
||||
theme,
|
||||
style,
|
||||
|
|
@ -107,7 +136,8 @@ where
|
|||
viewport,
|
||||
);
|
||||
} else {
|
||||
self.body.draw(
|
||||
self.body.as_widget().draw(
|
||||
&tree.children[0],
|
||||
renderer,
|
||||
theme,
|
||||
style,
|
||||
|
|
@ -131,7 +161,7 @@ where
|
|||
|
||||
let title_bar_size = title_bar_layout.size();
|
||||
|
||||
let mut body_layout = self.body.layout(
|
||||
let mut body_layout = self.body.as_widget().layout(
|
||||
renderer,
|
||||
&layout::Limits::new(
|
||||
Size::ZERO,
|
||||
|
|
@ -149,12 +179,13 @@ where
|
|||
vec![title_bar_layout, body_layout],
|
||||
)
|
||||
} else {
|
||||
self.body.layout(renderer, limits)
|
||||
self.body.as_widget().layout(renderer, limits)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn on_event(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
event: Event,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
|
|
@ -169,6 +200,7 @@ where
|
|||
let mut children = layout.children();
|
||||
|
||||
event_status = title_bar.on_event(
|
||||
&mut tree.children[1],
|
||||
event.clone(),
|
||||
children.next().unwrap(),
|
||||
cursor_position,
|
||||
|
|
@ -185,7 +217,8 @@ where
|
|||
let body_status = if is_picked {
|
||||
event::Status::Ignored
|
||||
} else {
|
||||
self.body.on_event(
|
||||
self.body.as_widget_mut().on_event(
|
||||
&mut tree.children[0],
|
||||
event,
|
||||
body_layout,
|
||||
cursor_position,
|
||||
|
|
@ -200,6 +233,7 @@ where
|
|||
|
||||
pub(crate) fn mouse_interaction(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
|
|
@ -218,6 +252,7 @@ where
|
|||
}
|
||||
|
||||
let mouse_interaction = title_bar.mouse_interaction(
|
||||
&tree.children[1],
|
||||
title_bar_layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
|
|
@ -230,25 +265,46 @@ where
|
|||
};
|
||||
|
||||
self.body
|
||||
.mouse_interaction(body_layout, cursor_position, viewport, renderer)
|
||||
.as_widget()
|
||||
.mouse_interaction(
|
||||
&tree.children[0],
|
||||
body_layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
renderer,
|
||||
)
|
||||
.max(title_bar_interaction)
|
||||
}
|
||||
|
||||
pub(crate) fn overlay(
|
||||
&mut self,
|
||||
pub(crate) fn overlay<'b>(
|
||||
&'b self,
|
||||
tree: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
) -> Option<overlay::Element<'_, Message, Renderer>> {
|
||||
if let Some(title_bar) = self.title_bar.as_mut() {
|
||||
) -> Option<overlay::Element<'b, Message, Renderer>> {
|
||||
if let Some(title_bar) = self.title_bar.as_ref() {
|
||||
let mut children = layout.children();
|
||||
let title_bar_layout = children.next()?;
|
||||
|
||||
match title_bar.overlay(title_bar_layout, renderer) {
|
||||
let mut states = tree.children.iter_mut();
|
||||
let body_state = states.next().unwrap();
|
||||
let title_bar_state = states.next().unwrap();
|
||||
|
||||
match title_bar.overlay(title_bar_state, title_bar_layout, renderer)
|
||||
{
|
||||
Some(overlay) => Some(overlay),
|
||||
None => self.body.overlay(children.next()?, renderer),
|
||||
None => self.body.as_widget().overlay(
|
||||
body_state,
|
||||
children.next()?,
|
||||
renderer,
|
||||
),
|
||||
}
|
||||
} else {
|
||||
self.body.overlay(layout, renderer)
|
||||
self.body.as_widget().overlay(
|
||||
&mut tree.children[0],
|
||||
layout,
|
||||
renderer,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,8 +31,6 @@ pub struct State<T> {
|
|||
///
|
||||
/// [`PaneGrid`]: crate::widget::PaneGrid
|
||||
pub internal: Internal,
|
||||
|
||||
pub(super) action: Action,
|
||||
}
|
||||
|
||||
impl<T> State<T> {
|
||||
|
|
@ -54,11 +52,7 @@ impl<T> State<T> {
|
|||
let internal =
|
||||
Internal::from_configuration(&mut panes, config.into(), 0);
|
||||
|
||||
State {
|
||||
panes,
|
||||
internal,
|
||||
action: Action::Idle,
|
||||
}
|
||||
State { panes, internal }
|
||||
}
|
||||
|
||||
/// Returns the total amount of panes in the [`State`].
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ use crate::mouse;
|
|||
use crate::overlay;
|
||||
use crate::renderer;
|
||||
use crate::widget::container;
|
||||
use crate::widget::Tree;
|
||||
use crate::{
|
||||
Clipboard, Element, Layout, Padding, Point, Rectangle, Shell, Size,
|
||||
};
|
||||
|
|
@ -86,11 +87,37 @@ where
|
|||
Renderer: crate::Renderer,
|
||||
Renderer::Theme: container::StyleSheet,
|
||||
{
|
||||
pub(super) fn state(&self) -> Tree {
|
||||
let children = if let Some(controls) = self.controls.as_ref() {
|
||||
vec![Tree::new(&self.content), Tree::new(controls)]
|
||||
} else {
|
||||
vec![Tree::new(&self.content), Tree::empty()]
|
||||
};
|
||||
|
||||
Tree {
|
||||
children,
|
||||
..Tree::empty()
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn diff(&self, tree: &mut Tree) {
|
||||
if tree.children.len() == 2 {
|
||||
if let Some(controls) = self.controls.as_ref() {
|
||||
tree.children[1].diff(controls);
|
||||
}
|
||||
|
||||
tree.children[0].diff(&self.content);
|
||||
} else {
|
||||
*tree = self.state();
|
||||
}
|
||||
}
|
||||
|
||||
/// Draws the [`TitleBar`] with the provided [`Renderer`] and [`Layout`].
|
||||
///
|
||||
/// [`Renderer`]: crate::Renderer
|
||||
/// [`Renderer`]: iced_native::Renderer
|
||||
pub fn draw(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
renderer: &mut Renderer,
|
||||
theme: &Renderer::Theme,
|
||||
inherited_style: &renderer::Style,
|
||||
|
|
@ -118,14 +145,15 @@ where
|
|||
|
||||
if let Some(controls) = &self.controls {
|
||||
let controls_layout = children.next().unwrap();
|
||||
if title_layout.bounds().width + controls_layout.bounds().width
|
||||
> padded.bounds().width
|
||||
{
|
||||
show_title = false;
|
||||
}
|
||||
|
||||
if show_controls || self.always_show_controls {
|
||||
if title_layout.bounds().width + controls_layout.bounds().width
|
||||
> padded.bounds().width
|
||||
{
|
||||
show_title = false;
|
||||
}
|
||||
controls.draw(
|
||||
controls.as_widget().draw(
|
||||
&tree.children[1],
|
||||
renderer,
|
||||
theme,
|
||||
&inherited_style,
|
||||
|
|
@ -137,7 +165,8 @@ where
|
|||
}
|
||||
|
||||
if show_title {
|
||||
self.content.draw(
|
||||
self.content.as_widget().draw(
|
||||
&tree.children[0],
|
||||
renderer,
|
||||
theme,
|
||||
&inherited_style,
|
||||
|
|
@ -186,11 +215,14 @@ where
|
|||
|
||||
let title_layout = self
|
||||
.content
|
||||
.as_widget()
|
||||
.layout(renderer, &layout::Limits::new(Size::ZERO, max_size));
|
||||
|
||||
let title_size = title_layout.size();
|
||||
|
||||
let mut node = if let Some(controls) = &self.controls {
|
||||
let mut controls_layout = controls
|
||||
.as_widget()
|
||||
.layout(renderer, &layout::Limits::new(Size::ZERO, max_size));
|
||||
|
||||
let controls_size = controls_layout.size();
|
||||
|
|
@ -221,6 +253,7 @@ where
|
|||
|
||||
pub(crate) fn on_event(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
event: Event,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
|
|
@ -243,7 +276,8 @@ where
|
|||
show_title = false;
|
||||
}
|
||||
|
||||
controls.on_event(
|
||||
controls.as_widget_mut().on_event(
|
||||
&mut tree.children[1],
|
||||
event.clone(),
|
||||
controls_layout,
|
||||
cursor_position,
|
||||
|
|
@ -256,7 +290,8 @@ where
|
|||
};
|
||||
|
||||
let title_status = if show_title {
|
||||
self.content.on_event(
|
||||
self.content.as_widget_mut().on_event(
|
||||
&mut tree.children[0],
|
||||
event,
|
||||
title_layout,
|
||||
cursor_position,
|
||||
|
|
@ -273,6 +308,7 @@ where
|
|||
|
||||
pub(crate) fn mouse_interaction(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
|
|
@ -284,7 +320,8 @@ where
|
|||
let mut children = padded.children();
|
||||
let title_layout = children.next().unwrap();
|
||||
|
||||
let title_interaction = self.content.mouse_interaction(
|
||||
let title_interaction = self.content.as_widget().mouse_interaction(
|
||||
&tree.children[0],
|
||||
title_layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
|
|
@ -293,7 +330,8 @@ where
|
|||
|
||||
if let Some(controls) = &self.controls {
|
||||
let controls_layout = children.next().unwrap();
|
||||
let controls_interaction = controls.mouse_interaction(
|
||||
let controls_interaction = controls.as_widget().mouse_interaction(
|
||||
&tree.children[1],
|
||||
controls_layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
|
|
@ -312,11 +350,12 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn overlay(
|
||||
&mut self,
|
||||
pub(crate) fn overlay<'b>(
|
||||
&'b self,
|
||||
tree: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
) -> Option<overlay::Element<'_, Message, Renderer>> {
|
||||
) -> Option<overlay::Element<'b, Message, Renderer>> {
|
||||
let mut children = layout.children();
|
||||
let padded = children.next()?;
|
||||
|
||||
|
|
@ -327,12 +366,23 @@ where
|
|||
content, controls, ..
|
||||
} = self;
|
||||
|
||||
content.overlay(title_layout, renderer).or_else(move || {
|
||||
controls.as_mut().and_then(|controls| {
|
||||
let controls_layout = children.next()?;
|
||||
let mut states = tree.children.iter_mut();
|
||||
let title_state = states.next().unwrap();
|
||||
let controls_state = states.next().unwrap();
|
||||
|
||||
controls.overlay(controls_layout, renderer)
|
||||
content
|
||||
.as_widget()
|
||||
.overlay(title_state, title_layout, renderer)
|
||||
.or_else(move || {
|
||||
controls.as_ref().and_then(|controls| {
|
||||
let controls_layout = children.next()?;
|
||||
|
||||
controls.as_widget().overlay(
|
||||
controls_state,
|
||||
controls_layout,
|
||||
renderer,
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,8 +9,7 @@ use crate::overlay::menu::{self, Menu};
|
|||
use crate::renderer;
|
||||
use crate::text::{self, Text};
|
||||
use crate::touch;
|
||||
use crate::widget::container;
|
||||
use crate::widget::scrollable;
|
||||
use crate::widget::tree::{self, Tree};
|
||||
use crate::{
|
||||
Clipboard, Element, Layout, Length, Padding, Point, Rectangle, Shell, Size,
|
||||
Widget,
|
||||
|
|
@ -27,8 +26,7 @@ where
|
|||
Renderer: text::Renderer,
|
||||
Renderer::Theme: StyleSheet,
|
||||
{
|
||||
state: &'a mut State<T>,
|
||||
on_selected: Box<dyn Fn(T) -> Message>,
|
||||
on_selected: Box<dyn Fn(T) -> Message + 'a>,
|
||||
options: Cow<'a, [T]>,
|
||||
placeholder: Option<String>,
|
||||
selected: Option<T>,
|
||||
|
|
@ -39,35 +37,6 @@ where
|
|||
style: <Renderer::Theme as StyleSheet>::Style,
|
||||
}
|
||||
|
||||
/// The local state of a [`PickList`].
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct State<T> {
|
||||
menu: menu::State,
|
||||
keyboard_modifiers: keyboard::Modifiers,
|
||||
is_open: bool,
|
||||
hovered_option: Option<usize>,
|
||||
last_selection: Option<T>,
|
||||
}
|
||||
|
||||
impl<T> State<T> {
|
||||
/// Creates a new [`State`] for a [`PickList`].
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
menu: menu::State::default(),
|
||||
keyboard_modifiers: keyboard::Modifiers::default(),
|
||||
is_open: bool::default(),
|
||||
hovered_option: Option::default(),
|
||||
last_selection: Option::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Default for State<T> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: 'a, Message, Renderer> PickList<'a, T, Message, Renderer>
|
||||
where
|
||||
T: ToString + Eq,
|
||||
|
|
@ -78,17 +47,14 @@ where
|
|||
/// The default padding of a [`PickList`].
|
||||
pub const DEFAULT_PADDING: Padding = Padding::new(5);
|
||||
|
||||
/// Creates a new [`PickList`] with the given [`State`], a list of options,
|
||||
/// the current selected value, and the message to produce when an option is
|
||||
/// selected.
|
||||
/// Creates a new [`PickList`] with the given list of options, the current
|
||||
/// selected value, and the message to produce when an option is selected.
|
||||
pub fn new(
|
||||
state: &'a mut State<T>,
|
||||
options: impl Into<Cow<'a, [T]>>,
|
||||
selected: Option<T>,
|
||||
on_selected: impl Fn(T) -> Message + 'static,
|
||||
on_selected: impl Fn(T) -> Message + 'a,
|
||||
) -> Self {
|
||||
Self {
|
||||
state,
|
||||
on_selected: Box::new(on_selected),
|
||||
options: options.into(),
|
||||
placeholder: None,
|
||||
|
|
@ -141,6 +107,168 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a, T: 'a, Message, Renderer> Widget<Message, Renderer>
|
||||
for PickList<'a, T, Message, Renderer>
|
||||
where
|
||||
T: Clone + ToString + Eq + 'static,
|
||||
[T]: ToOwned<Owned = Vec<T>>,
|
||||
Message: 'a,
|
||||
Renderer: text::Renderer + 'a,
|
||||
Renderer::Theme: StyleSheet,
|
||||
{
|
||||
fn tag(&self) -> tree::Tag {
|
||||
tree::Tag::of::<State<T>>()
|
||||
}
|
||||
|
||||
fn state(&self) -> tree::State {
|
||||
tree::State::new(State::<T>::new())
|
||||
}
|
||||
|
||||
fn width(&self) -> Length {
|
||||
self.width
|
||||
}
|
||||
|
||||
fn height(&self) -> Length {
|
||||
Length::Shrink
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
layout(
|
||||
renderer,
|
||||
limits,
|
||||
self.width,
|
||||
self.padding,
|
||||
self.text_size,
|
||||
&self.font,
|
||||
self.placeholder.as_deref(),
|
||||
&self.options,
|
||||
)
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
event: Event,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
_renderer: &Renderer,
|
||||
_clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
) -> event::Status {
|
||||
update(
|
||||
event,
|
||||
layout,
|
||||
cursor_position,
|
||||
shell,
|
||||
self.on_selected.as_ref(),
|
||||
self.selected.as_ref(),
|
||||
&self.options,
|
||||
|| tree.state.downcast_mut::<State<T>>(),
|
||||
)
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
_tree: &Tree,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
_renderer: &Renderer,
|
||||
) -> mouse::Interaction {
|
||||
mouse_interaction(layout, cursor_position)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
_tree: &Tree,
|
||||
renderer: &mut Renderer,
|
||||
theme: &Renderer::Theme,
|
||||
_style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
) {
|
||||
draw(
|
||||
renderer,
|
||||
theme,
|
||||
layout,
|
||||
cursor_position,
|
||||
self.padding,
|
||||
self.text_size,
|
||||
&self.font,
|
||||
self.placeholder.as_deref(),
|
||||
self.selected.as_ref(),
|
||||
self.style,
|
||||
)
|
||||
}
|
||||
|
||||
fn overlay<'b>(
|
||||
&'b self,
|
||||
tree: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
_renderer: &Renderer,
|
||||
) -> Option<overlay::Element<'b, Message, Renderer>> {
|
||||
let state = tree.state.downcast_mut::<State<T>>();
|
||||
|
||||
overlay(
|
||||
layout,
|
||||
state,
|
||||
self.padding,
|
||||
self.text_size,
|
||||
self.font.clone(),
|
||||
&self.options,
|
||||
self.style,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: 'a, Message, Renderer> From<PickList<'a, T, Message, Renderer>>
|
||||
for Element<'a, Message, Renderer>
|
||||
where
|
||||
T: Clone + ToString + Eq + 'static,
|
||||
[T]: ToOwned<Owned = Vec<T>>,
|
||||
Message: 'a,
|
||||
Renderer: text::Renderer + 'a,
|
||||
Renderer::Theme: StyleSheet,
|
||||
{
|
||||
fn from(pick_list: PickList<'a, T, Message, Renderer>) -> Self {
|
||||
Self::new(pick_list)
|
||||
}
|
||||
}
|
||||
|
||||
/// The local state of a [`PickList`].
|
||||
#[derive(Debug)]
|
||||
pub struct State<T> {
|
||||
menu: menu::State,
|
||||
keyboard_modifiers: keyboard::Modifiers,
|
||||
is_open: bool,
|
||||
hovered_option: Option<usize>,
|
||||
last_selection: Option<T>,
|
||||
}
|
||||
|
||||
impl<T> State<T> {
|
||||
/// Creates a new [`State`] for a [`PickList`].
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
menu: menu::State::default(),
|
||||
keyboard_modifiers: keyboard::Modifiers::default(),
|
||||
is_open: bool::default(),
|
||||
hovered_option: Option::default(),
|
||||
last_selection: Option::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Default for State<T> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Computes the layout of a [`PickList`].
|
||||
pub fn layout<Renderer, T>(
|
||||
renderer: &Renderer,
|
||||
|
|
@ -429,127 +557,3 @@ pub fn draw<T, Renderer>(
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: 'a, Message, Renderer> Widget<Message, Renderer>
|
||||
for PickList<'a, T, Message, Renderer>
|
||||
where
|
||||
T: Clone + ToString + Eq,
|
||||
[T]: ToOwned<Owned = Vec<T>>,
|
||||
Message: 'static,
|
||||
Renderer: text::Renderer + 'a,
|
||||
Renderer::Theme: StyleSheet,
|
||||
{
|
||||
fn width(&self) -> Length {
|
||||
self.width
|
||||
}
|
||||
|
||||
fn height(&self) -> Length {
|
||||
Length::Shrink
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
layout(
|
||||
renderer,
|
||||
limits,
|
||||
self.width,
|
||||
self.padding,
|
||||
self.text_size,
|
||||
&self.font,
|
||||
self.placeholder.as_deref(),
|
||||
&self.options,
|
||||
)
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
&mut self,
|
||||
event: Event,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
_renderer: &Renderer,
|
||||
_clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
) -> event::Status {
|
||||
update(
|
||||
event,
|
||||
layout,
|
||||
cursor_position,
|
||||
shell,
|
||||
self.on_selected.as_ref(),
|
||||
self.selected.as_ref(),
|
||||
&self.options,
|
||||
|| &mut self.state,
|
||||
)
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
_renderer: &Renderer,
|
||||
) -> mouse::Interaction {
|
||||
mouse_interaction(layout, cursor_position)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
renderer: &mut Renderer,
|
||||
theme: &Renderer::Theme,
|
||||
_style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
) {
|
||||
draw(
|
||||
renderer,
|
||||
theme,
|
||||
layout,
|
||||
cursor_position,
|
||||
self.padding,
|
||||
self.text_size,
|
||||
&self.font,
|
||||
self.placeholder.as_deref(),
|
||||
self.selected.as_ref(),
|
||||
self.style,
|
||||
)
|
||||
}
|
||||
|
||||
fn overlay(
|
||||
&mut self,
|
||||
layout: Layout<'_>,
|
||||
_renderer: &Renderer,
|
||||
) -> Option<overlay::Element<'_, Message, Renderer>> {
|
||||
overlay(
|
||||
layout,
|
||||
self.state,
|
||||
self.padding,
|
||||
self.text_size,
|
||||
self.font.clone(),
|
||||
&self.options,
|
||||
self.style,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: 'a, Message, Renderer> From<PickList<'a, T, Message, Renderer>>
|
||||
for Element<'a, Message, Renderer>
|
||||
where
|
||||
T: Clone + ToString + Eq,
|
||||
[T]: ToOwned<Owned = Vec<T>>,
|
||||
Message: 'static,
|
||||
Renderer: text::Renderer + 'a,
|
||||
Renderer::Theme: StyleSheet
|
||||
+ container::StyleSheet
|
||||
+ scrollable::StyleSheet
|
||||
+ menu::StyleSheet,
|
||||
<Renderer::Theme as StyleSheet>::Style:
|
||||
Into<<Renderer::Theme as menu::StyleSheet>::Style>,
|
||||
{
|
||||
fn from(pick_list: PickList<'a, T, Message, Renderer>) -> Self {
|
||||
Element::new(pick_list)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
//! Provide progress feedback to your users.
|
||||
use crate::layout;
|
||||
use crate::renderer;
|
||||
use crate::widget::Tree;
|
||||
use crate::{Color, Element, Layout, Length, Point, Rectangle, Size, Widget};
|
||||
|
||||
use std::ops::RangeInclusive;
|
||||
|
|
@ -105,6 +106,7 @@ where
|
|||
|
||||
fn draw(
|
||||
&self,
|
||||
_state: &Tree,
|
||||
renderer: &mut Renderer,
|
||||
theme: &Renderer::Theme,
|
||||
_style: &renderer::Style,
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use crate::mouse;
|
|||
use crate::renderer;
|
||||
use crate::text;
|
||||
use crate::touch;
|
||||
use crate::widget::{self, Row, Text};
|
||||
use crate::widget::{self, Row, Text, Tree};
|
||||
use crate::{
|
||||
Alignment, Clipboard, Color, Element, Layout, Length, Point, Rectangle,
|
||||
Shell, Widget,
|
||||
|
|
@ -176,6 +176,7 @@ where
|
|||
|
||||
fn on_event(
|
||||
&mut self,
|
||||
_state: &mut Tree,
|
||||
event: Event,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
|
|
@ -200,6 +201,7 @@ where
|
|||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
_state: &Tree,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
|
|
@ -214,6 +216,7 @@ where
|
|||
|
||||
fn draw(
|
||||
&self,
|
||||
_state: &Tree,
|
||||
renderer: &mut Renderer,
|
||||
theme: &Renderer::Theme,
|
||||
style: &renderer::Style,
|
||||
|
|
|
|||
|
|
@ -1,16 +1,15 @@
|
|||
//! Distribute content horizontally.
|
||||
use crate::event::{self, Event};
|
||||
use crate::layout;
|
||||
use crate::layout::{self, Layout};
|
||||
use crate::mouse;
|
||||
use crate::overlay;
|
||||
use crate::renderer;
|
||||
use crate::widget::{Operation, Tree};
|
||||
use crate::{
|
||||
Alignment, Clipboard, Element, Layout, Length, Padding, Point, Rectangle,
|
||||
Shell, Widget,
|
||||
Alignment, Clipboard, Element, Length, Padding, Point, Rectangle, Shell,
|
||||
Widget,
|
||||
};
|
||||
|
||||
use std::u32;
|
||||
|
||||
/// A container that distributes its contents horizontally.
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Row<'a, Message, Renderer> {
|
||||
|
|
@ -18,8 +17,6 @@ pub struct Row<'a, Message, Renderer> {
|
|||
padding: Padding,
|
||||
width: Length,
|
||||
height: Length,
|
||||
max_width: u32,
|
||||
max_height: u32,
|
||||
align_items: Alignment,
|
||||
children: Vec<Element<'a, Message, Renderer>>,
|
||||
}
|
||||
|
|
@ -39,8 +36,6 @@ impl<'a, Message, Renderer> Row<'a, Message, Renderer> {
|
|||
padding: Padding::ZERO,
|
||||
width: Length::Shrink,
|
||||
height: Length::Shrink,
|
||||
max_width: u32::MAX,
|
||||
max_height: u32::MAX,
|
||||
align_items: Alignment::Start,
|
||||
children,
|
||||
}
|
||||
|
|
@ -74,18 +69,6 @@ impl<'a, Message, Renderer> Row<'a, Message, Renderer> {
|
|||
self
|
||||
}
|
||||
|
||||
/// Sets the maximum width of the [`Row`].
|
||||
pub fn max_width(mut self, max_width: u32) -> Self {
|
||||
self.max_width = max_width;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the maximum height of the [`Row`].
|
||||
pub fn max_height(mut self, max_height: u32) -> Self {
|
||||
self.max_height = max_height;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the vertical alignment of the contents of the [`Row`] .
|
||||
pub fn align_items(mut self, align: Alignment) -> Self {
|
||||
self.align_items = align;
|
||||
|
|
@ -93,10 +76,10 @@ impl<'a, Message, Renderer> Row<'a, Message, Renderer> {
|
|||
}
|
||||
|
||||
/// Adds an [`Element`] to the [`Row`].
|
||||
pub fn push<E>(mut self, child: E) -> Self
|
||||
where
|
||||
E: Into<Element<'a, Message, Renderer>>,
|
||||
{
|
||||
pub fn push(
|
||||
mut self,
|
||||
child: impl Into<Element<'a, Message, Renderer>>,
|
||||
) -> Self {
|
||||
self.children.push(child.into());
|
||||
self
|
||||
}
|
||||
|
|
@ -113,6 +96,14 @@ impl<'a, Message, Renderer> Widget<Message, Renderer>
|
|||
where
|
||||
Renderer: crate::Renderer,
|
||||
{
|
||||
fn children(&self) -> Vec<Tree> {
|
||||
self.children.iter().map(Tree::new).collect()
|
||||
}
|
||||
|
||||
fn diff(&self, tree: &mut Tree) {
|
||||
tree.diff_children(&self.children)
|
||||
}
|
||||
|
||||
fn width(&self) -> Length {
|
||||
self.width
|
||||
}
|
||||
|
|
@ -126,11 +117,7 @@ where
|
|||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
let limits = limits
|
||||
.max_width(self.max_width)
|
||||
.max_height(self.max_height)
|
||||
.width(self.width)
|
||||
.height(self.height);
|
||||
let limits = limits.width(self.width).height(self.height);
|
||||
|
||||
layout::flex::resolve(
|
||||
layout::flex::Axis::Horizontal,
|
||||
|
|
@ -143,8 +130,26 @@ where
|
|||
)
|
||||
}
|
||||
|
||||
fn operate(
|
||||
&self,
|
||||
tree: &mut Tree,
|
||||
layout: Layout<'_>,
|
||||
operation: &mut dyn Operation<Message>,
|
||||
) {
|
||||
operation.container(None, &mut |operation| {
|
||||
self.children
|
||||
.iter()
|
||||
.zip(&mut tree.children)
|
||||
.zip(layout.children())
|
||||
.for_each(|((child, state), layout)| {
|
||||
child.as_widget().operate(state, layout, operation);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
event: Event,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
|
|
@ -154,9 +159,11 @@ where
|
|||
) -> event::Status {
|
||||
self.children
|
||||
.iter_mut()
|
||||
.zip(&mut tree.children)
|
||||
.zip(layout.children())
|
||||
.map(|(child, layout)| {
|
||||
child.widget.on_event(
|
||||
.map(|((child, state), layout)| {
|
||||
child.as_widget_mut().on_event(
|
||||
state,
|
||||
event.clone(),
|
||||
layout,
|
||||
cursor_position,
|
||||
|
|
@ -170,6 +177,7 @@ where
|
|||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
|
|
@ -177,9 +185,11 @@ where
|
|||
) -> mouse::Interaction {
|
||||
self.children
|
||||
.iter()
|
||||
.zip(&tree.children)
|
||||
.zip(layout.children())
|
||||
.map(|(child, layout)| {
|
||||
child.widget.mouse_interaction(
|
||||
.map(|((child, state), layout)| {
|
||||
child.as_widget().mouse_interaction(
|
||||
state,
|
||||
layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
|
|
@ -192,6 +202,7 @@ where
|
|||
|
||||
fn draw(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
renderer: &mut Renderer,
|
||||
theme: &Renderer::Theme,
|
||||
style: &renderer::Style,
|
||||
|
|
@ -199,8 +210,14 @@ where
|
|||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
) {
|
||||
for (child, layout) in self.children.iter().zip(layout.children()) {
|
||||
child.draw(
|
||||
for ((child, state), layout) in self
|
||||
.children
|
||||
.iter()
|
||||
.zip(&tree.children)
|
||||
.zip(layout.children())
|
||||
{
|
||||
child.as_widget().draw(
|
||||
state,
|
||||
renderer,
|
||||
theme,
|
||||
style,
|
||||
|
|
@ -211,28 +228,23 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
fn overlay(
|
||||
&mut self,
|
||||
fn overlay<'b>(
|
||||
&'b self,
|
||||
tree: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
) -> Option<overlay::Element<'_, Message, Renderer>> {
|
||||
self.children
|
||||
.iter_mut()
|
||||
.zip(layout.children())
|
||||
.filter_map(|(child, layout)| {
|
||||
child.widget.overlay(layout, renderer)
|
||||
})
|
||||
.next()
|
||||
) -> Option<overlay::Element<'b, Message, Renderer>> {
|
||||
overlay::from_children(&self.children, tree, layout, renderer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> From<Row<'a, Message, Renderer>>
|
||||
for Element<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: 'a + crate::Renderer,
|
||||
Message: 'a,
|
||||
Renderer: crate::Renderer + 'a,
|
||||
{
|
||||
fn from(row: Row<'a, Message, Renderer>) -> Element<'a, Message, Renderer> {
|
||||
Element::new(row)
|
||||
fn from(row: Row<'a, Message, Renderer>) -> Self {
|
||||
Self::new(row)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
//! Display a horizontal or vertical rule for dividing content.
|
||||
use crate::layout;
|
||||
use crate::renderer;
|
||||
use crate::widget::Tree;
|
||||
use crate::{Color, Element, Layout, Length, Point, Rectangle, Size, Widget};
|
||||
|
||||
pub use iced_style::rule::{Appearance, FillMode, StyleSheet};
|
||||
|
|
@ -78,6 +79,7 @@ where
|
|||
|
||||
fn draw(
|
||||
&self,
|
||||
_state: &Tree,
|
||||
renderer: &mut Renderer,
|
||||
theme: &Renderer::Theme,
|
||||
_style: &renderer::Style,
|
||||
|
|
|
|||
|
|
@ -5,10 +5,12 @@ use crate::mouse;
|
|||
use crate::overlay;
|
||||
use crate::renderer;
|
||||
use crate::touch;
|
||||
use crate::widget::Column;
|
||||
use crate::widget;
|
||||
use crate::widget::operation::{self, Operation};
|
||||
use crate::widget::tree::{self, Tree};
|
||||
use crate::{
|
||||
Alignment, Background, Clipboard, Color, Element, Layout, Length, Padding,
|
||||
Point, Rectangle, Shell, Size, Vector, Widget,
|
||||
Background, Clipboard, Color, Command, Element, Layout, Length, Point,
|
||||
Rectangle, Shell, Size, Vector, Widget,
|
||||
};
|
||||
|
||||
use std::{f32, u32};
|
||||
|
|
@ -30,13 +32,12 @@ where
|
|||
Renderer: crate::Renderer,
|
||||
Renderer::Theme: StyleSheet,
|
||||
{
|
||||
state: &'a mut State,
|
||||
id: Option<Id>,
|
||||
height: Length,
|
||||
max_height: u32,
|
||||
scrollbar_width: u16,
|
||||
scrollbar_margin: u16,
|
||||
scroller_width: u16,
|
||||
content: Column<'a, Message, Renderer>,
|
||||
content: Element<'a, Message, Renderer>,
|
||||
on_scroll: Option<Box<dyn Fn(f32) -> Message + 'a>>,
|
||||
style: <Renderer::Theme as StyleSheet>::Style,
|
||||
}
|
||||
|
|
@ -46,40 +47,23 @@ where
|
|||
Renderer: crate::Renderer,
|
||||
Renderer::Theme: StyleSheet,
|
||||
{
|
||||
/// Creates a new [`Scrollable`] with the given [`State`].
|
||||
pub fn new(state: &'a mut State) -> Self {
|
||||
/// Creates a new [`Scrollable`].
|
||||
pub fn new(content: impl Into<Element<'a, Message, Renderer>>) -> Self {
|
||||
Scrollable {
|
||||
state,
|
||||
id: None,
|
||||
height: Length::Shrink,
|
||||
max_height: u32::MAX,
|
||||
scrollbar_width: 10,
|
||||
scrollbar_margin: 0,
|
||||
scroller_width: 10,
|
||||
content: Column::new(),
|
||||
content: content.into(),
|
||||
on_scroll: None,
|
||||
style: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the vertical spacing _between_ elements.
|
||||
///
|
||||
/// Custom margins per element do not exist in Iced. You should use this
|
||||
/// method instead! While less flexible, it helps you keep spacing between
|
||||
/// elements consistent.
|
||||
pub fn spacing(mut self, units: u16) -> Self {
|
||||
self.content = self.content.spacing(units);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the [`Padding`] of the [`Scrollable`].
|
||||
pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
|
||||
self.content = self.content.padding(padding);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the width of the [`Scrollable`].
|
||||
pub fn width(mut self, width: Length) -> Self {
|
||||
self.content = self.content.width(width);
|
||||
/// Sets the [`Id`] of the [`Scrollable`].
|
||||
pub fn id(mut self, id: Id) -> Self {
|
||||
self.id = Some(id);
|
||||
self
|
||||
}
|
||||
|
||||
|
|
@ -89,24 +73,6 @@ where
|
|||
self
|
||||
}
|
||||
|
||||
/// Sets the maximum width of the [`Scrollable`].
|
||||
pub fn max_width(mut self, max_width: u32) -> Self {
|
||||
self.content = self.content.max_width(max_width);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the maximum height of the [`Scrollable`] in pixels.
|
||||
pub fn max_height(mut self, max_height: u32) -> Self {
|
||||
self.max_height = max_height;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the horizontal alignment of the contents of the [`Scrollable`] .
|
||||
pub fn align_items(mut self, align_items: Alignment) -> Self {
|
||||
self.content = self.content.align_items(align_items);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the scrollbar width of the [`Scrollable`] .
|
||||
/// Silently enforces a minimum value of 1.
|
||||
pub fn scrollbar_width(mut self, scrollbar_width: u16) -> Self {
|
||||
|
|
@ -132,7 +98,7 @@ where
|
|||
///
|
||||
/// The function takes the new relative offset of the [`Scrollable`]
|
||||
/// (e.g. `0` means top, while `1` means bottom).
|
||||
pub fn on_scroll(mut self, f: impl Fn(f32) -> Message + 'static) -> Self {
|
||||
pub fn on_scroll(mut self, f: impl Fn(f32) -> Message + 'a) -> Self {
|
||||
self.on_scroll = Some(Box::new(f));
|
||||
self
|
||||
}
|
||||
|
|
@ -145,15 +111,233 @@ where
|
|||
self.style = style.into();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds an element to the [`Scrollable`].
|
||||
pub fn push<E>(mut self, child: E) -> Self
|
||||
where
|
||||
E: Into<Element<'a, Message, Renderer>>,
|
||||
{
|
||||
self.content = self.content.push(child);
|
||||
self
|
||||
impl<'a, Message, Renderer> Widget<Message, Renderer>
|
||||
for Scrollable<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: crate::Renderer,
|
||||
Renderer::Theme: StyleSheet,
|
||||
{
|
||||
fn tag(&self) -> tree::Tag {
|
||||
tree::Tag::of::<State>()
|
||||
}
|
||||
|
||||
fn state(&self) -> tree::State {
|
||||
tree::State::new(State::new())
|
||||
}
|
||||
|
||||
fn children(&self) -> Vec<Tree> {
|
||||
vec![Tree::new(&self.content)]
|
||||
}
|
||||
|
||||
fn diff(&self, tree: &mut Tree) {
|
||||
tree.diff_children(std::slice::from_ref(&self.content))
|
||||
}
|
||||
|
||||
fn width(&self) -> Length {
|
||||
self.content.as_widget().width()
|
||||
}
|
||||
|
||||
fn height(&self) -> Length {
|
||||
self.height
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
layout(
|
||||
renderer,
|
||||
limits,
|
||||
Widget::<Message, Renderer>::width(self),
|
||||
self.height,
|
||||
u32::MAX,
|
||||
|renderer, limits| {
|
||||
self.content.as_widget().layout(renderer, limits)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn operate(
|
||||
&self,
|
||||
tree: &mut Tree,
|
||||
layout: Layout<'_>,
|
||||
operation: &mut dyn Operation<Message>,
|
||||
) {
|
||||
let state = tree.state.downcast_mut::<State>();
|
||||
|
||||
operation.scrollable(state, self.id.as_ref().map(|id| &id.0));
|
||||
|
||||
operation.container(None, &mut |operation| {
|
||||
self.content.as_widget().operate(
|
||||
&mut tree.children[0],
|
||||
layout.children().next().unwrap(),
|
||||
operation,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
event: Event,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
renderer: &Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
) -> event::Status {
|
||||
update(
|
||||
tree.state.downcast_mut::<State>(),
|
||||
event,
|
||||
layout,
|
||||
cursor_position,
|
||||
clipboard,
|
||||
shell,
|
||||
self.scrollbar_width,
|
||||
self.scrollbar_margin,
|
||||
self.scroller_width,
|
||||
&self.on_scroll,
|
||||
|event, layout, cursor_position, clipboard, shell| {
|
||||
self.content.as_widget_mut().on_event(
|
||||
&mut tree.children[0],
|
||||
event,
|
||||
layout,
|
||||
cursor_position,
|
||||
renderer,
|
||||
clipboard,
|
||||
shell,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
renderer: &mut Renderer,
|
||||
theme: &Renderer::Theme,
|
||||
style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
) {
|
||||
draw(
|
||||
tree.state.downcast_ref::<State>(),
|
||||
renderer,
|
||||
theme,
|
||||
layout,
|
||||
cursor_position,
|
||||
self.scrollbar_width,
|
||||
self.scrollbar_margin,
|
||||
self.scroller_width,
|
||||
self.style,
|
||||
|renderer, layout, cursor_position, viewport| {
|
||||
self.content.as_widget().draw(
|
||||
&tree.children[0],
|
||||
renderer,
|
||||
theme,
|
||||
style,
|
||||
layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
renderer: &Renderer,
|
||||
) -> mouse::Interaction {
|
||||
mouse_interaction(
|
||||
tree.state.downcast_ref::<State>(),
|
||||
layout,
|
||||
cursor_position,
|
||||
self.scrollbar_width,
|
||||
self.scrollbar_margin,
|
||||
self.scroller_width,
|
||||
|layout, cursor_position, viewport| {
|
||||
self.content.as_widget().mouse_interaction(
|
||||
&tree.children[0],
|
||||
layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
renderer,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn overlay<'b>(
|
||||
&'b self,
|
||||
tree: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
) -> Option<overlay::Element<'b, Message, Renderer>> {
|
||||
self.content
|
||||
.as_widget()
|
||||
.overlay(
|
||||
&mut tree.children[0],
|
||||
layout.children().next().unwrap(),
|
||||
renderer,
|
||||
)
|
||||
.map(|overlay| {
|
||||
let bounds = layout.bounds();
|
||||
let content_layout = layout.children().next().unwrap();
|
||||
let content_bounds = content_layout.bounds();
|
||||
let offset = tree
|
||||
.state
|
||||
.downcast_ref::<State>()
|
||||
.offset(bounds, content_bounds);
|
||||
|
||||
overlay.translate(Vector::new(0.0, -(offset as f32)))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> From<Scrollable<'a, Message, Renderer>>
|
||||
for Element<'a, Message, Renderer>
|
||||
where
|
||||
Message: 'a,
|
||||
Renderer: 'a + crate::Renderer,
|
||||
Renderer::Theme: StyleSheet,
|
||||
{
|
||||
fn from(
|
||||
text_input: Scrollable<'a, Message, Renderer>,
|
||||
) -> Element<'a, Message, Renderer> {
|
||||
Element::new(text_input)
|
||||
}
|
||||
}
|
||||
|
||||
/// The identifier of a [`Scrollable`].
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct Id(widget::Id);
|
||||
|
||||
impl Id {
|
||||
/// Creates a custom [`Id`].
|
||||
pub fn new(id: impl Into<std::borrow::Cow<'static, str>>) -> Self {
|
||||
Self(widget::Id::new(id))
|
||||
}
|
||||
|
||||
/// Creates a unique [`Id`].
|
||||
///
|
||||
/// This function produces a different [`Id`] every time it is called.
|
||||
pub fn unique() -> Self {
|
||||
Self(widget::Id::unique())
|
||||
}
|
||||
}
|
||||
|
||||
/// Produces a [`Command`] that snaps the [`Scrollable`] with the given [`Id`]
|
||||
/// to the provided `percentage`.
|
||||
pub fn snap_to<Message: 'static>(id: Id, percentage: f32) -> Command<Message> {
|
||||
Command::widget(operation::scrollable::snap_to(id.0, percentage))
|
||||
}
|
||||
|
||||
/// Computes the layout of a [`Scrollable`].
|
||||
|
|
@ -625,145 +809,6 @@ fn notify_on_scroll<Message>(
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> Widget<Message, Renderer>
|
||||
for Scrollable<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: crate::Renderer,
|
||||
Renderer::Theme: StyleSheet,
|
||||
{
|
||||
fn width(&self) -> Length {
|
||||
Widget::<Message, Renderer>::width(&self.content)
|
||||
}
|
||||
|
||||
fn height(&self) -> Length {
|
||||
self.height
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
layout(
|
||||
renderer,
|
||||
limits,
|
||||
Widget::<Message, Renderer>::width(self),
|
||||
self.height,
|
||||
self.max_height,
|
||||
|renderer, limits| self.content.layout(renderer, limits),
|
||||
)
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
&mut self,
|
||||
event: Event,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
renderer: &Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
) -> event::Status {
|
||||
update(
|
||||
self.state,
|
||||
event,
|
||||
layout,
|
||||
cursor_position,
|
||||
clipboard,
|
||||
shell,
|
||||
self.scrollbar_width,
|
||||
self.scrollbar_margin,
|
||||
self.scroller_width,
|
||||
&self.on_scroll,
|
||||
|event, layout, cursor_position, clipboard, shell| {
|
||||
self.content.on_event(
|
||||
event,
|
||||
layout,
|
||||
cursor_position,
|
||||
renderer,
|
||||
clipboard,
|
||||
shell,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
renderer: &Renderer,
|
||||
) -> mouse::Interaction {
|
||||
mouse_interaction(
|
||||
self.state,
|
||||
layout,
|
||||
cursor_position,
|
||||
self.scrollbar_width,
|
||||
self.scrollbar_margin,
|
||||
self.scroller_width,
|
||||
|layout, cursor_position, viewport| {
|
||||
self.content.mouse_interaction(
|
||||
layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
renderer,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
renderer: &mut Renderer,
|
||||
theme: &Renderer::Theme,
|
||||
style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
) {
|
||||
draw(
|
||||
self.state,
|
||||
renderer,
|
||||
theme,
|
||||
layout,
|
||||
cursor_position,
|
||||
self.scrollbar_width,
|
||||
self.scrollbar_margin,
|
||||
self.scroller_width,
|
||||
self.style,
|
||||
|renderer, layout, cursor_position, viewport| {
|
||||
self.content.draw(
|
||||
renderer,
|
||||
theme,
|
||||
style,
|
||||
layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn overlay(
|
||||
&mut self,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
) -> Option<overlay::Element<'_, Message, Renderer>> {
|
||||
let Self { content, state, .. } = self;
|
||||
|
||||
content
|
||||
.overlay(layout.children().next().unwrap(), renderer)
|
||||
.map(|overlay| {
|
||||
let bounds = layout.bounds();
|
||||
let content_layout = layout.children().next().unwrap();
|
||||
let content_bounds = content_layout.bounds();
|
||||
let offset = state.offset(bounds, content_bounds);
|
||||
|
||||
overlay.translate(Vector::new(0.0, -(offset as f32)))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// The local state of a [`Scrollable`].
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct State {
|
||||
|
|
@ -782,6 +827,12 @@ impl Default for State {
|
|||
}
|
||||
}
|
||||
|
||||
impl operation::Scrollable for State {
|
||||
fn snap_to(&mut self, percentage: f32) {
|
||||
State::snap_to(self, percentage);
|
||||
}
|
||||
}
|
||||
|
||||
/// The local state of a [`Scrollable`].
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum Offset {
|
||||
|
|
@ -926,17 +977,3 @@ struct Scroller {
|
|||
/// The bounds of the [`Scroller`].
|
||||
bounds: Rectangle,
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> From<Scrollable<'a, Message, Renderer>>
|
||||
for Element<'a, Message, Renderer>
|
||||
where
|
||||
Message: 'a,
|
||||
Renderer: 'a + crate::Renderer,
|
||||
Renderer::Theme: StyleSheet,
|
||||
{
|
||||
fn from(
|
||||
scrollable: Scrollable<'a, Message, Renderer>,
|
||||
) -> Element<'a, Message, Renderer> {
|
||||
Element::new(scrollable)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ use crate::layout;
|
|||
use crate::mouse;
|
||||
use crate::renderer;
|
||||
use crate::touch;
|
||||
use crate::widget::tree::{self, Tree};
|
||||
use crate::{
|
||||
Background, Clipboard, Color, Element, Layout, Length, Point, Rectangle,
|
||||
Shell, Size, Widget,
|
||||
|
|
@ -29,15 +30,15 @@ pub use iced_style::slider::{Appearance, Handle, HandleShape, StyleSheet};
|
|||
/// # use iced_native::renderer::Null;
|
||||
/// #
|
||||
/// # type Slider<'a, T, Message> = slider::Slider<'a, T, Message, Null>;
|
||||
/// #
|
||||
/// #[derive(Clone)]
|
||||
/// pub enum Message {
|
||||
/// SliderChanged(f32),
|
||||
/// }
|
||||
///
|
||||
/// let state = &mut slider::State::new();
|
||||
/// let value = 50.0;
|
||||
///
|
||||
/// Slider::new(state, 0.0..=100.0, value, Message::SliderChanged);
|
||||
/// Slider::new(0.0..=100.0, value, Message::SliderChanged);
|
||||
/// ```
|
||||
///
|
||||
/// 
|
||||
|
|
@ -47,11 +48,10 @@ where
|
|||
Renderer: crate::Renderer,
|
||||
Renderer::Theme: StyleSheet,
|
||||
{
|
||||
state: &'a mut State,
|
||||
range: RangeInclusive<T>,
|
||||
step: T,
|
||||
value: T,
|
||||
on_change: Box<dyn Fn(T) -> Message>,
|
||||
on_change: Box<dyn Fn(T) -> Message + 'a>,
|
||||
on_release: Option<Message>,
|
||||
width: Length,
|
||||
height: u16,
|
||||
|
|
@ -71,20 +71,14 @@ where
|
|||
/// Creates a new [`Slider`].
|
||||
///
|
||||
/// It expects:
|
||||
/// * the local [`State`] of the [`Slider`]
|
||||
/// * an inclusive range of possible values
|
||||
/// * the current value of the [`Slider`]
|
||||
/// * a function that will be called when the [`Slider`] is dragged.
|
||||
/// It receives the new value of the [`Slider`] and must produce a
|
||||
/// `Message`.
|
||||
pub fn new<F>(
|
||||
state: &'a mut State,
|
||||
range: RangeInclusive<T>,
|
||||
value: T,
|
||||
on_change: F,
|
||||
) -> Self
|
||||
pub fn new<F>(range: RangeInclusive<T>, value: T, on_change: F) -> Self
|
||||
where
|
||||
F: 'static + Fn(T) -> Message,
|
||||
F: 'a + Fn(T) -> Message,
|
||||
{
|
||||
let value = if value >= *range.start() {
|
||||
value
|
||||
|
|
@ -99,7 +93,6 @@ where
|
|||
};
|
||||
|
||||
Slider {
|
||||
state,
|
||||
value,
|
||||
range,
|
||||
step: T::from(1),
|
||||
|
|
@ -150,6 +143,120 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a, T, Message, Renderer> Widget<Message, Renderer>
|
||||
for Slider<'a, T, Message, Renderer>
|
||||
where
|
||||
T: Copy + Into<f64> + num_traits::FromPrimitive,
|
||||
Message: Clone,
|
||||
Renderer: crate::Renderer,
|
||||
Renderer::Theme: StyleSheet,
|
||||
{
|
||||
fn tag(&self) -> tree::Tag {
|
||||
tree::Tag::of::<State>()
|
||||
}
|
||||
|
||||
fn state(&self) -> tree::State {
|
||||
tree::State::new(State::new())
|
||||
}
|
||||
|
||||
fn width(&self) -> Length {
|
||||
self.width
|
||||
}
|
||||
|
||||
fn height(&self) -> Length {
|
||||
Length::Shrink
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
_renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
let limits =
|
||||
limits.width(self.width).height(Length::Units(self.height));
|
||||
|
||||
let size = limits.resolve(Size::ZERO);
|
||||
|
||||
layout::Node::new(size)
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
event: Event,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
_renderer: &Renderer,
|
||||
_clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
) -> event::Status {
|
||||
update(
|
||||
event,
|
||||
layout,
|
||||
cursor_position,
|
||||
shell,
|
||||
tree.state.downcast_mut::<State>(),
|
||||
&mut self.value,
|
||||
&self.range,
|
||||
self.step,
|
||||
self.on_change.as_ref(),
|
||||
&self.on_release,
|
||||
)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
renderer: &mut Renderer,
|
||||
theme: &Renderer::Theme,
|
||||
_style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
) {
|
||||
draw(
|
||||
renderer,
|
||||
layout,
|
||||
cursor_position,
|
||||
tree.state.downcast_ref::<State>(),
|
||||
self.value,
|
||||
&self.range,
|
||||
theme,
|
||||
self.style,
|
||||
)
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
_renderer: &Renderer,
|
||||
) -> mouse::Interaction {
|
||||
mouse_interaction(
|
||||
layout,
|
||||
cursor_position,
|
||||
tree.state.downcast_ref::<State>(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T, Message, Renderer> From<Slider<'a, T, Message, Renderer>>
|
||||
for Element<'a, Message, Renderer>
|
||||
where
|
||||
T: 'a + Copy + Into<f64> + num_traits::FromPrimitive,
|
||||
Message: 'a + Clone,
|
||||
Renderer: 'a + crate::Renderer,
|
||||
Renderer::Theme: StyleSheet,
|
||||
{
|
||||
fn from(
|
||||
slider: Slider<'a, T, Message, Renderer>,
|
||||
) -> Element<'a, Message, Renderer> {
|
||||
Element::new(slider)
|
||||
}
|
||||
}
|
||||
|
||||
/// Processes an [`Event`] and updates the [`State`] of a [`Slider`]
|
||||
/// accordingly.
|
||||
pub fn update<Message, T>(
|
||||
|
|
@ -366,102 +473,3 @@ impl State {
|
|||
State::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T, Message, Renderer> Widget<Message, Renderer>
|
||||
for Slider<'a, T, Message, Renderer>
|
||||
where
|
||||
T: Copy + Into<f64> + num_traits::FromPrimitive,
|
||||
Message: Clone,
|
||||
Renderer: crate::Renderer,
|
||||
Renderer::Theme: StyleSheet,
|
||||
{
|
||||
fn width(&self) -> Length {
|
||||
self.width
|
||||
}
|
||||
|
||||
fn height(&self) -> Length {
|
||||
Length::Shrink
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
_renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
let limits =
|
||||
limits.width(self.width).height(Length::Units(self.height));
|
||||
|
||||
let size = limits.resolve(Size::ZERO);
|
||||
|
||||
layout::Node::new(size)
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
&mut self,
|
||||
event: Event,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
_renderer: &Renderer,
|
||||
_clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
) -> event::Status {
|
||||
update(
|
||||
event,
|
||||
layout,
|
||||
cursor_position,
|
||||
shell,
|
||||
self.state,
|
||||
&mut self.value,
|
||||
&self.range,
|
||||
self.step,
|
||||
self.on_change.as_ref(),
|
||||
&self.on_release,
|
||||
)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
renderer: &mut Renderer,
|
||||
theme: &Renderer::Theme,
|
||||
_style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
) {
|
||||
draw(
|
||||
renderer,
|
||||
layout,
|
||||
cursor_position,
|
||||
self.state,
|
||||
self.value,
|
||||
&self.range,
|
||||
theme,
|
||||
self.style,
|
||||
)
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
_renderer: &Renderer,
|
||||
) -> mouse::Interaction {
|
||||
mouse_interaction(layout, cursor_position, self.state)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T, Message, Renderer> From<Slider<'a, T, Message, Renderer>>
|
||||
for Element<'a, Message, Renderer>
|
||||
where
|
||||
T: 'a + Copy + Into<f64> + num_traits::FromPrimitive,
|
||||
Message: 'a + Clone,
|
||||
Renderer: 'a + crate::Renderer,
|
||||
Renderer::Theme: StyleSheet,
|
||||
{
|
||||
fn from(
|
||||
slider: Slider<'a, T, Message, Renderer>,
|
||||
) -> Element<'a, Message, Renderer> {
|
||||
Element::new(slider)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
//! Distribute content vertically.
|
||||
use crate::layout;
|
||||
use crate::renderer;
|
||||
use crate::widget::Tree;
|
||||
use crate::{Element, Layout, Length, Point, Rectangle, Size, Widget};
|
||||
|
||||
/// An amount of empty space.
|
||||
|
|
@ -59,6 +60,7 @@ where
|
|||
|
||||
fn draw(
|
||||
&self,
|
||||
_state: &Tree,
|
||||
_renderer: &mut Renderer,
|
||||
_theme: &Renderer::Theme,
|
||||
_style: &renderer::Style,
|
||||
|
|
|
|||
|
|
@ -1,13 +1,16 @@
|
|||
//! Display vector graphics in your application.
|
||||
use crate::layout;
|
||||
use crate::renderer;
|
||||
use crate::svg::{self, Handle};
|
||||
use crate::svg;
|
||||
use crate::widget::Tree;
|
||||
use crate::{
|
||||
ContentFit, Element, Layout, Length, Point, Rectangle, Size, Vector, Widget,
|
||||
};
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub use svg::Handle;
|
||||
|
||||
/// A vector graphics image.
|
||||
///
|
||||
/// An [`Svg`] image resizes smoothly without losing any quality.
|
||||
|
|
@ -109,6 +112,7 @@ where
|
|||
|
||||
fn draw(
|
||||
&self,
|
||||
_state: &Tree,
|
||||
renderer: &mut Renderer,
|
||||
_theme: &Renderer::Theme,
|
||||
_style: &renderer::Style,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ use crate::alignment;
|
|||
use crate::layout;
|
||||
use crate::renderer;
|
||||
use crate::text;
|
||||
use crate::widget::Tree;
|
||||
use crate::{Element, Layout, Length, Point, Rectangle, Size, Widget};
|
||||
|
||||
pub use iced_style::text::{Appearance, StyleSheet};
|
||||
|
|
@ -44,9 +45,9 @@ where
|
|||
Renderer::Theme: StyleSheet,
|
||||
{
|
||||
/// Create a new fragment of [`Text`] with the given contents.
|
||||
pub fn new<T: Into<String>>(label: T) -> Self {
|
||||
pub fn new<T: ToString>(label: T) -> Self {
|
||||
Text {
|
||||
content: label.into(),
|
||||
content: label.to_string(),
|
||||
size: None,
|
||||
font: Default::default(),
|
||||
width: Length::Shrink,
|
||||
|
|
@ -145,6 +146,7 @@ where
|
|||
|
||||
fn draw(
|
||||
&self,
|
||||
_state: &Tree,
|
||||
renderer: &mut Renderer,
|
||||
theme: &Renderer::Theme,
|
||||
style: &renderer::Style,
|
||||
|
|
@ -243,3 +245,13 @@ where
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> From<&'a str> for Element<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: text::Renderer + 'a,
|
||||
Renderer::Theme: StyleSheet,
|
||||
{
|
||||
fn from(contents: &'a str) -> Self {
|
||||
Text::new(contents).into()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,9 +19,12 @@ use crate::mouse::{self, click};
|
|||
use crate::renderer;
|
||||
use crate::text::{self, Text};
|
||||
use crate::touch;
|
||||
use crate::widget;
|
||||
use crate::widget::operation::{self, Operation};
|
||||
use crate::widget::tree::{self, Tree};
|
||||
use crate::{
|
||||
Clipboard, Color, Element, Layout, Length, Padding, Point, Rectangle,
|
||||
Shell, Size, Vector, Widget,
|
||||
Clipboard, Color, Command, Element, Layout, Length, Padding, Point,
|
||||
Rectangle, Shell, Size, Vector, Widget,
|
||||
};
|
||||
|
||||
pub use iced_style::text_input::{Appearance, StyleSheet};
|
||||
|
|
@ -30,20 +33,15 @@ pub use iced_style::text_input::{Appearance, StyleSheet};
|
|||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use iced_native::renderer::Null;
|
||||
/// # use iced_native::widget::text_input;
|
||||
/// #
|
||||
/// # pub type TextInput<'a, Message> = iced_native::widget::TextInput<'a, Message, Null>;
|
||||
/// # pub type TextInput<'a, Message> = iced_native::widget::TextInput<'a, Message, iced_native::renderer::Null>;
|
||||
/// #[derive(Debug, Clone)]
|
||||
/// enum Message {
|
||||
/// TextInputChanged(String),
|
||||
/// }
|
||||
///
|
||||
/// let mut state = text_input::State::new();
|
||||
/// let value = "Some text";
|
||||
///
|
||||
/// let input = TextInput::new(
|
||||
/// &mut state,
|
||||
/// "This is the placeholder...",
|
||||
/// value,
|
||||
/// Message::TextInputChanged,
|
||||
|
|
@ -57,7 +55,7 @@ where
|
|||
Renderer: text::Renderer,
|
||||
Renderer::Theme: StyleSheet,
|
||||
{
|
||||
state: &'a mut State,
|
||||
id: Option<Id>,
|
||||
placeholder: String,
|
||||
value: Value,
|
||||
is_secure: bool,
|
||||
|
|
@ -80,21 +78,15 @@ where
|
|||
/// Creates a new [`TextInput`].
|
||||
///
|
||||
/// It expects:
|
||||
/// - some [`State`]
|
||||
/// - a placeholder
|
||||
/// - the current value
|
||||
/// - a function that produces a message when the [`TextInput`] changes
|
||||
pub fn new<F>(
|
||||
state: &'a mut State,
|
||||
placeholder: &str,
|
||||
value: &str,
|
||||
on_change: F,
|
||||
) -> Self
|
||||
/// - a placeholder,
|
||||
/// - the current value, and
|
||||
/// - a function that produces a message when the [`TextInput`] changes.
|
||||
pub fn new<F>(placeholder: &str, value: &str, on_change: F) -> Self
|
||||
where
|
||||
F: 'a + Fn(String) -> Message,
|
||||
{
|
||||
TextInput {
|
||||
state,
|
||||
id: None,
|
||||
placeholder: String::from(placeholder),
|
||||
value: Value::new(value),
|
||||
is_secure: false,
|
||||
|
|
@ -109,6 +101,12 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// Sets the [`Id`] of the [`TextInput`].
|
||||
pub fn id(mut self, id: Id) -> Self {
|
||||
self.id = Some(id);
|
||||
self
|
||||
}
|
||||
|
||||
/// Converts the [`TextInput`] into a secure password input.
|
||||
pub fn password(mut self) -> Self {
|
||||
self.is_secure = true;
|
||||
|
|
@ -127,7 +125,7 @@ where
|
|||
|
||||
/// Sets the [`Font`] of the [`TextInput`].
|
||||
///
|
||||
/// [`Font`]: crate::text::Renderer::Font
|
||||
/// [`Font`]: text::Renderer::Font
|
||||
pub fn font(mut self, font: Renderer::Font) -> Self {
|
||||
self.font = font;
|
||||
self
|
||||
|
|
@ -166,17 +164,13 @@ where
|
|||
self
|
||||
}
|
||||
|
||||
/// Returns the current [`State`] of the [`TextInput`].
|
||||
pub fn state(&self) -> &State {
|
||||
self.state
|
||||
}
|
||||
|
||||
/// Draws the [`TextInput`] with the given [`Renderer`], overriding its
|
||||
/// [`Value`] if provided.
|
||||
/// [`text_input::Value`] if provided.
|
||||
///
|
||||
/// [`Renderer`]: text::Renderer
|
||||
pub fn draw(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
renderer: &mut Renderer,
|
||||
theme: &Renderer::Theme,
|
||||
layout: Layout<'_>,
|
||||
|
|
@ -188,7 +182,7 @@ where
|
|||
theme,
|
||||
layout,
|
||||
cursor_position,
|
||||
self.state,
|
||||
tree.state.downcast_ref::<State>(),
|
||||
value.unwrap_or(&self.value),
|
||||
&self.placeholder,
|
||||
self.size,
|
||||
|
|
@ -199,6 +193,150 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> Widget<Message, Renderer>
|
||||
for TextInput<'a, Message, Renderer>
|
||||
where
|
||||
Message: Clone,
|
||||
Renderer: text::Renderer,
|
||||
Renderer::Theme: StyleSheet,
|
||||
{
|
||||
fn tag(&self) -> tree::Tag {
|
||||
tree::Tag::of::<State>()
|
||||
}
|
||||
|
||||
fn state(&self) -> tree::State {
|
||||
tree::State::new(State::new())
|
||||
}
|
||||
|
||||
fn width(&self) -> Length {
|
||||
self.width
|
||||
}
|
||||
|
||||
fn height(&self) -> Length {
|
||||
Length::Shrink
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
layout(renderer, limits, self.width, self.padding, self.size)
|
||||
}
|
||||
|
||||
fn operate(
|
||||
&self,
|
||||
tree: &mut Tree,
|
||||
_layout: Layout<'_>,
|
||||
operation: &mut dyn Operation<Message>,
|
||||
) {
|
||||
let state = tree.state.downcast_mut::<State>();
|
||||
|
||||
operation.focusable(state, self.id.as_ref().map(|id| &id.0));
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
event: Event,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
renderer: &Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
) -> event::Status {
|
||||
update(
|
||||
event,
|
||||
layout,
|
||||
cursor_position,
|
||||
renderer,
|
||||
clipboard,
|
||||
shell,
|
||||
&mut self.value,
|
||||
self.size,
|
||||
&self.font,
|
||||
self.is_secure,
|
||||
self.on_change.as_ref(),
|
||||
self.on_paste.as_deref(),
|
||||
&self.on_submit,
|
||||
|| tree.state.downcast_mut::<State>(),
|
||||
)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
renderer: &mut Renderer,
|
||||
theme: &Renderer::Theme,
|
||||
_style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
) {
|
||||
draw(
|
||||
renderer,
|
||||
theme,
|
||||
layout,
|
||||
cursor_position,
|
||||
tree.state.downcast_ref::<State>(),
|
||||
&self.value,
|
||||
&self.placeholder,
|
||||
self.size,
|
||||
&self.font,
|
||||
self.is_secure,
|
||||
self.style,
|
||||
)
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
_state: &Tree,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
_renderer: &Renderer,
|
||||
) -> mouse::Interaction {
|
||||
mouse_interaction(layout, cursor_position)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> From<TextInput<'a, Message, Renderer>>
|
||||
for Element<'a, Message, Renderer>
|
||||
where
|
||||
Message: 'a + Clone,
|
||||
Renderer: 'a + text::Renderer,
|
||||
Renderer::Theme: StyleSheet,
|
||||
{
|
||||
fn from(
|
||||
text_input: TextInput<'a, Message, Renderer>,
|
||||
) -> Element<'a, Message, Renderer> {
|
||||
Element::new(text_input)
|
||||
}
|
||||
}
|
||||
|
||||
/// The identifier of a [`TextInput`].
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct Id(widget::Id);
|
||||
|
||||
impl Id {
|
||||
/// Creates a custom [`Id`].
|
||||
pub fn new(id: impl Into<std::borrow::Cow<'static, str>>) -> Self {
|
||||
Self(widget::Id::new(id))
|
||||
}
|
||||
|
||||
/// Creates a unique [`Id`].
|
||||
///
|
||||
/// This function produces a different [`Id`] every time it is called.
|
||||
pub fn unique() -> Self {
|
||||
Self(widget::Id::unique())
|
||||
}
|
||||
}
|
||||
|
||||
/// Produces a [`Command`] that focuses the [`TextInput`] with the given [`Id`].
|
||||
pub fn focus<Message: 'static>(id: Id) -> Command<Message> {
|
||||
Command::widget(operation::focusable::focus(id.0))
|
||||
}
|
||||
|
||||
/// Computes the layout of a [`TextInput`].
|
||||
pub fn layout<Renderer>(
|
||||
renderer: &Renderer,
|
||||
|
|
@ -777,93 +915,6 @@ pub fn mouse_interaction(
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> Widget<Message, Renderer>
|
||||
for TextInput<'a, Message, Renderer>
|
||||
where
|
||||
Message: Clone,
|
||||
Renderer: text::Renderer,
|
||||
Renderer::Theme: StyleSheet,
|
||||
{
|
||||
fn width(&self) -> Length {
|
||||
self.width
|
||||
}
|
||||
|
||||
fn height(&self) -> Length {
|
||||
Length::Shrink
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
layout(renderer, limits, self.width, self.padding, self.size)
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
&mut self,
|
||||
event: Event,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
renderer: &Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
) -> event::Status {
|
||||
update(
|
||||
event,
|
||||
layout,
|
||||
cursor_position,
|
||||
renderer,
|
||||
clipboard,
|
||||
shell,
|
||||
&mut self.value,
|
||||
self.size,
|
||||
&self.font,
|
||||
self.is_secure,
|
||||
self.on_change.as_ref(),
|
||||
self.on_paste.as_deref(),
|
||||
&self.on_submit,
|
||||
|| &mut self.state,
|
||||
)
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
_renderer: &Renderer,
|
||||
) -> mouse::Interaction {
|
||||
mouse_interaction(layout, cursor_position)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
renderer: &mut Renderer,
|
||||
theme: &Renderer::Theme,
|
||||
_style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
) {
|
||||
self.draw(renderer, theme, layout, cursor_position, None)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> From<TextInput<'a, Message, Renderer>>
|
||||
for Element<'a, Message, Renderer>
|
||||
where
|
||||
Message: 'a + Clone,
|
||||
Renderer: 'a + text::Renderer,
|
||||
Renderer::Theme: StyleSheet,
|
||||
{
|
||||
fn from(
|
||||
text_input: TextInput<'a, Message, Renderer>,
|
||||
) -> Element<'a, Message, Renderer> {
|
||||
Element::new(text_input)
|
||||
}
|
||||
}
|
||||
|
||||
/// The state of a [`TextInput`].
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct State {
|
||||
|
|
@ -907,6 +958,7 @@ impl State {
|
|||
/// Focuses the [`TextInput`].
|
||||
pub fn focus(&mut self) {
|
||||
self.is_focused = true;
|
||||
self.move_cursor_to_end();
|
||||
}
|
||||
|
||||
/// Unfocuses the [`TextInput`].
|
||||
|
|
@ -935,6 +987,20 @@ impl State {
|
|||
}
|
||||
}
|
||||
|
||||
impl operation::Focusable for State {
|
||||
fn is_focused(&self) -> bool {
|
||||
State::is_focused(self)
|
||||
}
|
||||
|
||||
fn focus(&mut self) {
|
||||
State::focus(self)
|
||||
}
|
||||
|
||||
fn unfocus(&mut self) {
|
||||
State::unfocus(self)
|
||||
}
|
||||
}
|
||||
|
||||
mod platform {
|
||||
use crate::keyboard;
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use crate::layout;
|
|||
use crate::mouse;
|
||||
use crate::renderer;
|
||||
use crate::text;
|
||||
use crate::widget::{self, Row, Text};
|
||||
use crate::widget::{self, Row, Text, Tree};
|
||||
use crate::{
|
||||
Alignment, Clipboard, Element, Event, Layout, Length, Point, Rectangle,
|
||||
Shell, Widget,
|
||||
|
|
@ -180,6 +180,7 @@ where
|
|||
|
||||
fn on_event(
|
||||
&mut self,
|
||||
_state: &mut Tree,
|
||||
event: Event,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
|
|
@ -205,6 +206,7 @@ where
|
|||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
_state: &Tree,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
_viewport: &Rectangle,
|
||||
|
|
@ -219,6 +221,7 @@ where
|
|||
|
||||
fn draw(
|
||||
&self,
|
||||
_state: &Tree,
|
||||
renderer: &mut Renderer,
|
||||
theme: &Renderer::Theme,
|
||||
style: &renderer::Style,
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@ use crate::renderer;
|
|||
use crate::text;
|
||||
use crate::widget;
|
||||
use crate::widget::container;
|
||||
use crate::widget::text::Text;
|
||||
use crate::widget::overlay;
|
||||
use crate::widget::{Text, Tree};
|
||||
use crate::{
|
||||
Clipboard, Element, Event, Layout, Length, Padding, Point, Rectangle,
|
||||
Shell, Size, Vector, Widget,
|
||||
|
|
@ -14,7 +15,7 @@ use crate::{
|
|||
|
||||
/// An element to display a widget over another.
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Tooltip<'a, Message, Renderer>
|
||||
pub struct Tooltip<'a, Message, Renderer: text::Renderer>
|
||||
where
|
||||
Renderer: text::Renderer,
|
||||
Renderer::Theme: container::StyleSheet + widget::text::StyleSheet,
|
||||
|
|
@ -89,6 +90,153 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> Widget<Message, Renderer>
|
||||
for Tooltip<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: text::Renderer,
|
||||
Renderer::Theme: container::StyleSheet + widget::text::StyleSheet,
|
||||
{
|
||||
fn children(&self) -> Vec<Tree> {
|
||||
vec![Tree::new(&self.content)]
|
||||
}
|
||||
|
||||
fn diff(&self, tree: &mut Tree) {
|
||||
tree.diff_children(std::slice::from_ref(&self.content))
|
||||
}
|
||||
|
||||
fn width(&self) -> Length {
|
||||
self.content.as_widget().width()
|
||||
}
|
||||
|
||||
fn height(&self) -> Length {
|
||||
self.content.as_widget().height()
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
self.content.as_widget().layout(renderer, limits)
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
&mut self,
|
||||
tree: &mut Tree,
|
||||
event: Event,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
renderer: &Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
) -> event::Status {
|
||||
self.content.as_widget_mut().on_event(
|
||||
&mut tree.children[0],
|
||||
event,
|
||||
layout,
|
||||
cursor_position,
|
||||
renderer,
|
||||
clipboard,
|
||||
shell,
|
||||
)
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
renderer: &Renderer,
|
||||
) -> mouse::Interaction {
|
||||
self.content.as_widget().mouse_interaction(
|
||||
&tree.children[0],
|
||||
layout.children().next().unwrap(),
|
||||
cursor_position,
|
||||
viewport,
|
||||
renderer,
|
||||
)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
tree: &Tree,
|
||||
renderer: &mut Renderer,
|
||||
theme: &Renderer::Theme,
|
||||
inherited_style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
) {
|
||||
self.content.as_widget().draw(
|
||||
&tree.children[0],
|
||||
renderer,
|
||||
theme,
|
||||
inherited_style,
|
||||
layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
);
|
||||
|
||||
let tooltip = &self.tooltip;
|
||||
|
||||
draw(
|
||||
renderer,
|
||||
theme,
|
||||
inherited_style,
|
||||
layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
self.position,
|
||||
self.gap,
|
||||
self.padding,
|
||||
self.style,
|
||||
|renderer, limits| {
|
||||
Widget::<(), Renderer>::layout(tooltip, renderer, limits)
|
||||
},
|
||||
|renderer, defaults, layout, cursor_position, viewport| {
|
||||
Widget::<(), Renderer>::draw(
|
||||
tooltip,
|
||||
&Tree::empty(),
|
||||
renderer,
|
||||
theme,
|
||||
defaults,
|
||||
layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn overlay<'b>(
|
||||
&'b self,
|
||||
tree: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
) -> Option<overlay::Element<'b, Message, Renderer>> {
|
||||
self.content.as_widget().overlay(
|
||||
&mut tree.children[0],
|
||||
layout,
|
||||
renderer,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> From<Tooltip<'a, Message, Renderer>>
|
||||
for Element<'a, Message, Renderer>
|
||||
where
|
||||
Message: 'a,
|
||||
Renderer: 'a + text::Renderer,
|
||||
Renderer::Theme: container::StyleSheet + widget::text::StyleSheet,
|
||||
{
|
||||
fn from(
|
||||
tooltip: Tooltip<'a, Message, Renderer>,
|
||||
) -> Element<'a, Message, Renderer> {
|
||||
Element::new(tooltip)
|
||||
}
|
||||
}
|
||||
|
||||
/// The position of the tooltip. Defaults to following the cursor.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Position {
|
||||
|
|
@ -220,122 +368,3 @@ pub fn draw<Renderer>(
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> Widget<Message, Renderer>
|
||||
for Tooltip<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: text::Renderer,
|
||||
Renderer::Theme: container::StyleSheet + widget::text::StyleSheet,
|
||||
{
|
||||
fn width(&self) -> Length {
|
||||
self.content.width()
|
||||
}
|
||||
|
||||
fn height(&self) -> Length {
|
||||
self.content.height()
|
||||
}
|
||||
|
||||
fn layout(
|
||||
&self,
|
||||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
self.content.layout(renderer, limits)
|
||||
}
|
||||
|
||||
fn on_event(
|
||||
&mut self,
|
||||
event: Event,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
renderer: &Renderer,
|
||||
clipboard: &mut dyn Clipboard,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
) -> event::Status {
|
||||
self.content.widget.on_event(
|
||||
event,
|
||||
layout,
|
||||
cursor_position,
|
||||
renderer,
|
||||
clipboard,
|
||||
shell,
|
||||
)
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
renderer: &Renderer,
|
||||
) -> mouse::Interaction {
|
||||
self.content.mouse_interaction(
|
||||
layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
renderer,
|
||||
)
|
||||
}
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
renderer: &mut Renderer,
|
||||
theme: &Renderer::Theme,
|
||||
inherited_style: &renderer::Style,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
) {
|
||||
self.content.draw(
|
||||
renderer,
|
||||
theme,
|
||||
inherited_style,
|
||||
layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
);
|
||||
|
||||
let tooltip = &self.tooltip;
|
||||
|
||||
draw(
|
||||
renderer,
|
||||
theme,
|
||||
inherited_style,
|
||||
layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
self.position,
|
||||
self.gap,
|
||||
self.padding,
|
||||
self.style,
|
||||
|renderer, limits| {
|
||||
Widget::<(), Renderer>::layout(tooltip, renderer, limits)
|
||||
},
|
||||
|renderer, defaults, layout, cursor_position, viewport| {
|
||||
Widget::<(), Renderer>::draw(
|
||||
tooltip,
|
||||
renderer,
|
||||
theme,
|
||||
defaults,
|
||||
layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
);
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> From<Tooltip<'a, Message, Renderer>>
|
||||
for Element<'a, Message, Renderer>
|
||||
where
|
||||
Message: 'a,
|
||||
Renderer: 'a + text::Renderer,
|
||||
Renderer::Theme: container::StyleSheet + widget::text::StyleSheet,
|
||||
{
|
||||
fn from(
|
||||
tooltip: Tooltip<'a, Message, Renderer>,
|
||||
) -> Element<'a, Message, Renderer> {
|
||||
Element::new(tooltip)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
187
native/src/widget/tree.rs
Normal file
187
native/src/widget/tree.rs
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
//! Store internal widget state in a state tree to ensure continuity.
|
||||
use crate::Widget;
|
||||
|
||||
use std::any::{self, Any};
|
||||
use std::borrow::Borrow;
|
||||
use std::fmt;
|
||||
|
||||
/// A persistent state widget tree.
|
||||
///
|
||||
/// A [`Tree`] is normally associated with a specific widget in the widget tree.
|
||||
#[derive(Debug)]
|
||||
pub struct Tree {
|
||||
/// The tag of the [`Tree`].
|
||||
pub tag: Tag,
|
||||
|
||||
/// The [`State`] of the [`Tree`].
|
||||
pub state: State,
|
||||
|
||||
/// The children of the root widget of the [`Tree`].
|
||||
pub children: Vec<Tree>,
|
||||
}
|
||||
|
||||
impl Tree {
|
||||
/// Creates an empty, stateless [`Tree`] with no children.
|
||||
pub fn empty() -> Self {
|
||||
Self {
|
||||
tag: Tag::stateless(),
|
||||
state: State::None,
|
||||
children: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new [`Tree`] for the provided [`Element`].
|
||||
pub fn new<'a, Message, Renderer>(
|
||||
widget: impl Borrow<dyn Widget<Message, Renderer> + 'a>,
|
||||
) -> Self
|
||||
where
|
||||
Renderer: crate::Renderer,
|
||||
{
|
||||
let widget = widget.borrow();
|
||||
|
||||
Self {
|
||||
tag: widget.tag(),
|
||||
state: widget.state(),
|
||||
children: widget.children(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Reconciliates the current tree with the provided [`Element`].
|
||||
///
|
||||
/// If the tag of the [`Element`] matches the tag of the [`Tree`], then the
|
||||
/// [`Element`] proceeds with the reconciliation (i.e. [`Widget::diff`] is called).
|
||||
///
|
||||
/// Otherwise, the whole [`Tree`] is recreated.
|
||||
///
|
||||
/// [`Widget::diff`]: crate::Widget::diff
|
||||
pub fn diff<'a, Message, Renderer>(
|
||||
&mut self,
|
||||
new: impl Borrow<dyn Widget<Message, Renderer> + 'a>,
|
||||
) where
|
||||
Renderer: crate::Renderer,
|
||||
{
|
||||
if self.tag == new.borrow().tag() {
|
||||
new.borrow().diff(self)
|
||||
} else {
|
||||
*self = Self::new(new);
|
||||
}
|
||||
}
|
||||
|
||||
/// Reconciliates the children of the tree with the provided list of [`Element`].
|
||||
pub fn diff_children<'a, Message, Renderer>(
|
||||
&mut self,
|
||||
new_children: &[impl Borrow<dyn Widget<Message, Renderer> + 'a>],
|
||||
) where
|
||||
Renderer: crate::Renderer,
|
||||
{
|
||||
self.diff_children_custom(
|
||||
new_children,
|
||||
|tree, widget| tree.diff(widget.borrow()),
|
||||
|widget| Self::new(widget.borrow()),
|
||||
)
|
||||
}
|
||||
|
||||
/// Reconciliates the children of the tree with the provided list of [`Element`] using custom
|
||||
/// logic both for diffing and creating new widget state.
|
||||
pub fn diff_children_custom<T>(
|
||||
&mut self,
|
||||
new_children: &[T],
|
||||
diff: impl Fn(&mut Tree, &T),
|
||||
new_state: impl Fn(&T) -> Self,
|
||||
) {
|
||||
if self.children.len() > new_children.len() {
|
||||
self.children.truncate(new_children.len());
|
||||
}
|
||||
|
||||
for (child_state, new) in
|
||||
self.children.iter_mut().zip(new_children.iter())
|
||||
{
|
||||
diff(child_state, new);
|
||||
}
|
||||
|
||||
if self.children.len() < new_children.len() {
|
||||
self.children.extend(
|
||||
new_children[self.children.len()..].iter().map(new_state),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The identifier of some widget state.
|
||||
#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)]
|
||||
pub struct Tag(any::TypeId);
|
||||
|
||||
impl Tag {
|
||||
/// Creates a [`Tag`] for a state of type `T`.
|
||||
pub fn of<T>() -> Self
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
Self(any::TypeId::of::<T>())
|
||||
}
|
||||
|
||||
/// Creates a [`Tag`] for a stateless widget.
|
||||
pub fn stateless() -> Self {
|
||||
Self::of::<()>()
|
||||
}
|
||||
}
|
||||
|
||||
/// The internal [`State`] of a widget.
|
||||
pub enum State {
|
||||
/// No meaningful internal state.
|
||||
None,
|
||||
|
||||
/// Some meaningful internal state.
|
||||
Some(Box<dyn Any>),
|
||||
}
|
||||
|
||||
impl State {
|
||||
/// Creates a new [`State`].
|
||||
pub fn new<T>(state: T) -> Self
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
State::Some(Box::new(state))
|
||||
}
|
||||
|
||||
/// Downcasts the [`State`] to `T` and returns a reference to it.
|
||||
///
|
||||
/// # Panics
|
||||
/// This method will panic if the downcast fails or the [`State`] is [`State::None`].
|
||||
pub fn downcast_ref<T>(&self) -> &T
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
match self {
|
||||
State::None => panic!("Downcast on stateless state"),
|
||||
State::Some(state) => {
|
||||
state.downcast_ref().expect("Downcast widget state")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Downcasts the [`State`] to `T` and returns a mutable reference to it.
|
||||
///
|
||||
/// # Panics
|
||||
/// This method will panic if the downcast fails or the [`State`] is [`State::None`].
|
||||
pub fn downcast_mut<T>(&mut self) -> &mut T
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
match self {
|
||||
State::None => panic!("Downcast on stateless state"),
|
||||
State::Some(state) => {
|
||||
state.downcast_mut().expect("Downcast widget state")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for State {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::None => write!(f, "State::None"),
|
||||
Self::Some(_) => write!(f, "State::Some"),
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue