Merge branch 'master' into feature/text-selection

This commit is contained in:
Héctor Ramón Jiménez 2020-03-24 19:08:21 +01:00
commit e77fa175aa
90 changed files with 3623 additions and 538 deletions

View file

@ -243,7 +243,7 @@ where
}
/// Computes the _layout_ hash of the [`Element`].
///
///
/// [`Element`]: struct.Element.html
pub fn hash_layout(&self, state: &mut Hasher) {
self.widget.hash_layout(state);

View file

@ -1,8 +1,5 @@
//! Build keyboard events.
mod event;
mod key_code;
mod modifiers_state;
pub use event::Event;
pub use key_code::KeyCode;
pub use modifiers_state::ModifiersState;
pub use iced_core::keyboard::{KeyCode, ModifiersState};

View file

@ -1,198 +0,0 @@
/// The symbolic name of a keyboard key.
///
/// This is mostly the `KeyCode` type found in [`winit`].
///
/// [`winit`]: https://docs.rs/winit/0.20.0-alpha3/winit/
#[derive(Debug, Hash, Ord, PartialOrd, PartialEq, Eq, Clone, Copy)]
#[repr(u32)]
#[allow(missing_docs)]
pub enum KeyCode {
/// The '1' key over the letters.
Key1,
/// The '2' key over the letters.
Key2,
/// The '3' key over the letters.
Key3,
/// The '4' key over the letters.
Key4,
/// The '5' key over the letters.
Key5,
/// The '6' key over the letters.
Key6,
/// The '7' key over the letters.
Key7,
/// The '8' key over the letters.
Key8,
/// The '9' key over the letters.
Key9,
/// The '0' key over the 'O' and 'P' keys.
Key0,
A,
B,
C,
D,
E,
F,
G,
H,
I,
J,
K,
L,
M,
N,
O,
P,
Q,
R,
S,
T,
U,
V,
W,
X,
Y,
Z,
/// The Escape key, next to F1
Escape,
F1,
F2,
F3,
F4,
F5,
F6,
F7,
F8,
F9,
F10,
F11,
F12,
F13,
F14,
F15,
F16,
F17,
F18,
F19,
F20,
F21,
F22,
F23,
F24,
/// Print Screen/SysRq
Snapshot,
/// Scroll Lock
Scroll,
/// Pause/Break key, next to Scroll lock
Pause,
/// `Insert`, next to Backspace
Insert,
Home,
Delete,
End,
PageDown,
PageUp,
Left,
Up,
Right,
Down,
Backspace,
Enter,
Space,
/// The "Compose" key on Linux
Compose,
Caret,
Numlock,
Numpad0,
Numpad1,
Numpad2,
Numpad3,
Numpad4,
Numpad5,
Numpad6,
Numpad7,
Numpad8,
Numpad9,
AbntC1,
AbntC2,
Add,
Apostrophe,
Apps,
At,
Ax,
Backslash,
Calculator,
Capital,
Colon,
Comma,
Convert,
Decimal,
Divide,
Equals,
Grave,
Kana,
Kanji,
LAlt,
LBracket,
LControl,
LShift,
LWin,
Mail,
MediaSelect,
MediaStop,
Minus,
Multiply,
Mute,
MyComputer,
NavigateForward, // also called "Prior"
NavigateBackward, // also called "Next"
NextTrack,
NoConvert,
NumpadComma,
NumpadEnter,
NumpadEquals,
OEM102,
Period,
PlayPause,
Power,
PrevTrack,
RAlt,
RBracket,
RControl,
RShift,
RWin,
Semicolon,
Slash,
Sleep,
Stop,
Subtract,
Sysrq,
Tab,
Underline,
Unlabeled,
VolumeDown,
VolumeUp,
Wake,
WebBack,
WebFavorites,
WebForward,
WebHome,
WebRefresh,
WebSearch,
WebStop,
Yen,
Copy,
Paste,
Cut,
}

View file

@ -1,15 +0,0 @@
/// The current state of the keyboard modifiers.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ModifiersState {
/// Whether a shift key is pressed
pub shift: bool,
/// Whether a control key is pressed
pub control: bool,
/// Whether an alt key is pressed
pub alt: bool,
/// Whether a logo key is pressed (e.g. windows key, command key...)
pub logo: bool,
}

View file

