summaryrefslogtreecommitdiffstats
path: root/widget/src
diff options
context:
space:
mode:
authorLibravatar Héctor Ramón Jiménez <hector0193@gmail.com>2023-04-17 23:46:18 +0200
committerLibravatar Héctor Ramón Jiménez <hector0193@gmail.com>2023-04-17 23:46:18 +0200
commit619ba9294d5e0f7913f82838d78018fc6714d26a (patch)
treeb0631aa66875e0e9f486665227c306832ac19a5b /widget/src
parentd206b82ebb7617337cd378bd19542a7abf8a4529 (diff)
parente3730106e9d4f75de199e1b83cf285b8ff031968 (diff)
downloadiced-619ba9294d5e0f7913f82838d78018fc6714d26a.tar.gz
iced-619ba9294d5e0f7913f82838d78018fc6714d26a.tar.bz2
iced-619ba9294d5e0f7913f82838d78018fc6714d26a.zip
Merge branch 'advanced-text' into incremental-rendering
Diffstat (limited to 'widget/src')
-rw-r--r--widget/src/checkbox.rs22
-rw-r--r--widget/src/helpers.rs17
-rw-r--r--widget/src/lazy/component.rs252
-rw-r--r--widget/src/lib.rs3
-rw-r--r--widget/src/mouse_area.rs311
-rw-r--r--widget/src/pane_grid.rs2
-rw-r--r--widget/src/radio.rs37
-rw-r--r--widget/src/scrollable.rs52
-rw-r--r--widget/src/slider.rs78
-rw-r--r--widget/src/text_input.rs202
-rw-r--r--widget/src/vertical_slider.rs76
11 files changed, 827 insertions, 225 deletions
diff --git a/widget/src/checkbox.rs b/widget/src/checkbox.rs
index d1f886c6..6505cfdd 100644
--- a/widget/src/checkbox.rs
+++ b/widget/src/checkbox.rs
@@ -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>,
+}
diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs
index a43e7248..336ac4ee 100644
--- a/widget/src/helpers.rs
+++ b/widget/src/helpers.rs
@@ -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)
+}
diff --git a/widget/src/lazy/component.rs b/widget/src/lazy/component.rs
index 0b8070af..49ae68af 100644
--- a/widget/src/lazy/component.rs
+++ b/widget/src/lazy/component.rs
@@ -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)]
@@ -428,6 +521,9 @@ impl<'a, 'b, Message, Renderer, Event, S>
self.overlay
.as_ref()
.unwrap()
+ .0
+ .as_ref()
+ .unwrap()
.borrow_overlay()
.as_ref()
.map(f)
@@ -440,6 +536,9 @@ impl<'a, 'b, Message, Renderer, Event, S>
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();
}
diff --git a/widget/src/lib.rs b/widget/src/lib.rs
index a3e7c8bc..904f62ad 100644
--- a/widget/src/lib.rs
+++ b/widget/src/lib.rs
@@ -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;
diff --git a/widget/src/mouse_area.rs b/widget/src/mouse_area.rs
new file mode 100644
index 00000000..0232c494
--- /dev/null
+++ b/widget/src/mouse_area.rs
@@ -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
+}
diff --git a/widget/src/pane_grid.rs b/widget/src/pane_grid.rs
index 257c0144..67145e8e 100644
--- a/widget/src/pane_grid.rs
+++ b/widget/src/pane_grid.rs
@@ -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;
diff --git a/widget/src/radio.rs b/widget/src/radio.rs
index c2b6b017..c3229aed 100644
--- a/widget/src/radio.rs
+++ b/widget/src/radio.rs
@@ -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
+/// );
///
-/// ![Radio buttons drawn by `iced_wgpu`](https://github.com/iced-rs/iced/blob/7760618fb112074bc40b148944521f312152012a/docs/images/radio.png?raw=true)
+/// 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
diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs
index 5a7481f7..161ae664 100644
--- a/widget/src/scrollable.rs
+++ b/widget/src/scrollable.rs
@@ -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,
}
}
}
diff --git a/widget/src/slider.rs b/widget/src/slider.rs
index e1153d2d..5a884e21 100644
--- a/widget/src/slider.rs
+++ b/widget/src/slider.rs
@@ -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,
+ 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 + handle_offset.round(),
+ x: bounds.x + offset,
y: rail_y - handle_height / 2.0,
width: handle_width,
height: handle_height,
diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs
index 4f018284..9db382f7 100644
--- a/widget/src/text_input.rs
+++ b/widget/src/text_input.rs
@@ -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);
/// ```
/// ![Text input drawn by `iced_wgpu`](https://github.com/iced-rs/iced/blob/7760618fb112074bc40b148944521f312152012a/docs/images/text_input.png?raw=true)
@@ -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);
+
+ 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.size().pad(padding), vec![text])
+ 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());
@@ -961,6 +1092,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)
},
@@ -989,9 +1122,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()
}
diff --git a/widget/src/vertical_slider.rs b/widget/src/vertical_slider.rs
index 62dc997f..a7551aef 100644
--- a/widget/src/vertical_slider.rs
+++ b/widget/src/vertical_slider.rs
@@ -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 - 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 + handle_offset.round(),
+ x: rail_x - handle_height / 2.0,
+ y: bounds.y + offset,
width: handle_height,
height: handle_width,
},