diff --git a/core/src/length.rs b/core/src/length.rs index 4c139895..5f24169f 100644 --- a/core/src/length.rs +++ b/core/src/length.rs @@ -48,12 +48,21 @@ impl Length { /// Specifically: /// - [`Length::Shrink`] if [`Length::Shrink`] or [`Length::Fixed`]. /// - [`Length::Fill`] otherwise. - pub fn fluid(&self) -> Length { + pub fn fluid(&self) -> Self { match self { Length::Fill | Length::FillPortion(_) => Length::Fill, Length::Shrink | Length::Fixed(_) => Length::Shrink, } } + + /// Adapts the [`Length`] so it can contain the other [`Length`] and + /// match its fluidity. + pub fn enclose(self, other: Length) -> Self { + match (self, other) { + (Length::Shrink, Length::Fill | Length::FillPortion(_)) => other, + _ => self, + } + } } impl From for Length { diff --git a/widget/src/column.rs b/widget/src/column.rs index e59a0809..d37ef695 100644 --- a/widget/src/column.rs +++ b/widget/src/column.rs @@ -115,15 +115,10 @@ where child: impl Into>, ) -> Self { let child = child.into(); - let size = child.as_widget().size_hint(); + let child_size = child.as_widget().size_hint(); - if size.width.is_fill() { - self.width = Length::Fill; - } - - if size.height.is_fill() { - self.height = Length::Fill; - } + self.width = self.width.enclose(child_size.width); + self.height = self.height.enclose(child_size.height); self.children.push(child); self diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs index 71b0579d..ed385ea5 100644 --- a/widget/src/helpers.rs +++ b/widget/src/helpers.rs @@ -15,6 +15,7 @@ use crate::rule::{self, Rule}; use crate::runtime::Command; use crate::scrollable::{self, Scrollable}; use crate::slider::{self, Slider}; +use crate::style::application; use crate::text::{self, Text}; use crate::text_editor::{self, TextEditor}; use crate::text_input::{self, TextInput}; @@ -445,6 +446,7 @@ pub fn themer<'a, Message, Theme, Renderer>( ) -> Themer<'a, Message, Theme, Renderer> where Renderer: core::Renderer, + Theme: application::StyleSheet, { Themer::new(theme, content) } diff --git a/widget/src/keyed/column.rs b/widget/src/keyed/column.rs index ce74e701..8a8d5fe7 100644 --- a/widget/src/keyed/column.rs +++ b/widget/src/keyed/column.rs @@ -110,15 +110,10 @@ where child: impl Into>, ) -> Self { let child = child.into(); - let size = child.as_widget().size_hint(); + let child_size = child.as_widget().size_hint(); - if size.width.is_fill() { - self.width = Length::Fill; - } - - if size.height.is_fill() { - self.height = Length::Fill; - } + self.width = self.width.enclose(child_size.width); + self.height = self.height.enclose(child_size.height); self.keys.push(key); self.children.push(child); diff --git a/widget/src/pane_grid.rs b/widget/src/pane_grid.rs index 37562504..478a7024 100644 --- a/widget/src/pane_grid.rs +++ b/widget/src/pane_grid.rs @@ -46,6 +46,9 @@ use crate::core::{ Vector, Widget, }; +const DRAG_DEADBAND_DISTANCE: f32 = 10.0; +const THICKNESS_RATIO: f32 = 25.0; + /// A collection of panes distributed using either vertical or horizontal splits /// to completely fill the space available. /// @@ -532,8 +535,6 @@ pub fn update<'a, Message, T: Draggable>( on_drag: &Option Message + 'a>>, on_resize: &Option<(f32, Box Message + 'a>)>, ) -> event::Status { - const DRAG_DEADBAND_DISTANCE: f32 = 10.0; - let mut event_status = event::Status::Ignored; match event { @@ -575,6 +576,7 @@ pub fn update<'a, Message, T: Draggable>( shell, contents, on_click, + on_drag, ); } } @@ -586,6 +588,7 @@ pub fn update<'a, Message, T: Draggable>( shell, contents, on_click, + on_drag, ); } } @@ -594,38 +597,44 @@ pub fn update<'a, Message, T: Draggable>( Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) | Event::Touch(touch::Event::FingerLifted { .. }) | Event::Touch(touch::Event::FingerLost { .. }) => { - if let Some((pane, _)) = action.picked_pane() { + if let Some((pane, origin)) = action.picked_pane() { if let Some(on_drag) = on_drag { if let Some(cursor_position) = cursor.position() { - let event = if let Some(edge) = - in_edge(layout, cursor_position) + if cursor_position.distance(origin) + > DRAG_DEADBAND_DISTANCE { - DragEvent::Dropped { - pane, - target: Target::Edge(edge), - } - } else { - let dropped_region = contents - .zip(layout.children()) - .find_map(|(target, layout)| { - layout_region(layout, cursor_position) - .map(|region| (target, region)) - }); - - match dropped_region { - Some(((target, _), region)) - if pane != target => - { - DragEvent::Dropped { - pane, - target: Target::Pane(target, region), - } + let event = if let Some(edge) = + in_edge(layout, cursor_position) + { + DragEvent::Dropped { + pane, + target: Target::Edge(edge), } - _ => DragEvent::Canceled { pane }, - } - }; + } else { + let dropped_region = contents + .zip(layout.children()) + .find_map(|(target, layout)| { + layout_region(layout, cursor_position) + .map(|region| (target, region)) + }); - shell.publish(on_drag(event)); + match dropped_region { + Some(((target, _), region)) + if pane != target => + { + DragEvent::Dropped { + pane, + target: Target::Pane( + target, region, + ), + } + } + _ => DragEvent::Canceled { pane }, + } + }; + + shell.publish(on_drag(event)); + } } } @@ -638,49 +647,7 @@ pub fn update<'a, Message, T: Draggable>( } Event::Mouse(mouse::Event::CursorMoved { .. }) | Event::Touch(touch::Event::FingerMoved { .. }) => { - if let Some((_, origin)) = action.clicked_pane() { - if let Some(on_drag) = &on_drag { - let bounds = layout.bounds(); - - if let Some(cursor_position) = cursor.position_over(bounds) - { - let mut clicked_region = contents - .zip(layout.children()) - .filter(|(_, layout)| { - layout.bounds().contains(cursor_position) - }); - - if let Some(((pane, content), layout)) = - clicked_region.next() - { - if content - .can_be_dragged_at(layout, cursor_position) - { - let pane_position = layout.position(); - - let new_origin = cursor_position - - Vector::new( - pane_position.x, - pane_position.y, - ); - - if new_origin.distance(origin) - > DRAG_DEADBAND_DISTANCE - { - *action = state::Action::Dragging { - pane, - origin, - }; - - shell.publish(on_drag(DragEvent::Picked { - pane, - })); - } - } - } - } - } - } else if let Some((_, on_resize)) = on_resize { + if let Some((_, on_resize)) = on_resize { if let Some((split, _)) = action.picked_split() { let bounds = layout.bounds(); @@ -755,6 +722,7 @@ fn click_pane<'a, Message, T>( shell: &mut Shell<'_, Message>, contents: impl Iterator, on_click: &Option Message + 'a>>, + on_drag: &Option Message + 'a>>, ) where T: Draggable, { @@ -762,15 +730,21 @@ fn click_pane<'a, Message, T>( .zip(layout.children()) .filter(|(_, layout)| layout.bounds().contains(cursor_position)); - if let Some(((pane, _), layout)) = clicked_region.next() { + if let Some(((pane, content), layout)) = clicked_region.next() { if let Some(on_click) = &on_click { shell.publish(on_click(pane)); } - let pane_position = layout.position(); - let origin = - cursor_position - Vector::new(pane_position.x, pane_position.y); - *action = state::Action::Clicking { pane, origin }; + if let Some(on_drag) = &on_drag { + if content.can_be_dragged_at(layout, cursor_position) { + *action = state::Action::Dragging { + pane, + origin: cursor_position, + }; + + shell.publish(on_drag(DragEvent::Picked { pane })); + } + } } } @@ -783,7 +757,7 @@ pub fn mouse_interaction( spacing: f32, resize_leeway: Option, ) -> Option { - if action.clicked_pane().is_some() || action.picked_pane().is_some() { + if action.picked_pane().is_some() { return Some(mouse::Interaction::Grabbing); } @@ -841,7 +815,13 @@ pub fn draw( Theme: StyleSheet, Renderer: crate::core::Renderer, { - let picked_pane = action.picked_pane(); + let picked_pane = action.picked_pane().filter(|(_, origin)| { + cursor + .position() + .map(|position| position.distance(*origin)) + .unwrap_or_default() + > DRAG_DEADBAND_DISTANCE + }); let picked_split = action .picked_split() @@ -962,8 +942,7 @@ pub fn draw( if let Some(cursor_position) = cursor.position() { let bounds = layout.bounds(); - let translation = cursor_position - - Point::new(bounds.x + origin.x, bounds.y + origin.y); + let translation = cursor_position - Point::new(origin.x, origin.y); renderer.with_translation(translation, |renderer| { renderer.with_layer(bounds, |renderer| { @@ -1020,8 +999,6 @@ pub fn draw( } } -const THICKNESS_RATIO: f32 = 25.0; - fn in_edge(layout: Layout<'_>, cursor: Point) -> Option { let bounds = layout.bounds(); diff --git a/widget/src/pane_grid/state.rs b/widget/src/pane_grid/state.rs index 5d1fe254..481cd770 100644 --- a/widget/src/pane_grid/state.rs +++ b/widget/src/pane_grid/state.rs @@ -403,15 +403,6 @@ pub enum Action { /// /// [`PaneGrid`]: super::PaneGrid Idle, - /// A [`Pane`] in the [`PaneGrid`] is being clicked. - /// - /// [`PaneGrid`]: super::PaneGrid - Clicking { - /// The [`Pane`] being clicked. - pane: Pane, - /// The starting [`Point`] of the click interaction. - origin: Point, - }, /// A [`Pane`] in the [`PaneGrid`] is being dragged. /// /// [`PaneGrid`]: super::PaneGrid @@ -441,14 +432,6 @@ impl Action { } } - /// Returns the current [`Pane`] that is being clicked, if any. - pub fn clicked_pane(&self) -> Option<(Pane, Point)> { - match *self { - Action::Clicking { pane, origin, .. } => Some((pane, origin)), - _ => None, - } - } - /// Returns the current [`Split`] that is being dragged, if any. pub fn picked_split(&self) -> Option<(Split, Axis)> { match *self { diff --git a/widget/src/row.rs b/widget/src/row.rs index b41b5380..47feff9c 100644 --- a/widget/src/row.rs +++ b/widget/src/row.rs @@ -106,15 +106,10 @@ where child: impl Into>, ) -> Self { let child = child.into(); - let size = child.as_widget().size_hint(); + let child_size = child.as_widget().size_hint(); - if size.width.is_fill() { - self.width = Length::Fill; - } - - if size.height.is_fill() { - self.height = Length::Fill; - } + self.width = self.width.enclose(child_size.width); + self.height = self.height.enclose(child_size.height); self.children.push(child); self diff --git a/widget/src/scrollable.rs b/widget/src/scrollable.rs index c4873648..f736d92e 100644 --- a/widget/src/scrollable.rs +++ b/widget/src/scrollable.rs @@ -907,7 +907,15 @@ pub fn draw( theme.active(style) }; - let idle_scrollbar = theme.active(style).scrollbar; + let scrollbar_style = |is_dragging: bool, mouse_over_scrollbar: bool| { + if is_dragging { + theme.dragging(style).scrollbar + } else if cursor_over_scrollable.is_some() { + theme.hovered(style, mouse_over_scrollbar).scrollbar + } else { + theme.active(style).scrollbar + } + }; container::draw_background( renderer, @@ -984,13 +992,10 @@ pub fn draw( if let Some(scrollbar) = scrollbars.y { draw_scrollbar( renderer, - if mouse_over_y_scrollbar - || state.y_scroller_grabbed_at.is_some() - { - appearance.scrollbar - } else { - idle_scrollbar - }, + scrollbar_style( + state.y_scroller_grabbed_at.is_some(), + mouse_over_y_scrollbar, + ), &scrollbar, ); } @@ -998,13 +1003,10 @@ pub fn draw( if let Some(scrollbar) = scrollbars.x { draw_scrollbar( renderer, - if mouse_over_x_scrollbar - || state.x_scroller_grabbed_at.is_some() - { - appearance.scrollbar - } else { - idle_scrollbar - }, + scrollbar_style( + state.x_scroller_grabbed_at.is_some(), + mouse_over_x_scrollbar, + ), &scrollbar, ); } diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs index 79432fe9..bad3ef4d 100644 --- a/widget/src/text_editor.rs +++ b/widget/src/text_editor.rs @@ -686,6 +686,37 @@ impl Update { text, .. } if state.is_focused => { + match key.as_ref() { + keyboard::Key::Named(key::Named::Enter) => { + return edit(Edit::Enter); + } + keyboard::Key::Named(key::Named::Backspace) => { + return edit(Edit::Backspace); + } + keyboard::Key::Named(key::Named::Delete) => { + return edit(Edit::Delete); + } + keyboard::Key::Named(key::Named::Escape) => { + return Some(Self::Unfocus); + } + keyboard::Key::Character("c") + if modifiers.command() => + { + return Some(Self::Copy); + } + keyboard::Key::Character("x") + if modifiers.command() => + { + return Some(Self::Cut); + } + keyboard::Key::Character("v") + if modifiers.command() && !modifiers.alt() => + { + return Some(Self::Paste); + } + _ => {} + } + if let Some(text) = text { if let Some(c) = text.chars().find(|c| !c.is_control()) { @@ -711,36 +742,7 @@ impl Update { } } - match key.as_ref() { - keyboard::Key::Named(key::Named::Enter) => { - edit(Edit::Enter) - } - keyboard::Key::Named(key::Named::Backspace) => { - edit(Edit::Backspace) - } - keyboard::Key::Named(key::Named::Delete) => { - edit(Edit::Delete) - } - keyboard::Key::Named(key::Named::Escape) => { - Some(Self::Unfocus) - } - keyboard::Key::Character("c") - if modifiers.command() => - { - Some(Self::Copy) - } - keyboard::Key::Character("x") - if modifiers.command() => - { - Some(Self::Cut) - } - keyboard::Key::Character("v") - if modifiers.command() && !modifiers.alt() => - { - Some(Self::Paste) - } - _ => None, - } + None } _ => None, }, diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs index 73346b3d..92c4892c 100644 --- a/widget/src/text_input.rs +++ b/widget/src/text_input.rs @@ -762,12 +762,94 @@ where let modifiers = state.keyboard_modifiers; focus.updated_at = Instant::now(); + match key.as_ref() { + keyboard::Key::Character("c") + if state.keyboard_modifiers.command() => + { + if let Some((start, end)) = + state.cursor.selection(value) + { + clipboard.write( + clipboard::Kind::Standard, + value.select(start, end).to_string(), + ); + } + + return event::Status::Captured; + } + keyboard::Key::Character("x") + if state.keyboard_modifiers.command() => + { + if let Some((start, end)) = + state.cursor.selection(value) + { + clipboard.write( + clipboard::Kind::Standard, + value.select(start, end).to_string(), + ); + } + + let mut editor = Editor::new(value, &mut state.cursor); + editor.delete(); + + let message = (on_input)(editor.contents()); + shell.publish(message); + + update_cache(state, value); + + return event::Status::Captured; + } + keyboard::Key::Character("v") + if state.keyboard_modifiers.command() + && !state.keyboard_modifiers.alt() => + { + let content = match state.is_pasting.take() { + Some(content) => content, + None => { + let content: String = clipboard + .read(clipboard::Kind::Standard) + .unwrap_or_default() + .chars() + .filter(|c| !c.is_control()) + .collect(); + + Value::new(&content) + } + }; + + let mut editor = Editor::new(value, &mut state.cursor); + + editor.paste(content.clone()); + + let message = if let Some(paste) = &on_paste { + (paste)(editor.contents()) + } else { + (on_input)(editor.contents()) + }; + shell.publish(message); + + state.is_pasting = Some(content); + + update_cache(state, value); + + return event::Status::Captured; + } + keyboard::Key::Character("a") + if state.keyboard_modifiers.command() => + { + state.cursor.select_all(value); + + return event::Status::Captured; + } + _ => {} + } + if let Some(text) = text { state.is_pasting = None; - let c = text.chars().next().unwrap_or_default(); - - if !c.is_control() { + if let Some(c) = + text.chars().next().filter(|c| !c.is_control()) + { let mut editor = Editor::new(value, &mut state.cursor); editor.insert(c); @@ -880,76 +962,6 @@ where state.cursor.move_to(value.len()); } } - keyboard::Key::Character("c") - if state.keyboard_modifiers.command() => - { - if let Some((start, end)) = - state.cursor.selection(value) - { - clipboard.write( - clipboard::Kind::Standard, - value.select(start, end).to_string(), - ); - } - } - keyboard::Key::Character("x") - if state.keyboard_modifiers.command() => - { - if let Some((start, end)) = - state.cursor.selection(value) - { - clipboard.write( - clipboard::Kind::Standard, - value.select(start, end).to_string(), - ); - } - - let mut editor = Editor::new(value, &mut state.cursor); - editor.delete(); - - let message = (on_input)(editor.contents()); - shell.publish(message); - - update_cache(state, value); - } - keyboard::Key::Character("v") - if state.keyboard_modifiers.command() - && !state.keyboard_modifiers.alt() => - { - let content = match state.is_pasting.take() { - Some(content) => content, - None => { - let content: String = clipboard - .read(clipboard::Kind::Standard) - .unwrap_or_default() - .chars() - .filter(|c| !c.is_control()) - .collect(); - - Value::new(&content) - } - }; - - let mut editor = Editor::new(value, &mut state.cursor); - - editor.paste(content.clone()); - - let message = if let Some(paste) = &on_paste { - (paste)(editor.contents()) - } else { - (on_input)(editor.contents()) - }; - shell.publish(message); - - state.is_pasting = Some(content); - - update_cache(state, value); - } - keyboard::Key::Character("a") - if state.keyboard_modifiers.command() => - { - state.cursor.select_all(value); - } keyboard::Key::Named(key::Named::Escape) => { state.is_focused = None; state.is_dragging = false; diff --git a/widget/src/themer.rs b/widget/src/themer.rs index e6ca6cfe..3a5fd823 100644 --- a/widget/src/themer.rs +++ b/widget/src/themer.rs @@ -1,3 +1,4 @@ +use crate::container; use crate::core::event::{self, Event}; use crate::core::layout; use crate::core::mouse; @@ -6,9 +7,10 @@ use crate::core::renderer; use crate::core::widget::tree::{self, Tree}; use crate::core::widget::Operation; use crate::core::{ - Clipboard, Element, Layout, Length, Point, Rectangle, Shell, Size, Vector, - Widget, + Background, Clipboard, Element, Layout, Length, Point, Rectangle, Shell, + Size, Vector, Widget, }; +use crate::style::application; /// A widget that applies any `Theme` to its contents. /// @@ -18,14 +20,18 @@ use crate::core::{ pub struct Themer<'a, Message, Theme, Renderer> where Renderer: crate::core::Renderer, + Theme: application::StyleSheet, { content: Element<'a, Message, Theme, Renderer>, theme: Theme, + style: Theme::Style, + show_background: bool, } impl<'a, Message, Theme, Renderer> Themer<'a, Message, Theme, Renderer> where Renderer: crate::core::Renderer, + Theme: application::StyleSheet, { /// Creates an empty [`Themer`] that applies the given `Theme` /// to the provided `content`. @@ -34,16 +40,25 @@ where T: Into>, { Self { - theme, content: content.into(), + theme, + style: Theme::Style::default(), + show_background: false, } } + + /// Sets whether to draw the background color of the `Theme`. + pub fn background(mut self, background: bool) -> Self { + self.show_background = background; + self + } } impl<'a, AnyTheme, Message, Theme, Renderer> Widget for Themer<'a, Message, Theme, Renderer> where Renderer: crate::core::Renderer, + Theme: application::StyleSheet, { fn tag(&self) -> tree::Tag { self.content.as_widget().tag() @@ -120,16 +135,33 @@ where tree: &Tree, renderer: &mut Renderer, _theme: &AnyTheme, - renderer_style: &renderer::Style, + _style: &renderer::Style, layout: Layout<'_>, cursor: mouse::Cursor, viewport: &Rectangle, ) { + let appearance = self.theme.appearance(&self.style); + + if self.show_background { + container::draw_background( + renderer, + &container::Appearance { + background: Some(Background::Color( + appearance.background_color, + )), + ..container::Appearance::default() + }, + layout.bounds(), + ); + } + self.content.as_widget().draw( tree, renderer, &self.theme, - renderer_style, + &renderer::Style { + text_color: appearance.text_color, + }, layout, cursor, viewport, @@ -248,7 +280,7 @@ impl<'a, AnyTheme, Message, Theme, Renderer> for Element<'a, Message, AnyTheme, Renderer> where Message: 'a, - Theme: 'a, + Theme: 'a + application::StyleSheet, Renderer: 'a + crate::core::Renderer, { fn from( diff --git a/widget/src/tooltip.rs b/widget/src/tooltip.rs index 3751739a..d8a1e131 100644 --- a/widget/src/tooltip.rs +++ b/widget/src/tooltip.rs @@ -119,6 +119,10 @@ where self.content.as_widget().size() } + fn size_hint(&self) -> Size { + self.content.as_widget().size_hint() + } + fn layout( &self, tree: &mut widget::Tree,