@ -20,7 +20,7 @@ impl Limits {
///
/// [`Limits`]: struct.Limits.html
/// [`Size`]: ../struct.Size.html
pub fn new(min: Size, max: Size) -> Limits {
pub const fn new(min: Size, max: Size) -> Limits {
Limits {
min,
max,

View file

@ -12,7 +12,7 @@ impl Node {
///
/// [`Node`]: struct.Node.html
/// [`Size`]: ../struct.Size.html
pub fn new(size: Size) -> Self {
pub const fn new(size: Size) -> Self {
Self::with_children(size, Vec::new())
}
@ -20,7 +20,7 @@ impl Node {
///
/// [`Node`]: struct.Node.html
/// [`Size`]: ../struct.Size.html
pub fn with_children(size: Size, children: Vec<Node>) -> Self {
pub const fn with_children(size: Size, children: Vec<Node>) -> Self {
Node {
bounds: Rectangle {
x: 0.0,

View file

@ -21,8 +21,8 @@
//! # Usage
//! The strategy to use this crate depends on your particular use case. If you
//! want to:
//! - Implement a custom shell or integrate it in your own system, you should
//! check out the [`UserInterface`] type.
//! - Implement a custom shell or integrate it in your own system, check out the
//! [`UserInterface`] type.
//! - Build a new renderer, see the [renderer] module.
//! - Build a custom widget, start at the [`Widget`] trait.
//!

View file

@ -21,6 +21,12 @@ pub enum MouseCursor {
/// The cursor is over a text widget.
Text,
/// The cursor is resizing a widget horizontally.
ResizingHorizontally,
/// The cursor is resizing a widget vertically.
ResizingVertically,
}
impl Default for MouseCursor {

View file

@ -25,6 +25,7 @@ pub mod checkbox;
pub mod column;
pub mod container;
pub mod image;
pub mod pane_grid;
pub mod progress_bar;
pub mod radio;
pub mod row;
@ -46,6 +47,8 @@ pub use container::Container;
#[doc(no_inline)]
pub use image::Image;
#[doc(no_inline)]
pub use pane_grid::PaneGrid;
#[doc(no_inline)]
pub use progress_bar::ProgressBar;
#[doc(no_inline)]
pub use radio::Radio;

View file

@ -30,6 +30,15 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> {
///
/// [`Column`]: struct.Column.html
pub fn new() -> Self {
Self::with_children(Vec::new())
}
/// Creates a [`Column`] with the given elements.
///
/// [`Column`]: struct.Column.html
pub fn with_children(
children: Vec<Element<'a, Message, Renderer>>,
) -> Self {
Column {
spacing: 0,
padding: 0,
@ -38,7 +47,7 @@ impl<'a, Message, Renderer> Column<'a, Message, Renderer> {
max_width: u32::MAX,
max_height: u32::MAX,
align_items: Align::Start,
children: Vec::new(),
children,
}
}
@ -210,7 +219,7 @@ pub trait Renderer: crate::Renderer + Sized {
/// - the [`Layout`] of the [`Column`] and its children
/// - the cursor position
///
/// [`Column`]: struct.Row.html
/// [`Column`]: struct.Column.html
/// [`Layout`]: ../layout/struct.Layout.html
fn draw<Message>(
&mut self,

View file

@ -77,7 +77,7 @@ where
self.max_height = max_height;
self
}
/// Sets the content alignment for the horizontal axis of the [`Container`].
///
/// [`Container`]: struct.Container.html

View file

@ -18,7 +18,7 @@ use std::{
/// ```
///
/// <img src="https://github.com/hecrj/iced/blob/9712b319bb7a32848001b96bd84977430f14b623/examples/resources/ferris.png?raw=true" width="300">
#[derive(Debug)]
#[derive(Debug, Hash)]
pub struct Image {
handle: Handle,
width: Length,
@ -125,6 +125,21 @@ impl Handle {
Self::from_data(Data::Path(path.into()))
}
/// Creates an image [`Handle`] containing the image pixels directly. This
/// function expects the input data to be provided as a `Vec<u8>` of BGRA
/// pixels.
///
/// This is useful if you have already decoded your image.
///
/// [`Handle`]: struct.Handle.html
pub fn from_pixels(width: u32, height: u32, pixels: Vec<u8>) -> Handle {
Self::from_data(Data::Pixels {
width,
height,
pixels,
})
}
/// Creates an image [`Handle`] containing the image data directly.
///
/// This is useful if you already have your image loaded in-memory, maybe
@ -188,6 +203,16 @@ pub enum Data {
/// In-memory data
Bytes(Vec<u8>),
/// Decoded image pixels in BGRA format.
Pixels {
/// The width of the image.
width: u32,
/// The height of the image.
height: u32,
/// The pixels.
pixels: Vec<u8>,
},
}
impl std::fmt::Debug for Data {
@ -195,6 +220,9 @@ impl std::fmt::Debug for Data {
match self {
Data::Path(path) => write!(f, "Path({:?})", path),
Data::Bytes(_) => write!(f, "Bytes(...)"),
Data::Pixels { width, height, .. } => {
write!(f, "Pixels({} * {})", width, height)
}
}
}
}

View file

@ -0,0 +1,647 @@
//! Let your users split regions of your application and organize layout dynamically.
//!
//! [![Pane grid - Iced](https://thumbs.gfycat.com/MixedFlatJellyfish-small.gif)](https://gfycat.com/mixedflatjellyfish)
mod axis;
mod direction;
mod node;
mod pane;
mod split;
mod state;
pub use axis::Axis;
pub use direction::Direction;
pub use pane::Pane;
pub use split::Split;
pub use state::{Focus, State};
use crate::{
input::{keyboard, mouse, ButtonState},
layout, Clipboard, Element, Event, Hasher, Layout, Length, Point, Size,
Widget,
};
/// A collection of panes distributed using either vertical or horizontal splits
/// to completely fill the space available.
///
/// [![Pane grid - Iced](https://thumbs.gfycat.com/MixedFlatJellyfish-small.gif)](https://gfycat.com/mixedflatjellyfish)
///
/// This distribution of space is common in tiling window managers (like
/// [`awesome`](https://awesomewm.org/), [`i3`](https://i3wm.org/), or even
/// [`tmux`](https://github.com/tmux/tmux)).
///
/// A [`PaneGrid`] supports:
///
/// * Vertical and horizontal splits
/// * Tracking of the last active pane
/// * Mouse-based resizing
/// * Drag and drop to reorganize panes
/// * Hotkey support
/// * Configurable modifier keys
/// * [`State`] API to perform actions programmatically (`split`, `swap`, `resize`, etc.)
///
/// ## Example
///
/// ```
/// # use iced_native::{pane_grid, Text};
/// #
/// # type PaneGrid<'a, Message> =
/// # iced_native::PaneGrid<'a, Message, iced_native::renderer::Null>;
/// #
/// enum PaneState {
/// SomePane,
/// AnotherKindOfPane,
/// }
///
/// enum Message {
/// PaneDragged(pane_grid::DragEvent),
/// PaneResized(pane_grid::ResizeEvent),
/// }
///
/// let (mut state, _) = pane_grid::State::new(PaneState::SomePane);
///
/// let pane_grid = PaneGrid::new(&mut state, |pane, state, focus| {
/// match state {
/// PaneState::SomePane => Text::new("This is some pane"),
/// PaneState::AnotherKindOfPane => Text::new("This is another kind of pane"),
/// }.into()
/// })
/// .on_drag(Message::PaneDragged)
/// .on_resize(Message::PaneResized);
/// ```
///
/// [`PaneGrid`]: struct.PaneGrid.html
/// [`State`]: struct.State.html
#[allow(missing_debug_implementations)]
pub struct PaneGrid<'a, Message, Renderer> {
state: &'a mut state::Internal,
pressed_modifiers: &'a mut keyboard::ModifiersState,
elements: Vec<(Pane, Element<'a, Message, Renderer>)>,
width: Length,
height: Length,
spacing: u16,
modifier_keys: keyboard::ModifiersState,
on_drag: Option<Box<dyn Fn(DragEvent) -> Message>>,
on_resize: Option<Box<dyn Fn(ResizeEvent) -> Message>>,
on_key_press: Option<Box<dyn Fn(KeyPressEvent) -> Option<Message>>>,
}
impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> {
/// Creates a [`PaneGrid`] with the given [`State`] and view function.
///
/// The view function will be called to display each [`Pane`] present in the
/// [`State`].
///
/// [`PaneGrid`]: struct.PaneGrid.html
/// [`State`]: struct.State.html
/// [`Pane`]: struct.Pane.html
pub fn new<T>(
state: &'a mut State<T>,
view: impl Fn(
Pane,
&'a mut T,
Option<Focus>,
) -> Element<'a, Message, Renderer>,
) -> Self {
let elements = {
let action = state.internal.action();
let current_focus = action.focus();
state
.panes
.iter_mut()
.map(move |(pane, pane_state)| {
let focus = match current_focus {
Some((focused_pane, focus))
if *pane == focused_pane =>
{
Some(focus)
}
_ => None,
};
(*pane, view(*pane, pane_state, focus))
})
.collect()
};
Self {
state: &mut state.internal,
pressed_modifiers: &mut state.modifiers,
elements,
width: Length::Fill,
height: Length::Fill,
spacing: 0,
modifier_keys: keyboard::ModifiersState {
control: true,
..Default::default()
},
on_drag: None,
on_resize: None,
on_key_press: None,
}
}
/// Sets the width of the [`PaneGrid`].
///
/// [`PaneGrid`]: struct.PaneGrid.html
pub fn width(mut self, width: Length) -> Self {
self.width = width;
self
}
/// Sets the height of the [`PaneGrid`].
///
/// [`PaneGrid`]: struct.PaneGrid.html
pub fn height(mut self, height: Length) -> Self {
self.height = height;
self
}
/// Sets the spacing _between_ the panes of the [`PaneGrid`].
///
/// [`PaneGrid`]: struct.PaneGrid.html
pub fn spacing(mut self, units: u16) -> Self {
self.spacing = units;
self
}
/// Sets the modifier keys of the [`PaneGrid`].
///
/// The modifier keys will need to be pressed to trigger dragging, resizing,
/// and key events.
///
/// The default modifier key is `Ctrl`.
///
/// [`PaneGrid`]: struct.PaneGrid.html
pub fn modifier_keys(
mut self,
modifier_keys: keyboard::ModifiersState,
) -> Self {
self.modifier_keys = modifier_keys;
self
}
/// Enables the drag and drop interactions of the [`PaneGrid`], which will
/// use the provided function to produce messages.
///
/// Panes can be dragged using `Modifier keys + Left click`.
///
/// [`PaneGrid`]: struct.PaneGrid.html
pub fn on_drag(
mut self,
f: impl Fn(DragEvent) -> Message + 'static,
) -> Self {
self.on_drag = Some(Box::new(f));
self
}
/// Enables the resize interactions of the [`PaneGrid`], which will
/// use the provided function to produce messages.
///
/// Panes can be resized using `Modifier keys + Right click`.
///
/// [`PaneGrid`]: struct.PaneGrid.html
pub fn on_resize(
mut self,
f: impl Fn(ResizeEvent) -> Message + 'static,
) -> Self {
self.on_resize = Some(Box::new(f));
self
}
/// Captures hotkey interactions with the [`PaneGrid`], using the provided
/// function to produce messages.
///
/// The function will be called when:
/// - a [`Pane`] is focused
/// - a key is pressed
/// - all the modifier keys are pressed
///
/// If the function returns `None`, the key press event will be discarded
/// without producing any message.
///
/// This method is particularly useful to implement hotkey interactions.
/// For instance, you can use it to enable splitting, swapping, or resizing
/// panes by pressing combinations of keys.
///
/// [`PaneGrid`]: struct.PaneGrid.html
/// [`Pane`]: struct.Pane.html
pub fn on_key_press(
mut self,
f: impl Fn(KeyPressEvent) -> Option<Message> + 'static,
) -> Self {
self.on_key_press = Some(Box::new(f));
self
}
fn trigger_resize(
&mut self,
layout: Layout<'_>,
cursor_position: Point,
messages: &mut Vec<Message>,
) {
if let Some(on_resize) = &self.on_resize {
if let Some((split, _)) = self.state.picked_split() {
let bounds = layout.bounds();
let splits = self.state.splits(
f32::from(self.spacing),
Size::new(bounds.width, bounds.height),
);
if let Some((axis, rectangle, _)) = splits.get(&split) {
let ratio = match axis {
Axis::Horizontal => {
let position =
cursor_position.y - bounds.y - rectangle.y;
(position / rectangle.height).max(0.1).min(0.9)
}
Axis::Vertical => {
let position =
cursor_position.x - bounds.x - rectangle.x;
(position / rectangle.width).max(0.1).min(0.9)
}
};
messages.push(on_resize(ResizeEvent { split, ratio }));
}
}
}
}
}
/// An event produced during a drag and drop interaction of a [`PaneGrid`].
///
/// [`PaneGrid`]: struct.PaneGrid.html
#[derive(Debug, Clone, Copy)]
pub enum DragEvent {
/// A [`Pane`] was picked for dragging.
///
/// [`Pane`]: struct.Pane.html
Picked {
/// The picked [`Pane`].
///
/// [`Pane`]: struct.Pane.html
pane: Pane,
},
/// A [`Pane`] was dropped on top of another [`Pane`].
///
/// [`Pane`]: struct.Pane.html
Dropped {
/// The picked [`Pane`].
///
/// [`Pane`]: struct.Pane.html
pane: Pane,
/// The [`Pane`] where the picked one was dropped on.
///
/// [`Pane`]: struct.Pane.html
target: Pane,
},
/// A [`Pane`] was picked and then dropped outside of other [`Pane`]
/// boundaries.
///
/// [`Pane`]: struct.Pane.html
Canceled {
/// The picked [`Pane`].
///
/// [`Pane`]: struct.Pane.html
pane: Pane,
},
}
/// An event produced during a resize interaction of a [`PaneGrid`].
///
/// [`PaneGrid`]: struct.PaneGrid.html
#[derive(Debug, Clone, Copy)]
pub struct ResizeEvent {
/// The [`Split`] that is being dragged for resizing.
///
/// [`Split`]: struct.Split.html
pub split: Split,
/// The new ratio of the [`Split`].
///
/// The ratio is a value in [0, 1], representing the exact position of a
/// [`Split`] between two panes.
///
/// [`Split`]: struct.Split.html
pub ratio: f32,
}
/// An event produced during a key press interaction of a [`PaneGrid`].
///
/// [`PaneGrid`]: struct.PaneGrid.html
#[derive(Debug, Clone, Copy)]
pub struct KeyPressEvent {
/// The key that was pressed.
pub key_code: keyboard::KeyCode,
/// The state of the modifier keys when the key was pressed.
pub modifiers: keyboard::ModifiersState,
}
impl<'a, Message, Renderer> Widget<Message, Renderer>
for PaneGrid<'a, Message, Renderer>
where
Renderer: self::Renderer + 'static,
Message: 'static,
{
fn width(&self) -> Length {
self.width
}
fn height(&self) -> Length {
self.height
}
fn layout(
&self,
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
let limits = limits.width(self.width).height(self.height);
let size = limits.resolve(Size::ZERO);
let regions = self.state.regions(f32::from(self.spacing), size);
let children = self
.elements
.iter()
.filter_map(|(pane, element)| {
let region = regions.get(pane)?;
let size = Size::new(region.width, region.height);
let mut node =
element.layout(renderer, &layout::Limits::new(size, size));
node.move_to(Point::new(region.x, region.y));
Some(node)
})
.collect();
layout::Node::with_children(size, children)
}
fn on_event(
&mut self,
event: Event,
layout: Layout<'_>,
cursor_position: Point,
messages: &mut Vec<Message>,
renderer: &Renderer,
clipboard: Option<&dyn Clipboard>,
) {
match event {
Event::Mouse(mouse::Event::Input {
button: mouse::Button::Left,
state,
}) => match state {
ButtonState::Pressed => {
let mut clicked_region =
self.elements.iter().zip(layout.children()).filter(
|(_, layout)| {
layout.bounds().contains(cursor_position)
},
);
if let Some(((pane, _), _)) = clicked_region.next() {
match &self.on_drag {
Some(on_drag)
if self
.pressed_modifiers
.matches(self.modifier_keys) =>
{
self.state.pick_pane(pane);
messages.push(on_drag(DragEvent::Picked {
pane: *pane,
}));
}
_ => {
self.state.focus(pane);
}
}
} else {
self.state.unfocus();
}
}
ButtonState::Released => {
if let Some(pane) = self.state.picked_pane() {
self.state.focus(&pane);
if let Some(on_drag) = &self.on_drag {
let mut dropped_region = self
.elements
.iter()
.zip(layout.children())
.filter(|(_, layout)| {
layout.bounds().contains(cursor_position)
});
let event = match dropped_region.next() {
Some(((target, _), _)) if pane != *target => {
DragEvent::Dropped {
pane,
target: *target,
}
}
_ => DragEvent::Canceled { pane },
};
messages.push(on_drag(event));
}
}
}
},
Event::Mouse(mouse::Event::Input {
button: mouse::Button::Right,
state: ButtonState::Pressed,
}) if self.on_resize.is_some()
&& self.state.picked_pane().is_none()
&& self.pressed_modifiers.matches(self.modifier_keys) =>
{
let bounds = layout.bounds();
if bounds.contains(cursor_position) {
let relative_cursor = Point::new(
cursor_position.x - bounds.x,
cursor_position.y - bounds.y,
);
let splits = self.state.splits(
f32::from(self.spacing),
Size::new(bounds.width, bounds.height),
);
let mut sorted_splits: Vec<_> = splits
.iter()
.filter(|(_, (axis, rectangle, _))| match axis {
Axis::Horizontal => {
relative_cursor.x > rectangle.x
&& relative_cursor.x
< rectangle.x + rectangle.width
}
Axis::Vertical => {
relative_cursor.y > rectangle.y
&& relative_cursor.y
< rectangle.y + rectangle.height
}
})
.collect();
sorted_splits.sort_by_key(
|(_, (axis, rectangle, ratio))| {
let distance = match axis {
Axis::Horizontal => (relative_cursor.y
- (rectangle.y + rectangle.height * ratio))
.abs(),
Axis::Vertical => (relative_cursor.x
- (rectangle.x + rectangle.width * ratio))
.abs(),
};
distance.round() as u32
},
);
if let Some((split, (axis, _, _))) = sorted_splits.first() {
self.state.pick_split(split, *axis);
self.trigger_resize(layout, cursor_position, messages);
}
}
}
Event::Mouse(mouse::Event::Input {
button: mouse::Button::Right,
state: ButtonState::Released,
}) if self.state.picked_split().is_some() => {
self.state.drop_split();
}
Event::Mouse(mouse::Event::CursorMoved { .. }) => {
self.trigger_resize(layout, cursor_position, messages);
}
Event::Keyboard(keyboard::Event::Input {
modifiers,
key_code,
state,
}) => {
if let Some(on_key_press) = &self.on_key_press {
// TODO: Discard when event is captured
if state == ButtonState::Pressed {
if let Some(_) = self.state.active_pane() {
if modifiers.matches(self.modifier_keys) {
if let Some(message) =
on_key_press(KeyPressEvent {
key_code,
modifiers,
})
{
messages.push(message);
}
}
}
}
}
*self.pressed_modifiers = modifiers;
}
_ => {}
}
if self.state.picked_pane().is_none() {
{
self.elements.iter_mut().zip(layout.children()).for_each(
|((_, pane), layout)| {
pane.widget.on_event(
event.clone(),
layout,
cursor_position,
messages,
renderer,
clipboard,
)
},
);
}
}
}
fn draw(
&self,
renderer: &mut Renderer,
defaults: &Renderer::Defaults,
layout: Layout<'_>,
cursor_position: Point,
) -> Renderer::Output {
renderer.draw(
defaults,
&self.elements,
self.state.picked_pane(),
self.state.picked_split().map(|(_, axis)| axis),
layout,
cursor_position,
)
}
fn hash_layout(&self, state: &mut Hasher) {
use std::hash::Hash;
std::any::TypeId::of::<PaneGrid<'_, Message, Renderer>>().hash(state);
self.width.hash(state);
self.height.hash(state);
self.state.hash_layout(state);
for (_, element) in &self.elements {
element.hash_layout(state);
}
}
}
/// The renderer of a [`PaneGrid`].
///
/// Your [renderer] will need to implement this trait before being
/// able to use a [`PaneGrid`] in your user interface.
///
/// [`PaneGrid`]: struct.PaneGrid.html
/// [renderer]: ../../renderer/index.html
pub trait Renderer: crate::Renderer + Sized {
/// Draws a [`PaneGrid`].
///
/// It receives:
/// - the elements of the [`PaneGrid`]
/// - the [`Pane`] that is currently being dragged
/// - the [`Axis`] that is currently being resized
/// - the [`Layout`] of the [`PaneGrid`] and its elements
/// - the cursor position
///
/// [`PaneGrid`]: struct.PaneGrid.html
/// [`Pane`]: struct.Pane.html
/// [`Layout`]: ../layout/struct.Layout.html
fn draw<Message>(
&mut self,
defaults: &Self::Defaults,
content: &[(Pane, Element<'_, Message, Self>)],
dragging: Option<Pane>,
resizing: Option<Axis>,
layout: Layout<'_>,
cursor_position: Point,
) -> Self::Output;
}
impl<'a, Message, Renderer> From<PaneGrid<'a, Message, Renderer>>
for Element<'a, Message, Renderer>
where
Renderer: self::Renderer + 'static,
Message: 'static,
{
fn from(
pane_grid: PaneGrid<'a, Message, Renderer>,
) -> Element<'a, Message, Renderer> {
Element::new(pane_grid)
}
}

View file

@ -0,0 +1,54 @@
use crate::Rectangle;
/// A fixed reference line for the measurement of coordinates.
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub enum Axis {
/// The horizontal axis: —
Horizontal,
/// The vertical axis: |
Vertical,
}
impl Axis {
pub(super) fn split(
&self,
rectangle: &Rectangle,
ratio: f32,
halved_spacing: f32,
) -> (Rectangle, Rectangle) {
match self {
Axis::Horizontal => {
let height_top = (rectangle.height * ratio).round();
let height_bottom = rectangle.height - height_top;
(
Rectangle {
height: height_top - halved_spacing,
..*rectangle
},
Rectangle {
y: rectangle.y + height_top + halved_spacing,
height: height_bottom - halved_spacing,
..*rectangle
},
)
}
Axis::Vertical => {
let width_left = (rectangle.width * ratio).round();
let width_right = rectangle.width - width_left;
(
Rectangle {
width: width_left - halved_spacing,
..*rectangle
},
Rectangle {
x: rectangle.x + width_left + halved_spacing,
width: width_right - halved_spacing,
..*rectangle
},
)
}
}
}
}

View file

@ -0,0 +1,12 @@
/// A four cardinal direction.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Direction {
/// ↑
Up,
/// ↓
Down,
/// ←
Left,
/// →
Right,
}

View file

@ -0,0 +1,199 @@
use crate::{
pane_grid::{Axis, Pane, Split},
Rectangle, Size,
};
use std::collections::HashMap;
#[derive(Debug, Clone, Hash)]
pub enum Node {
Split {
id: Split,
axis: Axis,
ratio: u32,
a: Box<Node>,
b: Box<Node>,
},
Pane(Pane),
}
impl Node {
pub fn find(&mut self, pane: &Pane) -> Option<&mut Node> {
match self {
Node::Split { a, b, .. } => {
a.find(pane).or_else(move || b.find(pane))
}
Node::Pane(p) => {
if p == pane {
Some(self)
} else {
None
}
}
}
}
pub fn split(&mut self, id: Split, axis: Axis, new_pane: Pane) {
*self = Node::Split {
id,
axis,
ratio: 500_000,
a: Box::new(self.clone()),
b: Box::new(Node::Pane(new_pane)),
};
}
pub fn update(&mut self, f: &impl Fn(&mut Node)) {
match self {
Node::Split { a, b, .. } => {
a.update(f);
b.update(f);
}
_ => {}
}
f(self);
}
pub fn resize(&mut self, split: &Split, percentage: f32) -> bool {
match self {
Node::Split {
id, ratio, a, b, ..
} => {
if id == split {
*ratio = (percentage * 1_000_000.0).round() as u32;
true
} else if a.resize(split, percentage) {
true
} else {
b.resize(split, percentage)
}
}
Node::Pane(_) => false,
}
}
pub fn remove(&mut self, pane: &Pane) -> Option<Pane> {
match self {
Node::Split { a, b, .. } => {
if a.pane() == Some(*pane) {
*self = *b.clone();
Some(self.first_pane())
} else if b.pane() == Some(*pane) {
*self = *a.clone();
Some(self.first_pane())
} else {
a.remove(pane).or_else(|| b.remove(pane))
}
}
Node::Pane(_) => None,
}
}
pub fn regions(
&self,
spacing: f32,
size: Size,
) -> HashMap<Pane, Rectangle> {
let mut regions = HashMap::new();
self.compute_regions(
spacing / 2.0,
&Rectangle {
x: 0.0,
y: 0.0,
width: size.width,
height: size.height,
},
&mut regions,
);
regions
}
pub fn splits(
&self,
spacing: f32,
size: Size,
) -> HashMap<Split, (Axis, Rectangle, f32)> {
let mut splits = HashMap::new();
self.compute_splits(
spacing / 2.0,
&Rectangle {
x: 0.0,
y: 0.0,
width: size.width,
height: size.height,
},
&mut splits,
);
splits
}
pub fn pane(&self) -> Option<Pane> {
match self {
Node::Split { .. } => None,
Node::Pane(pane) => Some(*pane),
}
}
pub fn first_pane(&self) -> Pane {
match self {
Node::Split { a, .. } => a.first_pane(),
Node::Pane(pane) => *pane,
}
}
fn compute_regions(
&self,
halved_spacing: f32,
current: &Rectangle,
regions: &mut HashMap<Pane, Rectangle>,
) {
match self {
Node::Split {
axis, ratio, a, b, ..
} => {
let ratio = *ratio as f32 / 1_000_000.0;
let (region_a, region_b) =
axis.split(current, ratio, halved_spacing);
a.compute_regions(halved_spacing, &region_a, regions);
b.compute_regions(halved_spacing, &region_b, regions);
}
Node::Pane(pane) => {
let _ = regions.insert(*pane, *current);
}
}
}
fn compute_splits(
&self,
halved_spacing: f32,
current: &Rectangle,
splits: &mut HashMap<Split, (Axis, Rectangle, f32)>,
) {
match self {
Node::Split {
axis,
ratio,
a,
b,
id,
} => {
let ratio = *ratio as f32 / 1_000_000.0;
let (region_a, region_b) =
axis.split(current, ratio, halved_spacing);
let _ = splits.insert(*id, (*axis, *current, ratio));
a.compute_splits(halved_spacing, &region_a, splits);
b.compute_splits(halved_spacing, &region_b, splits);
}
Node::Pane(_) => {}
}
}
}

View file

@ -0,0 +1,5 @@
/// A rectangular region in a [`PaneGrid`] used to display widgets.
///
/// [`PaneGrid`]: struct.PaneGrid.html
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Pane(pub(super) usize);

View file

@ -0,0 +1,5 @@
/// A divider that splits a region in a [`PaneGrid`] into two different panes.
///
/// [`PaneGrid`]: struct.PaneGrid.html
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Split(pub(super) usize);

View file

@ -0,0 +1,368 @@
use crate::{
input::keyboard,
pane_grid::{node::Node, Axis, Direction, Pane, Split},
Hasher, Point, Rectangle, Size,
};
use std::collections::HashMap;
/// The state of a [`PaneGrid`].
///
/// It keeps track of the state of each [`Pane`] and the position of each
/// [`Split`].
///
/// The [`State`] needs to own any mutable contents a [`Pane`] may need. This is
/// why this struct is generic over the type `T`. Values of this type are
/// provided to the view function of [`PaneGrid::new`] for displaying each
/// [`Pane`].
///
/// [`PaneGrid`]: struct.PaneGrid.html
/// [`PaneGrid::new`]: struct.PaneGrid.html#method.new
/// [`Pane`]: struct.Pane.html
/// [`Split`]: struct.Split.html
/// [`State`]: struct.State.html
#[derive(Debug)]
pub struct State<T> {
pub(super) panes: HashMap<Pane, T>,
pub(super) internal: Internal,
pub(super) modifiers: keyboard::ModifiersState,
}
/// The current focus of a [`Pane`].
///
/// [`Pane`]: struct.Pane.html
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Focus {
/// The [`Pane`] is just focused.
///
/// [`Pane`]: struct.Pane.html
Idle,
/// The [`Pane`] is being dragged.
///
/// [`Pane`]: struct.Pane.html
Dragging,
}
impl<T> State<T> {
/// Creates a new [`State`], initializing the first pane with the provided
/// state.
///
/// Alongside the [`State`], it returns the first [`Pane`] identifier.
///
/// [`State`]: struct.State.html
/// [`Pane`]: struct.Pane.html
pub fn new(first_pane_state: T) -> (Self, Pane) {
let first_pane = Pane(0);
let mut panes = HashMap::new();
let _ = panes.insert(first_pane, first_pane_state);
(
State {
panes,
internal: Internal {
layout: Node::Pane(first_pane),
last_id: 0,
action: Action::Idle { focus: None },
},
modifiers: keyboard::ModifiersState::default(),
},
first_pane,
)
}
/// Returns the total amount of panes in the [`State`].
///
/// [`State`]: struct.State.html
pub fn len(&self) -> usize {
self.panes.len()
}
/// Returns the internal state of the given [`Pane`], if it exists.
///
/// [`Pane`]: struct.Pane.html
pub fn get_mut(&mut self, pane: &Pane) -> Option<&mut T> {
self.panes.get_mut(pane)
}
/// Returns an iterator over all the panes of the [`State`], alongside its
/// internal state.
///
/// [`State`]: struct.State.html
pub fn iter(&self) -> impl Iterator<Item = (&Pane, &T)> {
self.panes.iter()
}
/// Returns a mutable iterator over all the panes of the [`State`],
/// alongside its internal state.
///
/// [`State`]: struct.State.html
pub fn iter_mut(&mut self) -> impl Iterator<Item = (&Pane, &mut T)> {
self.panes.iter_mut()
}
/// Returns the active [`Pane`] of the [`State`], if there is one.
///
/// A [`Pane`] is active if it is focused and is __not__ being dragged.
///
/// [`Pane`]: struct.Pane.html
/// [`State`]: struct.State.html
pub fn active(&self) -> Option<Pane> {
self.internal.active_pane()
}
/// Returns the adjacent [`Pane`] of another [`Pane`] in the given
/// direction, if there is one.
///
/// ## Example
/// You can combine this with [`State::active`] to find the pane that is
/// adjacent to the current active one, and then swap them. For instance:
///
/// ```
/// # use iced_native::pane_grid;
/// #
/// # let (mut state, _) = pane_grid::State::new(());
/// #
/// if let Some(active) = state.active() {
/// if let Some(adjacent) = state.adjacent(&active, pane_grid::Direction::Right) {
/// state.swap(&active, &adjacent);
/// }
/// }
/// ```
///
/// [`Pane`]: struct.Pane.html
/// [`State::active`]: struct.State.html#method.active
pub fn adjacent(&self, pane: &Pane, direction: Direction) -> Option<Pane> {
let regions =
self.internal.layout.regions(0.0, Size::new(4096.0, 4096.0));
let current_region = regions.get(pane)?;
let target = match direction {
Direction::Left => {
Point::new(current_region.x - 1.0, current_region.y + 1.0)
}
Direction::Right => Point::new(
current_region.x + current_region.width + 1.0,
current_region.y + 1.0,
),
Direction::Up => {
Point::new(current_region.x + 1.0, current_region.y - 1.0)
}
Direction::Down => Point::new(
current_region.x + 1.0,
current_region.y + current_region.height + 1.0,
),
};
let mut colliding_regions =
regions.iter().filter(|(_, region)| region.contains(target));
let (pane, _) = colliding_regions.next()?;
Some(*pane)
}
/// Focuses the given [`Pane`].
///
/// [`Pane`]: struct.Pane.html
pub fn focus(&mut self, pane: &Pane) {
self.internal.focus(pane);
}
/// Splits the given [`Pane`] into two in the given [`Axis`] and
/// initializing the new [`Pane`] with the provided internal state.
///
/// [`Pane`]: struct.Pane.html
/// [`Axis`]: enum.Axis.html
pub fn split(&mut self, axis: Axis, pane: &Pane, state: T) -> Option<Pane> {
let node = self.internal.layout.find(pane)?;
let new_pane = {
self.internal.last_id = self.internal.last_id.checked_add(1)?;
Pane(self.internal.last_id)
};
let new_split = {
self.internal.last_id = self.internal.last_id.checked_add(1)?;
Split(self.internal.last_id)
};
node.split(new_split, axis, new_pane);
let _ = self.panes.insert(new_pane, state);
self.focus(&new_pane);
Some(new_pane)
}
/// Swaps the position of the provided panes in the [`State`].
///
/// If you want to swap panes on drag and drop in your [`PaneGrid`], you
/// will need to call this method when handling a [`DragEvent`].
///
/// [`State`]: struct.State.html
/// [`PaneGrid`]: struct.PaneGrid.html
/// [`DragEvent`]: struct.DragEvent.html
pub fn swap(&mut self, a: &Pane, b: &Pane) {
self.internal.layout.update(&|node| match node {
Node::Split { .. } => {}
Node::Pane(pane) => {
if pane == a {
*node = Node::Pane(*b);
} else if pane == b {
*node = Node::Pane(*a);
}
}
});
}
/// Resizes two panes by setting the position of the provided [`Split`].
///
/// The ratio is a value in [0, 1], representing the exact position of a
/// [`Split`] between two panes.
///
/// If you want to enable resize interactions in your [`PaneGrid`], you will
/// need to call this method when handling a [`ResizeEvent`].
///
/// [`Split`]: struct.Split.html
/// [`PaneGrid`]: struct.PaneGrid.html
/// [`ResizeEvent`]: struct.ResizeEvent.html
pub fn resize(&mut self, split: &Split, ratio: f32) {
let _ = self.internal.layout.resize(split, ratio);
}
/// Closes the given [`Pane`] and returns its internal state, if it exists.
///
/// [`Pane`]: struct.Pane.html
pub fn close(&mut self, pane: &Pane) -> Option<T> {
if let Some(sibling) = self.internal.layout.remove(pane) {
self.focus(&sibling);
self.panes.remove(pane)
} else {
None
}
}
}
#[derive(Debug)]
pub struct Internal {
layout: Node,
last_id: usize,
action: Action,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Action {
Idle {
focus: Option<Pane>,
},
Dragging {
pane: Pane,
},
Resizing {
split: Split,
axis: Axis,
focus: Option<Pane>,
},
}
impl Action {
pub fn focus(&self) -> Option<(Pane, Focus)> {
match self {
Action::Idle { focus } | Action::Resizing { focus, .. } => {
focus.map(|pane| (pane, Focus::Idle))
}
Action::Dragging { pane } => Some((*pane, Focus::Dragging)),
}
}
}
impl Internal {
pub fn action(&self) -> Action {
self.action
}
pub fn active_pane(&self) -> Option<Pane> {
match self.action {
Action::Idle { focus } => focus,
_ => None,
}
}
pub fn picked_pane(&self) -> Option<Pane> {
match self.action {
Action::Dragging { pane } => Some(pane),
_ => None,
}
}
pub fn picked_split(&self) -> Option<(Split, Axis)> {
match self.action {
Action::Resizing { split, axis, .. } => Some((split, axis)),
_ => None,
}
}
pub fn regions(
&self,
spacing: f32,
size: Size,
) -> HashMap<Pane, Rectangle> {
self.layout.regions(spacing, size)
}
pub fn splits(
&self,
spacing: f32,
size: Size,
) -> HashMap<Split, (Axis, Rectangle, f32)> {
self.layout.splits(spacing, size)
}
pub fn focus(&mut self, pane: &Pane) {
self.action = Action::Idle { focus: Some(*pane) };
}
pub fn pick_pane(&mut self, pane: &Pane) {
self.action = Action::Dragging { pane: *pane };
}
pub fn pick_split(&mut self, split: &Split, axis: Axis) {
// TODO: Obtain `axis` from layout itself. Maybe we should implement
// `Node::find_split`
if self.picked_pane().is_some() {
return;
}
let focus = self.action.focus().map(|(pane, _)| pane);
self.action = Action::Resizing {
split: *split,
axis,
focus,
};
}
pub fn drop_split(&mut self) {
match self.action {
Action::Resizing { focus, .. } => {
self.action = Action::Idle { focus };
}
_ => {}
}
}
pub fn unfocus(&mut self) {
self.action = Action::Idle { focus: None };
}
pub fn hash_layout(&self, hasher: &mut Hasher) {
use std::hash::Hash;
self.layout.hash(hasher);
}
}

View file

@ -30,6 +30,15 @@ impl<'a, Message, Renderer> Row<'a, Message, Renderer> {
///
/// [`Row`]: struct.Row.html
pub fn new() -> Self {
Self::with_children(Vec::new())
}
/// Creates a [`Row`] with the given elements.
///
/// [`Row`]: struct.Row.html
pub fn with_children(
children: Vec<Element<'a, Message, Renderer>>,
) -> Self {
Row {
spacing: 0,
padding: 0,
@ -38,7 +47,7 @@ impl<'a, Message, Renderer> Row<'a, Message, Renderer> {
max_width: u32::MAX,
max_height: u32::MAX,
align_items: Align::Start,
children: Vec::new(),
children,
}
}

View file

@ -118,7 +118,7 @@ where
Renderer: 'static + self::Renderer + column::Renderer,
{
fn width(&self) -> Length {
Length::Fill
Widget::<Message, Renderer>::width(&self.content)
}
fn height(&self) -> Length {
@ -132,7 +132,7 @@ where
) -> layout::Node {
let limits = limits
.max_height(self.max_height)
.width(Length::Fill)
.width(Widget::<Message, Renderer>::width(&self.content))
.height(self.height);
let child_limits = layout::Limits::new(