summaryrefslogtreecommitdiffstats
path: root/native
diff options
context:
space:
mode:
Diffstat (limited to 'native')
-rw-r--r--native/src/event.rs21
-rw-r--r--native/src/layout/flex.rs12
-rw-r--r--native/src/layout/limits.rs9
-rw-r--r--native/src/lib.rs4
-rw-r--r--native/src/overlay/menu.rs24
-rw-r--r--native/src/program.rs2
-rw-r--r--native/src/renderer/null.rs22
-rw-r--r--native/src/user_interface.rs2
-rw-r--r--native/src/widget.rs13
-rw-r--r--native/src/widget/button.rs47
-rw-r--r--native/src/widget/checkbox.rs14
-rw-r--r--native/src/widget/column.rs15
-rw-r--r--native/src/widget/container.rs24
-rw-r--r--native/src/widget/image/viewer.rs25
-rw-r--r--native/src/widget/pane_grid.rs2
-rw-r--r--native/src/widget/pane_grid/content.rs17
-rw-r--r--native/src/widget/pane_grid/title_bar.rs81
-rw-r--r--native/src/widget/pick_list.rs69
-rw-r--r--native/src/widget/radio.rs26
-rw-r--r--native/src/widget/row.rs15
-rw-r--r--native/src/widget/scrollable.rs126
-rw-r--r--native/src/widget/text_input.rs63
-rw-r--r--native/src/widget/text_input/cursor.rs4
-rw-r--r--native/src/widget/text_input/editor.rs4
-rw-r--r--native/src/widget/toggler.rs277
25 files changed, 730 insertions, 188 deletions
diff --git a/native/src/event.rs b/native/src/event.rs
index 205bb797..1c26b5f2 100644
--- a/native/src/event.rs
+++ b/native/src/event.rs
@@ -23,6 +23,27 @@ pub enum Event {
/// A touch event
Touch(touch::Event),
+
+ /// A platform specific event
+ PlatformSpecific(PlatformSpecific),
+}
+
+/// A platform specific event
+#[derive(Debug, Clone, PartialEq)]
+pub enum PlatformSpecific {
+ /// A MacOS specific event
+ MacOS(MacOS),
+}
+
+/// Describes an event specific to MacOS
+#[derive(Debug, Clone, PartialEq)]
+pub enum MacOS {
+ /// Triggered when the app receives an URL from the system
+ ///
+ /// _**Note:** For this event to be triggered, the executable needs to be properly [bundled]!_
+ ///
+ /// [bundled]: https://developer.apple.com/library/archive/documentation/CoreFoundation/Conceptual/CFBundles/BundleTypes/BundleTypes.html#//apple_ref/doc/uid/10000123i-CH101-SW19
+ ReceivedUrl(String),
}
/// The status of an [`Event`] after being processed.
diff --git a/native/src/layout/flex.rs b/native/src/layout/flex.rs
index 4f6523fb..3d3ff82c 100644
--- a/native/src/layout/flex.rs
+++ b/native/src/layout/flex.rs
@@ -16,9 +16,10 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
+
use crate::{
layout::{Limits, Node},
- Align, Element, Point, Size,
+ Align, Element, Padding, Point, Size,
};
/// The main axis of a flex layout.
@@ -62,7 +63,7 @@ pub fn resolve<Message, Renderer>(
axis: Axis,
renderer: &Renderer,
limits: &Limits,
- padding: f32,
+ padding: Padding,
spacing: f32,
align_items: Align,
items: &[Element<'_, Message, Renderer>],
@@ -141,14 +142,15 @@ where
}
}
- let mut main = padding;
+ let pad = axis.pack(padding.left as f32, padding.top as f32);
+ let mut main = pad.0;
for (i, node) in nodes.iter_mut().enumerate() {
if i > 0 {
main += spacing;
}
- let (x, y) = axis.pack(main, padding);
+ let (x, y) = axis.pack(main, pad.1);
node.move_to(Point::new(x, y));
@@ -166,7 +168,7 @@ where
main += axis.main(size);
}
- let (width, height) = axis.pack(main - padding, cross);
+ let (width, height) = axis.pack(main - pad.0, cross);
let size = limits.resolve(Size::new(width, height));
Node::with_children(size.pad(padding), nodes)
diff --git a/native/src/layout/limits.rs b/native/src/layout/limits.rs
index a7bb5c9c..6d5f6563 100644
--- a/native/src/layout/limits.rs
+++ b/native/src/layout/limits.rs
@@ -1,4 +1,4 @@
-use crate::{Length, Size};
+use crate::{Length, Padding, Size};
/// A set of size constraints for layouting.
#[derive(Debug, Clone, Copy)]
@@ -117,8 +117,11 @@ impl Limits {
}
/// Shrinks the current [`Limits`] to account for the given padding.
- pub fn pad(&self, padding: f32) -> Limits {
- self.shrink(Size::new(padding * 2.0, padding * 2.0))
+ pub fn pad(&self, padding: Padding) -> Limits {
+ self.shrink(Size::new(
+ padding.horizontal() as f32,
+ padding.vertical() as f32,
+ ))
}
/// Shrinks the current [`Limits`] by the given [`Size`].
diff --git a/native/src/lib.rs b/native/src/lib.rs
index 20bbb1d0..cbb02506 100644
--- a/native/src/lib.rs
+++ b/native/src/lib.rs
@@ -61,8 +61,8 @@ mod debug;
mod debug;
pub use iced_core::{
- Align, Background, Color, Font, HorizontalAlignment, Length, Point,
- Rectangle, Size, Vector, VerticalAlignment,
+ menu, Align, Background, Color, Font, HorizontalAlignment, Length, Menu,
+ Padding, Point, Rectangle, Size, Vector, VerticalAlignment,
};
pub use iced_futures::{executor, futures, Command};
diff --git a/native/src/overlay/menu.rs b/native/src/overlay/menu.rs
index afb17bd3..f62dcb46 100644
--- a/native/src/overlay/menu.rs
+++ b/native/src/overlay/menu.rs
@@ -8,8 +8,8 @@ use crate::scrollable;
use crate::text;
use crate::touch;
use crate::{
- Clipboard, Container, Element, Hasher, Layout, Length, Point, Rectangle,
- Scrollable, Size, Vector, Widget,
+ Clipboard, Container, Element, Hasher, Layout, Length, Padding, Point,
+ Rectangle, Scrollable, Size, Vector, Widget,
};
/// A list of selectable options.
@@ -20,7 +20,7 @@ pub struct Menu<'a, T, Renderer: self::Renderer> {
hovered_option: &'a mut Option<usize>,
last_selection: &'a mut Option<T>,
width: u16,
- padding: u16,
+ padding: Padding,
text_size: Option<u16>,
font: Renderer::Font,
style: <Renderer as self::Renderer>::Style,
@@ -45,7 +45,7 @@ where
hovered_option,
last_selection,
width: 0,
- padding: 0,
+ padding: Padding::ZERO,
text_size: None,
font: Default::default(),
style: Default::default(),
@@ -58,9 +58,9 @@ where
self
}
- /// Sets the padding of the [`Menu`].
- pub fn padding(mut self, padding: u16) -> Self {
- self.padding = padding;
+ /// Sets the [`Padding`] of the [`Menu`].
+ pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
+ self.padding = padding.into();
self
}
@@ -261,7 +261,7 @@ struct List<'a, T, Renderer: self::Renderer> {
options: &'a [T],
hovered_option: &'a mut Option<usize>,
last_selection: &'a mut Option<T>,
- padding: u16,
+ padding: Padding,
text_size: Option<u16>,
font: Renderer::Font,
style: <Renderer as self::Renderer>::Style,
@@ -294,7 +294,7 @@ where
let size = {
let intrinsic = Size::new(
0.0,
- f32::from(text_size + self.padding * 2)
+ f32::from(text_size + self.padding.vertical())
* self.options.len() as f32,
);
@@ -345,7 +345,7 @@ where
*self.hovered_option = Some(
((cursor_position.y - bounds.y)
- / f32::from(text_size + self.padding * 2))
+ / f32::from(text_size + self.padding.vertical()))
as usize,
);
}
@@ -359,7 +359,7 @@ where
*self.hovered_option = Some(
((cursor_position.y - bounds.y)
- / f32::from(text_size + self.padding * 2))
+ / f32::from(text_size + self.padding.vertical()))
as usize,
);
@@ -430,7 +430,7 @@ pub trait Renderer:
viewport: &Rectangle,
options: &[T],
hovered_option: Option<usize>,
- padding: u16,
+ padding: Padding,
text_size: u16,
font: Self::Font,
style: &<Self as Renderer>::Style,
diff --git a/native/src/program.rs b/native/src/program.rs
index 066c29d8..75fab094 100644
--- a/native/src/program.rs
+++ b/native/src/program.rs
@@ -11,7 +11,7 @@ pub trait Program: Sized {
type Renderer: Renderer;
/// The type of __messages__ your [`Program`] will produce.
- type Message: std::fmt::Debug + Send;
+ type Message: std::fmt::Debug + Clone + Send;
/// The type of [`Clipboard`] your [`Program`] will use.
type Clipboard: Clipboard;
diff --git a/native/src/renderer/null.rs b/native/src/renderer/null.rs
index 9e91d29f..bb57c163 100644
--- a/native/src/renderer/null.rs
+++ b/native/src/renderer/null.rs
@@ -1,7 +1,7 @@
use crate::{
button, checkbox, column, container, pane_grid, progress_bar, radio, row,
- scrollable, slider, text, text_input, Color, Element, Font,
- HorizontalAlignment, Layout, Point, Rectangle, Renderer, Size,
+ scrollable, slider, text, text_input, toggler, Color, Element, Font,
+ HorizontalAlignment, Layout, Padding, Point, Rectangle, Renderer, Size,
VerticalAlignment,
};
@@ -145,7 +145,7 @@ impl text_input::Renderer for Null {
}
impl button::Renderer for Null {
- const DEFAULT_PADDING: u16 = 0;
+ const DEFAULT_PADDING: Padding = Padding::ZERO;
type Style = ();
@@ -288,3 +288,19 @@ impl pane_grid::Renderer for Null {
) {
}
}
+
+impl toggler::Renderer for Null {
+ type Style = ();
+
+ const DEFAULT_SIZE: u16 = 20;
+
+ fn draw(
+ &mut self,
+ _bounds: Rectangle,
+ _is_checked: bool,
+ _is_mouse_over: bool,
+ _label: Option<Self::Output>,
+ _style: &Self::Style,
+ ) {
+ }
+}
diff --git a/native/src/user_interface.rs b/native/src/user_interface.rs
index 475faf8d..8e0d7d1c 100644
--- a/native/src/user_interface.rs
+++ b/native/src/user_interface.rs
@@ -16,7 +16,7 @@ use std::hash::Hasher;
/// The [`integration` example] uses a [`UserInterface`] to integrate Iced in
/// an existing graphical application.
///
-/// [`integration` example]: https://github.com/hecrj/iced/tree/0.2/examples/integration
+/// [`integration` example]: https://github.com/hecrj/iced/tree/0.3/examples/integration
#[allow(missing_debug_implementations)]
pub struct UserInterface<'a, Message, Renderer> {
root: Element<'a, Message, Renderer>,
diff --git a/native/src/widget.rs b/native/src/widget.rs
index 791c53a3..43c1b023 100644
--- a/native/src/widget.rs
+++ b/native/src/widget.rs
@@ -36,6 +36,7 @@ pub mod space;
pub mod svg;
pub mod text;
pub mod text_input;
+pub mod toggler;
pub mod tooltip;
#[doc(no_inline)]
@@ -73,6 +74,8 @@ pub use text::Text;
#[doc(no_inline)]
pub use text_input::TextInput;
#[doc(no_inline)]
+pub use toggler::Toggler;
+#[doc(no_inline)]
pub use tooltip::Tooltip;
use crate::event::{self, Event};
@@ -96,12 +99,12 @@ use crate::{Clipboard, Hasher, Layout, Length, Point, Rectangle};
/// - [`geometry`], a custom widget showcasing how to draw geometry with the
/// `Mesh2D` primitive in [`iced_wgpu`].
///
-/// [examples]: https://github.com/hecrj/iced/tree/0.2/examples
-/// [`bezier_tool`]: https://github.com/hecrj/iced/tree/0.2/examples/bezier_tool
-/// [`custom_widget`]: https://github.com/hecrj/iced/tree/0.2/examples/custom_widget
-/// [`geometry`]: https://github.com/hecrj/iced/tree/0.2/examples/geometry
+/// [examples]: https://github.com/hecrj/iced/tree/0.3/examples
+/// [`bezier_tool`]: https://github.com/hecrj/iced/tree/0.3/examples/bezier_tool
+/// [`custom_widget`]: https://github.com/hecrj/iced/tree/0.3/examples/custom_widget
+/// [`geometry`]: https://github.com/hecrj/iced/tree/0.3/examples/geometry
/// [`lyon`]: https://github.com/nical/lyon
-/// [`iced_wgpu`]: https://github.com/hecrj/iced/tree/0.2/wgpu
+/// [`iced_wgpu`]: https://github.com/hecrj/iced/tree/0.3/wgpu
pub trait Widget<Message, Renderer>
where
Renderer: crate::Renderer,
diff --git a/native/src/widget/button.rs b/native/src/widget/button.rs
index 99e98fd1..c469a0e5 100644
--- a/native/src/widget/button.rs
+++ b/native/src/widget/button.rs
@@ -7,7 +7,8 @@ use crate::mouse;
use crate::overlay;
use crate::touch;
use crate::{
- Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Widget,
+ Clipboard, Element, Hasher, Layout, Length, Padding, Point, Rectangle,
+ Widget,
};
use std::hash::Hash;
@@ -28,6 +29,29 @@ use std::hash::Hash;
/// let button = Button::new(&mut state, Text::new("Press me!"))
/// .on_press(Message::ButtonPressed);
/// ```
+///
+/// If a [`Button::on_press`] handler is not set, the resulting [`Button`] will
+/// be disabled:
+///
+/// ```
+/// # use iced_native::{button, Text};
+/// #
+/// # type Button<'a, Message> =
+/// # iced_native::Button<'a, Message, iced_native::renderer::Null>;
+/// #
+/// #[derive(Clone)]
+/// enum Message {
+/// ButtonPressed,
+/// }
+///
+/// fn disabled_button(state: &mut button::State) -> Button<'_, Message> {
+/// Button::new(state, Text::new("I'm disabled!"))
+/// }
+///
+/// fn enabled_button(state: &mut button::State) -> Button<'_, Message> {
+/// disabled_button(state).on_press(Message::ButtonPressed)
+/// }
+/// ```
#[allow(missing_debug_implementations)]
pub struct Button<'a, Message, Renderer: self::Renderer> {
state: &'a mut State,
@@ -37,7 +61,7 @@ pub struct Button<'a, Message, Renderer: self::Renderer> {
height: Length,
min_width: u32,
min_height: u32,
- padding: u16,
+ padding: Padding,
style: Renderer::Style,
}
@@ -89,13 +113,14 @@ where
self
}
- /// Sets the padding of the [`Button`].
- pub fn padding(mut self, padding: u16) -> Self {
- self.padding = padding;
+ /// Sets the [`Padding`] of the [`Button`].
+ pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
+ self.padding = padding.into();
self
}
/// Sets the message that will be produced when the [`Button`] is pressed.
+ /// If on_press isn't set, button will be disabled.
pub fn on_press(mut self, msg: Message) -> Self {
self.on_press = Some(msg);
self
@@ -140,18 +165,20 @@ where
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
- let padding = f32::from(self.padding);
let limits = limits
.min_width(self.min_width)
.min_height(self.min_height)
.width(self.width)
.height(self.height)
- .pad(padding);
+ .pad(self.padding);
let mut content = self.content.layout(renderer, &limits);
- content.move_to(Point::new(padding, padding));
+ content.move_to(Point::new(
+ self.padding.left.into(),
+ self.padding.top.into(),
+ ));
- let size = limits.resolve(content.size()).pad(padding);
+ let size = limits.resolve(content.size()).pad(self.padding);
layout::Node::with_children(size, vec![content])
}
@@ -258,7 +285,7 @@ where
/// [renderer]: crate::renderer
pub trait Renderer: crate::Renderer + Sized {
/// The default padding of a [`Button`].
- const DEFAULT_PADDING: u16;
+ const DEFAULT_PADDING: Padding;
/// The style supported by this renderer.
type Style: Default;
diff --git a/native/src/widget/checkbox.rs b/native/src/widget/checkbox.rs
index 6ce2e973..0f21c873 100644
--- a/native/src/widget/checkbox.rs
+++ b/native/src/widget/checkbox.rs
@@ -8,8 +8,8 @@ use crate::row;
use crate::text;
use crate::touch;
use crate::{
- Align, Clipboard, Element, Hasher, HorizontalAlignment, Layout, Length,
- Point, Rectangle, Row, Text, VerticalAlignment, Widget,
+ Align, Clipboard, Color, Element, Hasher, HorizontalAlignment, Layout,
+ Length, Point, Rectangle, Row, Text, VerticalAlignment, Widget,
};
/// A box that can be checked.
@@ -39,6 +39,7 @@ pub struct Checkbox<Message, Renderer: self::Renderer + text::Renderer> {
spacing: u16,
text_size: Option<u16>,
font: Renderer::Font,
+ text_color: Option<Color>,
style: Renderer::Style,
}
@@ -66,6 +67,7 @@ impl<Message, Renderer: self::Renderer + text::Renderer>
spacing: Renderer::DEFAULT_SPACING,
text_size: None,
font: Renderer::Font::default(),
+ text_color: None,
style: Renderer::Style::default(),
}
}
@@ -102,6 +104,12 @@ impl<Message, Renderer: self::Renderer + text::Renderer>
self
}
+ /// Sets the text color of the [`Checkbox`] button.
+ pub fn text_color(mut self, color: Color) -> Self {
+ self.text_color = Some(color);
+ self
+ }
+
/// Sets the style of the [`Checkbox`].
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
self.style = style.into();
@@ -193,7 +201,7 @@ where
&self.label,
self.text_size.unwrap_or(renderer.default_size()),
self.font,
- None,
+ self.text_color,
HorizontalAlignment::Left,
VerticalAlignment::Center,
);
diff --git a/native/src/widget/column.rs b/native/src/widget/column.rs
index d7f0365a..52a2e80c 100644
--- a/native/src/widget/column.rs
+++ b/native/src/widget/column.rs
@@ -5,7 +5,8 @@ use crate::event::{self, Event};
use crate::layout;
use crate::overlay;
use crate::{
- Align, Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Widget,
+ Align, Clipboard, Element, Hasher, Layout, Length, Padding, Point,
+ Rectangle, Widget,
};
use std::u32;
@@ -14,7 +15,7 @@ use std::u32;
#[allow(missing_debug_implementations)]
pub struct Column<'a, Message, Renderer> {
spacing: u16,
- padding: u16,
+ padding: Padding,
width: Length,
height: Length,
max_width: u32,
@@ -35,7 +36,7 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> {
) -> Self {
Column {
spacing: 0,
- padding: 0,
+ padding: Padding::ZERO,
width: Length::Shrink,
height: Length::Shrink,
max_width: u32::MAX,
@@ -55,9 +56,9 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> {
self
}
- /// Sets the padding of the [`Column`].
- pub fn padding(mut self, units: u16) -> Self {
- self.padding = units;
+ /// Sets the [`Padding`] of the [`Column`].
+ pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
+ self.padding = padding.into();
self
}
@@ -129,7 +130,7 @@ where
layout::flex::Axis::Vertical,
renderer,
&limits,
- self.padding as f32,
+ self.padding,
self.spacing as f32,
self.align_items,
&self.children,
diff --git a/native/src/widget/container.rs b/native/src/widget/container.rs
index 69fe699b..69aee64d 100644
--- a/native/src/widget/container.rs
+++ b/native/src/widget/container.rs
@@ -5,7 +5,8 @@ use crate::event::{self, Event};
use crate::layout;
use crate::overlay;
use crate::{
- Align, Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Widget,
+ Align, Clipboard, Element, Hasher, Layout, Length, Padding, Point,
+ Rectangle, Widget,
};
use std::u32;
@@ -15,7 +16,7 @@ use std::u32;
/// It is normally used for alignment purposes.
#[allow(missing_debug_implementations)]
pub struct Container<'a, Message, Renderer: self::Renderer> {
- padding: u16,
+ padding: Padding,
width: Length,
height: Length,
max_width: u32,
@@ -36,7 +37,7 @@ where
T: Into<Element<'a, Message, Renderer>>,
{
Container {
- padding: 0,
+ padding: Padding::ZERO,
width: Length::Shrink,
height: Length::Shrink,
max_width: u32::MAX,
@@ -48,9 +49,9 @@ where
}
}
- /// Sets the padding of the [`Container`].
- pub fn padding(mut self, units: u16) -> Self {
- self.padding = units;
+ /// Sets the [`Padding`] of the [`Container`].
+ pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
+ self.padding = padding.into();
self
}
@@ -127,23 +128,24 @@ where
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
- let padding = f32::from(self.padding);
-
let limits = limits
.loose()
.max_width(self.max_width)
.max_height(self.max_height)
.width(self.width)
.height(self.height)
- .pad(padding);
+ .pad(self.padding);
let mut content = self.content.layout(renderer, &limits.loose());
let size = limits.resolve(content.size());
- content.move_to(Point::new(padding, padding));
+ content.move_to(Point::new(
+ self.padding.left.into(),
+ self.padding.top.into(),
+ ));
content.align(self.horizontal_alignment, self.vertical_alignment, size);
- layout::Node::with_children(size.pad(padding), vec![content])
+ layout::Node::with_children(size.pad(self.padding), vec![content])
}
fn on_event(
diff --git a/native/src/widget/image/viewer.rs b/native/src/widget/image/viewer.rs
index a006c0af..405daf00 100644
--- a/native/src/widget/image/viewer.rs
+++ b/native/src/widget/image/viewer.rs
@@ -132,19 +132,30 @@ where
) -> layout::Node {
let (width, height) = renderer.dimensions(&self.handle);
- let aspect_ratio = width as f32 / height as f32;
-
let mut size = limits
.width(self.width)
.height(self.height)
.resolve(Size::new(width as f32, height as f32));
- let viewport_aspect_ratio = size.width / size.height;
-
- if viewport_aspect_ratio > aspect_ratio {
- size.width = width as f32 * size.height / height as f32;
+ let expansion_size = if height > width {
+ self.width
} else {
- size.height = height as f32 * size.width / width as f32;
+ self.height
+ };
+
+ // Only calculate viewport sizes if the images are constrained to a limited space.
+ // If they are Fill|Portion let them expand within their alotted space.
+ match expansion_size {
+ Length::Shrink | Length::Units(_) => {
+ let aspect_ratio = width as f32 / height as f32;
+ let viewport_aspect_ratio = size.width / size.height;
+ if viewport_aspect_ratio > aspect_ratio {
+ size.width = width as f32 * size.height / height as f32;
+ } else {
+ size.height = height as f32 * size.width / width as f32;
+ }
+ }
+ Length::Fill | Length::FillPortion(_) => {}
}
layout::Node::new(size)
diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs
index 44028f5e..b72172cc 100644
--- a/native/src/widget/pane_grid.rs
+++ b/native/src/widget/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/hecrj/iced/tree/0.2/examples/pane_grid
+//! [`pane_grid` example]: https://github.com/hecrj/iced/tree/0.3/examples/pane_grid
mod axis;
mod configuration;
mod content;
diff --git a/native/src/widget/pane_grid/content.rs b/native/src/widget/pane_grid/content.rs
index f028ec25..b0110393 100644
--- a/native/src/widget/pane_grid/content.rs
+++ b/native/src/widget/pane_grid/content.rs
@@ -193,18 +193,17 @@ where
&mut self,
layout: Layout<'_>,
) -> Option<overlay::Element<'_, Message, Renderer>> {
- let body_layout = if self.title_bar.is_some() {
+ if let Some(title_bar) = self.title_bar.as_mut() {
let mut children = layout.children();
+ let title_bar_layout = children.next()?;
- // Overlays only allowed in the pane body, for now at least.
- let _title_bar_layout = children.next();
-
- children.next()?
+ match title_bar.overlay(title_bar_layout) {
+ Some(overlay) => Some(overlay),
+ None => self.body.overlay(children.next()?),
+ }
} else {
- layout
- };
-
- self.body.overlay(body_layout)
+ self.body.overlay(layout)
+ }
}
}
diff --git a/native/src/widget/pane_grid/title_bar.rs b/native/src/widget/pane_grid/title_bar.rs
index a1e5107e..070010f8 100644
--- a/native/src/widget/pane_grid/title_bar.rs
+++ b/native/src/widget/pane_grid/title_bar.rs
@@ -1,8 +1,11 @@
use crate::container;
use crate::event::{self, Event};
use crate::layout;
+use crate::overlay;
use crate::pane_grid;
-use crate::{Clipboard, Element, Hasher, Layout, Point, Rectangle, Size};
+use crate::{
+ Clipboard, Element, Hasher, Layout, Padding, Point, Rectangle, Size,
+};
/// The title bar of a [`Pane`].
///
@@ -11,7 +14,7 @@ use crate::{Clipboard, Element, Hasher, Layout, Point, Rectangle, Size};
pub struct TitleBar<'a, Message, Renderer: pane_grid::Renderer> {
content: Element<'a, Message, Renderer>,
controls: Option<Element<'a, Message, Renderer>>,
- padding: u16,
+ padding: Padding,
always_show_controls: bool,
style: <Renderer as container::Renderer>::Style,
}
@@ -28,7 +31,7 @@ where
Self {
content: content.into(),
controls: None,
- padding: 0,
+ padding: Padding::ZERO,
always_show_controls: false,
style: Default::default(),
}
@@ -43,9 +46,9 @@ where
self
}
- /// Sets the padding of the [`TitleBar`].
- pub fn padding(mut self, units: u16) -> Self {
- self.padding = units;
+ /// Sets the [`Padding`] of the [`TitleBar`].
+ pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
+ self.padding = padding.into();
self
}
@@ -129,15 +132,16 @@ where
if layout.bounds().contains(cursor_position) {
let mut children = layout.children();
let padded = children.next().unwrap();
+ let mut children = padded.children();
+ let title_layout = children.next().unwrap();
if self.controls.is_some() {
- let mut children = padded.children();
- let _ = children.next().unwrap();
let controls_layout = children.next().unwrap();
!controls_layout.bounds().contains(cursor_position)
+ && !title_layout.bounds().contains(cursor_position)
} else {
- true
+ !title_layout.bounds().contains(cursor_position)
}
} else {
false
@@ -160,8 +164,7 @@ where
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
- let padding = f32::from(self.padding);
- let limits = limits.pad(padding);
+ let limits = limits.pad(self.padding);
let max_size = limits.max();
let title_layout = self
@@ -191,9 +194,12 @@ where
)
};
- node.move_to(Point::new(padding, padding));
+ node.move_to(Point::new(
+ self.padding.left.into(),
+ self.padding.top.into(),
+ ));
- layout::Node::with_children(node.size().pad(padding), vec![node])
+ layout::Node::with_children(node.size().pad(self.padding), vec![node])
}
pub(crate) fn on_event(
@@ -205,16 +211,17 @@ where
clipboard: &mut dyn Clipboard,
messages: &mut Vec<Message>,
) -> event::Status {
- if let Some(controls) = &mut self.controls {
- let mut children = layout.children();
- let padded = children.next().unwrap();
+ let mut children = layout.children();
+ let padded = children.next().unwrap();
- let mut children = padded.children();
- let _ = children.next();
+ let mut children = padded.children();
+ let title_layout = children.next().unwrap();
+
+ let control_status = if let Some(controls) = &mut self.controls {
let controls_layout = children.next().unwrap();
controls.on_event(
- event,
+ event.clone(),
controls_layout,
cursor_position,
renderer,
@@ -223,6 +230,40 @@ where
)
} else {
event::Status::Ignored
- }
+ };
+
+ let title_status = self.content.on_event(
+ event,
+ title_layout,
+ cursor_position,
+ renderer,
+ clipboard,
+ messages,
+ );
+
+ control_status.merge(title_status)
+ }
+
+ pub(crate) fn overlay(
+ &mut self,
+ layout: Layout<'_>,
+ ) -> Option<overlay::Element<'_, Message, Renderer>> {
+ let mut children = layout.children();
+ let padded = children.next()?;
+
+ let mut children = padded.children();
+ let title_layout = children.next()?;
+
+ let Self {
+ content, controls, ..
+ } = self;
+
+ content.overlay(title_layout).or_else(move || {
+ controls.as_mut().and_then(|controls| {
+ let controls_layout = children.next()?;
+
+ controls.overlay(controls_layout)
+ })
+ })
}
}
diff --git a/native/src/widget/pick_list.rs b/native/src/widget/pick_list.rs
index ddd0d0a2..f4b60fc4 100644
--- a/native/src/widget/pick_list.rs
+++ b/native/src/widget/pick_list.rs
@@ -8,7 +8,8 @@ use crate::scrollable;
use crate::text;
use crate::touch;
use crate::{
- Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget,
+ Clipboard, Element, Hasher, Layout, Length, Padding, Point, Rectangle,
+ Size, Widget,
};
use std::borrow::Cow;
@@ -24,9 +25,10 @@ where
last_selection: &'a mut Option<T>,
on_selected: Box<dyn Fn(T) -> Message>,
options: Cow<'a, [T]>,
+ placeholder: Option<String>,
selected: Option<T>,
width: Length,
- padding: u16,
+ padding: Padding,
text_size: Option<u16>,
font: Renderer::Font,
style: <Renderer as self::Renderer>::Style,
@@ -81,6 +83,7 @@ where
last_selection,
on_selected: Box::new(on_selected),
options: options.into(),
+ placeholder: None,
selected,
width: Length::Shrink,
text_size: None,
@@ -90,15 +93,21 @@ where
}
}
+ /// Sets the placeholder of the [`PickList`].
+ pub fn placeholder(mut self, placeholder: impl Into<String>) -> Self {
+ self.placeholder = Some(placeholder.into());
+ self
+ }
+
/// Sets the width of the [`PickList`].
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
- /// Sets the padding of the [`PickList`].
- pub fn padding(mut self, padding: u16) -> Self {
- self.padding = padding;
+ /// Sets the [`Padding`] of the [`PickList`].
+ pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
+ self.padding = padding.into();
self
}
@@ -150,27 +159,37 @@ where
let limits = limits
.width(self.width)
.height(Length::Shrink)
- .pad(f32::from(self.padding));
+ .pad(self.padding);
let text_size = self.text_size.unwrap_or(renderer.default_size());
+ let font = self.font;
let max_width = match self.width {
Length::Shrink => {
+ let measure = |label: &str| -> u32 {
+ let (width, _) = renderer.measure(
+ label,
+ text_size,
+ font,
+ Size::new(f32::INFINITY, f32::INFINITY),
+ );
+
+ width.round() as u32
+ };
+
let labels = self.options.iter().map(ToString::to_string);
- labels
- .map(|label| {
- let (width, _) = renderer.measure(
- &label,
- text_size,
- Renderer::Font::default(),
- Size::new(f32::INFINITY, f32::INFINITY),
- );
-
- width.round() as u32
- })
- .max()
- .unwrap_or(100)
+ let labels_width =
+ labels.map(|label| measure(&label)).max().unwrap_or(100);
+
+ let placeholder_width = self
+ .placeholder
+ .as_ref()
+ .map(String::as_str)
+ .map(measure)
+ .unwrap_or(100);
+
+ labels_width.max(placeholder_width)
}
_ => 0,
};
@@ -179,11 +198,11 @@ where
let intrinsic = Size::new(
max_width as f32
+ f32::from(text_size)
- + f32::from(self.padding),
+ + f32::from(self.padding.left),
f32::from(text_size),
);
- limits.resolve(intrinsic).pad(f32::from(self.padding))
+ limits.resolve(intrinsic).pad(self.padding)
};
layout::Node::new(size)
@@ -194,6 +213,8 @@ where
match self.width {
Length::Shrink => {
+ self.placeholder.hash(state);
+
self.options
.iter()
.map(ToString::to_string)
@@ -301,6 +322,7 @@ where
layout.bounds(),
cursor_position,
self.selected.as_ref().map(ToString::to_string),
+ self.placeholder.as_ref().map(String::as_str),
self.padding,
self.text_size.unwrap_or(renderer.default_size()),
self.font,
@@ -345,7 +367,7 @@ where
/// [renderer]: crate::renderer
pub trait Renderer: text::Renderer + menu::Renderer {
/// The default padding of a [`PickList`].
- const DEFAULT_PADDING: u16;
+ const DEFAULT_PADDING: Padding;
/// The [`PickList`] style supported by this renderer.
type Style: Default;
@@ -361,7 +383,8 @@ pub trait Renderer: text::Renderer + menu::Renderer {
bounds: Rectangle,
cursor_position: Point,
selected: Option<String>,
- padding: u16,
+ placeholder: Option<&str>,
+ padding: Padding,
text_size: u16,
font: Self::Font,
style: &<Self as Renderer>::Style,
diff --git a/native/src/widget/radio.rs b/native/src/widget/radio.rs
index 9482a9b1..dee82d1f 100644
--- a/native/src/widget/radio.rs
+++ b/native/src/widget/radio.rs
@@ -1,17 +1,17 @@
//! Create choices using radio buttons.
+use std::hash::Hash;
+
use crate::event::{self, Event};
-use crate::layout;
use crate::mouse;
use crate::row;
use crate::text;
use crate::touch;
+use crate::{layout, Color};
use crate::{
Align, Clipboard, Element, Hasher, HorizontalAlignment, Layout, Length,
Point, Rectangle, Row, Text, VerticalAlignment, Widget,
};
-use std::hash::Hash;
-
/// A circular button representing a choice.
///
/// # Example
@@ -47,6 +47,8 @@ pub struct Radio<Message, Renderer: self::Renderer + text::Renderer> {
size: u16,
spacing: u16,
text_size: Option<u16>,
+ text_color: Option<Color>,
+ font: Renderer::Font,
style: Renderer::Style,
}
@@ -81,6 +83,8 @@ where
size: <Renderer as self::Renderer>::DEFAULT_SIZE,
spacing: Renderer::DEFAULT_SPACING, //15
text_size: None,
+ text_color: None,
+ font: Default::default(),
style: Renderer::Style::default(),
}
}
@@ -109,6 +113,18 @@ where
self
}
+ /// Sets the text color of the [`Radio`] button.
+ pub fn text_color(mut self, color: Color) -> Self {
+ self.text_color = Some(color);
+ self
+ }
+
+ /// Sets the text font of the [`Radio`] button.
+ pub fn font(mut self, font: Renderer::Font) -> Self {
+ self.font = font;
+ self
+ }
+
/// Sets the style of the [`Radio`] button.
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
self.style = style.into();
@@ -196,8 +212,8 @@ where
label_layout.bounds(),
&self.label,
self.text_size.unwrap_or(renderer.default_size()),
- Default::default(),
- None,
+ self.font,
+ self.text_color,
HorizontalAlignment::Left,
VerticalAlignment::Center,
);
diff --git a/native/src/widget/row.rs b/native/src/widget/row.rs
index 5634ab12..9ebc9145 100644
--- a/native/src/widget/row.rs
+++ b/native/src/widget/row.rs
@@ -3,7 +3,8 @@ use crate::event::{self, Event};
use crate::layout;
use crate::overlay;
use crate::{
- Align, Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Widget,
+ Align, Clipboard, Element, Hasher, Layout, Length, Padding, Point,
+ Rectangle, Widget,
};
use std::hash::Hash;
@@ -13,7 +14,7 @@ use std::u32;
#[allow(missing_debug_implementations)]
pub struct Row<'a, Message, Renderer> {
spacing: u16,
- padding: u16,
+ padding: Padding,
width: Length,
height: Length,
max_width: u32,
@@ -34,7 +35,7 @@ impl<'a, Message, Renderer> Row<'a, Message, Renderer> {
) -> Self {
Row {
spacing: 0,
- padding: 0,
+ padding: Padding::ZERO,
width: Length::Shrink,
height: Length::Shrink,
max_width: u32::MAX,
@@ -54,9 +55,9 @@ impl<'a, Message, Renderer> Row<'a, Message, Renderer> {
self
}
- /// Sets the padding of the [`Row`].
- pub fn padding(mut self, units: u16) -> Self {
- self.padding = units;
+ /// Sets the [`Padding`] of the [`Row`].
+ pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
+ self.padding = padding.into();
self
}
@@ -128,7 +129,7 @@ where
layout::flex::Axis::Horizontal,
renderer,
&limits,
- self.padding as f32,
+ self.padding,
self.spacing as f32,
self.align_items,
&self.children,
diff --git a/native/src/widget/scrollable.rs b/native/src/widget/scrollable.rs
index 70ebebe2..68da2e67 100644
--- a/native/src/widget/scrollable.rs
+++ b/native/src/widget/scrollable.rs
@@ -6,7 +6,7 @@ use crate::mouse;
use crate::overlay;
use crate::touch;
use crate::{
- Align, Clipboard, Column, Element, Hasher, Layout, Length, Point,
+ Align, Clipboard, Column, Element, Hasher, Layout, Length, Padding, Point,
Rectangle, Size, Vector, Widget,
};
@@ -23,6 +23,7 @@ pub struct Scrollable<'a, Message, Renderer: self::Renderer> {
scrollbar_margin: u16,
scroller_width: u16,
content: Column<'a, Message, Renderer>,
+ on_scroll: Option<Box<dyn Fn(f32) -> Message>>,
style: Renderer::Style,
}
@@ -37,6 +38,7 @@ impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> {
scrollbar_margin: 0,
scroller_width: 10,
content: Column::new(),
+ on_scroll: None,
style: Renderer::Style::default(),
}
}
@@ -51,9 +53,9 @@ impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> {
self
}
- /// Sets the padding of the [`Scrollable`].
- pub fn padding(mut self, units: u16) -> Self {
- self.content = self.content.padding(units);
+ /// Sets the [`Padding`] of the [`Scrollable`].
+ pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
+ self.content = self.content.padding(padding);
self
}
@@ -101,12 +103,22 @@ impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> {
}
/// Sets the scroller width of the [`Scrollable`] .
- /// Silently enforces a minimum value of 1.
+ ///
+ /// It silently enforces a minimum value of 1.
pub fn scroller_width(mut self, scroller_width: u16) -> Self {
self.scroller_width = scroller_width.max(1);
self
}
+ /// Sets a function to call when the [`Scrollable`] is scrolled.
+ ///
+ /// The function takes the new relative offset of the [`Scrollable`]
+ /// (e.g. `0` means top, while `1` means bottom).
+ pub fn on_scroll(mut self, f: impl Fn(f32) -> Message + 'static) -> Self {
+ self.on_scroll = Some(Box::new(f));
+ self
+ }
+
/// Sets the style of the [`Scrollable`] .
pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
self.style = style.into();
@@ -121,6 +133,24 @@ impl<'a, Message, Renderer: self::Renderer> Scrollable<'a, Message, Renderer> {
self.content = self.content.push(child);
self
}
+
+ fn notify_on_scroll(
+ &self,
+ bounds: Rectangle,
+ content_bounds: Rectangle,
+ messages: &mut Vec<Message>,
+ ) {
+ if content_bounds.height <= bounds.height {
+ return;
+ }
+
+ if let Some(on_scroll) = &self.on_scroll {
+ messages.push(on_scroll(
+ self.state.offset.absolute(bounds, content_bounds)
+ / (content_bounds.height - bounds.height),
+ ));
+ }
+ }
}
impl<'a, Message, Renderer> Widget<Message, Renderer>
@@ -228,6 +258,8 @@ where
}
}
+ self.notify_on_scroll(bounds, content_bounds, messages);
+
return event::Status::Captured;
}
Event::Touch(event) => {
@@ -251,6 +283,12 @@ where
self.state.scroll_box_touched_at =
Some(cursor_position);
+
+ self.notify_on_scroll(
+ bounds,
+ content_bounds,
+ messages,
+ );
}
}
touch::Event::FingerLifted { .. }
@@ -290,6 +328,8 @@ where
content_bounds,
);
+ self.notify_on_scroll(bounds, content_bounds, messages);
+
return event::Status::Captured;
}
}
@@ -317,6 +357,12 @@ where
self.state.scroller_grabbed_at =
Some(scroller_grabbed_at);
+ self.notify_on_scroll(
+ bounds,
+ content_bounds,
+ messages,
+ );
+
return event::Status::Captured;
}
}
@@ -418,11 +464,44 @@ where
}
/// The local state of a [`Scrollable`].
-#[derive(Debug, Clone, Copy, Default)]
+#[derive(Debug, Clone, Copy)]
pub struct State {
scroller_grabbed_at: Option<f32>,
scroll_box_touched_at: Option<Point>,
- offset: f32,
+ offset: Offset,
+}
+
+impl Default for State {
+ fn default() -> Self {
+ Self {
+ scroller_grabbed_at: None,
+ scroll_box_touched_at: None,
+ offset: Offset::Absolute(0.0),
+ }
+ }
+}
+
+/// The local state of a [`Scrollable`].
+#[derive(Debug, Clone, Copy)]
+enum Offset {
+ Absolute(f32),
+ Relative(f32),
+}
+
+impl Offset {
+ fn absolute(self, bounds: Rectangle, content_bounds: Rectangle) -> f32 {
+ match self {
+ Self::Absolute(absolute) => {
+ let hidden_content =
+ (content_bounds.height - bounds.height).max(0.0);
+
+ absolute.min(hidden_content)
+ }
+ Self::Relative(percentage) => {
+ ((content_bounds.height - bounds.height) * percentage).max(0.0)
+ }
+ }
+ }
}
impl State {
@@ -443,13 +522,14 @@ impl State {
return;
}
- self.offset = (self.offset - delta_y)
- .max(0.0)
- .min((content_bounds.height - bounds.height) as f32);
+ self.offset = Offset::Absolute(
+ (self.offset.absolute(bounds, content_bounds) - delta_y)
+ .max(0.0)
+ .min((content_bounds.height - bounds.height) as f32),
+ );
}
- /// Moves the scroll position to a relative amount, given the bounds of
- /// the [`Scrollable`] and its contents.
+ /// Scrolls the [`Scrollable`] to a relative amount.
///
/// `0` represents scrollbar at the top, while `1` represents scrollbar at
/// the bottom.
@@ -459,17 +539,29 @@ impl State {
bounds: Rectangle,
content_bounds: Rectangle,
) {
+ self.snap_to(percentage);
+ self.unsnap(bounds, content_bounds);
+ }
+
+ /// Snaps the scroll position to a relative amount.
+ ///
+ /// `0` represents scrollbar at the top, while `1` represents scrollbar at
+ /// the bottom.
+ pub fn snap_to(&mut self, percentage: f32) {
+ self.offset = Offset::Relative(percentage.max(0.0).min(1.0));
+ }
+
+ /// Unsnaps the current scroll position, if snapped, given the bounds of the
+ /// [`Scrollable`] and its contents.
+ pub fn unsnap(&mut self, bounds: Rectangle, content_bounds: Rectangle) {
self.offset =
- ((content_bounds.height - bounds.height) * percentage).max(0.0);
+ Offset::Absolute(self.offset.absolute(bounds, content_bounds));
}
/// Returns the current scrolling offset of the [`State`], given the bounds
/// of the [`Scrollable`] and its contents.
pub fn offset(&self, bounds: Rectangle, content_bounds: Rectangle) -> u32 {
- let hidden_content =
- (content_bounds.height - bounds.height).max(0.0).round() as u32;
-
- self.offset.min(hidden_content as f32) as u32
+ self.offset.absolute(bounds, content_bounds) as u32
}
/// Returns whether the scroller is currently grabbed or not.
diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs
index de6032b7..cec1e485 100644
--- a/native/src/widget/text_input.rs
+++ b/native/src/widget/text_input.rs
@@ -18,7 +18,8 @@ use crate::mouse::{self, click};
use crate::text;
use crate::touch;
use crate::{
- Clipboard, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget,
+ Clipboard, Element, Hasher, Layout, Length, Padding, Point, Rectangle,
+ Size, Widget,
};
use std::u32;
@@ -56,7 +57,7 @@ pub struct TextInput<'a, Message, Renderer: self::Renderer> {
font: Renderer::Font,
width: Length,
max_width: u32,
- padding: u16,
+ padding: Padding,
size: Option<u16>,
on_change: Box<dyn Fn(String) -> Message>,
on_submit: Option<Message>,
@@ -92,7 +93,7 @@ where
font: Default::default(),
width: Length::Fill,
max_width: u32::MAX,
- padding: 0,
+ padding: Padding::ZERO,
size: None,
on_change: Box::new(on_change),
on_submit: None,
@@ -126,9 +127,9 @@ where
self
}
- /// Sets the padding of the [`TextInput`].
- pub fn padding(mut self, units: u16) -> Self {
- self.padding = units;
+ /// Sets the [`Padding`] of the [`TextInput`].
+ pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
+ self.padding = padding.into();
self
}
@@ -223,19 +224,21 @@ where
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
- let padding = self.padding as f32;
let text_size = self.size.unwrap_or(renderer.default_size());
let limits = limits
- .pad(padding)
+ .pad(self.padding)
.width(self.width)
.max_width(self.max_width)
.height(Length::Units(text_size));
let mut text = layout::Node::new(limits.resolve(Size::ZERO));
- text.move_to(Point::new(padding, padding));
+ text.move_to(Point::new(
+ self.padding.left.into(),
+ self.padding.top.into(),
+ ));
- layout::Node::with_children(text.size().pad(padding), vec![text])
+ layout::Node::with_children(text.size().pad(self.padding), vec![text])
}
fn on_event(
@@ -359,7 +362,7 @@ where
Event::Keyboard(keyboard::Event::CharacterReceived(c))
if self.state.is_focused
&& self.state.is_pasting.is_none()
- && !self.state.keyboard_modifiers.is_command_pressed()
+ && !self.state.keyboard_modifiers.command()
&& !c.is_control() =>
{
let mut editor =
@@ -447,7 +450,7 @@ where
if platform::is_jump_modifier_pressed(modifiers)
&& !self.is_secure
{
- if modifiers.shift {
+ if modifiers.shift() {
self.state
.cursor
.select_left_by_words(&self.value);
@@ -456,7 +459,7 @@ where
.cursor
.move_left_by_words(&self.value);
}
- } else if modifiers.shift {
+ } else if modifiers.shift() {
self.state.cursor.select_left(&self.value)
} else {
self.state.cursor.move_left(&self.value);
@@ -466,7 +469,7 @@ where
if platform::is_jump_modifier_pressed(modifiers)
&& !self.is_secure
{
- if modifiers.shift {
+ if modifiers.shift() {
self.state
.cursor
.select_right_by_words(&self.value);
@@ -475,14 +478,14 @@ where
.cursor
.move_right_by_words(&self.value);
}
- } else if modifiers.shift {
+ } else if modifiers.shift() {
self.state.cursor.select_right(&self.value)
} else {
self.state.cursor.move_right(&self.value);
}
}
keyboard::KeyCode::Home => {
- if modifiers.shift {
+ if modifiers.shift() {
self.state.cursor.select_range(
self.state.cursor.start(&self.value),
0,
@@ -492,7 +495,7 @@ where
}
}
keyboard::KeyCode::End => {
- if modifiers.shift {
+ if modifiers.shift() {
self.state.cursor.select_range(
self.state.cursor.start(&self.value),
self.value.len(),
@@ -502,10 +505,7 @@ where
}
}
keyboard::KeyCode::C
- if self
- .state
- .keyboard_modifiers
- .is_command_pressed() =>
+ if self.state.keyboard_modifiers.command() =>
{
match self.state.cursor.selection(&self.value) {
Some((start, end)) => {
@@ -517,10 +517,7 @@ where
}
}
keyboard::KeyCode::X
- if self
- .state
- .keyboard_modifiers
- .is_command_pressed() =>
+ if self.state.keyboard_modifiers.command() =>
{
match self.state.cursor.selection(&self.value) {
Some((start, end)) => {
@@ -542,7 +539,7 @@ where
messages.push(message);
}
keyboard::KeyCode::V => {
- if self.state.keyboard_modifiers.is_command_pressed() {
+ if self.state.keyboard_modifiers.command() {
let content = match self.state.is_pasting.take() {
Some(content) => content,
None => {
@@ -573,10 +570,7 @@ where
}
}
keyboard::KeyCode::A
- if self
- .state
- .keyboard_modifiers
- .is_command_pressed() =>
+ if self.state.keyboard_modifiers.command() =>
{
self.state.cursor.select_all(&self.value);
}
@@ -792,6 +786,11 @@ impl State {
pub fn move_cursor_to(&mut self, position: usize) {
self.cursor.move_to(position);
}
+
+ /// Selects all the content of the [`TextInput`].
+ pub fn select_all(&mut self) {
+ self.cursor.select_range(0, usize::MAX);
+ }
}
// TODO: Reduce allocations
@@ -855,9 +854,9 @@ mod platform {
pub fn is_jump_modifier_pressed(modifiers: keyboard::Modifiers) -> bool {
if cfg!(target_os = "macos") {
- modifiers.alt
+ modifiers.alt()
} else {
- modifiers.control
+ modifiers.control()
}
}
}
diff --git a/native/src/widget/text_input/cursor.rs b/native/src/widget/text_input/cursor.rs
index 1e7aee83..4f3b159b 100644
--- a/native/src/widget/text_input/cursor.rs
+++ b/native/src/widget/text_input/cursor.rs
@@ -113,7 +113,7 @@ impl Cursor {
State::Selection { start, end } if end > 0 => {
self.select_range(start, end - 1)
}
- _ => (),
+ _ => {}
}
}
@@ -125,7 +125,7 @@ impl Cursor {
State::Selection { start, end } if end < value.len() => {
self.select_range(start, end + 1)
}
- _ => (),
+ _ => {}
}
}
diff --git a/native/src/widget/text_input/editor.rs b/native/src/widget/text_input/editor.rs
index 20e42567..0b50a382 100644
--- a/native/src/widget/text_input/editor.rs
+++ b/native/src/widget/text_input/editor.rs
@@ -20,7 +20,7 @@ impl<'a> Editor<'a> {
self.cursor.move_left(self.value);
self.value.remove_many(left, right);
}
- _ => (),
+ _ => {}
}
self.value.insert(self.cursor.end(self.value), character);
@@ -35,7 +35,7 @@ impl<'a> Editor<'a> {
self.cursor.move_left(self.value);
self.value.remove_many(left, right);
}
- _ => (),
+ _ => {}
}
self.value.insert_many(self.cursor.end(self.value), content);
diff --git a/native/src/widget/toggler.rs b/native/src/widget/toggler.rs
new file mode 100644
index 00000000..4035276c
--- /dev/null
+++ b/native/src/widget/toggler.rs
@@ -0,0 +1,277 @@
+//! Show toggle controls using togglers.
+use std::hash::Hash;
+
+use crate::{
+ event, layout, mouse, row, text, Align, Clipboard, Element, Event, Hasher,
+ HorizontalAlignment, Layout, Length, Point, Rectangle, Row, Text,
+ VerticalAlignment, Widget,
+};
+
+/// A toggler widget
+///
+/// # Example
+///
+/// ```
+/// # type Toggler<Message> = iced_native::Toggler<Message, iced_native::renderer::Null>;
+/// #
+/// pub enum Message {
+/// TogglerToggled(bool),
+/// }
+///
+/// let is_active = true;
+///
+/// Toggler::new(is_active, String::from("Toggle me!"), |b| Message::TogglerToggled(b));
+/// ```
+#[allow(missing_debug_implementations)]
+pub struct Toggler<Message, Renderer: self::Renderer + text::Renderer> {
+ is_active: bool,
+ on_toggle: Box<dyn Fn(bool) -> Message>,
+ label: Option<String>,
+ width: Length,
+ size: u16,
+ text_size: Option<u16>,
+ text_alignment: HorizontalAlignment,
+ spacing: u16,
+ font: Renderer::Font,
+ style: Renderer::Style,
+}
+
+impl<Message, Renderer: self::Renderer + text::Renderer>
+ Toggler<Message, Renderer>
+{
+ /// Creates a new [`Toggler`].
+ ///
+ /// It expects:
+ /// * a boolean describing whether the [`Toggler`] is checked or not
+ /// * An optional label for the [`Toggler`]
+ /// * a function that will be called when the [`Toggler`] is toggled. It
+ /// will receive the new state of the [`Toggler`] and must produce a
+ /// `Message`.
+ pub fn new<F>(
+ is_active: bool,
+ label: impl Into<Option<String>>,
+ f: F,
+ ) -> Self
+ where
+ F: 'static + Fn(bool) -> Message,
+ {
+ Toggler {
+ is_active,
+ on_toggle: Box::new(f),
+ label: label.into(),
+ width: Length::Fill,
+ size: <Renderer as self::Renderer>::DEFAULT_SIZE,
+ text_size: None,
+ text_alignment: HorizontalAlignment::Left,
+ spacing: 0,
+ font: Renderer::Font::default(),
+ style: Renderer::Style::default(),
+ }
+ }
+
+ /// Sets the size of the [`Toggler`].
+ pub fn size(mut self, size: u16) -> Self {
+ self.size = size;
+ self
+ }
+
+ /// Sets the width of the [`Toggler`].
+ pub fn width(mut self, width: Length) -> Self {
+ self.width = width;
+ self
+ }
+
+ /// Sets the text size o the [`Toggler`].
+ pub fn text_size(mut self, text_size: u16) -> Self {
+ self.text_size = Some(text_size);
+ self
+ }
+
+ /// Sets the horizontal alignment of the text of the [`Toggler`]
+ pub fn text_alignment(mut self, alignment: HorizontalAlignment) -> Self {
+ self.text_alignment = alignment;
+ self
+ }
+
+ /// Sets the spacing between the [`Toggler`] and the text.
+ pub fn spacing(mut self, spacing: u16) -> Self {
+ self.spacing = spacing;
+ self
+ }
+
+ /// Sets the [`Font`] of the text of the [`Toggler`]
+ pub fn font(mut self, font: Renderer::Font) -> Self {
+ self.font = font;
+ self
+ }
+
+ /// Sets the style of the [`Toggler`].
+ pub fn style(mut self, style: impl Into<Renderer::Style>) -> Self {
+ self.style = style.into();
+ self
+ }
+}
+
+impl<Message, Renderer> Widget<Message, Renderer> for Toggler<Message, Renderer>
+where
+ Renderer: self::Renderer + text::Renderer + row::Renderer,
+{
+ fn width(&self) -> Length {
+ self.width
+ }
+
+ fn height(&self) -> Length {
+ Length::Shrink
+ }
+
+ fn layout(
+ &self,
+ renderer: &Renderer,
+ limits: &layout::Limits,
+ ) -> layout::Node {
+ let mut row = Row::<(), Renderer>::new()
+ .width(self.width)
+ .spacing(self.spacing)
+ .align_items(Align::Center);
+
+ if let Some(label) = &self.label {
+ row = row.push(
+ Text::new(label)
+ .horizontal_alignment(self.text_alignment)
+ .font(self.font)
+ .width(self.width)
+ .size(self.text_size.unwrap_or(renderer.default_size())),
+ );
+ }
+
+ row = row.push(
+ Row::new()
+ .width(Length::Units(2 * self.size))
+ .height(Length::Units(self.size)),
+ );
+
+ row.layout(renderer, limits)
+ }
+
+ fn on_event(
+ &mut self,
+ event: Event,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ _renderer: &Renderer,
+ _clipboard: &mut dyn Clipboard,
+ messages: &mut Vec<Message>,
+ ) -> event::Status {
+ match event {
+ Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => {
+ let mouse_over = layout.bounds().contains(cursor_position);
+
+ if mouse_over {
+ messages.push((self.on_toggle)(!self.is_active));
+
+ event::Status::Captured
+ } else {
+ event::Status::Ignored
+ }
+ }
+ _ => event::Status::Ignored,
+ }
+ }
+
+ fn draw(
+ &self,
+ renderer: &mut Renderer,
+ defaults: &Renderer::Defaults,
+ layout: Layout<'_>,
+ cursor_position: Point,
+ _viewport: &Rectangle,
+ ) -> Renderer::Output {
+ let bounds = layout.bounds();
+ let mut children = layout.children();
+
+ let label = match &self.label {
+ Some(label) => {
+ let label_layout = children.next().unwrap();
+
+ Some(text::Renderer::draw(
+ renderer,
+ defaults,
+ label_layout.bounds(),
+ &label,
+ self.text_size.unwrap_or(renderer.default_size()),
+ self.font,
+ None,
+ self.text_alignment,
+ VerticalAlignment::Center,
+ ))
+ }
+
+ None => None,
+ };
+
+ let toggler_layout = children.next().unwrap();
+ let toggler_bounds = toggler_layout.bounds();
+
+ let is_mouse_over = bounds.contains(cursor_position);
+
+ self::Renderer::draw(
+ renderer,
+ toggler_bounds,
+ self.is_active,
+ is_mouse_over,
+ label,
+ &self.style,
+ )
+ }
+
+ fn hash_layout(&self, state: &mut Hasher) {
+ struct Marker;
+ std::any::TypeId::of::<Marker>().hash(state);
+
+ self.label.hash(state)
+ }
+}
+
+/// The renderer of a [`Toggler`].
+///
+/// Your [renderer] will need to implement this trait before being
+/// able to use a [`Toggler`] in your user interface.
+///
+/// [renderer]: ../../renderer/index.html
+pub trait Renderer: crate::Renderer {
+ /// The style supported by this renderer.
+ type Style: Default;
+
+ /// The default size of a [`Toggler`].
+ const DEFAULT_SIZE: u16;
+
+ /// Draws a [`Toggler`].
+ ///
+ /// It receives:
+ /// * the bounds of the [`Toggler`]
+ /// * whether the [`Toggler`] is activated or not
+ /// * whether the mouse is over the [`Toggler`] or not
+ /// * the drawn label of the [`Toggler`]
+ /// * the style of the [`Toggler`]
+ fn draw(
+ &mut self,
+ bounds: Rectangle,
+ is_active: bool,
+ is_mouse_over: bool,
+ label: Option<Self::Output>,
+ style: &Self::Style,
+ ) -> Self::Output;
+}
+
+impl<'a, Message, Renderer> From<Toggler<Message, Renderer>>
+ for Element<'a, Message, Renderer>
+where
+ Renderer: 'a + self::Renderer + text::Renderer + row::Renderer,
+ Message: 'a,
+{
+ fn from(
+ toggler: Toggler<Message, Renderer>,
+ ) -> Element<'a, Message, Renderer> {
+ Element::new(toggler)
+ }
+}