diff options
Diffstat (limited to 'native')
-rw-r--r-- | native/src/layout/limits.rs | 23 | ||||
-rw-r--r-- | native/src/widget/checkbox.rs | 4 | ||||
-rw-r--r-- | native/src/widget/helpers.rs | 4 | ||||
-rw-r--r-- | native/src/widget/operation/focusable.rs | 4 | ||||
-rw-r--r-- | native/src/widget/pick_list.rs | 97 | ||||
-rw-r--r-- | native/src/widget/text_input.rs | 12 | ||||
-rw-r--r-- | native/src/widget/toggler.rs | 18 | ||||
-rw-r--r-- | native/src/window.rs | 2 | ||||
-rw-r--r-- | native/src/window/action.rs | 43 | ||||
-rw-r--r-- | native/src/window/user_attention.rs | 21 |
10 files changed, 184 insertions, 44 deletions
diff --git a/native/src/layout/limits.rs b/native/src/layout/limits.rs index 33a452d0..4cbb970d 100644 --- a/native/src/layout/limits.rs +++ b/native/src/layout/limits.rs @@ -1,3 +1,4 @@ +#![allow(clippy::manual_clamp)] use crate::{Length, Padding, Size}; /// A set of size constraints for layouting. @@ -51,7 +52,7 @@ impl Limits { } Length::Units(units) => { let new_width = - (units as f32).clamp(self.min.width, self.max.width); + (units as f32).min(self.max.width).max(self.min.width); self.min.width = new_width; self.max.width = new_width; @@ -73,7 +74,7 @@ impl Limits { } Length::Units(units) => { let new_height = - (units as f32).clamp(self.min.height, self.max.height); + (units as f32).min(self.max.height).max(self.min.height); self.min.height = new_height; self.max.height = new_height; @@ -86,14 +87,16 @@ impl Limits { /// Applies a minimum width constraint to the current [`Limits`]. pub fn min_width(mut self, min_width: u32) -> Limits { - self.min.width = self.min.width.clamp(min_width as f32, self.max.width); + self.min.width = + self.min.width.max(min_width as f32).min(self.max.width); self } /// Applies a maximum width constraint to the current [`Limits`]. pub fn max_width(mut self, max_width: u32) -> Limits { - self.max.width = self.max.width.clamp(self.min.width, max_width as f32); + self.max.width = + self.max.width.min(max_width as f32).max(self.min.width); self } @@ -101,7 +104,7 @@ impl Limits { /// Applies a minimum height constraint to the current [`Limits`]. pub fn min_height(mut self, min_height: u32) -> Limits { self.min.height = - self.min.height.clamp(min_height as f32, self.max.height); + self.min.height.max(min_height as f32).min(self.max.height); self } @@ -109,7 +112,7 @@ impl Limits { /// Applies a maximum height constraint to the current [`Limits`]. pub fn max_height(mut self, max_height: u32) -> Limits { self.max.height = - self.max.height.clamp(self.min.height, max_height as f32); + self.max.height.min(max_height as f32).max(self.min.height); self } @@ -155,10 +158,14 @@ impl Limits { /// intrinsic size of some content. pub fn resolve(&self, intrinsic_size: Size) -> Size { Size::new( - intrinsic_size.width.clamp(self.fill.width, self.max.width), + intrinsic_size + .width + .min(self.max.width) + .max(self.fill.width), intrinsic_size .height - .clamp(self.fill.height, self.max.height), + .min(self.max.height) + .max(self.fill.height), ) } } diff --git a/native/src/widget/checkbox.rs b/native/src/widget/checkbox.rs index bec5c448..b46433c2 100644 --- a/native/src/widget/checkbox.rs +++ b/native/src/widget/checkbox.rs @@ -27,7 +27,7 @@ pub use iced_style::checkbox::{Appearance, StyleSheet}; /// /// let is_checked = true; /// -/// Checkbox::new(is_checked, "Toggle me!", Message::CheckboxToggled); +/// Checkbox::new("Toggle me!", is_checked, Message::CheckboxToggled); /// ``` /// ///  @@ -67,7 +67,7 @@ where /// * a function that will be called when the [`Checkbox`] is toggled. It /// will receive the new state of the [`Checkbox`] and must produce a /// `Message`. - pub fn new<F>(is_checked: bool, label: impl Into<String>, f: F) -> Self + pub fn new<F>(label: impl Into<String>, is_checked: bool, f: F) -> Self where F: 'a + Fn(bool) -> Message, { diff --git a/native/src/widget/helpers.rs b/native/src/widget/helpers.rs index 8cc1ae82..dfd949f6 100644 --- a/native/src/widget/helpers.rs +++ b/native/src/widget/helpers.rs @@ -129,7 +129,7 @@ where Renderer: crate::text::Renderer, Renderer::Theme: widget::checkbox::StyleSheet + widget::text::StyleSheet, { - widget::Checkbox::new(is_checked, label, f) + widget::Checkbox::new(label, is_checked, f) } /// Creates a new [`Radio`]. @@ -162,7 +162,7 @@ where Renderer: crate::text::Renderer, Renderer::Theme: widget::toggler::StyleSheet, { - widget::Toggler::new(is_checked, label, f) + widget::Toggler::new(label, is_checked, f) } /// Creates a new [`TextInput`]. diff --git a/native/src/widget/operation/focusable.rs b/native/src/widget/operation/focusable.rs index 0067006b..312e4894 100644 --- a/native/src/widget/operation/focusable.rs +++ b/native/src/widget/operation/focusable.rs @@ -18,10 +18,10 @@ pub trait Focusable { #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub struct Count { /// The index of the current focused widget, if any. - focused: Option<usize>, + pub focused: Option<usize>, /// The total amount of focusable widgets. - total: usize, + pub total: usize, } /// Produces an [`Operation`] that focuses the widget with the given [`Id`]. diff --git a/native/src/widget/pick_list.rs b/native/src/widget/pick_list.rs index 52cb1ad1..c2853314 100644 --- a/native/src/widget/pick_list.rs +++ b/native/src/widget/pick_list.rs @@ -20,6 +20,60 @@ use std::borrow::Cow; pub use iced_style::pick_list::{Appearance, StyleSheet}; +/// The handle to the right side of the [`PickList`]. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Handle<Renderer> +where + Renderer: text::Renderer, +{ + /// Displays an arrow icon (▼). + /// + /// This is the default. + Arrow { + /// Font size of the content. + size: Option<u16>, + }, + /// A custom handle. + Custom { + /// Font that will be used to display the `text`, + font: Renderer::Font, + /// Text that will be shown. + text: String, + /// Font size of the content. + size: Option<u16>, + }, + /// No handle will be shown. + None, +} + +impl<Renderer> Default for Handle<Renderer> +where + Renderer: text::Renderer, +{ + fn default() -> Self { + Self::Arrow { size: None } + } +} + +impl<Renderer> Handle<Renderer> +where + Renderer: text::Renderer, +{ + fn content(&self) -> Option<(Renderer::Font, String, Option<u16>)> { + match self { + Self::Arrow { size } => Some(( + Renderer::ICON_FONT, + Renderer::ARROW_DOWN_ICON.to_string(), + *size, + )), + Self::Custom { font, text, size } => { + Some((font.clone(), text.clone(), *size)) + } + Self::None => None, + } + } +} + /// A widget for selecting a single value from a list of options. #[allow(missing_debug_implementations)] pub struct PickList<'a, T, Message, Renderer> @@ -36,6 +90,7 @@ where padding: Padding, text_size: Option<u16>, font: Renderer::Font, + handle: Handle<Renderer>, style: <Renderer::Theme as StyleSheet>::Style, } @@ -67,9 +122,10 @@ where placeholder: None, selected, width: Length::Shrink, - text_size: None, padding: Self::DEFAULT_PADDING, + text_size: None, font: Default::default(), + handle: Default::default(), style: Default::default(), } } @@ -104,6 +160,12 @@ where self } + /// Sets the [`Handle`] of the [`PickList`]. + pub fn handle(mut self, handle: Handle<Renderer>) -> Self { + self.handle = handle; + self + } + /// Sets the style of the [`PickList`]. pub fn style( mut self, @@ -214,6 +276,7 @@ where &self.font, self.placeholder.as_deref(), self.selected.as_ref(), + &self.handle, &self.style, ) } @@ -515,6 +578,7 @@ pub fn draw<T, Renderer>( font: &Renderer::Font, placeholder: Option<&str>, selected: Option<&T>, + handle: &Handle<Renderer>, style: &<Renderer::Theme as StyleSheet>::Style, ) where Renderer: text::Renderer, @@ -541,19 +605,24 @@ pub fn draw<T, Renderer>( style.background, ); - renderer.fill_text(Text { - content: &Renderer::ARROW_DOWN_ICON.to_string(), - font: Renderer::ICON_FONT, - size: bounds.height * style.icon_size, - bounds: Rectangle { - x: bounds.x + bounds.width - f32::from(padding.horizontal()), - y: bounds.center_y(), - ..bounds - }, - color: style.text_color, - horizontal_alignment: alignment::Horizontal::Right, - vertical_alignment: alignment::Vertical::Center, - }); + if let Some((font, text, size)) = handle.content() { + let size = f32::from(size.unwrap_or_else(|| renderer.default_size())); + + renderer.fill_text(Text { + content: &text, + size, + font, + color: style.handle_color, + bounds: Rectangle { + x: bounds.x + bounds.width - f32::from(padding.horizontal()), + y: bounds.center_y() - size / 2.0, + height: size, + ..bounds + }, + horizontal_alignment: alignment::Horizontal::Right, + vertical_alignment: alignment::Vertical::Top, + }); + } let label = selected.map(ToString::to_string); diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs index 05b47ff9..8b4514e3 100644 --- a/native/src/widget/text_input.rs +++ b/native/src/widget/text_input.rs @@ -454,9 +454,17 @@ where ) } else { None - }; + } + .unwrap_or(0); - state.cursor.move_to(position.unwrap_or(0)); + if state.keyboard_modifiers.shift() { + state.cursor.select_range( + state.cursor.start(value), + position, + ); + } else { + state.cursor.move_to(position); + } state.is_dragging = true; } click::Kind::Double => { diff --git a/native/src/widget/toggler.rs b/native/src/widget/toggler.rs index 1ae65ba6..f0a944a3 100644 --- a/native/src/widget/toggler.rs +++ b/native/src/widget/toggler.rs @@ -24,9 +24,9 @@ pub use iced_style::toggler::{Appearance, StyleSheet}; /// TogglerToggled(bool), /// } /// -/// let is_active = true; +/// let is_toggled = true; /// -/// Toggler::new(is_active, String::from("Toggle me!"), |b| Message::TogglerToggled(b)); +/// Toggler::new(String::from("Toggle me!"), is_toggled, |b| Message::TogglerToggled(b)); /// ``` #[allow(missing_debug_implementations)] pub struct Toggler<'a, Message, Renderer> @@ -34,7 +34,7 @@ where Renderer: text::Renderer, Renderer::Theme: StyleSheet, { - is_active: bool, + is_toggled: bool, on_toggle: Box<dyn Fn(bool) -> Message + 'a>, label: Option<String>, width: Length, @@ -63,15 +63,15 @@ where /// 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>>, + is_toggled: bool, f: F, ) -> Self where F: 'a + Fn(bool) -> Message, { Toggler { - is_active, + is_toggled, on_toggle: Box::new(f), label: label.into(), width: Length::Fill, @@ -193,7 +193,7 @@ where let mouse_over = layout.bounds().contains(cursor_position); if mouse_over { - shell.publish((self.on_toggle)(!self.is_active)); + shell.publish((self.on_toggle)(!self.is_toggled)); event::Status::Captured } else { @@ -260,9 +260,9 @@ where let is_mouse_over = bounds.contains(cursor_position); let style = if is_mouse_over { - theme.hovered(&self.style, self.is_active) + theme.hovered(&self.style, self.is_toggled) } else { - theme.active(&self.style, self.is_active) + theme.active(&self.style, self.is_toggled) }; let border_radius = bounds.height / BORDER_RADIUS_RATIO; @@ -289,7 +289,7 @@ where let toggler_foreground_bounds = Rectangle { x: bounds.x - + if self.is_active { + + if self.is_toggled { bounds.width - 2.0 * space - (bounds.height - (4.0 * space)) } else { 2.0 * space diff --git a/native/src/window.rs b/native/src/window.rs index f910b8f2..1b97e655 100644 --- a/native/src/window.rs +++ b/native/src/window.rs @@ -2,7 +2,9 @@ mod action; mod event; mod mode; +mod user_attention; pub use action::Action; pub use event::Event; pub use mode::Mode; +pub use user_attention::UserAttention; diff --git a/native/src/window/action.rs b/native/src/window/action.rs index da307e97..37fcc273 100644 --- a/native/src/window/action.rs +++ b/native/src/window/action.rs @@ -1,4 +1,4 @@ -use crate::window::Mode; +use crate::window::{Mode, UserAttention}; use iced_futures::MaybeSend; use std::fmt; @@ -35,6 +35,8 @@ pub enum Action<T> { }, /// Set the [`Mode`] of the window. SetMode(Mode), + /// Fetch the current [`Mode`] of the window. + FetchMode(Box<dyn FnOnce(Mode) -> T + 'static>), /// Sets the window to maximized or back ToggleMaximize, /// Toggles whether window has decorations @@ -42,8 +44,31 @@ pub enum Action<T> { /// - **X11:** Not implemented. /// - **Web:** Unsupported. ToggleDecorations, - /// Fetch the current [`Mode`] of the window. - FetchMode(Box<dyn FnOnce(Mode) -> T + 'static>), + /// Requests user attention to the window, this has no effect if the application + /// is already focused. How requesting for user attention manifests is platform dependent, + /// see [`UserAttentionType`] for details. + /// + /// Providing `None` will unset the request for user attention. Unsetting the request for + /// user attention might not be done automatically by the WM when the window receives input. + /// + /// ## Platform-specific + /// + /// - **iOS / Android / Web:** Unsupported. + /// - **macOS:** `None` has no effect. + /// - **X11:** Requests for user attention must be manually cleared. + /// - **Wayland:** Requires `xdg_activation_v1` protocol, `None` has no effect. + RequestUserAttention(Option<UserAttention>), + /// Brings the window to the front and sets input focus. Has no effect if the window is + /// already in focus, minimized, or not visible. + /// + /// This method steals input focus from other applications. Do not use this method unless + /// you are certain that's what the user wants. Focus stealing can cause an extremely disruptive + /// user experience. + /// + /// ## Platform-specific + /// + /// - **Web / Wayland:** Unsupported. + GainFocus, } impl<T> Action<T> { @@ -63,9 +88,13 @@ impl<T> Action<T> { Self::Minimize(bool) => Action::Minimize(bool), Self::Move { x, y } => Action::Move { x, y }, Self::SetMode(mode) => Action::SetMode(mode), + Self::FetchMode(o) => Action::FetchMode(Box::new(move |s| f(o(s)))), Self::ToggleMaximize => Action::ToggleMaximize, Self::ToggleDecorations => Action::ToggleDecorations, - Self::FetchMode(o) => Action::FetchMode(Box::new(move |s| f(o(s)))), + Self::RequestUserAttention(attention_type) => { + Action::RequestUserAttention(attention_type) + } + Self::GainFocus => Action::GainFocus, } } } @@ -86,9 +115,13 @@ impl<T> fmt::Debug for Action<T> { write!(f, "Action::Move {{ x: {}, y: {} }}", x, y) } Self::SetMode(mode) => write!(f, "Action::SetMode({:?})", mode), + Self::FetchMode(_) => write!(f, "Action::FetchMode"), Self::ToggleMaximize => write!(f, "Action::ToggleMaximize"), Self::ToggleDecorations => write!(f, "Action::ToggleDecorations"), - Self::FetchMode(_) => write!(f, "Action::FetchMode"), + Self::RequestUserAttention(_) => { + write!(f, "Action::RequestUserAttention") + } + Self::GainFocus => write!(f, "Action::GainFocus"), } } } diff --git a/native/src/window/user_attention.rs b/native/src/window/user_attention.rs new file mode 100644 index 00000000..b03dfeef --- /dev/null +++ b/native/src/window/user_attention.rs @@ -0,0 +1,21 @@ +/// The type of user attention to request. +/// +/// ## Platform-specific +/// +/// - **X11:** Sets the WM's `XUrgencyHint`. No distinction between [`Critical`] and [`Informational`]. +/// +/// [`Critical`]: Self::Critical +/// [`Informational`]: Self::Informational +#[derive(Debug, Clone, Copy)] +pub enum UserAttention { + /// ## Platform-specific + /// + /// - **macOS:** Bounces the dock icon until the application is in focus. + /// - **Windows:** Flashes both the window and the taskbar button until the application is in focus. + Critical, + /// ## Platform-specific + /// + /// - **macOS:** Bounces the dock icon once. + /// - **Windows:** Flashes the taskbar button until the application is in focus. + Informational, +} |