Merge branch 'master' into sentinel

This commit is contained in:
Héctor Ramón Jiménez 2024-02-28 15:14:52 +01:00
commit caecbf20ef
No known key found for this signature in database
GPG key ID: 7CC46565708259A7
12 changed files with 257 additions and 249 deletions

View file

@ -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<Pixels> for Length {

View file

@ -115,15 +115,10 @@ where
child: impl Into<Element<'a, Message, Theme, Renderer>>,
) -> 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

View file

@ -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)
}

View file

@ -110,15 +110,10 @@ where
child: impl Into<Element<'a, Message, Theme, Renderer>>,
) -> 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);

View file

@ -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<Box<dyn Fn(DragEvent) -> Message + 'a>>,
on_resize: &Option<(f32, Box<dyn Fn(ResizeEvent) -> 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<Item = (Pane, T)>,
on_click: &Option<Box<dyn Fn(Pane) -> Message + 'a>>,
on_drag: &Option<Box<dyn Fn(DragEvent) -> 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<f32>,
) -> Option<mouse::Interaction> {
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, Renderer, T>(
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<Theme, Renderer, T>(
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<Theme, Renderer, T>(
}
}
const THICKNESS_RATIO: f32 = 25.0;
fn in_edge(layout: Layout<'_>, cursor: Point) -> Option<Edge> {
let bounds = layout.bounds();

View file

@ -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 {

View file

@ -106,15 +106,10 @@ where
child: impl Into<Element<'a, Message, Theme, Renderer>>,
) -> 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

View file

@ -907,7 +907,15 @@ pub fn draw<Theme, Renderer>(
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<Theme, Renderer>(
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<Theme, Renderer>(
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,
);
}

View file

@ -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,
},

View file

@ -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;

View file

@ -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<Element<'a, Message, Theme, Renderer>>,
{
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<Message, AnyTheme, Renderer>
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(

View file

@ -119,6 +119,10 @@ where
self.content.as_widget().size()
}
fn size_hint(&self) -> Size<Length> {
self.content.as_widget().size_hint()
}
fn layout(
&self,
tree: &mut widget::Tree,