Merge branch 'master' into advanced-text
This commit is contained in:
commit
4bae457c37
73 changed files with 1586 additions and 703 deletions
|
|
@ -16,7 +16,7 @@ num-traits = "0.2"
|
|||
thiserror = "1"
|
||||
|
||||
[dependencies.iced_runtime]
|
||||
version = "0.9"
|
||||
version = "0.1"
|
||||
path = "../runtime"
|
||||
|
||||
[dependencies.iced_renderer]
|
||||
|
|
@ -24,7 +24,7 @@ version = "0.1"
|
|||
path = "../renderer"
|
||||
|
||||
[dependencies.iced_style]
|
||||
version = "0.7"
|
||||
version = "0.8"
|
||||
path = "../style"
|
||||
|
||||
[dependencies.ouroboros]
|
||||
|
|
|
|||
|
|
@ -15,17 +15,6 @@ use crate::{Row, Text};
|
|||
|
||||
pub use iced_style::checkbox::{Appearance, StyleSheet};
|
||||
|
||||
/// The icon in a [`Checkbox`].
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Icon<Font> {
|
||||
/// Font that will be used to display the `code_point`,
|
||||
pub font: Font,
|
||||
/// The unicode code point that will be used as the icon.
|
||||
pub code_point: char,
|
||||
/// Font size of the content.
|
||||
pub size: Option<f32>,
|
||||
}
|
||||
|
||||
/// A box that can be checked.
|
||||
///
|
||||
/// # Example
|
||||
|
|
@ -321,3 +310,14 @@ where
|
|||
Element::new(checkbox)
|
||||
}
|
||||
}
|
||||
|
||||
/// The icon in a [`Checkbox`].
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Icon<Font> {
|
||||
/// Font that will be used to display the `code_point`,
|
||||
pub font: Font,
|
||||
/// The unicode code point that will be used as the icon.
|
||||
pub code_point: char,
|
||||
/// Font size of the content.
|
||||
pub size: Option<f32>,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ use crate::text::{self, Text};
|
|||
use crate::text_input::{self, TextInput};
|
||||
use crate::toggler::{self, Toggler};
|
||||
use crate::tooltip::{self, Tooltip};
|
||||
use crate::{Column, Row, Space, VerticalSlider};
|
||||
use crate::{Column, MouseArea, Row, Space, VerticalSlider};
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::ops::RangeInclusive;
|
||||
|
|
@ -163,7 +163,7 @@ where
|
|||
Renderer::Theme: radio::StyleSheet,
|
||||
V: Copy + Eq,
|
||||
{
|
||||
Radio::new(value, label, selected, on_click)
|
||||
Radio::new(label, value, selected, on_click)
|
||||
}
|
||||
|
||||
/// Creates a new [`Toggler`].
|
||||
|
|
@ -187,14 +187,13 @@ where
|
|||
pub fn text_input<'a, Message, Renderer>(
|
||||
placeholder: &str,
|
||||
value: &str,
|
||||
on_change: impl Fn(String) -> Message + 'a,
|
||||
) -> TextInput<'a, Message, Renderer>
|
||||
where
|
||||
Message: Clone,
|
||||
Renderer: core::text::Renderer,
|
||||
Renderer::Theme: text_input::StyleSheet,
|
||||
{
|
||||
TextInput::new(placeholder, value, on_change)
|
||||
TextInput::new(placeholder, value)
|
||||
}
|
||||
|
||||
/// Creates a new [`Slider`].
|
||||
|
|
@ -360,3 +359,13 @@ where
|
|||
{
|
||||
Command::widget(operation::focusable::focus_next())
|
||||
}
|
||||
|
||||
/// A container intercepting mouse events.
|
||||
pub fn mouse_area<'a, Message, Renderer>(
|
||||
widget: impl Into<Element<'a, Message, Renderer>>,
|
||||
) -> MouseArea<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: core::Renderer,
|
||||
{
|
||||
MouseArea::new(widget)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ use crate::core::{
|
|||
use ouroboros::self_referencing;
|
||||
use std::cell::RefCell;
|
||||
use std::marker::PhantomData;
|
||||
use std::rc::Rc;
|
||||
|
||||
/// A reusable, custom widget that uses The Elm Architecture.
|
||||
///
|
||||
|
|
@ -58,6 +59,8 @@ pub trait Component<Message, Renderer> {
|
|||
}
|
||||
}
|
||||
|
||||
struct Tag<T>(T);
|
||||
|
||||
/// Turns an implementor of [`Component`] into an [`Element`] that can be
|
||||
/// embedded in any application.
|
||||
pub fn view<'a, C, Message, Renderer>(
|
||||
|
|
@ -79,11 +82,13 @@ where
|
|||
}
|
||||
.build(),
|
||||
)),
|
||||
tree: RefCell::new(Rc::new(RefCell::new(None))),
|
||||
})
|
||||
}
|
||||
|
||||
struct Instance<'a, Message, Renderer, Event, S> {
|
||||
state: RefCell<Option<State<'a, Message, Renderer, Event, S>>>,
|
||||
tree: RefCell<Rc<RefCell<Option<Tree>>>>,
|
||||
}
|
||||
|
||||
#[self_referencing]
|
||||
|
|
@ -100,40 +105,91 @@ struct State<'a, Message: 'a, Renderer: 'a, Event: 'a, S: 'a> {
|
|||
|
||||
impl<'a, Message, Renderer, Event, S> Instance<'a, Message, Renderer, Event, S>
|
||||
where
|
||||
S: Default,
|
||||
S: Default + 'static,
|
||||
Renderer: renderer::Renderer,
|
||||
{
|
||||
fn rebuild_element(&self, state: &S) {
|
||||
let heads = self.state.borrow_mut().take().unwrap().into_heads();
|
||||
fn diff_self(&self) {
|
||||
self.with_element(|element| {
|
||||
self.tree
|
||||
.borrow_mut()
|
||||
.borrow_mut()
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.diff_children(std::slice::from_ref(&element));
|
||||
});
|
||||
}
|
||||
|
||||
*self.state.borrow_mut() = Some(
|
||||
StateBuilder {
|
||||
component: heads.component,
|
||||
message: PhantomData,
|
||||
state: PhantomData,
|
||||
element_builder: |component| Some(component.view(state)),
|
||||
}
|
||||
.build(),
|
||||
);
|
||||
fn rebuild_element_if_necessary(&self) {
|
||||
let inner = self.state.borrow_mut().take().unwrap();
|
||||
if inner.borrow_element().is_none() {
|
||||
let heads = inner.into_heads();
|
||||
|
||||
*self.state.borrow_mut() = Some(
|
||||
StateBuilder {
|
||||
component: heads.component,
|
||||
message: PhantomData,
|
||||
state: PhantomData,
|
||||
element_builder: |component| {
|
||||
Some(
|
||||
component.view(
|
||||
self.tree
|
||||
.borrow()
|
||||
.borrow()
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.state
|
||||
.downcast_ref::<S>(),
|
||||
),
|
||||
)
|
||||
},
|
||||
}
|
||||
.build(),
|
||||
);
|
||||
self.diff_self();
|
||||
} else {
|
||||
*self.state.borrow_mut() = Some(inner);
|
||||
}
|
||||
}
|
||||
|
||||
fn rebuild_element_with_operation(
|
||||
&self,
|
||||
state: &mut S,
|
||||
operation: &mut dyn widget::Operation<Message>,
|
||||
) {
|
||||
let heads = self.state.borrow_mut().take().unwrap().into_heads();
|
||||
|
||||
heads.component.operate(state, operation);
|
||||
heads.component.operate(
|
||||
self.tree
|
||||
.borrow_mut()
|
||||
.borrow_mut()
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.state
|
||||
.downcast_mut(),
|
||||
operation,
|
||||
);
|
||||
|
||||
*self.state.borrow_mut() = Some(
|
||||
StateBuilder {
|
||||
component: heads.component,
|
||||
message: PhantomData,
|
||||
state: PhantomData,
|
||||
element_builder: |component| Some(component.view(state)),
|
||||
element_builder: |component| {
|
||||
Some(
|
||||
component.view(
|
||||
self.tree
|
||||
.borrow()
|
||||
.borrow()
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.state
|
||||
.downcast_ref(),
|
||||
),
|
||||
)
|
||||
},
|
||||
}
|
||||
.build(),
|
||||
);
|
||||
self.diff_self();
|
||||
}
|
||||
|
||||
fn with_element<T>(
|
||||
|
|
@ -147,6 +203,7 @@ where
|
|||
&self,
|
||||
f: impl FnOnce(&mut Element<'_, Event, Renderer>) -> T,
|
||||
) -> T {
|
||||
self.rebuild_element_if_necessary();
|
||||
self.state
|
||||
.borrow_mut()
|
||||
.as_mut()
|
||||
|
|
@ -162,24 +219,27 @@ where
|
|||
Renderer: core::Renderer,
|
||||
{
|
||||
fn tag(&self) -> tree::Tag {
|
||||
struct Tag<T>(T);
|
||||
tree::Tag::of::<Tag<S>>()
|
||||
}
|
||||
|
||||
fn state(&self) -> tree::State {
|
||||
tree::State::new(S::default())
|
||||
let state = Rc::new(RefCell::new(Some(Tree {
|
||||
tag: tree::Tag::of::<Tag<S>>(),
|
||||
state: tree::State::new(S::default()),
|
||||
children: vec![Tree::empty()],
|
||||
})));
|
||||
*self.tree.borrow_mut() = state.clone();
|
||||
tree::State::new(state)
|
||||
}
|
||||
|
||||
fn children(&self) -> Vec<Tree> {
|
||||
self.rebuild_element(&S::default());
|
||||
self.with_element(|element| vec![Tree::new(element)])
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn diff(&self, tree: &mut Tree) {
|
||||
self.rebuild_element(tree.state.downcast_ref());
|
||||
self.with_element(|element| {
|
||||
tree.diff_children(std::slice::from_ref(&element))
|
||||
})
|
||||
let tree = tree.state.downcast_ref::<Rc<RefCell<Option<Tree>>>>();
|
||||
*self.tree.borrow_mut() = tree.clone();
|
||||
self.rebuild_element_if_necessary();
|
||||
}
|
||||
|
||||
fn width(&self) -> Length {
|
||||
|
|
@ -213,9 +273,10 @@ where
|
|||
let mut local_messages = Vec::new();
|
||||
let mut local_shell = Shell::new(&mut local_messages);
|
||||
|
||||
let t = tree.state.downcast_mut::<Rc<RefCell<Option<Tree>>>>();
|
||||
let event_status = self.with_element_mut(|element| {
|
||||
element.as_widget_mut().on_event(
|
||||
&mut tree.children[0],
|
||||
&mut t.borrow_mut().as_mut().unwrap().children[0],
|
||||
event,
|
||||
layout,
|
||||
cursor_position,
|
||||
|
|
@ -235,9 +296,10 @@ where
|
|||
let mut heads = self.state.take().unwrap().into_heads();
|
||||
|
||||
for message in local_messages.into_iter().filter_map(|message| {
|
||||
heads
|
||||
.component
|
||||
.update(tree.state.downcast_mut::<S>(), message)
|
||||
heads.component.update(
|
||||
t.borrow_mut().as_mut().unwrap().state.downcast_mut(),
|
||||
message,
|
||||
)
|
||||
}) {
|
||||
shell.publish(message);
|
||||
}
|
||||
|
|
@ -247,17 +309,11 @@ where
|
|||
component: heads.component,
|
||||
message: PhantomData,
|
||||
state: PhantomData,
|
||||
element_builder: |state| {
|
||||
Some(state.view(tree.state.downcast_ref::<S>()))
|
||||
},
|
||||
element_builder: |_| None,
|
||||
}
|
||||
.build(),
|
||||
));
|
||||
|
||||
self.with_element(|element| {
|
||||
tree.diff_children(std::slice::from_ref(&element))
|
||||
});
|
||||
|
||||
shell.invalidate_layout();
|
||||
}
|
||||
|
||||
|
|
@ -271,10 +327,7 @@ where
|
|||
renderer: &Renderer,
|
||||
operation: &mut dyn widget::Operation<Message>,
|
||||
) {
|
||||
self.rebuild_element_with_operation(
|
||||
tree.state.downcast_mut(),
|
||||
operation,
|
||||
);
|
||||
self.rebuild_element_with_operation(operation);
|
||||
|
||||
struct MapOperation<'a, B> {
|
||||
operation: &'a mut dyn widget::Operation<B>,
|
||||
|
|
@ -308,13 +361,28 @@ where
|
|||
) {
|
||||
self.operation.text_input(state, id);
|
||||
}
|
||||
|
||||
fn scrollable(
|
||||
&mut self,
|
||||
state: &mut dyn widget::operation::Scrollable,
|
||||
id: Option<&widget::Id>,
|
||||
) {
|
||||
self.operation.scrollable(state, id);
|
||||
}
|
||||
|
||||
fn custom(
|
||||
&mut self,
|
||||
state: &mut dyn std::any::Any,
|
||||
id: Option<&widget::Id>,
|
||||
) {
|
||||
self.operation.custom(state, id);
|
||||
}
|
||||
}
|
||||
|
||||
let tree = tree.state.downcast_mut::<Rc<RefCell<Option<Tree>>>>();
|
||||
self.with_element(|element| {
|
||||
tree.diff_children(std::slice::from_ref(&element));
|
||||
|
||||
element.as_widget().operate(
|
||||
&mut tree.children[0],
|
||||
&mut tree.borrow_mut().as_mut().unwrap().children[0],
|
||||
layout,
|
||||
renderer,
|
||||
&mut MapOperation { operation },
|
||||
|
|
@ -332,9 +400,10 @@ where
|
|||
cursor_position: Point,
|
||||
viewport: &Rectangle,
|
||||
) {
|
||||
let tree = tree.state.downcast_ref::<Rc<RefCell<Option<Tree>>>>();
|
||||
self.with_element(|element| {
|
||||
element.as_widget().draw(
|
||||
&tree.children[0],
|
||||
&tree.borrow().as_ref().unwrap().children[0],
|
||||
renderer,
|
||||
theme,
|
||||
style,
|
||||
|
|
@ -353,9 +422,10 @@ where
|
|||
viewport: &Rectangle,
|
||||
renderer: &Renderer,
|
||||
) -> mouse::Interaction {
|
||||
let tree = tree.state.downcast_ref::<Rc<RefCell<Option<Tree>>>>();
|
||||
self.with_element(|element| {
|
||||
element.as_widget().mouse_interaction(
|
||||
&tree.children[0],
|
||||
&tree.borrow().as_ref().unwrap().children[0],
|
||||
layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
|
|
@ -370,25 +440,34 @@ where
|
|||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
) -> Option<overlay::Element<'b, Message, Renderer>> {
|
||||
let overlay = OverlayBuilder {
|
||||
instance: self,
|
||||
tree,
|
||||
types: PhantomData,
|
||||
overlay_builder: |instance, tree| {
|
||||
instance.state.get_mut().as_mut().unwrap().with_element_mut(
|
||||
move |element| {
|
||||
element.as_mut().unwrap().as_widget_mut().overlay(
|
||||
&mut tree.children[0],
|
||||
layout,
|
||||
renderer,
|
||||
)
|
||||
},
|
||||
)
|
||||
},
|
||||
}
|
||||
.build();
|
||||
self.rebuild_element_if_necessary();
|
||||
let tree = tree
|
||||
.state
|
||||
.downcast_mut::<Rc<RefCell<Option<Tree>>>>()
|
||||
.borrow_mut()
|
||||
.take()
|
||||
.unwrap();
|
||||
let overlay = Overlay(Some(
|
||||
InnerBuilder {
|
||||
instance: self,
|
||||
tree,
|
||||
types: PhantomData,
|
||||
overlay_builder: |instance, tree| {
|
||||
instance.state.get_mut().as_mut().unwrap().with_element_mut(
|
||||
move |element| {
|
||||
element.as_mut().unwrap().as_widget_mut().overlay(
|
||||
&mut tree.children[0],
|
||||
layout,
|
||||
renderer,
|
||||
)
|
||||
},
|
||||
)
|
||||
},
|
||||
}
|
||||
.build(),
|
||||
));
|
||||
|
||||
let has_overlay = overlay.with_overlay(|overlay| {
|
||||
let has_overlay = overlay.0.as_ref().unwrap().with_overlay(|overlay| {
|
||||
overlay.as_ref().map(overlay::Element::position)
|
||||
});
|
||||
|
||||
|
|
@ -403,10 +482,24 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
struct Overlay<'a, 'b, Message, Renderer, Event, S>(
|
||||
Option<Inner<'a, 'b, Message, Renderer, Event, S>>,
|
||||
);
|
||||
|
||||
impl<'a, 'b, Message, Renderer, Event, S> Drop
|
||||
for Overlay<'a, 'b, Message, Renderer, Event, S>
|
||||
{
|
||||
fn drop(&mut self) {
|
||||
if let Some(heads) = self.0.take().map(|inner| inner.into_heads()) {
|
||||
*heads.instance.tree.borrow_mut().borrow_mut() = Some(heads.tree);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[self_referencing]
|
||||
struct Overlay<'a, 'b, Message, Renderer, Event, S> {
|
||||
struct Inner<'a, 'b, Message, Renderer, Event, S> {
|
||||
instance: &'a mut Instance<'b, Message, Renderer, Event, S>,
|
||||
tree: &'a mut Tree,
|
||||
tree: Tree,
|
||||
types: PhantomData<(Message, Event, S)>,
|
||||
|
||||
#[borrows(mut instance, mut tree)]
|
||||
|
|
@ -426,6 +519,9 @@ impl<'a, 'b, Message, Renderer, Event, S>
|
|||
f: impl FnOnce(&overlay::Element<'_, Event, Renderer>) -> T,
|
||||
) -> Option<T> {
|
||||
self.overlay
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.0
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.borrow_overlay()
|
||||
|
|
@ -438,6 +534,9 @@ impl<'a, 'b, Message, Renderer, Event, S>
|
|||
f: impl FnOnce(&mut overlay::Element<'_, Event, Renderer>) -> T,
|
||||
) -> Option<T> {
|
||||
self.overlay
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.0
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.with_overlay_mut(|overlay| overlay.as_mut().map(f))
|
||||
|
|
@ -523,42 +622,37 @@ where
|
|||
local_shell.revalidate_layout(|| shell.invalidate_layout());
|
||||
|
||||
if !local_messages.is_empty() {
|
||||
let overlay = self.overlay.take().unwrap().into_heads();
|
||||
let mut heads = overlay.instance.state.take().unwrap().into_heads();
|
||||
let mut inner =
|
||||
self.overlay.take().unwrap().0.take().unwrap().into_heads();
|
||||
let mut heads = inner.instance.state.take().unwrap().into_heads();
|
||||
|
||||
for message in local_messages.into_iter().filter_map(|message| {
|
||||
heads
|
||||
.component
|
||||
.update(overlay.tree.state.downcast_mut::<S>(), message)
|
||||
.update(inner.tree.state.downcast_mut(), message)
|
||||
}) {
|
||||
shell.publish(message);
|
||||
}
|
||||
|
||||
*overlay.instance.state.borrow_mut() = Some(
|
||||
*inner.instance.state.borrow_mut() = Some(
|
||||
StateBuilder {
|
||||
component: heads.component,
|
||||
message: PhantomData,
|
||||
state: PhantomData,
|
||||
element_builder: |state| {
|
||||
Some(state.view(overlay.tree.state.downcast_ref::<S>()))
|
||||
},
|
||||
element_builder: |_| None,
|
||||
}
|
||||
.build(),
|
||||
);
|
||||
|
||||
overlay.instance.with_element(|element| {
|
||||
overlay.tree.diff_children(std::slice::from_ref(&element))
|
||||
});
|
||||
|
||||
self.overlay = Some(
|
||||
OverlayBuilder {
|
||||
instance: overlay.instance,
|
||||
tree: overlay.tree,
|
||||
self.overlay = Some(Overlay(Some(
|
||||
InnerBuilder {
|
||||
instance: inner.instance,
|
||||
tree: inner.tree,
|
||||
types: PhantomData,
|
||||
overlay_builder: |_, _| None,
|
||||
}
|
||||
.build(),
|
||||
);
|
||||
)));
|
||||
|
||||
shell.invalidate_layout();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ pub use iced_runtime::core;
|
|||
pub use iced_style as style;
|
||||
|
||||
mod column;
|
||||
mod mouse_area;
|
||||
mod row;
|
||||
|
||||
pub mod button;
|
||||
|
|
@ -63,6 +64,8 @@ pub use column::Column;
|
|||
#[doc(no_inline)]
|
||||
pub use container::Container;
|
||||
#[doc(no_inline)]
|
||||
pub use mouse_area::MouseArea;
|
||||
#[doc(no_inline)]
|
||||
pub use pane_grid::PaneGrid;
|
||||
#[doc(no_inline)]
|
||||
pub use pick_list::PickList;
|
||||
|
|
|
|||
311
widget/src/mouse_area.rs
Normal file
311
widget/src/mouse_area.rs
Normal file
|
|
@ -0,0 +1,311 @@
|
|||
//! A container for capturing mouse events.
|
||||
|
||||
use crate::core::event::{self, Event};
|
||||
use crate::core::layout;
|
||||
use crate::core::mouse;
|
||||
use crate::core::overlay;
|
||||
use crate::core::renderer;
|
||||
use crate::core::touch;
|
||||
use crate::core::widget::{tree, Operation, Tree};
|
||||
use crate::core::{
|
||||
Clipboard, Element, Layout, Length, Point, Rectangle, Shell, Widget,
|
||||
};
|
||||
|
||||
/// Emit messages on mouse events.
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct MouseArea<'a, Message, Renderer> {
|
||||
content: Element<'a, Message, Renderer>,
|
||||
on_press: Option<Message>,
|
||||
on_release: Option<Message>,
|
||||
on_right_press: Option<Message>,
|
||||
on_right_release: Option<Message>,
|
||||
on_middle_press: Option<Message>,
|
||||
on_middle_release: Option<Message>,
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> MouseArea<'a, Message, Renderer> {
|
||||
/// The message to emit on a left button press.
|
||||
#[must_use]
|
||||
pub fn on_press(mut self, message: Message) -> Self {
|
||||
self.on_press = Some(message);
|
||||
self
|
||||
}
|
||||
|
||||
/// The message to emit on a left button release.
|
||||
#[must_use]
|
||||
pub fn on_release(mut self, message: Message) -> Self {
|
||||
self.on_release = Some(message);
|
||||
self
|
||||
}
|
||||
|
||||
/// The message to emit on a right button press.
|
||||
#[must_use]
|
||||
pub fn on_right_press(mut self, message: Message) -> Self {
|
||||
self.on_right_press = Some(message);
|
||||
self
|
||||
}
|
||||
|
||||
/// The message to emit on a right button release.
|
||||
#[must_use]
|
||||
pub fn on_right_release(mut self, message: Message) -> Self {
|
||||
self.on_right_release = Some(message);
|
||||
self
|
||||
}
|
||||
|
||||
/// The message to emit on a middle button press.
|
||||
#[must_use]
|
||||
pub fn on_middle_press(mut self, message: Message) -> Self {
|
||||
self.on_middle_press = Some(message);
|
||||
self
|
||||
}
|
||||
|
||||
/// The message to emit on a middle button release.
|
||||
#[must_use]
|
||||
pub fn on_middle_release(mut self, message: Message) -> Self {
|
||||
self.on_middle_release = Some(message);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Local state of the [`MouseArea`].
|
||||
#[derive(Default)]
|
||||
struct State {
|
||||
// TODO: Support on_mouse_enter and on_mouse_exit
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> MouseArea<'a, Message, Renderer> {
|
||||
/// Creates a [`MouseArea`] with the given content.
|
||||
pub fn new(content: impl Into<Element<'a, Message, Renderer>>) -> Self {
|
||||
MouseArea {
|
||||
content: content.into(),
|
||||
on_press: None,
|
||||
on_release: None,
|
||||
on_right_press: None,
|
||||
on_right_release: None,
|
||||
on_middle_press: None,
|
||||
on_middle_release: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> Widget<Message, Renderer>
|
||||
for MouseArea<'a, Message, Renderer>
|
||||
where
|
||||
Renderer: renderer::Renderer,
|
||||
Message: Clone,
|
||||
{
|
||||
fn tag(&self) -> tree::Tag {
|
||||
tree::Tag::of::<State>()
|
||||
}
|
||||
|
||||
fn state(&self) -> tree::State {
|
||||
tree::State::new(State::default())
|
||||
}
|
||||
|
||||
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 operate(
|
||||
&self,
|
||||
tree: &mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
operation: &mut dyn Operation<Message>,
|
||||
) {
|
||||
self.content.as_widget().operate(
|
||||
&mut tree.children[0],
|
||||
layout,
|
||||
renderer,
|
||||
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,
|
||||
cursor_position,
|
||||
renderer,
|
||||
clipboard,
|
||||
shell,
|
||||
) {
|
||||
return event::Status::Captured;
|
||||
}
|
||||
|
||||
update(self, &event, layout, cursor_position, 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,
|
||||
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,
|
||||
) {
|
||||
self.content.as_widget().draw(
|
||||
&tree.children[0],
|
||||
renderer,
|
||||
theme,
|
||||
renderer_style,
|
||||
layout,
|
||||
cursor_position,
|
||||
viewport,
|
||||
);
|
||||
}
|
||||
|
||||
fn overlay<'b>(
|
||||
&'b mut self,
|
||||
tree: &'b mut Tree,
|
||||
layout: Layout<'_>,
|
||||
renderer: &Renderer,
|
||||
) -> Option<overlay::Element<'b, Message, Renderer>> {
|
||||
self.content.as_widget_mut().overlay(
|
||||
&mut tree.children[0],
|
||||
layout,
|
||||
renderer,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Message, Renderer> From<MouseArea<'a, Message, Renderer>>
|
||||
for Element<'a, Message, Renderer>
|
||||
where
|
||||
Message: 'a + Clone,
|
||||
Renderer: 'a + renderer::Renderer,
|
||||
{
|
||||
fn from(
|
||||
area: MouseArea<'a, Message, Renderer>,
|
||||
) -> Element<'a, Message, Renderer> {
|
||||
Element::new(area)
|
||||
}
|
||||
}
|
||||
|
||||
/// Processes the given [`Event`] and updates the [`State`] of an [`MouseArea`]
|
||||
/// accordingly.
|
||||
fn update<Message: Clone, Renderer>(
|
||||
widget: &mut MouseArea<'_, Message, Renderer>,
|
||||
event: &Event,
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
shell: &mut Shell<'_, Message>,
|
||||
) -> event::Status {
|
||||
if !layout.bounds().contains(cursor_position) {
|
||||
return event::Status::Ignored;
|
||||
}
|
||||
|
||||
if let Some(message) = widget.on_press.as_ref() {
|
||||
if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
|
||||
| Event::Touch(touch::Event::FingerPressed { .. }) = event
|
||||
{
|
||||
shell.publish(message.clone());
|
||||
|
||||
return event::Status::Captured;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(message) = widget.on_release.as_ref() {
|
||||
if let Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
|
||||
| Event::Touch(touch::Event::FingerLifted { .. }) = event
|
||||
{
|
||||
shell.publish(message.clone());
|
||||
|
||||
return event::Status::Captured;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(message) = widget.on_right_press.as_ref() {
|
||||
if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Right)) =
|
||||
event
|
||||
{
|
||||
shell.publish(message.clone());
|
||||
|
||||
return event::Status::Captured;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(message) = widget.on_right_release.as_ref() {
|
||||
if let Event::Mouse(mouse::Event::ButtonReleased(
|
||||
mouse::Button::Right,
|
||||
)) = event
|
||||
{
|
||||
shell.publish(message.clone());
|
||||
|
||||
return event::Status::Captured;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(message) = widget.on_middle_press.as_ref() {
|
||||
if let Event::Mouse(mouse::Event::ButtonPressed(
|
||||
mouse::Button::Middle,
|
||||
)) = event
|
||||
{
|
||||
shell.publish(message.clone());
|
||||
|
||||
return event::Status::Captured;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(message) = widget.on_middle_release.as_ref() {
|
||||
if let Event::Mouse(mouse::Event::ButtonReleased(
|
||||
mouse::Button::Middle,
|
||||
)) = event
|
||||
{
|
||||
shell.publish(message.clone());
|
||||
|
||||
return event::Status::Captured;
|
||||
}
|
||||
}
|
||||
|
||||
event::Status::Ignored
|
||||
}
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
//! The [`pane_grid` example] showcases how to use a [`PaneGrid`] with resizing,
|
||||
//! drag and drop, and hotkey support.
|
||||
//!
|
||||
//! [`pane_grid` example]: https://github.com/iced-rs/iced/tree/0.8/examples/pane_grid
|
||||
//! [`pane_grid` example]: https://github.com/iced-rs/iced/tree/0.9/examples/pane_grid
|
||||
mod axis;
|
||||
mod configuration;
|
||||
mod content;
|
||||
|
|
|
|||
|
|
@ -22,10 +22,13 @@ pub use iced_style::radio::{Appearance, StyleSheet};
|
|||
/// # type Radio<Message> =
|
||||
/// # iced_widget::Radio<Message, iced_widget::renderer::Renderer<iced_widget::style::Theme>>;
|
||||
/// #
|
||||
/// # use iced_widget::column;
|
||||
/// #[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
/// pub enum Choice {
|
||||
/// A,
|
||||
/// B,
|
||||
/// C,
|
||||
/// All,
|
||||
/// }
|
||||
///
|
||||
/// #[derive(Debug, Clone, Copy)]
|
||||
|
|
@ -35,12 +38,36 @@ pub use iced_style::radio::{Appearance, StyleSheet};
|
|||
///
|
||||
/// let selected_choice = Some(Choice::A);
|
||||
///
|
||||
/// Radio::new(Choice::A, "This is A", selected_choice, Message::RadioSelected);
|
||||
/// let a = Radio::new(
|
||||
/// "A",
|
||||
/// Choice::A,
|
||||
/// selected_choice,
|
||||
/// Message::RadioSelected,
|
||||
/// );
|
||||
///
|
||||
/// Radio::new(Choice::B, "This is B", selected_choice, Message::RadioSelected);
|
||||
/// let b = Radio::new(
|
||||
/// "B",
|
||||
/// Choice::B,
|
||||
/// selected_choice,
|
||||
/// Message::RadioSelected,
|
||||
/// );
|
||||
///
|
||||
/// let c = Radio::new(
|
||||
/// "C",
|
||||
/// Choice::C,
|
||||
/// selected_choice,
|
||||
/// Message::RadioSelected,
|
||||
/// );
|
||||
///
|
||||
/// let all = Radio::new(
|
||||
/// "All of the above",
|
||||
/// Choice::All,
|
||||
/// selected_choice,
|
||||
/// Message::RadioSelected
|
||||
/// );
|
||||
///
|
||||
/// let content = column![a, b, c, all];
|
||||
/// ```
|
||||
///
|
||||
/// 
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Radio<Message, Renderer = crate::Renderer>
|
||||
where
|
||||
|
|
@ -79,8 +106,8 @@ where
|
|||
/// * a function that will be called when the [`Radio`] is selected. It
|
||||
/// receives the value of the radio and must produce a `Message`.
|
||||
pub fn new<F, V>(
|
||||
value: V,
|
||||
label: impl Into<String>,
|
||||
value: V,
|
||||
selected: Option<V>,
|
||||
f: F,
|
||||
) -> Self
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ where
|
|||
Renderer::Theme: StyleSheet,
|
||||
{
|
||||
id: Option<Id>,
|
||||
width: Length,
|
||||
height: Length,
|
||||
vertical: Properties,
|
||||
horizontal: Option<Properties>,
|
||||
|
|
@ -44,6 +45,7 @@ where
|
|||
pub fn new(content: impl Into<Element<'a, Message, Renderer>>) -> Self {
|
||||
Scrollable {
|
||||
id: None,
|
||||
width: Length::Shrink,
|
||||
height: Length::Shrink,
|
||||
vertical: Properties::default(),
|
||||
horizontal: None,
|
||||
|
|
@ -59,6 +61,12 @@ where
|
|||
self
|
||||
}
|
||||
|
||||
/// Sets the width of the [`Scrollable`].
|
||||
pub fn width(mut self, width: impl Into<Length>) -> Self {
|
||||
self.width = width.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the height of the [`Scrollable`].
|
||||
pub fn height(mut self, height: impl Into<Length>) -> Self {
|
||||
self.height = height.into();
|
||||
|
|
@ -167,7 +175,7 @@ where
|
|||
}
|
||||
|
||||
fn width(&self) -> Length {
|
||||
self.content.as_widget().width()
|
||||
self.width
|
||||
}
|
||||
|
||||
fn height(&self) -> Length {
|
||||
|
|
@ -182,7 +190,7 @@ where
|
|||
layout(
|
||||
renderer,
|
||||
limits,
|
||||
Widget::<Message, Renderer>::width(self),
|
||||
self.width,
|
||||
self.height,
|
||||
self.horizontal.is_some(),
|
||||
|renderer, limits| {
|
||||
|
|
@ -391,15 +399,7 @@ pub fn layout<Renderer>(
|
|||
horizontal_enabled: bool,
|
||||
layout_content: impl FnOnce(&Renderer, &layout::Limits) -> layout::Node,
|
||||
) -> layout::Node {
|
||||
let limits = limits
|
||||
.max_height(f32::INFINITY)
|
||||
.max_width(if horizontal_enabled {
|
||||
f32::INFINITY
|
||||
} else {
|
||||
limits.max().width
|
||||
})
|
||||
.width(width)
|
||||
.height(height);
|
||||
let limits = limits.width(width).height(height);
|
||||
|
||||
let child_limits = layout::Limits::new(
|
||||
Size::new(limits.min().width, 0.0),
|
||||
|
|
@ -851,8 +851,8 @@ pub fn draw<Renderer>(
|
|||
if let Some(scrollbar) = scrollbars.y {
|
||||
let style = if state.y_scroller_grabbed_at.is_some() {
|
||||
theme.dragging(style)
|
||||
} else if mouse_over_y_scrollbar {
|
||||
theme.hovered(style)
|
||||
} else if mouse_over_scrollable {
|
||||
theme.hovered(style, mouse_over_y_scrollbar)
|
||||
} else {
|
||||
theme.active(style)
|
||||
};
|
||||
|
|
@ -864,8 +864,8 @@ pub fn draw<Renderer>(
|
|||
if let Some(scrollbar) = scrollbars.x {
|
||||
let style = if state.x_scroller_grabbed_at.is_some() {
|
||||
theme.dragging_horizontal(style)
|
||||
} else if mouse_over_x_scrollbar {
|
||||
theme.hovered_horizontal(style)
|
||||
} else if mouse_over_scrollable {
|
||||
theme.hovered_horizontal(style, mouse_over_x_scrollbar)
|
||||
} else {
|
||||
theme.active_horizontal(style)
|
||||
};
|
||||
|
|
@ -889,7 +889,7 @@ pub fn draw<Renderer>(
|
|||
}
|
||||
|
||||
fn notify_on_scroll<Message>(
|
||||
state: &State,
|
||||
state: &mut State,
|
||||
on_scroll: &Option<Box<dyn Fn(RelativeOffset) -> Message + '_>>,
|
||||
bounds: Rectangle,
|
||||
content_bounds: Rectangle,
|
||||
|
|
@ -910,7 +910,23 @@ fn notify_on_scroll<Message>(
|
|||
.absolute(bounds.height, content_bounds.height)
|
||||
/ (content_bounds.height - bounds.height);
|
||||
|
||||
shell.publish(on_scroll(RelativeOffset { x, y }))
|
||||
let new_offset = RelativeOffset { x, y };
|
||||
|
||||
// Don't publish redundant offsets to shell
|
||||
if let Some(prev_offset) = state.last_notified {
|
||||
let unchanged = |a: f32, b: f32| {
|
||||
(a - b).abs() <= f32::EPSILON || (a.is_nan() && b.is_nan())
|
||||
};
|
||||
|
||||
if unchanged(prev_offset.x, new_offset.x)
|
||||
&& unchanged(prev_offset.y, new_offset.y)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
shell.publish(on_scroll(new_offset));
|
||||
state.last_notified = Some(new_offset);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -923,6 +939,7 @@ pub struct State {
|
|||
offset_x: Offset,
|
||||
x_scroller_grabbed_at: Option<f32>,
|
||||
keyboard_modifiers: keyboard::Modifiers,
|
||||
last_notified: Option<RelativeOffset>,
|
||||
}
|
||||
|
||||
impl Default for State {
|
||||
|
|
@ -934,6 +951,7 @@ impl Default for State {
|
|||
offset_x: Offset::Absolute(0.0),
|
||||
x_scroller_grabbed_at: None,
|
||||
keyboard_modifiers: keyboard::Modifiers::default(),
|
||||
last_notified: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,13 +8,15 @@ use crate::core::renderer;
|
|||
use crate::core::touch;
|
||||
use crate::core::widget::tree::{self, Tree};
|
||||
use crate::core::{
|
||||
Background, Clipboard, Color, Element, Layout, Length, Pixels, Point,
|
||||
Rectangle, Shell, Size, Widget,
|
||||
Clipboard, Color, Element, Layout, Length, Pixels, Point, Rectangle, Shell,
|
||||
Size, Widget,
|
||||
};
|
||||
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
pub use iced_style::slider::{Appearance, Handle, HandleShape, StyleSheet};
|
||||
pub use iced_style::slider::{
|
||||
Appearance, Handle, HandleShape, Rail, StyleSheet,
|
||||
};
|
||||
|
||||
/// An horizontal bar and a handle that selects a single value from a range of
|
||||
/// values.
|
||||
|
|
@ -366,38 +368,6 @@ pub fn draw<T, R>(
|
|||
style_sheet.active(style)
|
||||
};
|
||||
|
||||
let rail_y = bounds.y + (bounds.height / 2.0).round();
|
||||
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds: Rectangle {
|
||||
x: bounds.x,
|
||||
y: rail_y - 1.0,
|
||||
width: bounds.width,
|
||||
height: 2.0,
|
||||
},
|
||||
border_radius: 0.0.into(),
|
||||
border_width: 0.0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
},
|
||||
style.rail_colors.0,
|
||||
);
|
||||
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds: Rectangle {
|
||||
x: bounds.x,
|
||||
y: rail_y + 1.0,
|
||||
width: bounds.width,
|
||||
height: 2.0,
|
||||
},
|
||||
border_radius: 0.0.into(),
|
||||
border_width: 0.0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
},
|
||||
Background::Color(style.rail_colors.1),
|
||||
);
|
||||
|
||||
let (handle_width, handle_height, handle_border_radius) = match style
|
||||
.handle
|
||||
.shape
|
||||
|
|
@ -416,17 +386,49 @@ pub fn draw<T, R>(
|
|||
(start.into() as f32, end.into() as f32)
|
||||
};
|
||||
|
||||
let handle_offset = if range_start >= range_end {
|
||||
let offset = if range_start >= range_end {
|
||||
0.0
|
||||
} else {
|
||||
(bounds.width - handle_width) * (value - range_start)
|
||||
(bounds.width - handle_width / 2.0) * (value - range_start)
|
||||
/ (range_end - range_start)
|
||||
};
|
||||
|
||||
let rail_y = bounds.y + bounds.height / 2.0;
|
||||
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds: Rectangle {
|
||||
x: bounds.x + handle_offset.round(),
|
||||
x: bounds.x,
|
||||
y: rail_y - style.rail.width / 2.0,
|
||||
width: offset + handle_width / 2.0,
|
||||
height: style.rail.width,
|
||||
},
|
||||
border_radius: Default::default(),
|
||||
border_width: 0.0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
},
|
||||
style.rail.colors.0,
|
||||
);
|
||||
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds: Rectangle {
|
||||
x: bounds.x + offset + handle_width / 2.0,
|
||||
y: rail_y - style.rail.width / 2.0,
|
||||
width: bounds.width - offset,
|
||||
height: style.rail.width,
|
||||
},
|
||||
border_radius: Default::default(),
|
||||
border_width: 0.0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
},
|
||||
style.rail.colors.1,
|
||||
);
|
||||
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds: Rectangle {
|
||||
x: bounds.x + offset,
|
||||
y: rail_y - handle_height / 2.0,
|
||||
width: handle_width,
|
||||
height: handle_height,
|
||||
|
|
|
|||
|
|
@ -49,8 +49,8 @@ pub use iced_style::text_input::{Appearance, StyleSheet};
|
|||
/// let input = TextInput::new(
|
||||
/// "This is the placeholder...",
|
||||
/// value,
|
||||
/// Message::TextInputChanged,
|
||||
/// )
|
||||
/// .on_input(Message::TextInputChanged)
|
||||
/// .padding(10);
|
||||
/// ```
|
||||
/// 
|
||||
|
|
@ -68,9 +68,10 @@ where
|
|||
width: Length,
|
||||
padding: Padding,
|
||||
size: Option<f32>,
|
||||
on_change: Box<dyn Fn(String) -> Message + 'a>,
|
||||
on_input: Option<Box<dyn Fn(String) -> Message + 'a>>,
|
||||
on_paste: Option<Box<dyn Fn(String) -> Message + 'a>>,
|
||||
on_submit: Option<Message>,
|
||||
icon: Option<Icon<Renderer::Font>>,
|
||||
style: <Renderer::Theme as StyleSheet>::Style,
|
||||
}
|
||||
|
||||
|
|
@ -84,12 +85,8 @@ where
|
|||
///
|
||||
/// It expects:
|
||||
/// - 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,
|
||||
{
|
||||
/// - the current value
|
||||
pub fn new(placeholder: &str, value: &str) -> Self {
|
||||
TextInput {
|
||||
id: None,
|
||||
placeholder: String::from(placeholder),
|
||||
|
|
@ -99,9 +96,10 @@ where
|
|||
width: Length::Fill,
|
||||
padding: Padding::new(5.0),
|
||||
size: None,
|
||||
on_change: Box::new(on_change),
|
||||
on_input: None,
|
||||
on_paste: None,
|
||||
on_submit: None,
|
||||
icon: None,
|
||||
style: Default::default(),
|
||||
}
|
||||
}
|
||||
|
|
@ -118,6 +116,25 @@ where
|
|||
self
|
||||
}
|
||||
|
||||
/// Sets the message that should be produced when some text is typed into
|
||||
/// the [`TextInput`].
|
||||
///
|
||||
/// If this method is not called, the [`TextInput`] will be disabled.
|
||||
pub fn on_input<F>(mut self, callback: F) -> Self
|
||||
where
|
||||
F: 'a + Fn(String) -> Message,
|
||||
{
|
||||
self.on_input = Some(Box::new(callback));
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the message that should be produced when the [`TextInput`] is
|
||||
/// focused and the enter key is pressed.
|
||||
pub fn on_submit(mut self, message: Message) -> Self {
|
||||
self.on_submit = Some(message);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the message that should be produced when some text is pasted into
|
||||
/// the [`TextInput`].
|
||||
pub fn on_paste(
|
||||
|
|
@ -135,6 +152,13 @@ where
|
|||
self.font = Some(font);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the [`Icon`] of the [`TextInput`].
|
||||
pub fn icon(mut self, icon: Icon<Renderer::Font>) -> Self {
|
||||
self.icon = Some(icon);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the width of the [`TextInput`].
|
||||
pub fn width(mut self, width: impl Into<Length>) -> Self {
|
||||
self.width = width.into();
|
||||
|
|
@ -153,13 +177,6 @@ where
|
|||
self
|
||||
}
|
||||
|
||||
/// Sets the message that should be produced when the [`TextInput`] is
|
||||
/// focused and the enter key is pressed.
|
||||
pub fn on_submit(mut self, message: Message) -> Self {
|
||||
self.on_submit = Some(message);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the style of the [`TextInput`].
|
||||
pub fn style(
|
||||
mut self,
|
||||
|
|
@ -192,7 +209,9 @@ where
|
|||
&self.placeholder,
|
||||
self.size,
|
||||
self.font,
|
||||
self.on_input.is_none(),
|
||||
self.is_secure,
|
||||
self.icon.as_ref(),
|
||||
&self.style,
|
||||
)
|
||||
}
|
||||
|
|
@ -213,6 +232,18 @@ where
|
|||
tree::State::new(State::new())
|
||||
}
|
||||
|
||||
fn diff(&self, tree: &mut Tree) {
|
||||
let state = tree.state.downcast_mut::<State>();
|
||||
|
||||
// Unfocus text input if it becomes disabled
|
||||
if self.on_input.is_none() {
|
||||
state.last_click = None;
|
||||
state.is_focused = None;
|
||||
state.is_pasting = None;
|
||||
state.is_dragging = false;
|
||||
}
|
||||
}
|
||||
|
||||
fn width(&self) -> Length {
|
||||
self.width
|
||||
}
|
||||
|
|
@ -226,7 +257,14 @@ where
|
|||
renderer: &Renderer,
|
||||
limits: &layout::Limits,
|
||||
) -> layout::Node {
|
||||
layout(renderer, limits, self.width, self.padding, self.size)
|
||||
layout(
|
||||
renderer,
|
||||
limits,
|
||||
self.width,
|
||||
self.padding,
|
||||
self.size,
|
||||
self.icon.as_ref(),
|
||||
)
|
||||
}
|
||||
|
||||
fn operate(
|
||||
|
|
@ -263,7 +301,7 @@ where
|
|||
self.size,
|
||||
self.font,
|
||||
self.is_secure,
|
||||
self.on_change.as_ref(),
|
||||
self.on_input.as_deref(),
|
||||
self.on_paste.as_deref(),
|
||||
&self.on_submit,
|
||||
|| tree.state.downcast_mut::<State>(),
|
||||
|
|
@ -290,7 +328,9 @@ where
|
|||
&self.placeholder,
|
||||
self.size,
|
||||
self.font,
|
||||
self.on_input.is_none(),
|
||||
self.is_secure,
|
||||
self.icon.as_ref(),
|
||||
&self.style,
|
||||
)
|
||||
}
|
||||
|
|
@ -303,7 +343,7 @@ where
|
|||
_viewport: &Rectangle,
|
||||
_renderer: &Renderer,
|
||||
) -> mouse::Interaction {
|
||||
mouse_interaction(layout, cursor_position)
|
||||
mouse_interaction(layout, cursor_position, self.on_input.is_none())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -321,6 +361,30 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// The content of the [`Icon`].
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Icon<Font> {
|
||||
/// The font that will be used to display the `code_point`.
|
||||
pub font: Font,
|
||||
/// The unicode code point that will be used as the icon.
|
||||
pub code_point: char,
|
||||
/// The font size of the content.
|
||||
pub size: Option<f32>,
|
||||
/// The spacing between the [`Icon`] and the text in a [`TextInput`].
|
||||
pub spacing: f32,
|
||||
/// The side of a [`TextInput`] where to display the [`Icon`].
|
||||
pub side: Side,
|
||||
}
|
||||
|
||||
/// The side of a [`TextInput`].
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Side {
|
||||
/// The left side of a [`TextInput`].
|
||||
Left,
|
||||
/// The right side of a [`TextInput`].
|
||||
Right,
|
||||
}
|
||||
|
||||
/// The identifier of a [`TextInput`].
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct Id(widget::Id);
|
||||
|
|
@ -383,6 +447,7 @@ pub fn layout<Renderer>(
|
|||
width: Length,
|
||||
padding: Padding,
|
||||
size: Option<f32>,
|
||||
icon: Option<&Icon<Renderer::Font>>,
|
||||
) -> layout::Node
|
||||
where
|
||||
Renderer: text::Renderer,
|
||||
|
|
@ -391,10 +456,51 @@ where
|
|||
let padding = padding.fit(Size::ZERO, limits.max());
|
||||
let limits = limits.width(width).pad(padding).height(text_size * 1.2);
|
||||
|
||||
let mut text = layout::Node::new(limits.resolve(Size::ZERO));
|
||||
text.move_to(Point::new(padding.left, padding.top));
|
||||
let text_bounds = limits.resolve(Size::ZERO);
|
||||
|
||||
layout::Node::with_children(text.size().pad(padding), vec![text])
|
||||
if let Some(icon) = icon {
|
||||
let icon_width = renderer.measure_width(
|
||||
&icon.code_point.to_string(),
|
||||
icon.size.unwrap_or_else(|| renderer.default_size()),
|
||||
icon.font,
|
||||
);
|
||||
|
||||
let mut text_node = layout::Node::new(
|
||||
text_bounds - Size::new(icon_width + icon.spacing, 0.0),
|
||||
);
|
||||
|
||||
let mut icon_node =
|
||||
layout::Node::new(Size::new(icon_width, text_bounds.height));
|
||||
|
||||
match icon.side {
|
||||
Side::Left => {
|
||||
text_node.move_to(Point::new(
|
||||
padding.left + icon_width + icon.spacing,
|
||||
padding.top,
|
||||
));
|
||||
|
||||
icon_node.move_to(Point::new(padding.left, padding.top));
|
||||
}
|
||||
Side::Right => {
|
||||
text_node.move_to(Point::new(padding.left, padding.top));
|
||||
|
||||
icon_node.move_to(Point::new(
|
||||
padding.left + text_bounds.width - icon_width,
|
||||
padding.top,
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
layout::Node::with_children(
|
||||
text_bounds.pad(padding),
|
||||
vec![text_node, icon_node],
|
||||
)
|
||||
} else {
|
||||
let mut text = layout::Node::new(text_bounds);
|
||||
text.move_to(Point::new(padding.left, padding.top));
|
||||
|
||||
layout::Node::with_children(text_bounds.pad(padding), vec![text])
|
||||
}
|
||||
}
|
||||
|
||||
/// Processes an [`Event`] and updates the [`State`] of a [`TextInput`]
|
||||
|
|
@ -410,7 +516,7 @@ pub fn update<'a, Message, Renderer>(
|
|||
size: Option<f32>,
|
||||
font: Option<Renderer::Font>,
|
||||
is_secure: bool,
|
||||
on_change: &dyn Fn(String) -> Message,
|
||||
on_input: Option<&dyn Fn(String) -> Message>,
|
||||
on_paste: Option<&dyn Fn(String) -> Message>,
|
||||
on_submit: &Option<Message>,
|
||||
state: impl FnOnce() -> &'a mut State,
|
||||
|
|
@ -423,7 +529,8 @@ where
|
|||
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
|
||||
| Event::Touch(touch::Event::FingerPressed { .. }) => {
|
||||
let state = state();
|
||||
let is_clicked = layout.bounds().contains(cursor_position);
|
||||
let is_clicked =
|
||||
layout.bounds().contains(cursor_position) && on_input.is_some();
|
||||
|
||||
state.is_focused = if is_clicked {
|
||||
state.is_focused.or_else(|| {
|
||||
|
|
@ -553,6 +660,8 @@ where
|
|||
let state = state();
|
||||
|
||||
if let Some(focus) = &mut state.is_focused {
|
||||
let Some(on_input) = on_input else { return event::Status::Ignored };
|
||||
|
||||
if state.is_pasting.is_none()
|
||||
&& !state.keyboard_modifiers.command()
|
||||
&& !c.is_control()
|
||||
|
|
@ -561,7 +670,7 @@ where
|
|||
|
||||
editor.insert(c);
|
||||
|
||||
let message = (on_change)(editor.contents());
|
||||
let message = (on_input)(editor.contents());
|
||||
shell.publish(message);
|
||||
|
||||
focus.updated_at = Instant::now();
|
||||
|
|
@ -574,6 +683,8 @@ where
|
|||
let state = state();
|
||||
|
||||
if let Some(focus) = &mut state.is_focused {
|
||||
let Some(on_input) = on_input else { return event::Status::Ignored };
|
||||
|
||||
let modifiers = state.keyboard_modifiers;
|
||||
focus.updated_at = Instant::now();
|
||||
|
||||
|
|
@ -599,7 +710,7 @@ where
|
|||
let mut editor = Editor::new(value, &mut state.cursor);
|
||||
editor.backspace();
|
||||
|
||||
let message = (on_change)(editor.contents());
|
||||
let message = (on_input)(editor.contents());
|
||||
shell.publish(message);
|
||||
}
|
||||
keyboard::KeyCode::Delete => {
|
||||
|
|
@ -619,7 +730,7 @@ where
|
|||
let mut editor = Editor::new(value, &mut state.cursor);
|
||||
editor.delete();
|
||||
|
||||
let message = (on_change)(editor.contents());
|
||||
let message = (on_input)(editor.contents());
|
||||
shell.publish(message);
|
||||
}
|
||||
keyboard::KeyCode::Left => {
|
||||
|
|
@ -694,7 +805,7 @@ where
|
|||
let mut editor = Editor::new(value, &mut state.cursor);
|
||||
editor.delete();
|
||||
|
||||
let message = (on_change)(editor.contents());
|
||||
let message = (on_input)(editor.contents());
|
||||
shell.publish(message);
|
||||
}
|
||||
keyboard::KeyCode::V => {
|
||||
|
|
@ -721,7 +832,7 @@ where
|
|||
let message = if let Some(paste) = &on_paste {
|
||||
(paste)(editor.contents())
|
||||
} else {
|
||||
(on_change)(editor.contents())
|
||||
(on_input)(editor.contents())
|
||||
};
|
||||
shell.publish(message);
|
||||
|
||||
|
|
@ -815,7 +926,9 @@ pub fn draw<Renderer>(
|
|||
placeholder: &str,
|
||||
size: Option<f32>,
|
||||
font: Option<Renderer::Font>,
|
||||
is_disabled: bool,
|
||||
is_secure: bool,
|
||||
icon: Option<&Icon<Renderer::Font>>,
|
||||
style: &<Renderer::Theme as StyleSheet>::Style,
|
||||
) where
|
||||
Renderer: text::Renderer,
|
||||
|
|
@ -825,11 +938,15 @@ pub fn draw<Renderer>(
|
|||
let value = secure_value.as_ref().unwrap_or(value);
|
||||
|
||||
let bounds = layout.bounds();
|
||||
let text_bounds = layout.children().next().unwrap().bounds();
|
||||
|
||||
let mut children_layout = layout.children();
|
||||
let text_bounds = children_layout.next().unwrap().bounds();
|
||||
|
||||
let is_mouse_over = bounds.contains(cursor_position);
|
||||
|
||||
let appearance = if state.is_focused() {
|
||||
let appearance = if is_disabled {
|
||||
theme.disabled(style)
|
||||
} else if state.is_focused() {
|
||||
theme.focused(style)
|
||||
} else if is_mouse_over {
|
||||
theme.hovered(style)
|
||||
|
|
@ -847,6 +964,20 @@ pub fn draw<Renderer>(
|
|||
appearance.background,
|
||||
);
|
||||
|
||||
if let Some(icon) = icon {
|
||||
let icon_layout = children_layout.next().unwrap();
|
||||
|
||||
renderer.fill_text(Text {
|
||||
content: &icon.code_point.to_string(),
|
||||
size: icon.size.unwrap_or_else(|| renderer.default_size()),
|
||||
font: icon.font,
|
||||
color: appearance.icon_color,
|
||||
bounds: icon_layout.bounds(),
|
||||
horizontal_alignment: alignment::Horizontal::Left,
|
||||
vertical_alignment: alignment::Vertical::Top,
|
||||
});
|
||||
}
|
||||
|
||||
let text = value.to_string();
|
||||
let font = font.unwrap_or_else(|| renderer.default_font());
|
||||
let size = size.unwrap_or_else(|| renderer.default_size());
|
||||
|
|
@ -959,6 +1090,8 @@ pub fn draw<Renderer>(
|
|||
content: if text.is_empty() { placeholder } else { &text },
|
||||
color: if text.is_empty() {
|
||||
theme.placeholder_color(style)
|
||||
} else if is_disabled {
|
||||
theme.disabled_color(style)
|
||||
} else {
|
||||
theme.value_color(style)
|
||||
},
|
||||
|
|
@ -987,9 +1120,14 @@ pub fn draw<Renderer>(
|
|||
pub fn mouse_interaction(
|
||||
layout: Layout<'_>,
|
||||
cursor_position: Point,
|
||||
is_disabled: bool,
|
||||
) -> mouse::Interaction {
|
||||
if layout.bounds().contains(cursor_position) {
|
||||
mouse::Interaction::Text
|
||||
if is_disabled {
|
||||
mouse::Interaction::NotAllowed
|
||||
} else {
|
||||
mouse::Interaction::Text
|
||||
}
|
||||
} else {
|
||||
mouse::Interaction::default()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@ use crate::core::renderer;
|
|||
use crate::core::touch;
|
||||
use crate::core::widget::tree::{self, Tree};
|
||||
use crate::core::{
|
||||
Background, Clipboard, Color, Element, Length, Pixels, Point, Rectangle,
|
||||
Shell, Size, Widget,
|
||||
Clipboard, Color, Element, Length, Pixels, Point, Rectangle, Shell, Size,
|
||||
Widget,
|
||||
};
|
||||
|
||||
/// An vertical bar and a handle that selects a single value from a range of
|
||||
|
|
@ -366,38 +366,6 @@ pub fn draw<T, R>(
|
|||
style_sheet.active(style)
|
||||
};
|
||||
|
||||
let rail_x = bounds.x + (bounds.width / 2.0).round();
|
||||
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds: Rectangle {
|
||||
x: rail_x - 1.0,
|
||||
y: bounds.y,
|
||||
width: 2.0,
|
||||
height: bounds.height,
|
||||
},
|
||||
border_radius: 0.0.into(),
|
||||
border_width: 0.0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
},
|
||||
style.rail_colors.0,
|
||||
);
|
||||
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds: Rectangle {
|
||||
x: rail_x + 1.0,
|
||||
y: bounds.y,
|
||||
width: 2.0,
|
||||
height: bounds.height,
|
||||
},
|
||||
border_radius: 0.0.into(),
|
||||
border_width: 0.0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
},
|
||||
Background::Color(style.rail_colors.1),
|
||||
);
|
||||
|
||||
let (handle_width, handle_height, handle_border_radius) = match style
|
||||
.handle
|
||||
.shape
|
||||
|
|
@ -416,18 +384,50 @@ pub fn draw<T, R>(
|
|||
(start.into() as f32, end.into() as f32)
|
||||
};
|
||||
|
||||
let handle_offset = if range_start >= range_end {
|
||||
let offset = if range_start >= range_end {
|
||||
0.0
|
||||
} else {
|
||||
(bounds.height - handle_width) * (value - range_end)
|
||||
(bounds.height - handle_width / 2.0) * (value - range_end)
|
||||
/ (range_start - range_end)
|
||||
};
|
||||
|
||||
let rail_x = bounds.x + bounds.width / 2.0;
|
||||
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds: Rectangle {
|
||||
x: rail_x - (handle_height / 2.0),
|
||||
y: bounds.y + handle_offset.round(),
|
||||
x: rail_x - style.rail.width / 2.0,
|
||||
y: bounds.y,
|
||||
width: style.rail.width,
|
||||
height: offset + handle_width / 2.0,
|
||||
},
|
||||
border_radius: Default::default(),
|
||||
border_width: 0.0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
},
|
||||
style.rail.colors.1,
|
||||
);
|
||||
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds: Rectangle {
|
||||
x: rail_x - style.rail.width / 2.0,
|
||||
y: bounds.y + offset + handle_width / 2.0,
|
||||
width: style.rail.width,
|
||||
height: bounds.height - offset,
|
||||
},
|
||||
border_radius: Default::default(),
|
||||
border_width: 0.0,
|
||||
border_color: Color::TRANSPARENT,
|
||||
},
|
||||
style.rail.colors.0,
|
||||
);
|
||||
|
||||
renderer.fill_quad(
|
||||
renderer::Quad {
|
||||
bounds: Rectangle {
|
||||
x: rail_x - handle_height / 2.0,
|
||||
y: bounds.y + offset,
|
||||
width: handle_height,
|
||||
height: handle_width,
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue