From 012b4adec7a87331b2d75f6bc5d2a0189dcd7ec5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 4 Mar 2020 04:10:26 +0100 Subject: [PATCH 01/53] Draft `Panes` widget and `panes` example --- Cargo.toml | 1 + examples/clock/Cargo.toml | 3 - examples/clock/README.md | 4 +- examples/clock/src/lib.rs | 187 +++++++++++++++++++++++ examples/clock/src/main.rs | 188 +---------------------- examples/panes/Cargo.toml | 11 ++ examples/panes/README.md | 18 +++ examples/panes/src/main.rs | 95 ++++++++++++ examples/stopwatch/src/lib.rs | 204 +++++++++++++++++++++++++ examples/stopwatch/src/main.rs | 204 +------------------------ native/src/element.rs | 2 +- native/src/lib.rs | 2 +- native/src/widget.rs | 3 + native/src/widget/panes.rs | 238 ++++++++++++++++++++++++++++++ src/widget.rs | 2 +- wgpu/src/renderer/widget.rs | 1 + wgpu/src/renderer/widget/panes.rs | 34 +++++ 17 files changed, 801 insertions(+), 396 deletions(-) create mode 100644 examples/clock/src/lib.rs create mode 100644 examples/panes/Cargo.toml create mode 100644 examples/panes/README.md create mode 100644 examples/panes/src/main.rs create mode 100644 examples/stopwatch/src/lib.rs create mode 100644 native/src/widget/panes.rs create mode 100644 wgpu/src/renderer/widget/panes.rs diff --git a/Cargo.toml b/Cargo.toml index 01231b70af..3b79abdb75 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,6 +44,7 @@ members = [ "examples/events", "examples/geometry", "examples/integration", + "examples/panes", "examples/pokedex", "examples/progress_bar", "examples/solar_system", diff --git a/examples/clock/Cargo.toml b/examples/clock/Cargo.toml index 308cbfbb0a..ab7714055d 100644 --- a/examples/clock/Cargo.toml +++ b/examples/clock/Cargo.toml @@ -5,9 +5,6 @@ authors = ["Héctor Ramón Jiménez "] edition = "2018" publish = false -[features] -canvas = [] - [dependencies] iced = { path = "../..", features = ["canvas", "async-std", "debug"] } iced_native = { path = "../../native" } diff --git a/examples/clock/README.md b/examples/clock/README.md index 175091805b..a87edad0ca 100644 --- a/examples/clock/README.md +++ b/examples/clock/README.md @@ -2,7 +2,7 @@ An application that uses the `Canvas` widget to draw a clock and its hands to display the current time. -The __[`main`]__ file contains all the code of the example. +The __[`lib`]__ file contains the relevant code of the example.
@@ -13,4 +13,4 @@ You can run it with `cargo run`: cargo run --package clock ``` -[`main`]: src/main.rs +[`lib`]: src/lib.rs diff --git a/examples/clock/src/lib.rs b/examples/clock/src/lib.rs new file mode 100644 index 0000000000..229ba8feaa --- /dev/null +++ b/examples/clock/src/lib.rs @@ -0,0 +1,187 @@ +use iced::{ + canvas, executor, Application, Canvas, Color, Command, Container, Element, + Length, Point, Subscription, Vector, +}; + +#[derive(Debug)] +pub struct Clock { + now: LocalTime, + clock: canvas::layer::Cache, +} + +#[derive(Debug, Clone, Copy)] +pub enum Message { + Tick(chrono::DateTime), +} + +impl Application for Clock { + type Executor = executor::Default; + type Message = Message; + + fn new() -> (Self, Command) { + ( + Clock { + now: chrono::Local::now().into(), + clock: canvas::layer::Cache::new(), + }, + Command::none(), + ) + } + + fn title(&self) -> String { + String::from("Clock - Iced") + } + + fn update(&mut self, message: Message) -> Command { + match message { + Message::Tick(local_time) => { + let now = local_time.into(); + + if now != self.now { + self.now = now; + self.clock.clear(); + } + } + } + + Command::none() + } + + fn subscription(&self) -> Subscription { + time::every(std::time::Duration::from_millis(500)).map(Message::Tick) + } + + fn view(&mut self) -> Element { + let canvas = Canvas::new() + .width(Length::Units(400)) + .height(Length::Units(400)) + .push(self.clock.with(&self.now)); + + Container::new(canvas) + .width(Length::Fill) + .height(Length::Fill) + .center_x() + .center_y() + .into() + } +} + +#[derive(Debug, PartialEq, Eq)] +struct LocalTime { + hour: u32, + minute: u32, + second: u32, +} + +impl From> for LocalTime { + fn from(date_time: chrono::DateTime) -> LocalTime { + use chrono::Timelike; + + LocalTime { + hour: date_time.hour(), + minute: date_time.minute(), + second: date_time.second(), + } + } +} + +impl canvas::Drawable for LocalTime { + fn draw(&self, frame: &mut canvas::Frame) { + let center = frame.center(); + let radius = frame.width().min(frame.height()) / 2.0; + let offset = Vector::new(center.x, center.y); + + let clock = canvas::Path::new(|path| path.circle(center, radius)); + + frame.fill( + &clock, + canvas::Fill::Color(Color::from_rgb8(0x12, 0x93, 0xD8)), + ); + + fn draw_hand( + n: u32, + total: u32, + length: f32, + offset: Vector, + path: &mut canvas::path::Builder, + ) { + let turns = n as f32 / total as f32; + let t = 2.0 * std::f32::consts::PI * (turns - 0.25); + + let x = length * t.cos(); + let y = length * t.sin(); + + path.line_to(Point::new(x, y) + offset); + } + + let hour_and_minute_hands = canvas::Path::new(|path| { + path.move_to(center); + draw_hand(self.hour, 12, 0.5 * radius, offset, path); + + path.move_to(center); + draw_hand(self.minute, 60, 0.8 * radius, offset, path) + }); + + frame.stroke( + &hour_and_minute_hands, + canvas::Stroke { + width: 6.0, + color: Color::WHITE, + line_cap: canvas::LineCap::Round, + ..canvas::Stroke::default() + }, + ); + + let second_hand = canvas::Path::new(|path| { + path.move_to(center); + draw_hand(self.second, 60, 0.8 * radius, offset, path) + }); + + frame.stroke( + &second_hand, + canvas::Stroke { + width: 3.0, + color: Color::WHITE, + line_cap: canvas::LineCap::Round, + ..canvas::Stroke::default() + }, + ); + } +} + +mod time { + use iced::futures; + + pub fn every( + duration: std::time::Duration, + ) -> iced::Subscription> { + iced::Subscription::from_recipe(Every(duration)) + } + + struct Every(std::time::Duration); + + impl iced_native::subscription::Recipe for Every + where + H: std::hash::Hasher, + { + type Output = chrono::DateTime; + + fn hash(&self, state: &mut H) { + use std::hash::Hash; + + std::any::TypeId::of::().hash(state); + self.0.hash(state); + } + + fn stream( + self: Box, + _input: futures::stream::BoxStream<'static, I>, + ) -> futures::stream::BoxStream<'static, Self::Output> { + use futures::stream::StreamExt; + + async_std::stream::interval(self.0) + .map(|_| chrono::Local::now()) + .boxed() + } + } +} diff --git a/examples/clock/src/main.rs b/examples/clock/src/main.rs index d8266f069b..454ff4f0c2 100644 --- a/examples/clock/src/main.rs +++ b/examples/clock/src/main.rs @@ -1,7 +1,5 @@ -use iced::{ - canvas, executor, Application, Canvas, Color, Command, Container, Element, - Length, Point, Settings, Subscription, Vector, -}; +use clock::Clock; +use iced::{Application, Settings}; pub fn main() { Clock::run(Settings { @@ -9,185 +7,3 @@ pub fn main() { ..Settings::default() }) } - -struct Clock { - now: LocalTime, - clock: canvas::layer::Cache, -} - -#[derive(Debug, Clone, Copy)] -enum Message { - Tick(chrono::DateTime), -} - -impl Application for Clock { - type Executor = executor::Default; - type Message = Message; - - fn new() -> (Self, Command) { - ( - Clock { - now: chrono::Local::now().into(), - clock: canvas::layer::Cache::new(), - }, - Command::none(), - ) - } - - fn title(&self) -> String { - String::from("Clock - Iced") - } - - fn update(&mut self, message: Message) -> Command { - match message { - Message::Tick(local_time) => { - let now = local_time.into(); - - if now != self.now { - self.now = now; - self.clock.clear(); - } - } - } - - Command::none() - } - - fn subscription(&self) -> Subscription { - time::every(std::time::Duration::from_millis(500)).map(Message::Tick) - } - - fn view(&mut self) -> Element { - let canvas = Canvas::new() - .width(Length::Units(400)) - .height(Length::Units(400)) - .push(self.clock.with(&self.now)); - - Container::new(canvas) - .width(Length::Fill) - .height(Length::Fill) - .center_x() - .center_y() - .into() - } -} - -#[derive(Debug, PartialEq, Eq)] -struct LocalTime { - hour: u32, - minute: u32, - second: u32, -} - -impl From> for LocalTime { - fn from(date_time: chrono::DateTime) -> LocalTime { - use chrono::Timelike; - - LocalTime { - hour: date_time.hour(), - minute: date_time.minute(), - second: date_time.second(), - } - } -} - -impl canvas::Drawable for LocalTime { - fn draw(&self, frame: &mut canvas::Frame) { - let center = frame.center(); - let radius = frame.width().min(frame.height()) / 2.0; - let offset = Vector::new(center.x, center.y); - - let clock = canvas::Path::new(|path| path.circle(center, radius)); - - frame.fill( - &clock, - canvas::Fill::Color(Color::from_rgb8(0x12, 0x93, 0xD8)), - ); - - fn draw_hand( - n: u32, - total: u32, - length: f32, - offset: Vector, - path: &mut canvas::path::Builder, - ) { - let turns = n as f32 / total as f32; - let t = 2.0 * std::f32::consts::PI * (turns - 0.25); - - let x = length * t.cos(); - let y = length * t.sin(); - - path.line_to(Point::new(x, y) + offset); - } - - let hour_and_minute_hands = canvas::Path::new(|path| { - path.move_to(center); - draw_hand(self.hour, 12, 0.5 * radius, offset, path); - - path.move_to(center); - draw_hand(self.minute, 60, 0.8 * radius, offset, path) - }); - - frame.stroke( - &hour_and_minute_hands, - canvas::Stroke { - width: 6.0, - color: Color::WHITE, - line_cap: canvas::LineCap::Round, - ..canvas::Stroke::default() - }, - ); - - let second_hand = canvas::Path::new(|path| { - path.move_to(center); - draw_hand(self.second, 60, 0.8 * radius, offset, path) - }); - - frame.stroke( - &second_hand, - canvas::Stroke { - width: 3.0, - color: Color::WHITE, - line_cap: canvas::LineCap::Round, - ..canvas::Stroke::default() - }, - ); - } -} - -mod time { - use iced::futures; - - pub fn every( - duration: std::time::Duration, - ) -> iced::Subscription> { - iced::Subscription::from_recipe(Every(duration)) - } - - struct Every(std::time::Duration); - - impl iced_native::subscription::Recipe for Every - where - H: std::hash::Hasher, - { - type Output = chrono::DateTime; - - fn hash(&self, state: &mut H) { - use std::hash::Hash; - - std::any::TypeId::of::().hash(state); - self.0.hash(state); - } - - fn stream( - self: Box, - _input: futures::stream::BoxStream<'static, I>, - ) -> futures::stream::BoxStream<'static, Self::Output> { - use futures::stream::StreamExt; - - async_std::stream::interval(self.0) - .map(|_| chrono::Local::now()) - .boxed() - } - } -} diff --git a/examples/panes/Cargo.toml b/examples/panes/Cargo.toml new file mode 100644 index 0000000000..174d2cde52 --- /dev/null +++ b/examples/panes/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "panes" +version = "0.1.0" +authors = ["Héctor Ramón Jiménez "] +edition = "2018" +publish = false + +[dependencies] +iced = { path = "../..", features = ["async-std"] } +clock = { path = "../clock" } +stopwatch = { path = "../stopwatch" } diff --git a/examples/panes/README.md b/examples/panes/README.md new file mode 100644 index 0000000000..4d9fc5b979 --- /dev/null +++ b/examples/panes/README.md @@ -0,0 +1,18 @@ +## Counter + +The classic counter example explained in the [`README`](../../README.md). + +The __[`main`]__ file contains all the code of the example. + + + +You can run it with `cargo run`: +``` +cargo run --package counter +``` + +[`main`]: src/main.rs diff --git a/examples/panes/src/main.rs b/examples/panes/src/main.rs new file mode 100644 index 0000000000..c1bf991aa7 --- /dev/null +++ b/examples/panes/src/main.rs @@ -0,0 +1,95 @@ +use iced::{ + panes, Application, Command, Element, Panes, Settings, Subscription, +}; + +use clock::{self, Clock}; +use stopwatch::{self, Stopwatch}; + +pub fn main() { + Launcher::run(Settings { + antialiasing: true, + ..Settings::default() + }) +} + +struct Launcher { + panes: panes::State, +} + +#[derive(Debug)] +enum Example { + Clock(Clock), + Stopwatch(Stopwatch), +} + +#[derive(Debug, Clone)] +enum Message { + Clock(panes::Pane, clock::Message), + Stopwatch(panes::Pane, stopwatch::Message), +} + +impl Application for Launcher { + type Executor = iced::executor::Default; + type Message = Message; + + fn new() -> (Self, Command) { + let (clock, _) = Clock::new(); + let (panes, _) = panes::State::new(Example::Clock(clock)); + + dbg!(&panes); + + (Self { panes }, Command::none()) + } + + fn title(&self) -> String { + String::from("Panes - Iced") + } + + fn update(&mut self, message: Message) -> Command { + match message { + Message::Clock(pane, message) => { + if let Some(Example::Clock(clock)) = self.panes.get_mut(&pane) { + let _ = clock.update(message); + } + } + Message::Stopwatch(pane, message) => { + if let Some(Example::Stopwatch(stopwatch)) = + self.panes.get_mut(&pane) + { + let _ = stopwatch.update(message); + } + } + } + + Command::none() + } + + fn subscription(&self) -> Subscription { + Subscription::batch(self.panes.iter().map(|(pane, example)| { + match example { + Example::Clock(clock) => clock + .subscription() + .map(move |message| Message::Clock(pane, message)), + + Example::Stopwatch(stopwatch) => stopwatch + .subscription() + .map(move |message| Message::Stopwatch(pane, message)), + } + })) + } + + fn view(&mut self) -> Element { + let Self { panes } = self; + + Panes::new(panes, |pane, example| match example { + Example::Clock(clock) => clock + .view() + .map(move |message| Message::Clock(pane, message)), + + Example::Stopwatch(stopwatch) => stopwatch + .view() + .map(move |message| Message::Stopwatch(pane, message)), + }) + .into() + } +} diff --git a/examples/stopwatch/src/lib.rs b/examples/stopwatch/src/lib.rs new file mode 100644 index 0000000000..0219470ba7 --- /dev/null +++ b/examples/stopwatch/src/lib.rs @@ -0,0 +1,204 @@ +use iced::{ + button, Align, Application, Button, Column, Command, Container, Element, + HorizontalAlignment, Length, Row, Subscription, Text, +}; +use std::time::{Duration, Instant}; + +#[derive(Debug)] +pub struct Stopwatch { + duration: Duration, + state: State, + toggle: button::State, + reset: button::State, +} + +#[derive(Debug)] +enum State { + Idle, + Ticking { last_tick: Instant }, +} + +#[derive(Debug, Clone)] +pub enum Message { + Toggle, + Reset, + Tick(Instant), +} + +impl Application for Stopwatch { + type Executor = iced_futures::executor::AsyncStd; + type Message = Message; + + fn new() -> (Stopwatch, Command) { + ( + Stopwatch { + duration: Duration::default(), + state: State::Idle, + toggle: button::State::new(), + reset: button::State::new(), + }, + Command::none(), + ) + } + + fn title(&self) -> String { + String::from("Stopwatch - Iced") + } + + fn update(&mut self, message: Message) -> Command { + match message { + Message::Toggle => match self.state { + State::Idle => { + self.state = State::Ticking { + last_tick: Instant::now(), + }; + } + State::Ticking { .. } => { + self.state = State::Idle; + } + }, + Message::Tick(now) => match &mut self.state { + State::Ticking { last_tick } => { + self.duration += now - *last_tick; + *last_tick = now; + } + _ => {} + }, + Message::Reset => { + self.duration = Duration::default(); + } + } + + Command::none() + } + + fn subscription(&self) -> Subscription { + match self.state { + State::Idle => Subscription::none(), + State::Ticking { .. } => { + time::every(Duration::from_millis(10)).map(Message::Tick) + } + } + } + + fn view(&mut self) -> Element { + const MINUTE: u64 = 60; + const HOUR: u64 = 60 * MINUTE; + + let seconds = self.duration.as_secs(); + + let duration = Text::new(format!( + "{:0>2}:{:0>2}:{:0>2}.{:0>2}", + seconds / HOUR, + (seconds % HOUR) / MINUTE, + seconds % MINUTE, + self.duration.subsec_millis() / 10, + )) + .size(40); + + let button = |state, label, style| { + Button::new( + state, + Text::new(label) + .horizontal_alignment(HorizontalAlignment::Center), + ) + .min_width(80) + .padding(10) + .style(style) + }; + + let toggle_button = { + let (label, color) = match self.state { + State::Idle => ("Start", style::Button::Primary), + State::Ticking { .. } => ("Stop", style::Button::Destructive), + }; + + button(&mut self.toggle, label, color).on_press(Message::Toggle) + }; + + let reset_button = + button(&mut self.reset, "Reset", style::Button::Secondary) + .on_press(Message::Reset); + + let controls = Row::new() + .spacing(20) + .push(toggle_button) + .push(reset_button); + + let content = Column::new() + .align_items(Align::Center) + .spacing(20) + .push(duration) + .push(controls); + + Container::new(content) + .width(Length::Fill) + .height(Length::Fill) + .center_x() + .center_y() + .into() + } +} + +mod time { + use iced::futures; + + pub fn every( + duration: std::time::Duration, + ) -> iced::Subscription { + iced::Subscription::from_recipe(Every(duration)) + } + + struct Every(std::time::Duration); + + impl iced_native::subscription::Recipe for Every + where + H: std::hash::Hasher, + { + type Output = std::time::Instant; + + fn hash(&self, state: &mut H) { + use std::hash::Hash; + + std::any::TypeId::of::().hash(state); + self.0.hash(state); + } + + fn stream( + self: Box, + _input: futures::stream::BoxStream<'static, I>, + ) -> futures::stream::BoxStream<'static, Self::Output> { + use futures::stream::StreamExt; + + async_std::stream::interval(self.0) + .map(|_| std::time::Instant::now()) + .boxed() + } + } +} + +mod style { + use iced::{button, Background, Color, Vector}; + + pub enum Button { + Primary, + Secondary, + Destructive, + } + + impl button::StyleSheet for Button { + fn active(&self) -> button::Style { + button::Style { + background: Some(Background::Color(match self { + Button::Primary => Color::from_rgb(0.11, 0.42, 0.87), + Button::Secondary => Color::from_rgb(0.5, 0.5, 0.5), + Button::Destructive => Color::from_rgb(0.8, 0.2, 0.2), + })), + border_radius: 12, + shadow_offset: Vector::new(1.0, 1.0), + text_color: Color::WHITE, + ..button::Style::default() + } + } + } +} diff --git a/examples/stopwatch/src/main.rs b/examples/stopwatch/src/main.rs index d84c481744..adcdffe456 100644 --- a/examples/stopwatch/src/main.rs +++ b/examples/stopwatch/src/main.rs @@ -1,206 +1,6 @@ -use iced::{ - button, Align, Application, Button, Column, Command, Container, Element, - HorizontalAlignment, Length, Row, Settings, Subscription, Text, -}; -use std::time::{Duration, Instant}; +use iced::{Application, Settings}; +use stopwatch::Stopwatch; pub fn main() { Stopwatch::run(Settings::default()) } - -struct Stopwatch { - duration: Duration, - state: State, - toggle: button::State, - reset: button::State, -} - -enum State { - Idle, - Ticking { last_tick: Instant }, -} - -#[derive(Debug, Clone)] -enum Message { - Toggle, - Reset, - Tick(Instant), -} - -impl Application for Stopwatch { - type Executor = iced_futures::executor::AsyncStd; - type Message = Message; - - fn new() -> (Stopwatch, Command) { - ( - Stopwatch { - duration: Duration::default(), - state: State::Idle, - toggle: button::State::new(), - reset: button::State::new(), - }, - Command::none(), - ) - } - - fn title(&self) -> String { - String::from("Stopwatch - Iced") - } - - fn update(&mut self, message: Message) -> Command { - match message { - Message::Toggle => match self.state { - State::Idle => { - self.state = State::Ticking { - last_tick: Instant::now(), - }; - } - State::Ticking { .. } => { - self.state = State::Idle; - } - }, - Message::Tick(now) => match &mut self.state { - State::Ticking { last_tick } => { - self.duration += now - *last_tick; - *last_tick = now; - } - _ => {} - }, - Message::Reset => { - self.duration = Duration::default(); - } - } - - Command::none() - } - - fn subscription(&self) -> Subscription { - match self.state { - State::Idle => Subscription::none(), - State::Ticking { .. } => { - time::every(Duration::from_millis(10)).map(Message::Tick) - } - } - } - - fn view(&mut self) -> Element { - const MINUTE: u64 = 60; - const HOUR: u64 = 60 * MINUTE; - - let seconds = self.duration.as_secs(); - - let duration = Text::new(format!( - "{:0>2}:{:0>2}:{:0>2}.{:0>2}", - seconds / HOUR, - (seconds % HOUR) / MINUTE, - seconds % MINUTE, - self.duration.subsec_millis() / 10, - )) - .size(40); - - let button = |state, label, style| { - Button::new( - state, - Text::new(label) - .horizontal_alignment(HorizontalAlignment::Center), - ) - .min_width(80) - .padding(10) - .style(style) - }; - - let toggle_button = { - let (label, color) = match self.state { - State::Idle => ("Start", style::Button::Primary), - State::Ticking { .. } => ("Stop", style::Button::Destructive), - }; - - button(&mut self.toggle, label, color).on_press(Message::Toggle) - }; - - let reset_button = - button(&mut self.reset, "Reset", style::Button::Secondary) - .on_press(Message::Reset); - - let controls = Row::new() - .spacing(20) - .push(toggle_button) - .push(reset_button); - - let content = Column::new() - .align_items(Align::Center) - .spacing(20) - .push(duration) - .push(controls); - - Container::new(content) - .width(Length::Fill) - .height(Length::Fill) - .center_x() - .center_y() - .into() - } -} - -mod time { - use iced::futures; - - pub fn every( - duration: std::time::Duration, - ) -> iced::Subscription { - iced::Subscription::from_recipe(Every(duration)) - } - - struct Every(std::time::Duration); - - impl iced_native::subscription::Recipe for Every - where - H: std::hash::Hasher, - { - type Output = std::time::Instant; - - fn hash(&self, state: &mut H) { - use std::hash::Hash; - - std::any::TypeId::of::().hash(state); - self.0.hash(state); - } - - fn stream( - self: Box, - _input: futures::stream::BoxStream<'static, I>, - ) -> futures::stream::BoxStream<'static, Self::Output> { - use futures::stream::StreamExt; - - async_std::stream::interval(self.0) - .map(|_| std::time::Instant::now()) - .boxed() - } - } -} - -mod style { - use iced::{button, Background, Color, Vector}; - - pub enum Button { - Primary, - Secondary, - Destructive, - } - - impl button::StyleSheet for Button { - fn active(&self) -> button::Style { - button::Style { - background: Some(Background::Color(match self { - Button::Primary => Color::from_rgb(0.11, 0.42, 0.87), - Button::Secondary => Color::from_rgb(0.5, 0.5, 0.5), - Button::Destructive => Color::from_rgb(0.8, 0.2, 0.2), - })), - border_radius: 12, - shadow_offset: Vector::new(1.0, 1.0), - text_color: Color::WHITE, - ..button::Style::default() - } - } - } -} diff --git a/native/src/element.rs b/native/src/element.rs index 276f761460..4e7c7fc60e 100644 --- a/native/src/element.rs +++ b/native/src/element.rs @@ -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); diff --git a/native/src/lib.rs b/native/src/lib.rs index e4e7baee38..4551a98299 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -34,7 +34,7 @@ //! [`window::Renderer`]: window/trait.Renderer.html //! [`UserInterface`]: struct.UserInterface.html //! [renderer]: renderer/index.html -#![deny(missing_docs)] +//#![deny(missing_docs)] #![deny(missing_debug_implementations)] #![deny(unused_results)] #![forbid(unsafe_code)] diff --git a/native/src/widget.rs b/native/src/widget.rs index f9424b0251..d97e836cb1 100644 --- a/native/src/widget.rs +++ b/native/src/widget.rs @@ -25,6 +25,7 @@ pub mod checkbox; pub mod column; pub mod container; pub mod image; +pub mod panes; 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 panes::Panes; +#[doc(no_inline)] pub use progress_bar::ProgressBar; #[doc(no_inline)] pub use radio::Radio; diff --git a/native/src/widget/panes.rs b/native/src/widget/panes.rs new file mode 100644 index 0000000000..d69d251ecf --- /dev/null +++ b/native/src/widget/panes.rs @@ -0,0 +1,238 @@ +use crate::{ + layout, Clipboard, Element, Event, Hasher, Layout, Length, Point, Size, + Widget, +}; + +use std::collections::HashMap; + +#[allow(missing_debug_implementations)] +pub struct Panes<'a, Message, Renderer> { + state: &'a mut Internal, + elements: Vec>, + width: Length, + height: Length, +} + +impl<'a, Message, Renderer> Panes<'a, Message, Renderer> { + pub fn new( + state: &'a mut State, + view: impl Fn(Pane, &'a mut T) -> Element<'a, Message, Renderer>, + ) -> Self { + let elements = state + .panes + .iter_mut() + .map(|(pane, state)| view(*pane, state)) + .collect(); + + Self { + state: &mut state.internal, + elements, + width: Length::Fill, + height: Length::Fill, + } + } + + /// Sets the width of the [`Panes`]. + /// + /// [`Panes`]: struct.Column.html + pub fn width(mut self, width: Length) -> Self { + self.width = width; + self + } + + /// Sets the height of the [`Panes`]. + /// + /// [`Panes`]: struct.Column.html + pub fn height(mut self, height: Length) -> Self { + self.height = height; + self + } +} + +impl<'a, Message, Renderer> Widget + for Panes<'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 children = self + .elements + .iter() + .map(|element| element.layout(renderer, &limits)) + .collect(); + + layout::Node::with_children(size, children) + } + + fn draw( + &self, + renderer: &mut Renderer, + defaults: &Renderer::Defaults, + layout: Layout<'_>, + cursor_position: Point, + ) -> Renderer::Output { + renderer.draw(defaults, &self.elements, layout, cursor_position) + } + + fn hash_layout(&self, state: &mut Hasher) { + use std::hash::Hash; + + std::any::TypeId::of::>().hash(state); + self.width.hash(state); + self.height.hash(state); + self.state.layout.hash(state); + + for element in &self.elements { + element.hash_layout(state); + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct Pane(usize); + +#[derive(Debug)] +pub struct State { + panes: HashMap, + internal: Internal, +} + +#[derive(Debug)] +struct Internal { + layout: Node, + last_pane: usize, + focused_pane: Option, +} + +impl State { + 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_pane: 0, + focused_pane: None, + }, + }, + first_pane, + ) + } + + pub fn get_mut(&mut self, pane: &Pane) -> Option<&mut T> { + self.panes.get_mut(pane) + } + + pub fn iter(&self) -> impl Iterator { + self.panes.iter().map(|(pane, state)| (*pane, state)) + } + + pub fn iter_mut(&mut self) -> impl Iterator { + self.panes.iter_mut().map(|(pane, state)| (*pane, state)) + } + + pub fn focused_pane(&self) -> Option { + self.internal.focused_pane + } + + pub fn focus(&mut self, pane: Pane) { + self.internal.focused_pane = Some(pane); + } + + pub fn split_vertically(&mut self, pane: &Pane, state: T) -> Option { + let new_pane = Pane(self.internal.last_pane.checked_add(1)?); + + // TODO + + Some(new_pane) + } + + pub fn split_horizontally( + &mut self, + pane: &Pane, + state: T, + ) -> Option { + let new_pane = Pane(self.internal.last_pane.checked_add(1)?); + + // TODO + + Some(new_pane) + } +} + +#[derive(Debug, Clone, Hash)] +enum Node { + Split { + kind: Split, + ratio: u32, + a: Box, + b: Box, + }, + Pane(Pane), +} + +#[derive(Debug, Clone, Copy, Hash)] +enum Split { + Horizontal, + Vertical, +} + +/// The renderer of some [`Panes`]. +/// +/// Your [renderer] will need to implement this trait before being +/// able to use [`Panes`] in your user interface. +/// +/// [`Panes`]: struct.Panes.html +/// [renderer]: ../../renderer/index.html +pub trait Renderer: crate::Renderer + Sized { + /// Draws some [`Panes`]. + /// + /// It receives: + /// - the children of the [`Column`] + /// - the [`Layout`] of the [`Column`] and its children + /// - the cursor position + /// + /// [`Column`]: struct.Row.html + /// [`Layout`]: ../layout/struct.Layout.html + fn draw( + &mut self, + defaults: &Self::Defaults, + content: &[Element<'_, Message, Self>], + layout: Layout<'_>, + cursor_position: Point, + ) -> Self::Output; +} + +impl<'a, Message, Renderer> From> + for Element<'a, Message, Renderer> +where + Renderer: self::Renderer + 'static, + Message: 'static, +{ + fn from( + panes: Panes<'a, Message, Renderer>, + ) -> Element<'a, Message, Renderer> { + Element::new(panes) + } +} diff --git a/src/widget.rs b/src/widget.rs index 7d3a1cefe6..de3301fa68 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -30,7 +30,7 @@ mod platform { pub use iced_winit::svg::{Handle, Svg}; } - pub use iced_winit::Text; + pub use iced_winit::{panes, Panes, Text}; #[doc(no_inline)] pub use { diff --git a/wgpu/src/renderer/widget.rs b/wgpu/src/renderer/widget.rs index 84f908e7dc..9a46552eb1 100644 --- a/wgpu/src/renderer/widget.rs +++ b/wgpu/src/renderer/widget.rs @@ -2,6 +2,7 @@ mod button; mod checkbox; mod column; mod container; +mod panes; mod progress_bar; mod radio; mod row; diff --git a/wgpu/src/renderer/widget/panes.rs b/wgpu/src/renderer/widget/panes.rs new file mode 100644 index 0000000000..67f92f52d9 --- /dev/null +++ b/wgpu/src/renderer/widget/panes.rs @@ -0,0 +1,34 @@ +use crate::{Primitive, Renderer}; +use iced_native::{panes, Element, Layout, MouseCursor, Point}; + +impl panes::Renderer for Renderer { + fn draw( + &mut self, + defaults: &Self::Defaults, + content: &[Element<'_, Message, Self>], + layout: Layout<'_>, + cursor_position: Point, + ) -> Self::Output { + let mut mouse_cursor = MouseCursor::OutOfBounds; + + ( + Primitive::Group { + primitives: content + .iter() + .zip(layout.children()) + .map(|(child, layout)| { + let (primitive, new_mouse_cursor) = + child.draw(self, defaults, layout, cursor_position); + + if new_mouse_cursor > mouse_cursor { + mouse_cursor = new_mouse_cursor; + } + + primitive + }) + .collect(), + }, + mouse_cursor, + ) + } +} From b6926d9ab4ae0c45049c3a8c19616939cbe9db95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 4 Mar 2020 21:56:59 +0100 Subject: [PATCH 02/53] Improve `Debug` implementation of `cache::State` --- wgpu/src/widget/canvas/layer/cache.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/wgpu/src/widget/canvas/layer/cache.rs b/wgpu/src/widget/canvas/layer/cache.rs index 3071cce036..8f26514211 100644 --- a/wgpu/src/widget/canvas/layer/cache.rs +++ b/wgpu/src/widget/canvas/layer/cache.rs @@ -21,7 +21,6 @@ pub struct Cache { state: RefCell, } -#[derive(Debug)] enum State { Empty, Filled { @@ -99,3 +98,17 @@ where mesh } } + +impl std::fmt::Debug for State { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + State::Empty => write!(f, "Empty"), + State::Filled { mesh, bounds } => f + .debug_struct("Filled") + .field("vertices", &mesh.vertices.len()) + .field("indices", &mesh.indices.len()) + .field("bounds", bounds) + .finish(), + } + } +} From d7f32d47ba352616328f72323cad18351b326ae8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 4 Mar 2020 22:01:57 +0100 Subject: [PATCH 03/53] Compute `panes` regions and focus on click --- examples/panes/src/main.rs | 5 +- native/src/widget/panes.rs | 198 +++++++++++++++++++++++++++--- wgpu/src/renderer/widget/panes.rs | 6 +- 3 files changed, 190 insertions(+), 19 deletions(-) diff --git a/examples/panes/src/main.rs b/examples/panes/src/main.rs index c1bf991aa7..65db2b40ba 100644 --- a/examples/panes/src/main.rs +++ b/examples/panes/src/main.rs @@ -12,6 +12,7 @@ pub fn main() { }) } +#[derive(Debug)] struct Launcher { panes: panes::State, } @@ -36,8 +37,6 @@ impl Application for Launcher { let (clock, _) = Clock::new(); let (panes, _) = panes::State::new(Example::Clock(clock)); - dbg!(&panes); - (Self { panes }, Command::none()) } @@ -61,6 +60,8 @@ impl Application for Launcher { } } + dbg!(self); + Command::none() } diff --git a/native/src/widget/panes.rs b/native/src/widget/panes.rs index d69d251ecf..69b54b4745 100644 --- a/native/src/widget/panes.rs +++ b/native/src/widget/panes.rs @@ -1,6 +1,7 @@ use crate::{ - layout, Clipboard, Element, Event, Hasher, Layout, Length, Point, Size, - Widget, + input::{mouse, ButtonState}, + layout, Clipboard, Element, Event, Hasher, Layout, Length, Point, + Rectangle, Size, Widget, }; use std::collections::HashMap; @@ -8,7 +9,7 @@ use std::collections::HashMap; #[allow(missing_debug_implementations)] pub struct Panes<'a, Message, Renderer> { state: &'a mut Internal, - elements: Vec>, + elements: Vec<(Pane, Element<'a, Message, Renderer>)>, width: Length, height: Length, } @@ -21,7 +22,7 @@ impl<'a, Message, Renderer> Panes<'a, Message, Renderer> { let elements = state .panes .iter_mut() - .map(|(pane, state)| view(*pane, state)) + .map(|(pane, state)| (*pane, view(*pane, state))) .collect(); Self { @@ -71,15 +72,67 @@ where let limits = limits.width(self.width).height(self.height); let size = limits.resolve(Size::ZERO); + let regions = self.state.layout.regions(size); + let children = self .elements .iter() - .map(|element| element.layout(renderer, &limits)) + .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, + renderer: &Renderer, + clipboard: Option<&dyn Clipboard>, + ) { + match event { + Event::Mouse(mouse::Event::Input { + button: mouse::Button::Left, + 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() { + self.state.focused_pane = Some(*pane); + } + } + _ => {} + } + + 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, @@ -98,7 +151,7 @@ where self.height.hash(state); self.state.layout.hash(state); - for element in &self.elements { + for (_, element) in &self.elements { element.hash_layout(state); } } @@ -161,11 +214,7 @@ impl State { } pub fn split_vertically(&mut self, pane: &Pane, state: T) -> Option { - let new_pane = Pane(self.internal.last_pane.checked_add(1)?); - - // TODO - - Some(new_pane) + self.split(Split::Vertical, pane, state) } pub fn split_horizontally( @@ -173,9 +222,22 @@ impl State { pane: &Pane, state: T, ) -> Option { - let new_pane = Pane(self.internal.last_pane.checked_add(1)?); + self.split(Split::Horizontal, pane, state) + } + + fn split(&mut self, kind: Split, pane: &Pane, state: T) -> Option { + let node = self.internal.layout.find(pane)?; + + let new_pane = { + self.internal.last_pane = self.internal.last_pane.checked_add(1)?; + + Pane(self.internal.last_pane) + }; + + node.split(kind, new_pane); - // TODO + let _ = self.panes.insert(new_pane, state); + self.internal.focused_pane = Some(new_pane); Some(new_pane) } @@ -192,12 +254,120 @@ enum Node { Pane(Pane), } +impl Node { + pub fn find(&mut self, pane: &Pane) -> Option<&mut Node> { + match self { + Node::Split { a, b, .. } => { + if let Some(node) = a.find(pane) { + Some(node) + } else { + b.find(pane) + } + } + Node::Pane(p) => { + if p == pane { + Some(self) + } else { + None + } + } + } + } + + pub fn split(&mut self, kind: Split, new_pane: Pane) { + *self = Node::Split { + kind, + ratio: 500_000, + a: Box::new(self.clone()), + b: Box::new(Node::Pane(new_pane)), + }; + } + + pub fn regions(&self, size: Size) -> HashMap { + let mut regions = HashMap::new(); + + self.compute_regions( + &Rectangle { + x: 0.0, + y: 0.0, + width: size.width, + height: size.height, + }, + &mut regions, + ); + + regions + } + + fn compute_regions( + &self, + current: &Rectangle, + regions: &mut HashMap, + ) { + match self { + Node::Split { kind, ratio, a, b } => { + let ratio = *ratio as f32 / 1_000_000.0; + let (region_a, region_b) = kind.apply(current, ratio); + + a.compute_regions(®ion_a, regions); + b.compute_regions(®ion_b, regions); + } + Node::Pane(pane) => { + let _ = regions.insert(*pane, *current); + } + } + } +} + #[derive(Debug, Clone, Copy, Hash)] enum Split { Horizontal, Vertical, } +impl Split { + pub fn apply( + &self, + rectangle: &Rectangle, + ratio: f32, + ) -> (Rectangle, Rectangle) { + match self { + Split::Horizontal => { + let width_left = rectangle.width * ratio; + let width_right = rectangle.width - width_left; + + ( + Rectangle { + width: width_left, + ..*rectangle + }, + Rectangle { + x: rectangle.x + width_left, + width: width_right, + ..*rectangle + }, + ) + } + Split::Vertical => { + let height_top = rectangle.height * ratio; + let height_bottom = rectangle.height - height_top; + + ( + Rectangle { + height: height_top, + ..*rectangle + }, + Rectangle { + x: rectangle.x + height_top, + height: height_bottom, + ..*rectangle + }, + ) + } + } + } +} + /// The renderer of some [`Panes`]. /// /// Your [renderer] will need to implement this trait before being @@ -218,7 +388,7 @@ pub trait Renderer: crate::Renderer + Sized { fn draw( &mut self, defaults: &Self::Defaults, - content: &[Element<'_, Message, Self>], + content: &[(Pane, Element<'_, Message, Self>)], layout: Layout<'_>, cursor_position: Point, ) -> Self::Output; diff --git a/wgpu/src/renderer/widget/panes.rs b/wgpu/src/renderer/widget/panes.rs index 67f92f52d9..74e588953d 100644 --- a/wgpu/src/renderer/widget/panes.rs +++ b/wgpu/src/renderer/widget/panes.rs @@ -5,7 +5,7 @@ impl panes::Renderer for Renderer { fn draw( &mut self, defaults: &Self::Defaults, - content: &[Element<'_, Message, Self>], + content: &[(panes::Pane, Element<'_, Message, Self>)], layout: Layout<'_>, cursor_position: Point, ) -> Self::Output { @@ -16,9 +16,9 @@ impl panes::Renderer for Renderer { primitives: content .iter() .zip(layout.children()) - .map(|(child, layout)| { + .map(|((_, pane), layout)| { let (primitive, new_mouse_cursor) = - child.draw(self, defaults, layout, cursor_position); + pane.draw(self, defaults, layout, cursor_position); if new_mouse_cursor > mouse_cursor { mouse_cursor = new_mouse_cursor; From 58adfcd5145d571739eda8cc655aa2f95814e541 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 4 Mar 2020 22:31:37 +0100 Subject: [PATCH 04/53] Fix `Split::apply` on vertical splits --- native/src/widget/panes.rs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/native/src/widget/panes.rs b/native/src/widget/panes.rs index 69b54b4745..22fa2b5a5b 100644 --- a/native/src/widget/panes.rs +++ b/native/src/widget/panes.rs @@ -160,6 +160,12 @@ where #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct Pane(usize); +impl Pane { + pub fn index(&self) -> usize { + self.0 + } +} + #[derive(Debug)] pub struct State { panes: HashMap, @@ -225,7 +231,12 @@ impl State { self.split(Split::Horizontal, pane, state) } - fn split(&mut self, kind: Split, pane: &Pane, state: T) -> Option { + pub fn split( + &mut self, + kind: Split, + pane: &Pane, + state: T, + ) -> Option { let node = self.internal.layout.find(pane)?; let new_pane = { @@ -320,13 +331,13 @@ impl Node { } #[derive(Debug, Clone, Copy, Hash)] -enum Split { +pub enum Split { Horizontal, Vertical, } impl Split { - pub fn apply( + fn apply( &self, rectangle: &Rectangle, ratio: f32, @@ -358,7 +369,7 @@ impl Split { ..*rectangle }, Rectangle { - x: rectangle.x + height_top, + y: rectangle.y + height_top, height: height_bottom, ..*rectangle }, From cc310f71cc11dca8fb0d144181b75a68ed3eb82a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 4 Mar 2020 22:31:59 +0100 Subject: [PATCH 05/53] Add split hotkeys to `panes` example --- examples/panes/Cargo.toml | 1 + examples/panes/src/main.rs | 97 +++++++++++++++++++++++++++++++++----- 2 files changed, 87 insertions(+), 11 deletions(-) diff --git a/examples/panes/Cargo.toml b/examples/panes/Cargo.toml index 174d2cde52..dc94cc2c40 100644 --- a/examples/panes/Cargo.toml +++ b/examples/panes/Cargo.toml @@ -7,5 +7,6 @@ publish = false [dependencies] iced = { path = "../..", features = ["async-std"] } +iced_native = { path = "../../native" } clock = { path = "../clock" } stopwatch = { path = "../stopwatch" } diff --git a/examples/panes/src/main.rs b/examples/panes/src/main.rs index 65db2b40ba..50b21fc50f 100644 --- a/examples/panes/src/main.rs +++ b/examples/panes/src/main.rs @@ -1,6 +1,7 @@ use iced::{ panes, Application, Command, Element, Panes, Settings, Subscription, }; +use iced_native::input::keyboard; use clock::{self, Clock}; use stopwatch::{self, Stopwatch}; @@ -27,6 +28,7 @@ enum Example { enum Message { Clock(panes::Pane, clock::Message), Stopwatch(panes::Pane, stopwatch::Message), + Split(panes::Split), } impl Application for Launcher { @@ -58,6 +60,21 @@ impl Application for Launcher { let _ = stopwatch.update(message); } } + Message::Split(kind) => { + if let Some(pane) = self.panes.focused_pane() { + let state = if pane.index() % 2 == 0 { + let (stopwatch, _) = Stopwatch::new(); + + Example::Stopwatch(stopwatch) + } else { + let (clock, _) = Clock::new(); + + Example::Clock(clock) + }; + + self.panes.split(kind, &pane, state); + } + } } dbg!(self); @@ -66,17 +83,26 @@ impl Application for Launcher { } fn subscription(&self) -> Subscription { - Subscription::batch(self.panes.iter().map(|(pane, example)| { - match example { - Example::Clock(clock) => clock - .subscription() - .map(move |message| Message::Clock(pane, message)), - - Example::Stopwatch(stopwatch) => stopwatch - .subscription() - .map(move |message| Message::Stopwatch(pane, message)), - } - })) + let panes_subscriptions = + Subscription::batch(self.panes.iter().map(|(pane, example)| { + match example { + Example::Clock(clock) => clock + .subscription() + .map(move |message| Message::Clock(pane, message)), + + Example::Stopwatch(stopwatch) => stopwatch + .subscription() + .map(move |message| Message::Stopwatch(pane, message)), + } + })); + + Subscription::batch(vec![ + events::key_released(keyboard::KeyCode::H) + .map(|_| Message::Split(panes::Split::Horizontal)), + events::key_released(keyboard::KeyCode::V) + .map(|_| Message::Split(panes::Split::Vertical)), + panes_subscriptions, + ]) } fn view(&mut self) -> Element { @@ -94,3 +120,52 @@ impl Application for Launcher { .into() } } + +mod events { + use iced_native::{ + futures::{ + self, + stream::{BoxStream, StreamExt}, + }, + input::{keyboard, ButtonState}, + subscription, Event, Hasher, Subscription, + }; + + pub fn key_released(key_code: keyboard::KeyCode) -> Subscription<()> { + Subscription::from_recipe(KeyReleased { key_code }) + } + + struct KeyReleased { + key_code: keyboard::KeyCode, + } + + impl subscription::Recipe for KeyReleased { + type Output = (); + + fn hash(&self, state: &mut Hasher) { + use std::hash::Hash; + + std::any::TypeId::of::().hash(state); + self.key_code.hash(state); + } + + fn stream( + self: Box, + events: subscription::EventStream, + ) -> BoxStream<'static, Self::Output> { + events + .filter(move |event| match event { + Event::Keyboard(keyboard::Event::Input { + key_code, + state: ButtonState::Released, + .. + }) if *key_code == self.key_code => { + futures::future::ready(true) + } + _ => futures::future::ready(false), + }) + .map(|_| ()) + .boxed() + } + } +} From a6531c840b97b1d30af5153c01fda69d09f43a08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 5 Mar 2020 02:08:53 +0100 Subject: [PATCH 06/53] Implement `Subscription::with` --- examples/panes/src/main.rs | 13 +++---- futures/src/subscription.rs | 70 +++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 6 deletions(-) diff --git a/examples/panes/src/main.rs b/examples/panes/src/main.rs index 50b21fc50f..b34ce205ba 100644 --- a/examples/panes/src/main.rs +++ b/examples/panes/src/main.rs @@ -77,8 +77,6 @@ impl Application for Launcher { } } - dbg!(self); - Command::none() } @@ -88,11 +86,14 @@ impl Application for Launcher { match example { Example::Clock(clock) => clock .subscription() - .map(move |message| Message::Clock(pane, message)), + .with(pane) + .map(|(pane, message)| Message::Clock(pane, message)), - Example::Stopwatch(stopwatch) => stopwatch - .subscription() - .map(move |message| Message::Stopwatch(pane, message)), + Example::Stopwatch(stopwatch) => { + stopwatch.subscription().with(pane).map( + |(pane, message)| Message::Stopwatch(pane, message), + ) + } } })); diff --git a/futures/src/subscription.rs b/futures/src/subscription.rs index b68444cd34..8eccb7be0d 100644 --- a/futures/src/subscription.rs +++ b/futures/src/subscription.rs @@ -72,6 +72,34 @@ where self.recipes } + /// Adds a value to the [`Subscription`] context. + /// + /// The value will be part of the identity of a [`Subscription`]. + /// + /// This is necessary if you want to use multiple instances of the same + /// [`Subscription`] to produce different kinds of messages based on some + /// external data. + /// + /// [`Subscription`]: struct.Subscription.html + pub fn with(mut self, value: T) -> Subscription + where + H: 'static, + E: 'static, + O: 'static, + T: std::hash::Hash + Clone + Send + Sync + 'static, + { + Subscription { + recipes: self + .recipes + .drain(..) + .map(|recipe| { + Box::new(With::new(recipe, value.clone())) + as Box> + }) + .collect(), + } + } + /// Transforms the [`Subscription`] output with the given function. /// /// [`Subscription`]: struct.Subscription.html @@ -187,3 +215,45 @@ where .boxed() } } + +struct With { + recipe: Box>, + value: B, +} + +impl With { + fn new(recipe: Box>, value: B) -> Self { + With { recipe, value } + } +} + +impl Recipe for With +where + A: 'static, + B: 'static + std::hash::Hash + Clone + Send + Sync, + H: std::hash::Hasher, +{ + type Output = (B, A); + + fn hash(&self, state: &mut H) { + use std::hash::Hash; + + std::any::TypeId::of::().hash(state); + self.value.hash(state); + self.recipe.hash(state); + } + + fn stream( + self: Box, + input: BoxStream<'static, E>, + ) -> futures::stream::BoxStream<'static, Self::Output> { + use futures::StreamExt; + + let value = self.value; + + self.recipe + .stream(input) + .map(move |element| (value.clone(), element)) + .boxed() + } +} From 15fad17f373c0aeb023a879f5e38440fdd944eca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 5 Mar 2020 03:12:45 +0100 Subject: [PATCH 07/53] Implement `panes::State::close` --- examples/panes/src/main.rs | 7 ++++++ native/src/widget/panes.rs | 50 +++++++++++++++++++++++++++++++++++--- 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/examples/panes/src/main.rs b/examples/panes/src/main.rs index b34ce205ba..34206a2c0a 100644 --- a/examples/panes/src/main.rs +++ b/examples/panes/src/main.rs @@ -29,6 +29,7 @@ enum Message { Clock(panes::Pane, clock::Message), Stopwatch(panes::Pane, stopwatch::Message), Split(panes::Split), + Close, } impl Application for Launcher { @@ -75,6 +76,11 @@ impl Application for Launcher { self.panes.split(kind, &pane, state); } } + Message::Close => { + if let Some(pane) = self.panes.focused_pane() { + self.panes.close(&pane); + } + } } Command::none() @@ -102,6 +108,7 @@ impl Application for Launcher { .map(|_| Message::Split(panes::Split::Horizontal)), events::key_released(keyboard::KeyCode::V) .map(|_| Message::Split(panes::Split::Vertical)), + events::key_released(keyboard::KeyCode::Q).map(|_| Message::Close), panes_subscriptions, ]) } diff --git a/native/src/widget/panes.rs b/native/src/widget/panes.rs index 22fa2b5a5b..2ffb2226db 100644 --- a/native/src/widget/panes.rs +++ b/native/src/widget/panes.rs @@ -199,6 +199,10 @@ impl State { ) } + pub fn len(&self) -> usize { + self.panes.len() + } + pub fn get_mut(&mut self, pane: &Pane) -> Option<&mut T> { self.panes.get_mut(pane) } @@ -252,6 +256,15 @@ impl State { Some(new_pane) } + + pub fn close(&mut self, pane: &Pane) -> Option { + if let Some(sibling) = self.internal.layout.remove(pane) { + self.internal.focused_pane = Some(sibling); + self.panes.remove(pane) + } else { + None + } + } } #[derive(Debug, Clone, Hash)] @@ -266,7 +279,7 @@ enum Node { } impl Node { - pub fn find(&mut self, pane: &Pane) -> Option<&mut Node> { + fn find(&mut self, pane: &Pane) -> Option<&mut Node> { match self { Node::Split { a, b, .. } => { if let Some(node) = a.find(pane) { @@ -285,7 +298,7 @@ impl Node { } } - pub fn split(&mut self, kind: Split, new_pane: Pane) { + fn split(&mut self, kind: Split, new_pane: Pane) { *self = Node::Split { kind, ratio: 500_000, @@ -294,6 +307,23 @@ impl Node { }; } + fn remove(&mut self, pane: &Pane) -> Option { + 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, size: Size) -> HashMap { let mut regions = HashMap::new(); @@ -310,6 +340,20 @@ impl Node { regions } + fn pane(&self) -> Option { + match self { + Node::Split { .. } => None, + Node::Pane(pane) => Some(*pane), + } + } + + fn first_pane(&self) -> Pane { + match self { + Node::Split { a, .. } => a.first_pane(), + Node::Pane(pane) => *pane, + } + } + fn compute_regions( &self, current: &Rectangle, @@ -330,7 +374,7 @@ impl Node { } } -#[derive(Debug, Clone, Copy, Hash)] +#[derive(Debug, Clone, Hash, PartialEq, Eq)] pub enum Split { Horizontal, Vertical, From 6151c528241d0a6ece88e6e664df1b50f8174ecb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 10 Mar 2020 02:57:13 +0100 Subject: [PATCH 08/53] Rename `Panes` widget to `PaneGrid` --- Cargo.toml | 2 +- examples/{panes => pane_grid}/Cargo.toml | 2 +- examples/{panes => pane_grid}/README.md | 0 examples/{panes => pane_grid}/src/main.rs | 18 +++++++++--------- native/src/widget.rs | 4 ++-- native/src/widget/{panes.rs => pane_grid.rs} | 12 ++++++------ src/widget.rs | 2 +- wgpu/src/renderer/widget.rs | 2 +- .../renderer/widget/{panes.rs => pane_grid.rs} | 6 +++--- 9 files changed, 24 insertions(+), 24 deletions(-) rename examples/{panes => pane_grid}/Cargo.toml (94%) rename examples/{panes => pane_grid}/README.md (100%) rename examples/{panes => pane_grid}/src/main.rs (90%) rename native/src/widget/{panes.rs => pane_grid.rs} (97%) rename wgpu/src/renderer/widget/{panes.rs => pane_grid.rs} (83%) diff --git a/Cargo.toml b/Cargo.toml index 3b79abdb75..37b20ec40e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,7 +44,7 @@ members = [ "examples/events", "examples/geometry", "examples/integration", - "examples/panes", + "examples/pane_grid", "examples/pokedex", "examples/progress_bar", "examples/solar_system", diff --git a/examples/panes/Cargo.toml b/examples/pane_grid/Cargo.toml similarity index 94% rename from examples/panes/Cargo.toml rename to examples/pane_grid/Cargo.toml index dc94cc2c40..6d8573bd04 100644 --- a/examples/panes/Cargo.toml +++ b/examples/pane_grid/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "panes" +name = "pane_grid" version = "0.1.0" authors = ["Héctor Ramón Jiménez "] edition = "2018" diff --git a/examples/panes/README.md b/examples/pane_grid/README.md similarity index 100% rename from examples/panes/README.md rename to examples/pane_grid/README.md diff --git a/examples/panes/src/main.rs b/examples/pane_grid/src/main.rs similarity index 90% rename from examples/panes/src/main.rs rename to examples/pane_grid/src/main.rs index 34206a2c0a..b103afc8f7 100644 --- a/examples/panes/src/main.rs +++ b/examples/pane_grid/src/main.rs @@ -1,5 +1,5 @@ use iced::{ - panes, Application, Command, Element, Panes, Settings, Subscription, + pane_grid, Application, Command, Element, PaneGrid, Settings, Subscription, }; use iced_native::input::keyboard; @@ -15,7 +15,7 @@ pub fn main() { #[derive(Debug)] struct Launcher { - panes: panes::State, + panes: pane_grid::State, } #[derive(Debug)] @@ -26,9 +26,9 @@ enum Example { #[derive(Debug, Clone)] enum Message { - Clock(panes::Pane, clock::Message), - Stopwatch(panes::Pane, stopwatch::Message), - Split(panes::Split), + Clock(pane_grid::Pane, clock::Message), + Stopwatch(pane_grid::Pane, stopwatch::Message), + Split(pane_grid::Split), Close, } @@ -38,7 +38,7 @@ impl Application for Launcher { fn new() -> (Self, Command) { let (clock, _) = Clock::new(); - let (panes, _) = panes::State::new(Example::Clock(clock)); + let (panes, _) = pane_grid::State::new(Example::Clock(clock)); (Self { panes }, Command::none()) } @@ -105,9 +105,9 @@ impl Application for Launcher { Subscription::batch(vec![ events::key_released(keyboard::KeyCode::H) - .map(|_| Message::Split(panes::Split::Horizontal)), + .map(|_| Message::Split(pane_grid::Split::Horizontal)), events::key_released(keyboard::KeyCode::V) - .map(|_| Message::Split(panes::Split::Vertical)), + .map(|_| Message::Split(pane_grid::Split::Vertical)), events::key_released(keyboard::KeyCode::Q).map(|_| Message::Close), panes_subscriptions, ]) @@ -116,7 +116,7 @@ impl Application for Launcher { fn view(&mut self) -> Element { let Self { panes } = self; - Panes::new(panes, |pane, example| match example { + PaneGrid::new(panes, |pane, example| match example { Example::Clock(clock) => clock .view() .map(move |message| Message::Clock(pane, message)), diff --git a/native/src/widget.rs b/native/src/widget.rs index d97e836cb1..88f819c99e 100644 --- a/native/src/widget.rs +++ b/native/src/widget.rs @@ -25,7 +25,7 @@ pub mod checkbox; pub mod column; pub mod container; pub mod image; -pub mod panes; +pub mod pane_grid; pub mod progress_bar; pub mod radio; pub mod row; @@ -47,7 +47,7 @@ pub use container::Container; #[doc(no_inline)] pub use image::Image; #[doc(no_inline)] -pub use panes::Panes; +pub use pane_grid::PaneGrid; #[doc(no_inline)] pub use progress_bar::ProgressBar; #[doc(no_inline)] diff --git a/native/src/widget/panes.rs b/native/src/widget/pane_grid.rs similarity index 97% rename from native/src/widget/panes.rs rename to native/src/widget/pane_grid.rs index 2ffb2226db..a03b7fcfa3 100644 --- a/native/src/widget/panes.rs +++ b/native/src/widget/pane_grid.rs @@ -7,14 +7,14 @@ use crate::{ use std::collections::HashMap; #[allow(missing_debug_implementations)] -pub struct Panes<'a, Message, Renderer> { +pub struct PaneGrid<'a, Message, Renderer> { state: &'a mut Internal, elements: Vec<(Pane, Element<'a, Message, Renderer>)>, width: Length, height: Length, } -impl<'a, Message, Renderer> Panes<'a, Message, Renderer> { +impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { pub fn new( state: &'a mut State, view: impl Fn(Pane, &'a mut T) -> Element<'a, Message, Renderer>, @@ -51,7 +51,7 @@ impl<'a, Message, Renderer> Panes<'a, Message, Renderer> { } impl<'a, Message, Renderer> Widget - for Panes<'a, Message, Renderer> + for PaneGrid<'a, Message, Renderer> where Renderer: self::Renderer + 'static, Message: 'static, @@ -146,7 +146,7 @@ where fn hash_layout(&self, state: &mut Hasher) { use std::hash::Hash; - std::any::TypeId::of::>().hash(state); + std::any::TypeId::of::>().hash(state); self.width.hash(state); self.height.hash(state); self.state.layout.hash(state); @@ -449,14 +449,14 @@ pub trait Renderer: crate::Renderer + Sized { ) -> Self::Output; } -impl<'a, Message, Renderer> From> +impl<'a, Message, Renderer> From> for Element<'a, Message, Renderer> where Renderer: self::Renderer + 'static, Message: 'static, { fn from( - panes: Panes<'a, Message, Renderer>, + panes: PaneGrid<'a, Message, Renderer>, ) -> Element<'a, Message, Renderer> { Element::new(panes) } diff --git a/src/widget.rs b/src/widget.rs index de3301fa68..cb099a65b6 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -30,7 +30,7 @@ mod platform { pub use iced_winit::svg::{Handle, Svg}; } - pub use iced_winit::{panes, Panes, Text}; + pub use iced_winit::{pane_grid, PaneGrid, Text}; #[doc(no_inline)] pub use { diff --git a/wgpu/src/renderer/widget.rs b/wgpu/src/renderer/widget.rs index 9a46552eb1..37421fbef9 100644 --- a/wgpu/src/renderer/widget.rs +++ b/wgpu/src/renderer/widget.rs @@ -2,7 +2,7 @@ mod button; mod checkbox; mod column; mod container; -mod panes; +mod pane_grid; mod progress_bar; mod radio; mod row; diff --git a/wgpu/src/renderer/widget/panes.rs b/wgpu/src/renderer/widget/pane_grid.rs similarity index 83% rename from wgpu/src/renderer/widget/panes.rs rename to wgpu/src/renderer/widget/pane_grid.rs index 74e588953d..022fea708c 100644 --- a/wgpu/src/renderer/widget/panes.rs +++ b/wgpu/src/renderer/widget/pane_grid.rs @@ -1,11 +1,11 @@ use crate::{Primitive, Renderer}; -use iced_native::{panes, Element, Layout, MouseCursor, Point}; +use iced_native::{pane_grid, Element, Layout, MouseCursor, Point}; -impl panes::Renderer for Renderer { +impl pane_grid::Renderer for Renderer { fn draw( &mut self, defaults: &Self::Defaults, - content: &[(panes::Pane, Element<'_, Message, Self>)], + content: &[(pane_grid::Pane, Element<'_, Message, Self>)], layout: Layout<'_>, cursor_position: Point, ) -> Self::Output { From 4e0e50ae273c24c8cfba7f190c2b69b2f9f91c54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 10 Mar 2020 06:46:11 +0100 Subject: [PATCH 09/53] Fix `Debug` implementation of `layer::cache::State` --- wgpu/src/widget/canvas/layer/cache.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/wgpu/src/widget/canvas/layer/cache.rs b/wgpu/src/widget/canvas/layer/cache.rs index be08d48d8b..f700245912 100644 --- a/wgpu/src/widget/canvas/layer/cache.rs +++ b/wgpu/src/widget/canvas/layer/cache.rs @@ -101,10 +101,9 @@ impl std::fmt::Debug for State { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { State::Empty => write!(f, "Empty"), - State::Filled { mesh, bounds } => f + State::Filled { primitive, bounds } => f .debug_struct("Filled") - .field("vertices", &mesh.vertices.len()) - .field("indices", &mesh.indices.len()) + .field("primitive", primitive) .field("bounds", bounds) .finish(), } From ed7c327b488da471dab6df210254d45983890cdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 10 Mar 2020 06:46:58 +0100 Subject: [PATCH 10/53] Implement `Default` for `keyboard::ModifiersState` --- native/src/input/keyboard/modifiers_state.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/native/src/input/keyboard/modifiers_state.rs b/native/src/input/keyboard/modifiers_state.rs index 4e3794b358..3058c06536 100644 --- a/native/src/input/keyboard/modifiers_state.rs +++ b/native/src/input/keyboard/modifiers_state.rs @@ -1,5 +1,5 @@ /// The current state of the keyboard modifiers. -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Default)] pub struct ModifiersState { /// Whether a shift key is pressed pub shift: bool, From eb070b965294707100b16472980f4794162cbc36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 10 Mar 2020 06:47:32 +0100 Subject: [PATCH 11/53] Draft drag and drop support for `PaneGrid` --- native/src/widget/pane_grid.rs | 164 +++++++++++++++++++++----- wgpu/src/renderer/widget/pane_grid.rs | 80 ++++++++++--- 2 files changed, 197 insertions(+), 47 deletions(-) diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index a03b7fcfa3..08ec046af6 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -1,5 +1,5 @@ use crate::{ - input::{mouse, ButtonState}, + input::{keyboard, mouse, ButtonState}, layout, Clipboard, Element, Event, Hasher, Layout, Length, Point, Rectangle, Size, Widget, }; @@ -12,6 +12,7 @@ pub struct PaneGrid<'a, Message, Renderer> { elements: Vec<(Pane, Element<'a, Message, Renderer>)>, width: Length, height: Length, + on_drop: Option Message>>, } impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { @@ -30,6 +31,7 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { elements, width: Length::Fill, height: Length::Fill, + on_drop: None, } } @@ -48,6 +50,17 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { self.height = height; self } + + pub fn on_drop(mut self, f: impl Fn(Drop) -> Message + 'static) -> Self { + self.on_drop = Some(Box::new(f)); + self + } +} + +#[derive(Debug, Clone, Copy)] +pub struct Drop { + pub pane: Pane, + pub target: Pane, } impl<'a, Message, Renderer> Widget @@ -105,32 +118,76 @@ where match event { Event::Mouse(mouse::Event::Input { button: mouse::Button::Left, - 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() { - self.state.focused_pane = Some(*pane); + 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() { + self.state.focus = if self.on_drop.is_some() + && self.state.modifiers.alt + { + Some(Focus::Dragging(*pane)) + } else { + Some(Focus::Idle(*pane)) + } + } } + ButtonState::Released => { + if let Some(on_drop) = &self.on_drop { + if let Some(Focus::Dragging(pane)) = self.state.focus { + let mut dropped_region = self + .elements + .iter() + .zip(layout.children()) + .filter(|(_, layout)| { + layout.bounds().contains(cursor_position) + }); + + if let Some(((target, _), _)) = + dropped_region.next() + { + if pane != *target { + messages.push(on_drop(Drop { + pane, + target: *target, + })); + } + } + + self.state.focus = Some(Focus::Idle(pane)); + } + } + } + }, + Event::Keyboard(keyboard::Event::Input { modifiers, .. }) => { + self.state.modifiers = modifiers; } _ => {} } - self.elements.iter_mut().zip(layout.children()).for_each( - |((_, pane), layout)| { - pane.widget.on_event( - event.clone(), - layout, - cursor_position, - messages, - renderer, - clipboard, - ) - }, - ); + match self.state.focus { + Some(Focus::Dragging(_)) => {} + _ => { + 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( @@ -140,7 +197,18 @@ where layout: Layout<'_>, cursor_position: Point, ) -> Renderer::Output { - renderer.draw(defaults, &self.elements, layout, cursor_position) + let dragging = match self.state.focus { + Some(Focus::Dragging(pane)) => Some(pane), + _ => None, + }; + + renderer.draw( + defaults, + &self.elements, + dragging, + layout, + cursor_position, + ) } fn hash_layout(&self, state: &mut Hasher) { @@ -176,7 +244,14 @@ pub struct State { struct Internal { layout: Node, last_pane: usize, - focused_pane: Option, + focus: Option, + modifiers: keyboard::ModifiersState, +} + +#[derive(Debug)] +enum Focus { + Idle(Pane), + Dragging(Pane), } impl State { @@ -192,7 +267,8 @@ impl State { internal: Internal { layout: Node::Pane(first_pane), last_pane: 0, - focused_pane: None, + focus: None, + modifiers: keyboard::ModifiersState::default(), }, }, first_pane, @@ -216,11 +292,15 @@ impl State { } pub fn focused_pane(&self) -> Option { - self.internal.focused_pane + match self.internal.focus { + Some(Focus::Idle(pane)) => Some(pane), + Some(Focus::Dragging(_)) => None, + None => None, + } } pub fn focus(&mut self, pane: Pane) { - self.internal.focused_pane = Some(pane); + self.internal.focus = Some(Focus::Idle(pane)); } pub fn split_vertically(&mut self, pane: &Pane, state: T) -> Option { @@ -252,14 +332,27 @@ impl State { node.split(kind, new_pane); let _ = self.panes.insert(new_pane, state); - self.internal.focused_pane = Some(new_pane); + self.internal.focus = Some(Focus::Idle(new_pane)); Some(new_pane) } + 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); + } + } + }); + } + pub fn close(&mut self, pane: &Pane) -> Option { if let Some(sibling) = self.internal.layout.remove(pane) { - self.internal.focused_pane = Some(sibling); + self.internal.focus = Some(Focus::Idle(sibling)); self.panes.remove(pane) } else { None @@ -307,6 +400,18 @@ impl Node { }; } + fn update(&mut self, f: &impl Fn(&mut Node)) { + match self { + Node::Split { a, b, .. } => { + a.update(f); + b.update(f); + } + _ => {} + } + + f(self); + } + fn remove(&mut self, pane: &Pane) -> Option { match self { Node::Split { a, b, .. } => { @@ -444,6 +549,7 @@ pub trait Renderer: crate::Renderer + Sized { &mut self, defaults: &Self::Defaults, content: &[(Pane, Element<'_, Message, Self>)], + dragging: Option, layout: Layout<'_>, cursor_position: Point, ) -> Self::Output; diff --git a/wgpu/src/renderer/widget/pane_grid.rs b/wgpu/src/renderer/widget/pane_grid.rs index 022fea708c..8fb4a1a95f 100644 --- a/wgpu/src/renderer/widget/pane_grid.rs +++ b/wgpu/src/renderer/widget/pane_grid.rs @@ -1,34 +1,78 @@ use crate::{Primitive, Renderer}; -use iced_native::{pane_grid, Element, Layout, MouseCursor, Point}; +use iced_native::{ + pane_grid::{self, Pane}, + Element, Layout, MouseCursor, Point, Rectangle, Vector, +}; impl pane_grid::Renderer for Renderer { fn draw( &mut self, defaults: &Self::Defaults, - content: &[(pane_grid::Pane, Element<'_, Message, Self>)], + content: &[(Pane, Element<'_, Message, Self>)], + dragging: Option, layout: Layout<'_>, cursor_position: Point, ) -> Self::Output { let mut mouse_cursor = MouseCursor::OutOfBounds; + let mut dragged_pane = None; + + let mut panes: Vec<_> = content + .iter() + .zip(layout.children()) + .enumerate() + .map(|(i, ((id, pane), layout))| { + let (primitive, new_mouse_cursor) = + pane.draw(self, defaults, layout, cursor_position); + + if new_mouse_cursor > mouse_cursor { + mouse_cursor = new_mouse_cursor; + } + + if Some(*id) == dragging { + dragged_pane = Some((i, layout)); + } + + primitive + }) + .collect(); + + let primitives = if let Some((index, layout)) = dragged_pane { + let pane = panes.remove(index); + let bounds = layout.bounds(); + + // TODO: Fix once proper layering is implemented. + // This is a pretty hacky way to achieve layering. + let clip = Primitive::Clip { + bounds: Rectangle { + x: cursor_position.x - bounds.width / 2.0, + y: cursor_position.y - bounds.height / 2.0, + width: bounds.width + 0.5, + height: bounds.height + 0.5, + }, + offset: Vector::new(0, 0), + content: Box::new(Primitive::Cached { + origin: Point::new( + cursor_position.x - bounds.x - bounds.width / 2.0, + cursor_position.y - bounds.y - bounds.height / 2.0, + ), + cache: std::sync::Arc::new(pane), + }), + }; + + panes.push(clip); + + panes + } else { + panes + }; ( - Primitive::Group { - primitives: content - .iter() - .zip(layout.children()) - .map(|((_, pane), layout)| { - let (primitive, new_mouse_cursor) = - pane.draw(self, defaults, layout, cursor_position); - - if new_mouse_cursor > mouse_cursor { - mouse_cursor = new_mouse_cursor; - } - - primitive - }) - .collect(), + Primitive::Group { primitives }, + if dragging.is_some() { + MouseCursor::Grabbing + } else { + mouse_cursor }, - mouse_cursor, ) } } From f11397c31a4139fedb90671a5d154c17749f2a7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 10 Mar 2020 06:49:25 +0100 Subject: [PATCH 12/53] Clip `scrollable` primitives only when necessary --- wgpu/src/renderer/widget/scrollable.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/wgpu/src/renderer/widget/scrollable.rs b/wgpu/src/renderer/widget/scrollable.rs index bfee7411b0..732523e3f5 100644 --- a/wgpu/src/renderer/widget/scrollable.rs +++ b/wgpu/src/renderer/widget/scrollable.rs @@ -58,14 +58,14 @@ impl scrollable::Renderer for Renderer { style_sheet: &Self::Style, (content, mouse_cursor): Self::Output, ) -> Self::Output { - let clip = Primitive::Clip { - bounds, - offset: Vector::new(0, offset), - content: Box::new(content), - }; - ( if let Some(scrollbar) = scrollbar { + let clip = Primitive::Clip { + bounds, + offset: Vector::new(0, offset), + content: Box::new(content), + }; + let style = if state.is_scroller_grabbed() { style_sheet.dragging() } else if is_mouse_over_scrollbar { @@ -115,7 +115,7 @@ impl scrollable::Renderer for Renderer { primitives: vec![clip, scrollbar, scroller], } } else { - clip + content }, if is_mouse_over_scrollbar || state.is_scroller_grabbed() { MouseCursor::Idle From df6e3f8da921a98c9a1e75a1790885a07485ee45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 11 Mar 2020 23:25:00 +0100 Subject: [PATCH 13/53] Expose `pane_grid::Focus` for state-based styling --- examples/pane_grid/src/main.rs | 2 +- native/src/widget/pane_grid.rs | 115 +++++++++++++++++++++++++-------- 2 files changed, 88 insertions(+), 29 deletions(-) diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs index b103afc8f7..eb8aaa5152 100644 --- a/examples/pane_grid/src/main.rs +++ b/examples/pane_grid/src/main.rs @@ -116,7 +116,7 @@ impl Application for Launcher { fn view(&mut self) -> Element { let Self { panes } = self; - PaneGrid::new(panes, |pane, example| match example { + PaneGrid::new(panes, |pane, example, _| match example { Example::Clock(clock) => clock .view() .map(move |message| Message::Clock(pane, message)), diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 08ec046af6..7fc1217681 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -18,13 +18,31 @@ pub struct PaneGrid<'a, Message, Renderer> { impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { pub fn new( state: &'a mut State, - view: impl Fn(Pane, &'a mut T) -> Element<'a, Message, Renderer>, + view: impl Fn( + Pane, + &'a mut T, + Option, + ) -> Element<'a, Message, Renderer>, ) -> Self { - let elements = state - .panes - .iter_mut() - .map(|(pane, state)| (*pane, view(*pane, state))) - .collect(); + let elements = { + let focused_pane = state.internal.focused_pane; + + state + .panes + .iter_mut() + .map(move |(pane, pane_state)| { + let focus = match focused_pane { + FocusedPane::Some { + pane: focused_pane, + focus, + } if *pane == focused_pane => Some(focus), + _ => None, + }; + + (*pane, view(*pane, pane_state, focus)) + }) + .collect() + }; Self { state: &mut state.internal, @@ -129,18 +147,28 @@ where ); if let Some(((pane, _), _)) = clicked_region.next() { - self.state.focus = if self.on_drop.is_some() + self.state.focused_pane = if self.on_drop.is_some() && self.state.modifiers.alt { - Some(Focus::Dragging(*pane)) + FocusedPane::Some { + pane: *pane, + focus: Focus::Dragging, + } } else { - Some(Focus::Idle(*pane)) + FocusedPane::Some { + pane: *pane, + focus: Focus::Idle, + } } } } ButtonState::Released => { if let Some(on_drop) = &self.on_drop { - if let Some(Focus::Dragging(pane)) = self.state.focus { + if let FocusedPane::Some { + pane, + focus: Focus::Dragging, + } = self.state.focused_pane + { let mut dropped_region = self .elements .iter() @@ -160,7 +188,10 @@ where } } - self.state.focus = Some(Focus::Idle(pane)); + self.state.focused_pane = FocusedPane::Some { + pane, + focus: Focus::Idle, + }; } } } @@ -171,8 +202,11 @@ where _ => {} } - match self.state.focus { - Some(Focus::Dragging(_)) => {} + match self.state.focused_pane { + FocusedPane::Some { + focus: Focus::Dragging, + .. + } => {} _ => { self.elements.iter_mut().zip(layout.children()).for_each( |((_, pane), layout)| { @@ -197,8 +231,11 @@ where layout: Layout<'_>, cursor_position: Point, ) -> Renderer::Output { - let dragging = match self.state.focus { - Some(Focus::Dragging(pane)) => Some(pane), + let dragging = match self.state.focused_pane { + FocusedPane::Some { + pane, + focus: Focus::Dragging, + } => Some(pane), _ => None, }; @@ -244,14 +281,20 @@ pub struct State { struct Internal { layout: Node, last_pane: usize, - focus: Option, + focused_pane: FocusedPane, modifiers: keyboard::ModifiersState, } -#[derive(Debug)] -enum Focus { - Idle(Pane), - Dragging(Pane), +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Focus { + Idle, + Dragging, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum FocusedPane { + None, + Some { pane: Pane, focus: Focus }, } impl State { @@ -267,7 +310,7 @@ impl State { internal: Internal { layout: Node::Pane(first_pane), last_pane: 0, - focus: None, + focused_pane: FocusedPane::None, modifiers: keyboard::ModifiersState::default(), }, }, @@ -292,15 +335,24 @@ impl State { } pub fn focused_pane(&self) -> Option { - match self.internal.focus { - Some(Focus::Idle(pane)) => Some(pane), - Some(Focus::Dragging(_)) => None, - None => None, + match self.internal.focused_pane { + FocusedPane::Some { + pane, + focus: Focus::Idle, + } => Some(pane), + FocusedPane::Some { + focus: Focus::Dragging, + .. + } => None, + FocusedPane::None => None, } } pub fn focus(&mut self, pane: Pane) { - self.internal.focus = Some(Focus::Idle(pane)); + self.internal.focused_pane = FocusedPane::Some { + pane, + focus: Focus::Idle, + }; } pub fn split_vertically(&mut self, pane: &Pane, state: T) -> Option { @@ -332,7 +384,10 @@ impl State { node.split(kind, new_pane); let _ = self.panes.insert(new_pane, state); - self.internal.focus = Some(Focus::Idle(new_pane)); + self.internal.focused_pane = FocusedPane::Some { + pane: new_pane, + focus: Focus::Idle, + }; Some(new_pane) } @@ -352,7 +407,11 @@ impl State { pub fn close(&mut self, pane: &Pane) -> Option { if let Some(sibling) = self.internal.layout.remove(pane) { - self.internal.focus = Some(Focus::Idle(sibling)); + self.internal.focused_pane = FocusedPane::Some { + pane: sibling, + focus: Focus::Idle, + }; + self.panes.remove(pane) } else { None From f09b4bd4f4113872eeac67320e79f81eac0a948f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 11 Mar 2020 23:26:45 +0100 Subject: [PATCH 14/53] Round region dimensions of panes to avoid overlaps --- native/src/widget/pane_grid.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 7fc1217681..3b68b99e73 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -552,7 +552,7 @@ impl Split { ) -> (Rectangle, Rectangle) { match self { Split::Horizontal => { - let width_left = rectangle.width * ratio; + let width_left = (rectangle.width * ratio).round(); let width_right = rectangle.width - width_left; ( @@ -568,7 +568,7 @@ impl Split { ) } Split::Vertical => { - let height_top = rectangle.height * ratio; + let height_top = (rectangle.height * ratio).round(); let height_bottom = rectangle.height - height_top; ( From 2d8d420949fc3b5c4390e32295cda64fb191488f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 11 Mar 2020 23:34:51 +0100 Subject: [PATCH 15/53] Replace `Panes` with `PaneGrid` in documentation --- native/src/widget/pane_grid.rs | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 3b68b99e73..3114816861 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -53,17 +53,17 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { } } - /// Sets the width of the [`Panes`]. + /// Sets the width of the [`PaneGrid`]. /// - /// [`Panes`]: struct.Column.html + /// [`PaneGrid`]: struct.Column.html pub fn width(mut self, width: Length) -> Self { self.width = width; self } - /// Sets the height of the [`Panes`]. + /// Sets the height of the [`PaneGrid`]. /// - /// [`Panes`]: struct.Column.html + /// [`PaneGrid`]: struct.Column.html pub fn height(mut self, height: Length) -> Self { self.height = height; self @@ -587,19 +587,20 @@ impl Split { } } -/// The renderer of some [`Panes`]. +/// The renderer of a [`PaneGrid`]. /// /// Your [renderer] will need to implement this trait before being -/// able to use [`Panes`] in your user interface. +/// able to use a [`PaneGrid`] in your user interface. /// -/// [`Panes`]: struct.Panes.html +/// [`PaneGrid`]: struct.PaneGrid.html /// [renderer]: ../../renderer/index.html pub trait Renderer: crate::Renderer + Sized { - /// Draws some [`Panes`]. + /// Draws a [`PaneGrid`]. /// /// It receives: - /// - the children of the [`Column`] - /// - the [`Layout`] of the [`Column`] and its children + /// - the elements of the [`PaneGrid`] + /// - the [`Pane`] that is currently being dragged + /// - the [`Layout`] of the [`PaneGrid`] and its elements /// - the cursor position /// /// [`Column`]: struct.Row.html @@ -621,8 +622,8 @@ where Message: 'static, { fn from( - panes: PaneGrid<'a, Message, Renderer>, + pane_grid: PaneGrid<'a, Message, Renderer>, ) -> Element<'a, Message, Renderer> { - Element::new(panes) + Element::new(pane_grid) } } From c2ced4cd59fcc4cc46c2030897382bc443b9ccd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Fri, 13 Mar 2020 07:35:44 +0100 Subject: [PATCH 16/53] Improve `PaneGrid` API by introducing `DragEvent` --- native/src/widget/pane_grid.rs | 76 +++++++++++++++++++--------------- 1 file changed, 43 insertions(+), 33 deletions(-) diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 3114816861..7999998b72 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -12,7 +12,7 @@ pub struct PaneGrid<'a, Message, Renderer> { elements: Vec<(Pane, Element<'a, Message, Renderer>)>, width: Length, height: Length, - on_drop: Option Message>>, + on_drag: Option Message>>, } impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { @@ -49,7 +49,7 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { elements, width: Length::Fill, height: Length::Fill, - on_drop: None, + on_drag: None, } } @@ -69,16 +69,20 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { self } - pub fn on_drop(mut self, f: impl Fn(Drop) -> Message + 'static) -> Self { - self.on_drop = Some(Box::new(f)); + pub fn on_drag( + mut self, + f: impl Fn(DragEvent) -> Message + 'static, + ) -> Self { + self.on_drag = Some(Box::new(f)); self } } #[derive(Debug, Clone, Copy)] -pub struct Drop { - pub pane: Pane, - pub target: Pane, +pub enum DragEvent { + Picked { pane: Pane }, + Dropped { pane: Pane, target: Pane }, + Canceled { pane: Pane }, } impl<'a, Message, Renderer> Widget @@ -147,28 +151,38 @@ where ); if let Some(((pane, _), _)) = clicked_region.next() { - self.state.focused_pane = if self.on_drop.is_some() - && self.state.modifiers.alt - { - FocusedPane::Some { - pane: *pane, - focus: Focus::Dragging, + match &self.on_drag { + Some(on_drag) if self.state.modifiers.alt => { + self.state.focused_pane = FocusedPane::Some { + pane: *pane, + focus: Focus::Dragging, + }; + + messages.push(on_drag(DragEvent::Picked { + pane: *pane, + })); } - } else { - FocusedPane::Some { - pane: *pane, - focus: Focus::Idle, + _ => { + self.state.focused_pane = FocusedPane::Some { + pane: *pane, + focus: Focus::Idle, + }; } } } } ButtonState::Released => { - if let Some(on_drop) = &self.on_drop { - if let FocusedPane::Some { + if let FocusedPane::Some { + pane, + focus: Focus::Dragging, + } = self.state.focused_pane + { + self.state.focused_pane = FocusedPane::Some { pane, - focus: Focus::Dragging, - } = self.state.focused_pane - { + focus: Focus::Idle, + }; + + if let Some(on_drag) = &self.on_drag { let mut dropped_region = self .elements .iter() @@ -177,21 +191,17 @@ where layout.bounds().contains(cursor_position) }); - if let Some(((target, _), _)) = - dropped_region.next() - { - if pane != *target { - messages.push(on_drop(Drop { + let event = match dropped_region.next() { + Some(((target, _), _)) if pane != *target => { + DragEvent::Dropped { pane, target: *target, - })); + } } - } - - self.state.focused_pane = FocusedPane::Some { - pane, - focus: Focus::Idle, + _ => DragEvent::Canceled { pane }, }; + + messages.push(on_drag(event)); } } } From 29bf51d25afdb07734700207dcc25b37357c04b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Fri, 13 Mar 2020 07:51:41 +0100 Subject: [PATCH 17/53] Implement `spacing` support for `PaneGrid` --- native/src/widget/pane_grid.rs | 43 +++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 7999998b72..92ddc6e012 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -12,6 +12,7 @@ pub struct PaneGrid<'a, Message, Renderer> { elements: Vec<(Pane, Element<'a, Message, Renderer>)>, width: Length, height: Length, + spacing: u16, on_drag: Option Message>>, } @@ -49,6 +50,7 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { elements, width: Length::Fill, height: Length::Fill, + spacing: 0, on_drag: None, } } @@ -69,6 +71,14 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { self } + /// Sets the spacing _between_ the panes of the [`PaneGrid`]. + /// + /// [`PaneGrid`]: struct.Column.html + pub fn spacing(mut self, units: u16) -> Self { + self.spacing = units; + self + } + pub fn on_drag( mut self, f: impl Fn(DragEvent) -> Message + 'static, @@ -107,7 +117,7 @@ where let limits = limits.width(self.width).height(self.height); let size = limits.resolve(Size::ZERO); - let regions = self.state.layout.regions(size); + let regions = self.state.layout.regions(f32::from(self.spacing), size); let children = self .elements @@ -498,10 +508,15 @@ impl Node { } } - pub fn regions(&self, size: Size) -> HashMap { + pub fn regions( + &self, + spacing: f32, + size: Size, + ) -> HashMap { let mut regions = HashMap::new(); self.compute_regions( + spacing / 2.0, &Rectangle { x: 0.0, y: 0.0, @@ -530,16 +545,18 @@ impl Node { fn compute_regions( &self, + halved_spacing: f32, current: &Rectangle, regions: &mut HashMap, ) { match self { Node::Split { kind, ratio, a, b } => { let ratio = *ratio as f32 / 1_000_000.0; - let (region_a, region_b) = kind.apply(current, ratio); + let (region_a, region_b) = + kind.apply(current, ratio, halved_spacing); - a.compute_regions(®ion_a, regions); - b.compute_regions(®ion_b, regions); + a.compute_regions(halved_spacing, ®ion_a, regions); + b.compute_regions(halved_spacing, ®ion_b, regions); } Node::Pane(pane) => { let _ = regions.insert(*pane, *current); @@ -559,11 +576,13 @@ impl Split { &self, rectangle: &Rectangle, ratio: f32, + halved_spacing: f32, ) -> (Rectangle, Rectangle) { match self { Split::Horizontal => { - let width_left = (rectangle.width * ratio).round(); - let width_right = rectangle.width - width_left; + let width_left = + (rectangle.width * ratio).round() - halved_spacing; + let width_right = rectangle.width - width_left - halved_spacing; ( Rectangle { @@ -571,15 +590,17 @@ impl Split { ..*rectangle }, Rectangle { - x: rectangle.x + width_left, + x: rectangle.x + width_left + halved_spacing, width: width_right, ..*rectangle }, ) } Split::Vertical => { - let height_top = (rectangle.height * ratio).round(); - let height_bottom = rectangle.height - height_top; + let height_top = + (rectangle.height * ratio).round() - halved_spacing; + let height_bottom = + rectangle.height - height_top - halved_spacing; ( Rectangle { @@ -587,7 +608,7 @@ impl Split { ..*rectangle }, Rectangle { - y: rectangle.y + height_top, + y: rectangle.y + height_top + halved_spacing, height: height_bottom, ..*rectangle }, From 0b12d706e3767c04b89d4d2692c31ff9fa715cc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Fri, 13 Mar 2020 07:54:14 +0100 Subject: [PATCH 18/53] Unfocus pane in `PaneGrid` on out-of-bounds click --- native/src/widget/pane_grid.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 92ddc6e012..78057bdaa9 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -179,6 +179,8 @@ where }; } } + } else { + self.state.focused_pane = FocusedPane::None; } } ButtonState::Released => { From b9f184fda4e5cd60b20b9f35cfbb81a6cf1fbd65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Fri, 13 Mar 2020 08:57:52 +0100 Subject: [PATCH 19/53] Draft `PaneGrid::focus_adjacent` --- native/src/widget/pane_grid.rs | 91 +++++++++++++++++++++++++++++++--- 1 file changed, 83 insertions(+), 8 deletions(-) diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 78057bdaa9..d93f8738a4 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -319,6 +319,14 @@ enum FocusedPane { Some { pane: Pane, focus: Focus }, } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Direction { + Top, + Bottom, + Left, + Right, +} + impl State { pub fn new(first_pane_state: T) -> (Self, Pane) { let first_pane = Pane(0); @@ -370,13 +378,20 @@ impl State { } } - pub fn focus(&mut self, pane: Pane) { + pub fn focus(&mut self, pane: &Pane) { self.internal.focused_pane = FocusedPane::Some { - pane, + pane: *pane, focus: Focus::Idle, }; } + pub fn focus_adjacent(&mut self, pane: &Pane, direction: Direction) { + if let Some(pane) = self.internal.layout.find_adjacent(pane, direction) + { + self.focus(&pane); + } + } + pub fn split_vertically(&mut self, pane: &Pane, state: T) -> Option { self.split(Split::Vertical, pane, state) } @@ -452,15 +467,17 @@ enum Node { Pane(Pane), } +#[derive(Debug)] +enum Branch { + First, + Second, +} + impl Node { fn find(&mut self, pane: &Pane) -> Option<&mut Node> { match self { Node::Split { a, b, .. } => { - if let Some(node) = a.find(pane) { - Some(node) - } else { - b.find(pane) - } + a.find(pane).or_else(move || b.find(pane)) } Node::Pane(p) => { if p == pane { @@ -472,6 +489,64 @@ impl Node { } } + fn find_adjacent( + &mut self, + pane: &Pane, + direction: Direction, + ) -> Option { + let (pane, _) = self.find_split(pane, &|kind, branch, a, b| match ( + direction, kind, branch, + ) { + (Direction::Top, Split::Vertical, Branch::Second) + | (Direction::Left, Split::Horizontal, Branch::Second) => { + Some(a.first_pane()) + } + (Direction::Bottom, Split::Vertical, Branch::First) + | (Direction::Right, Split::Horizontal, Branch::First) => { + Some(b.first_pane()) + } + _ => None, + }); + + pane + } + + fn find_split( + &mut self, + pane: &Pane, + callback: &impl Fn(Split, Branch, &Node, &Node) -> Option, + ) -> (Option, bool) { + match self { + Node::Split { a, b, kind, .. } => { + let kind = *kind; + let (result, found) = a.find_split(pane, callback); + + if result.is_some() { + (result, found) + } else if found { + (callback(kind, Branch::First, a, b), true) + } else { + let (result, found) = b.find_split(pane, callback); + + if result.is_some() { + (result, found) + } else if found { + (callback(kind, Branch::Second, a, b), true) + } else { + (None, false) + } + } + } + Node::Pane(p) => { + if p == pane { + (None, true) + } else { + (None, false) + } + } + } + } + fn split(&mut self, kind: Split, new_pane: Pane) { *self = Node::Split { kind, @@ -567,7 +642,7 @@ impl Node { } } -#[derive(Debug, Clone, Hash, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] pub enum Split { Horizontal, Vertical, From 26b9541bcab99bba66f2cf9bcae62a3adc95283c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Fri, 13 Mar 2020 09:55:59 +0100 Subject: [PATCH 20/53] Improve `PaneGrid::focus_adjacent` intuitiveness --- native/src/widget/pane_grid.rs | 94 ++++++++++------------------------ 1 file changed, 27 insertions(+), 67 deletions(-) diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index d93f8738a4..1ba1b417fd 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -386,9 +386,33 @@ impl State { } pub fn focus_adjacent(&mut self, pane: &Pane, direction: Direction) { - if let Some(pane) = self.internal.layout.find_adjacent(pane, direction) - { - self.focus(&pane); + let regions = + self.internal.layout.regions(0.0, Size::new(4096.0, 4096.0)); + + if let Some(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::Top => { + Point::new(current_region.x + 1.0, current_region.y - 1.0) + } + Direction::Bottom => 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)); + + if let Some((pane, _)) = colliding_regions.next() { + self.focus(&pane); + } } } @@ -467,12 +491,6 @@ enum Node { Pane(Pane), } -#[derive(Debug)] -enum Branch { - First, - Second, -} - impl Node { fn find(&mut self, pane: &Pane) -> Option<&mut Node> { match self { @@ -489,64 +507,6 @@ impl Node { } } - fn find_adjacent( - &mut self, - pane: &Pane, - direction: Direction, - ) -> Option { - let (pane, _) = self.find_split(pane, &|kind, branch, a, b| match ( - direction, kind, branch, - ) { - (Direction::Top, Split::Vertical, Branch::Second) - | (Direction::Left, Split::Horizontal, Branch::Second) => { - Some(a.first_pane()) - } - (Direction::Bottom, Split::Vertical, Branch::First) - | (Direction::Right, Split::Horizontal, Branch::First) => { - Some(b.first_pane()) - } - _ => None, - }); - - pane - } - - fn find_split( - &mut self, - pane: &Pane, - callback: &impl Fn(Split, Branch, &Node, &Node) -> Option, - ) -> (Option, bool) { - match self { - Node::Split { a, b, kind, .. } => { - let kind = *kind; - let (result, found) = a.find_split(pane, callback); - - if result.is_some() { - (result, found) - } else if found { - (callback(kind, Branch::First, a, b), true) - } else { - let (result, found) = b.find_split(pane, callback); - - if result.is_some() { - (result, found) - } else if found { - (callback(kind, Branch::Second, a, b), true) - } else { - (None, false) - } - } - } - Node::Pane(p) => { - if p == pane { - (None, true) - } else { - (None, false) - } - } - } - } - fn split(&mut self, kind: Split, new_pane: Pane) { *self = Node::Split { kind, From 858c086eeee4284a5a7b3e49cb3c112d204e53c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sat, 14 Mar 2020 03:39:20 +0100 Subject: [PATCH 21/53] Remove `pane_grid` example for now It's too contrived. I will work on something simpler. --- Cargo.toml | 1 - examples/clock/Cargo.toml | 3 + examples/clock/README.md | 4 +- examples/clock/src/lib.rs | 187 ------------------------------ examples/clock/src/main.rs | 188 +++++++++++++++++++++++++++++- examples/pane_grid/Cargo.toml | 12 -- examples/pane_grid/README.md | 18 --- examples/pane_grid/src/main.rs | 179 ----------------------------- examples/stopwatch/src/lib.rs | 204 --------------------------------- examples/stopwatch/src/main.rs | 204 ++++++++++++++++++++++++++++++++- 10 files changed, 393 insertions(+), 607 deletions(-) delete mode 100644 examples/clock/src/lib.rs delete mode 100644 examples/pane_grid/Cargo.toml delete mode 100644 examples/pane_grid/README.md delete mode 100644 examples/pane_grid/src/main.rs delete mode 100644 examples/stopwatch/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 37b20ec40e..01231b70af 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,7 +44,6 @@ members = [ "examples/events", "examples/geometry", "examples/integration", - "examples/pane_grid", "examples/pokedex", "examples/progress_bar", "examples/solar_system", diff --git a/examples/clock/Cargo.toml b/examples/clock/Cargo.toml index ab7714055d..308cbfbb0a 100644 --- a/examples/clock/Cargo.toml +++ b/examples/clock/Cargo.toml @@ -5,6 +5,9 @@ authors = ["Héctor Ramón Jiménez "] edition = "2018" publish = false +[features] +canvas = [] + [dependencies] iced = { path = "../..", features = ["canvas", "async-std", "debug"] } iced_native = { path = "../../native" } diff --git a/examples/clock/README.md b/examples/clock/README.md index a87edad0ca..175091805b 100644 --- a/examples/clock/README.md +++ b/examples/clock/README.md @@ -2,7 +2,7 @@ An application that uses the `Canvas` widget to draw a clock and its hands to display the current time. -The __[`lib`]__ file contains the relevant code of the example. +The __[`main`]__ file contains all the code of the example.
@@ -13,4 +13,4 @@ You can run it with `cargo run`: cargo run --package clock ``` -[`lib`]: src/lib.rs +[`main`]: src/main.rs diff --git a/examples/clock/src/lib.rs b/examples/clock/src/lib.rs deleted file mode 100644 index 229ba8feaa..0000000000 --- a/examples/clock/src/lib.rs +++ /dev/null @@ -1,187 +0,0 @@ -use iced::{ - canvas, executor, Application, Canvas, Color, Command, Container, Element, - Length, Point, Subscription, Vector, -}; - -#[derive(Debug)] -pub struct Clock { - now: LocalTime, - clock: canvas::layer::Cache, -} - -#[derive(Debug, Clone, Copy)] -pub enum Message { - Tick(chrono::DateTime), -} - -impl Application for Clock { - type Executor = executor::Default; - type Message = Message; - - fn new() -> (Self, Command) { - ( - Clock { - now: chrono::Local::now().into(), - clock: canvas::layer::Cache::new(), - }, - Command::none(), - ) - } - - fn title(&self) -> String { - String::from("Clock - Iced") - } - - fn update(&mut self, message: Message) -> Command { - match message { - Message::Tick(local_time) => { - let now = local_time.into(); - - if now != self.now { - self.now = now; - self.clock.clear(); - } - } - } - - Command::none() - } - - fn subscription(&self) -> Subscription { - time::every(std::time::Duration::from_millis(500)).map(Message::Tick) - } - - fn view(&mut self) -> Element { - let canvas = Canvas::new() - .width(Length::Units(400)) - .height(Length::Units(400)) - .push(self.clock.with(&self.now)); - - Container::new(canvas) - .width(Length::Fill) - .height(Length::Fill) - .center_x() - .center_y() - .into() - } -} - -#[derive(Debug, PartialEq, Eq)] -struct LocalTime { - hour: u32, - minute: u32, - second: u32, -} - -impl From> for LocalTime { - fn from(date_time: chrono::DateTime) -> LocalTime { - use chrono::Timelike; - - LocalTime { - hour: date_time.hour(), - minute: date_time.minute(), - second: date_time.second(), - } - } -} - -impl canvas::Drawable for LocalTime { - fn draw(&self, frame: &mut canvas::Frame) { - let center = frame.center(); - let radius = frame.width().min(frame.height()) / 2.0; - let offset = Vector::new(center.x, center.y); - - let clock = canvas::Path::new(|path| path.circle(center, radius)); - - frame.fill( - &clock, - canvas::Fill::Color(Color::from_rgb8(0x12, 0x93, 0xD8)), - ); - - fn draw_hand( - n: u32, - total: u32, - length: f32, - offset: Vector, - path: &mut canvas::path::Builder, - ) { - let turns = n as f32 / total as f32; - let t = 2.0 * std::f32::consts::PI * (turns - 0.25); - - let x = length * t.cos(); - let y = length * t.sin(); - - path.line_to(Point::new(x, y) + offset); - } - - let hour_and_minute_hands = canvas::Path::new(|path| { - path.move_to(center); - draw_hand(self.hour, 12, 0.5 * radius, offset, path); - - path.move_to(center); - draw_hand(self.minute, 60, 0.8 * radius, offset, path) - }); - - frame.stroke( - &hour_and_minute_hands, - canvas::Stroke { - width: 6.0, - color: Color::WHITE, - line_cap: canvas::LineCap::Round, - ..canvas::Stroke::default() - }, - ); - - let second_hand = canvas::Path::new(|path| { - path.move_to(center); - draw_hand(self.second, 60, 0.8 * radius, offset, path) - }); - - frame.stroke( - &second_hand, - canvas::Stroke { - width: 3.0, - color: Color::WHITE, - line_cap: canvas::LineCap::Round, - ..canvas::Stroke::default() - }, - ); - } -} - -mod time { - use iced::futures; - - pub fn every( - duration: std::time::Duration, - ) -> iced::Subscription> { - iced::Subscription::from_recipe(Every(duration)) - } - - struct Every(std::time::Duration); - - impl iced_native::subscription::Recipe for Every - where - H: std::hash::Hasher, - { - type Output = chrono::DateTime; - - fn hash(&self, state: &mut H) { - use std::hash::Hash; - - std::any::TypeId::of::().hash(state); - self.0.hash(state); - } - - fn stream( - self: Box, - _input: futures::stream::BoxStream<'static, I>, - ) -> futures::stream::BoxStream<'static, Self::Output> { - use futures::stream::StreamExt; - - async_std::stream::interval(self.0) - .map(|_| chrono::Local::now()) - .boxed() - } - } -} diff --git a/examples/clock/src/main.rs b/examples/clock/src/main.rs index 454ff4f0c2..d8266f069b 100644 --- a/examples/clock/src/main.rs +++ b/examples/clock/src/main.rs @@ -1,5 +1,7 @@ -use clock::Clock; -use iced::{Application, Settings}; +use iced::{ + canvas, executor, Application, Canvas, Color, Command, Container, Element, + Length, Point, Settings, Subscription, Vector, +}; pub fn main() { Clock::run(Settings { @@ -7,3 +9,185 @@ pub fn main() { ..Settings::default() }) } + +struct Clock { + now: LocalTime, + clock: canvas::layer::Cache, +} + +#[derive(Debug, Clone, Copy)] +enum Message { + Tick(chrono::DateTime), +} + +impl Application for Clock { + type Executor = executor::Default; + type Message = Message; + + fn new() -> (Self, Command) { + ( + Clock { + now: chrono::Local::now().into(), + clock: canvas::layer::Cache::new(), + }, + Command::none(), + ) + } + + fn title(&self) -> String { + String::from("Clock - Iced") + } + + fn update(&mut self, message: Message) -> Command { + match message { + Message::Tick(local_time) => { + let now = local_time.into(); + + if now != self.now { + self.now = now; + self.clock.clear(); + } + } + } + + Command::none() + } + + fn subscription(&self) -> Subscription { + time::every(std::time::Duration::from_millis(500)).map(Message::Tick) + } + + fn view(&mut self) -> Element { + let canvas = Canvas::new() + .width(Length::Units(400)) + .height(Length::Units(400)) + .push(self.clock.with(&self.now)); + + Container::new(canvas) + .width(Length::Fill) + .height(Length::Fill) + .center_x() + .center_y() + .into() + } +} + +#[derive(Debug, PartialEq, Eq)] +struct LocalTime { + hour: u32, + minute: u32, + second: u32, +} + +impl From> for LocalTime { + fn from(date_time: chrono::DateTime) -> LocalTime { + use chrono::Timelike; + + LocalTime { + hour: date_time.hour(), + minute: date_time.minute(), + second: date_time.second(), + } + } +} + +impl canvas::Drawable for LocalTime { + fn draw(&self, frame: &mut canvas::Frame) { + let center = frame.center(); + let radius = frame.width().min(frame.height()) / 2.0; + let offset = Vector::new(center.x, center.y); + + let clock = canvas::Path::new(|path| path.circle(center, radius)); + + frame.fill( + &clock, + canvas::Fill::Color(Color::from_rgb8(0x12, 0x93, 0xD8)), + ); + + fn draw_hand( + n: u32, + total: u32, + length: f32, + offset: Vector, + path: &mut canvas::path::Builder, + ) { + let turns = n as f32 / total as f32; + let t = 2.0 * std::f32::consts::PI * (turns - 0.25); + + let x = length * t.cos(); + let y = length * t.sin(); + + path.line_to(Point::new(x, y) + offset); + } + + let hour_and_minute_hands = canvas::Path::new(|path| { + path.move_to(center); + draw_hand(self.hour, 12, 0.5 * radius, offset, path); + + path.move_to(center); + draw_hand(self.minute, 60, 0.8 * radius, offset, path) + }); + + frame.stroke( + &hour_and_minute_hands, + canvas::Stroke { + width: 6.0, + color: Color::WHITE, + line_cap: canvas::LineCap::Round, + ..canvas::Stroke::default() + }, + ); + + let second_hand = canvas::Path::new(|path| { + path.move_to(center); + draw_hand(self.second, 60, 0.8 * radius, offset, path) + }); + + frame.stroke( + &second_hand, + canvas::Stroke { + width: 3.0, + color: Color::WHITE, + line_cap: canvas::LineCap::Round, + ..canvas::Stroke::default() + }, + ); + } +} + +mod time { + use iced::futures; + + pub fn every( + duration: std::time::Duration, + ) -> iced::Subscription> { + iced::Subscription::from_recipe(Every(duration)) + } + + struct Every(std::time::Duration); + + impl iced_native::subscription::Recipe for Every + where + H: std::hash::Hasher, + { + type Output = chrono::DateTime; + + fn hash(&self, state: &mut H) { + use std::hash::Hash; + + std::any::TypeId::of::().hash(state); + self.0.hash(state); + } + + fn stream( + self: Box, + _input: futures::stream::BoxStream<'static, I>, + ) -> futures::stream::BoxStream<'static, Self::Output> { + use futures::stream::StreamExt; + + async_std::stream::interval(self.0) + .map(|_| chrono::Local::now()) + .boxed() + } + } +} diff --git a/examples/pane_grid/Cargo.toml b/examples/pane_grid/Cargo.toml deleted file mode 100644 index 6d8573bd04..0000000000 --- a/examples/pane_grid/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "pane_grid" -version = "0.1.0" -authors = ["Héctor Ramón Jiménez "] -edition = "2018" -publish = false - -[dependencies] -iced = { path = "../..", features = ["async-std"] } -iced_native = { path = "../../native" } -clock = { path = "../clock" } -stopwatch = { path = "../stopwatch" } diff --git a/examples/pane_grid/README.md b/examples/pane_grid/README.md deleted file mode 100644 index 4d9fc5b979..0000000000 --- a/examples/pane_grid/README.md +++ /dev/null @@ -1,18 +0,0 @@ -## Counter - -The classic counter example explained in the [`README`](../../README.md). - -The __[`main`]__ file contains all the code of the example. - - - -You can run it with `cargo run`: -``` -cargo run --package counter -``` - -[`main`]: src/main.rs diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs deleted file mode 100644 index eb8aaa5152..0000000000 --- a/examples/pane_grid/src/main.rs +++ /dev/null @@ -1,179 +0,0 @@ -use iced::{ - pane_grid, Application, Command, Element, PaneGrid, Settings, Subscription, -}; -use iced_native::input::keyboard; - -use clock::{self, Clock}; -use stopwatch::{self, Stopwatch}; - -pub fn main() { - Launcher::run(Settings { - antialiasing: true, - ..Settings::default() - }) -} - -#[derive(Debug)] -struct Launcher { - panes: pane_grid::State, -} - -#[derive(Debug)] -enum Example { - Clock(Clock), - Stopwatch(Stopwatch), -} - -#[derive(Debug, Clone)] -enum Message { - Clock(pane_grid::Pane, clock::Message), - Stopwatch(pane_grid::Pane, stopwatch::Message), - Split(pane_grid::Split), - Close, -} - -impl Application for Launcher { - type Executor = iced::executor::Default; - type Message = Message; - - fn new() -> (Self, Command) { - let (clock, _) = Clock::new(); - let (panes, _) = pane_grid::State::new(Example::Clock(clock)); - - (Self { panes }, Command::none()) - } - - fn title(&self) -> String { - String::from("Panes - Iced") - } - - fn update(&mut self, message: Message) -> Command { - match message { - Message::Clock(pane, message) => { - if let Some(Example::Clock(clock)) = self.panes.get_mut(&pane) { - let _ = clock.update(message); - } - } - Message::Stopwatch(pane, message) => { - if let Some(Example::Stopwatch(stopwatch)) = - self.panes.get_mut(&pane) - { - let _ = stopwatch.update(message); - } - } - Message::Split(kind) => { - if let Some(pane) = self.panes.focused_pane() { - let state = if pane.index() % 2 == 0 { - let (stopwatch, _) = Stopwatch::new(); - - Example::Stopwatch(stopwatch) - } else { - let (clock, _) = Clock::new(); - - Example::Clock(clock) - }; - - self.panes.split(kind, &pane, state); - } - } - Message::Close => { - if let Some(pane) = self.panes.focused_pane() { - self.panes.close(&pane); - } - } - } - - Command::none() - } - - fn subscription(&self) -> Subscription { - let panes_subscriptions = - Subscription::batch(self.panes.iter().map(|(pane, example)| { - match example { - Example::Clock(clock) => clock - .subscription() - .with(pane) - .map(|(pane, message)| Message::Clock(pane, message)), - - Example::Stopwatch(stopwatch) => { - stopwatch.subscription().with(pane).map( - |(pane, message)| Message::Stopwatch(pane, message), - ) - } - } - })); - - Subscription::batch(vec![ - events::key_released(keyboard::KeyCode::H) - .map(|_| Message::Split(pane_grid::Split::Horizontal)), - events::key_released(keyboard::KeyCode::V) - .map(|_| Message::Split(pane_grid::Split::Vertical)), - events::key_released(keyboard::KeyCode::Q).map(|_| Message::Close), - panes_subscriptions, - ]) - } - - fn view(&mut self) -> Element { - let Self { panes } = self; - - PaneGrid::new(panes, |pane, example, _| match example { - Example::Clock(clock) => clock - .view() - .map(move |message| Message::Clock(pane, message)), - - Example::Stopwatch(stopwatch) => stopwatch - .view() - .map(move |message| Message::Stopwatch(pane, message)), - }) - .into() - } -} - -mod events { - use iced_native::{ - futures::{ - self, - stream::{BoxStream, StreamExt}, - }, - input::{keyboard, ButtonState}, - subscription, Event, Hasher, Subscription, - }; - - pub fn key_released(key_code: keyboard::KeyCode) -> Subscription<()> { - Subscription::from_recipe(KeyReleased { key_code }) - } - - struct KeyReleased { - key_code: keyboard::KeyCode, - } - - impl subscription::Recipe for KeyReleased { - type Output = (); - - fn hash(&self, state: &mut Hasher) { - use std::hash::Hash; - - std::any::TypeId::of::().hash(state); - self.key_code.hash(state); - } - - fn stream( - self: Box, - events: subscription::EventStream, - ) -> BoxStream<'static, Self::Output> { - events - .filter(move |event| match event { - Event::Keyboard(keyboard::Event::Input { - key_code, - state: ButtonState::Released, - .. - }) if *key_code == self.key_code => { - futures::future::ready(true) - } - _ => futures::future::ready(false), - }) - .map(|_| ()) - .boxed() - } - } -} diff --git a/examples/stopwatch/src/lib.rs b/examples/stopwatch/src/lib.rs deleted file mode 100644 index 0219470ba7..0000000000 --- a/examples/stopwatch/src/lib.rs +++ /dev/null @@ -1,204 +0,0 @@ -use iced::{ - button, Align, Application, Button, Column, Command, Container, Element, - HorizontalAlignment, Length, Row, Subscription, Text, -}; -use std::time::{Duration, Instant}; - -#[derive(Debug)] -pub struct Stopwatch { - duration: Duration, - state: State, - toggle: button::State, - reset: button::State, -} - -#[derive(Debug)] -enum State { - Idle, - Ticking { last_tick: Instant }, -} - -#[derive(Debug, Clone)] -pub enum Message { - Toggle, - Reset, - Tick(Instant), -} - -impl Application for Stopwatch { - type Executor = iced_futures::executor::AsyncStd; - type Message = Message; - - fn new() -> (Stopwatch, Command) { - ( - Stopwatch { - duration: Duration::default(), - state: State::Idle, - toggle: button::State::new(), - reset: button::State::new(), - }, - Command::none(), - ) - } - - fn title(&self) -> String { - String::from("Stopwatch - Iced") - } - - fn update(&mut self, message: Message) -> Command { - match message { - Message::Toggle => match self.state { - State::Idle => { - self.state = State::Ticking { - last_tick: Instant::now(), - }; - } - State::Ticking { .. } => { - self.state = State::Idle; - } - }, - Message::Tick(now) => match &mut self.state { - State::Ticking { last_tick } => { - self.duration += now - *last_tick; - *last_tick = now; - } - _ => {} - }, - Message::Reset => { - self.duration = Duration::default(); - } - } - - Command::none() - } - - fn subscription(&self) -> Subscription { - match self.state { - State::Idle => Subscription::none(), - State::Ticking { .. } => { - time::every(Duration::from_millis(10)).map(Message::Tick) - } - } - } - - fn view(&mut self) -> Element { - const MINUTE: u64 = 60; - const HOUR: u64 = 60 * MINUTE; - - let seconds = self.duration.as_secs(); - - let duration = Text::new(format!( - "{:0>2}:{:0>2}:{:0>2}.{:0>2}", - seconds / HOUR, - (seconds % HOUR) / MINUTE, - seconds % MINUTE, - self.duration.subsec_millis() / 10, - )) - .size(40); - - let button = |state, label, style| { - Button::new( - state, - Text::new(label) - .horizontal_alignment(HorizontalAlignment::Center), - ) - .min_width(80) - .padding(10) - .style(style) - }; - - let toggle_button = { - let (label, color) = match self.state { - State::Idle => ("Start", style::Button::Primary), - State::Ticking { .. } => ("Stop", style::Button::Destructive), - }; - - button(&mut self.toggle, label, color).on_press(Message::Toggle) - }; - - let reset_button = - button(&mut self.reset, "Reset", style::Button::Secondary) - .on_press(Message::Reset); - - let controls = Row::new() - .spacing(20) - .push(toggle_button) - .push(reset_button); - - let content = Column::new() - .align_items(Align::Center) - .spacing(20) - .push(duration) - .push(controls); - - Container::new(content) - .width(Length::Fill) - .height(Length::Fill) - .center_x() - .center_y() - .into() - } -} - -mod time { - use iced::futures; - - pub fn every( - duration: std::time::Duration, - ) -> iced::Subscription { - iced::Subscription::from_recipe(Every(duration)) - } - - struct Every(std::time::Duration); - - impl iced_native::subscription::Recipe for Every - where - H: std::hash::Hasher, - { - type Output = std::time::Instant; - - fn hash(&self, state: &mut H) { - use std::hash::Hash; - - std::any::TypeId::of::().hash(state); - self.0.hash(state); - } - - fn stream( - self: Box, - _input: futures::stream::BoxStream<'static, I>, - ) -> futures::stream::BoxStream<'static, Self::Output> { - use futures::stream::StreamExt; - - async_std::stream::interval(self.0) - .map(|_| std::time::Instant::now()) - .boxed() - } - } -} - -mod style { - use iced::{button, Background, Color, Vector}; - - pub enum Button { - Primary, - Secondary, - Destructive, - } - - impl button::StyleSheet for Button { - fn active(&self) -> button::Style { - button::Style { - background: Some(Background::Color(match self { - Button::Primary => Color::from_rgb(0.11, 0.42, 0.87), - Button::Secondary => Color::from_rgb(0.5, 0.5, 0.5), - Button::Destructive => Color::from_rgb(0.8, 0.2, 0.2), - })), - border_radius: 12, - shadow_offset: Vector::new(1.0, 1.0), - text_color: Color::WHITE, - ..button::Style::default() - } - } - } -} diff --git a/examples/stopwatch/src/main.rs b/examples/stopwatch/src/main.rs index adcdffe456..d84c481744 100644 --- a/examples/stopwatch/src/main.rs +++ b/examples/stopwatch/src/main.rs @@ -1,6 +1,206 @@ -use iced::{Application, Settings}; -use stopwatch::Stopwatch; +use iced::{ + button, Align, Application, Button, Column, Command, Container, Element, + HorizontalAlignment, Length, Row, Settings, Subscription, Text, +}; +use std::time::{Duration, Instant}; pub fn main() { Stopwatch::run(Settings::default()) } + +struct Stopwatch { + duration: Duration, + state: State, + toggle: button::State, + reset: button::State, +} + +enum State { + Idle, + Ticking { last_tick: Instant }, +} + +#[derive(Debug, Clone)] +enum Message { + Toggle, + Reset, + Tick(Instant), +} + +impl Application for Stopwatch { + type Executor = iced_futures::executor::AsyncStd; + type Message = Message; + + fn new() -> (Stopwatch, Command) { + ( + Stopwatch { + duration: Duration::default(), + state: State::Idle, + toggle: button::State::new(), + reset: button::State::new(), + }, + Command::none(), + ) + } + + fn title(&self) -> String { + String::from("Stopwatch - Iced") + } + + fn update(&mut self, message: Message) -> Command { + match message { + Message::Toggle => match self.state { + State::Idle => { + self.state = State::Ticking { + last_tick: Instant::now(), + }; + } + State::Ticking { .. } => { + self.state = State::Idle; + } + }, + Message::Tick(now) => match &mut self.state { + State::Ticking { last_tick } => { + self.duration += now - *last_tick; + *last_tick = now; + } + _ => {} + }, + Message::Reset => { + self.duration = Duration::default(); + } + } + + Command::none() + } + + fn subscription(&self) -> Subscription { + match self.state { + State::Idle => Subscription::none(), + State::Ticking { .. } => { + time::every(Duration::from_millis(10)).map(Message::Tick) + } + } + } + + fn view(&mut self) -> Element { + const MINUTE: u64 = 60; + const HOUR: u64 = 60 * MINUTE; + + let seconds = self.duration.as_secs(); + + let duration = Text::new(format!( + "{:0>2}:{:0>2}:{:0>2}.{:0>2}", + seconds / HOUR, + (seconds % HOUR) / MINUTE, + seconds % MINUTE, + self.duration.subsec_millis() / 10, + )) + .size(40); + + let button = |state, label, style| { + Button::new( + state, + Text::new(label) + .horizontal_alignment(HorizontalAlignment::Center), + ) + .min_width(80) + .padding(10) + .style(style) + }; + + let toggle_button = { + let (label, color) = match self.state { + State::Idle => ("Start", style::Button::Primary), + State::Ticking { .. } => ("Stop", style::Button::Destructive), + }; + + button(&mut self.toggle, label, color).on_press(Message::Toggle) + }; + + let reset_button = + button(&mut self.reset, "Reset", style::Button::Secondary) + .on_press(Message::Reset); + + let controls = Row::new() + .spacing(20) + .push(toggle_button) + .push(reset_button); + + let content = Column::new() + .align_items(Align::Center) + .spacing(20) + .push(duration) + .push(controls); + + Container::new(content) + .width(Length::Fill) + .height(Length::Fill) + .center_x() + .center_y() + .into() + } +} + +mod time { + use iced::futures; + + pub fn every( + duration: std::time::Duration, + ) -> iced::Subscription { + iced::Subscription::from_recipe(Every(duration)) + } + + struct Every(std::time::Duration); + + impl iced_native::subscription::Recipe for Every + where + H: std::hash::Hasher, + { + type Output = std::time::Instant; + + fn hash(&self, state: &mut H) { + use std::hash::Hash; + + std::any::TypeId::of::().hash(state); + self.0.hash(state); + } + + fn stream( + self: Box, + _input: futures::stream::BoxStream<'static, I>, + ) -> futures::stream::BoxStream<'static, Self::Output> { + use futures::stream::StreamExt; + + async_std::stream::interval(self.0) + .map(|_| std::time::Instant::now()) + .boxed() + } + } +} + +mod style { + use iced::{button, Background, Color, Vector}; + + pub enum Button { + Primary, + Secondary, + Destructive, + } + + impl button::StyleSheet for Button { + fn active(&self) -> button::Style { + button::Style { + background: Some(Background::Color(match self { + Button::Primary => Color::from_rgb(0.11, 0.42, 0.87), + Button::Secondary => Color::from_rgb(0.5, 0.5, 0.5), + Button::Destructive => Color::from_rgb(0.8, 0.2, 0.2), + })), + border_radius: 12, + shadow_offset: Vector::new(1.0, 1.0), + text_color: Color::WHITE, + ..button::Style::default() + } + } + } +} From 6e8585e88c89c9e3e18e49765d4d954000bd4674 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sat, 14 Mar 2020 04:06:32 +0100 Subject: [PATCH 22/53] Expose `adjacent_pane` instead of `focus_adjacent` --- native/src/widget/pane_grid.rs | 70 ++++++++++++++++++---------------- 1 file changed, 37 insertions(+), 33 deletions(-) diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 1ba1b417fd..ddbc7bfdfc 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -321,8 +321,8 @@ enum FocusedPane { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Direction { - Top, - Bottom, + Up, + Down, Left, Right, } @@ -378,42 +378,46 @@ impl State { } } - pub fn focus(&mut self, pane: &Pane) { - self.internal.focused_pane = FocusedPane::Some { - pane: *pane, - focus: Focus::Idle, - }; - } - - pub fn focus_adjacent(&mut self, pane: &Pane, direction: Direction) { + pub fn adjacent_pane( + &self, + pane: &Pane, + direction: Direction, + ) -> Option { let regions = self.internal.layout.regions(0.0, Size::new(4096.0, 4096.0)); - if let Some(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::Top => { - Point::new(current_region.x + 1.0, current_region.y - 1.0) - } - Direction::Bottom => 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 current_region = regions.get(pane)?; - if let Some((pane, _)) = colliding_regions.next() { - self.focus(&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) + } + + pub fn focus(&mut self, pane: &Pane) { + self.internal.focused_pane = FocusedPane::Some { + pane: *pane, + focus: Focus::Idle, + }; } pub fn split_vertically(&mut self, pane: &Pane, state: T) -> Option { From 2459648574abc11161390b24d8b0b621b5d39ab9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sat, 14 Mar 2020 04:47:14 +0100 Subject: [PATCH 23/53] Simplify `iter` and `iter_mut` in `pane_grid` --- native/src/widget/pane_grid.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index ddbc7bfdfc..a594832a8c 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -356,12 +356,12 @@ impl State { self.panes.get_mut(pane) } - pub fn iter(&self) -> impl Iterator { - self.panes.iter().map(|(pane, state)| (*pane, state)) + pub fn iter(&self) -> impl Iterator { + self.panes.iter() } - pub fn iter_mut(&mut self) -> impl Iterator { - self.panes.iter_mut().map(|(pane, state)| (*pane, state)) + pub fn iter_mut(&mut self) -> impl Iterator { + self.panes.iter_mut() } pub fn focused_pane(&self) -> Option { From 460565056e6787d5cc7cb0d1d49c9e8ff1f77850 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sat, 14 Mar 2020 04:53:57 +0100 Subject: [PATCH 24/53] Reuse `PaneGrid::focus` to remove some duplication --- native/src/widget/pane_grid.rs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index a594832a8c..32dc4236d0 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -449,10 +449,7 @@ impl State { node.split(kind, new_pane); let _ = self.panes.insert(new_pane, state); - self.internal.focused_pane = FocusedPane::Some { - pane: new_pane, - focus: Focus::Idle, - }; + self.focus(&new_pane); Some(new_pane) } @@ -472,11 +469,7 @@ impl State { pub fn close(&mut self, pane: &Pane) -> Option { if let Some(sibling) = self.internal.layout.remove(pane) { - self.internal.focused_pane = FocusedPane::Some { - pane: sibling, - focus: Focus::Idle, - }; - + self.focus(&sibling); self.panes.remove(pane) } else { None From 5c8ec4504b6541cdc588b91a6b2c7100b4a7cc77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sat, 14 Mar 2020 05:26:59 +0100 Subject: [PATCH 25/53] Create module boundaries for `pane_grid` logic --- native/src/widget/pane_grid.rs | 442 ++--------------------- native/src/widget/pane_grid/direction.rs | 7 + native/src/widget/pane_grid/node.rs | 128 +++++++ native/src/widget/pane_grid/pane.rs | 2 + native/src/widget/pane_grid/split.rs | 54 +++ native/src/widget/pane_grid/state.rs | 227 ++++++++++++ 6 files changed, 448 insertions(+), 412 deletions(-) create mode 100644 native/src/widget/pane_grid/direction.rs create mode 100644 native/src/widget/pane_grid/node.rs create mode 100644 native/src/widget/pane_grid/pane.rs create mode 100644 native/src/widget/pane_grid/split.rs create mode 100644 native/src/widget/pane_grid/state.rs diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 32dc4236d0..2272d32c35 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -1,14 +1,24 @@ +mod direction; +mod node; +mod pane; +mod split; +mod state; + +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, - Rectangle, Size, Widget, + layout, Clipboard, Element, Event, Hasher, Layout, Length, Point, Size, + Widget, }; -use std::collections::HashMap; - #[allow(missing_debug_implementations)] pub struct PaneGrid<'a, Message, Renderer> { - state: &'a mut Internal, + state: &'a mut state::Internal, + modifiers: &'a mut keyboard::ModifiersState, elements: Vec<(Pane, Element<'a, Message, Renderer>)>, width: Length, height: Length, @@ -26,14 +36,14 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { ) -> Element<'a, Message, Renderer>, ) -> Self { let elements = { - let focused_pane = state.internal.focused_pane; + let focused_pane = state.internal.focused(); state .panes .iter_mut() .map(move |(pane, pane_state)| { let focus = match focused_pane { - FocusedPane::Some { + state::FocusedPane::Some { pane: focused_pane, focus, } if *pane == focused_pane => Some(focus), @@ -47,6 +57,7 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { Self { state: &mut state.internal, + modifiers: &mut state.modifiers, elements, width: Length::Fill, height: Length::Fill, @@ -117,7 +128,7 @@ where let limits = limits.width(self.width).height(self.height); let size = limits.resolve(Size::ZERO); - let regions = self.state.layout.regions(f32::from(self.spacing), size); + let regions = self.state.regions(f32::from(self.spacing), size); let children = self .elements @@ -162,37 +173,24 @@ where if let Some(((pane, _), _)) = clicked_region.next() { match &self.on_drag { - Some(on_drag) if self.state.modifiers.alt => { - self.state.focused_pane = FocusedPane::Some { - pane: *pane, - focus: Focus::Dragging, - }; + Some(on_drag) if self.modifiers.alt => { + self.state.drag(pane); messages.push(on_drag(DragEvent::Picked { pane: *pane, })); } _ => { - self.state.focused_pane = FocusedPane::Some { - pane: *pane, - focus: Focus::Idle, - }; + self.state.focus(pane); } } } else { - self.state.focused_pane = FocusedPane::None; + self.state.unfocus(); } } ButtonState::Released => { - if let FocusedPane::Some { - pane, - focus: Focus::Dragging, - } = self.state.focused_pane - { - self.state.focused_pane = FocusedPane::Some { - pane, - focus: Focus::Idle, - }; + if let Some(pane) = self.state.dragged() { + self.state.focus(&pane); if let Some(on_drag) = &self.on_drag { let mut dropped_region = self @@ -219,17 +217,13 @@ where } }, Event::Keyboard(keyboard::Event::Input { modifiers, .. }) => { - self.state.modifiers = modifiers; + *self.modifiers = modifiers; } _ => {} } - match self.state.focused_pane { - FocusedPane::Some { - focus: Focus::Dragging, - .. - } => {} - _ => { + if self.state.dragged().is_none() { + { self.elements.iter_mut().zip(layout.children()).for_each( |((_, pane), layout)| { pane.widget.on_event( @@ -253,18 +247,10 @@ where layout: Layout<'_>, cursor_position: Point, ) -> Renderer::Output { - let dragging = match self.state.focused_pane { - FocusedPane::Some { - pane, - focus: Focus::Dragging, - } => Some(pane), - _ => None, - }; - renderer.draw( defaults, &self.elements, - dragging, + self.state.dragged(), layout, cursor_position, ) @@ -276,7 +262,7 @@ where std::any::TypeId::of::>().hash(state); self.width.hash(state); self.height.hash(state); - self.state.layout.hash(state); + self.state.hash_layout(state); for (_, element) in &self.elements { element.hash_layout(state); @@ -284,374 +270,6 @@ where } } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct Pane(usize); - -impl Pane { - pub fn index(&self) -> usize { - self.0 - } -} - -#[derive(Debug)] -pub struct State { - panes: HashMap, - internal: Internal, -} - -#[derive(Debug)] -struct Internal { - layout: Node, - last_pane: usize, - focused_pane: FocusedPane, - modifiers: keyboard::ModifiersState, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Focus { - Idle, - Dragging, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum FocusedPane { - None, - Some { pane: Pane, focus: Focus }, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Direction { - Up, - Down, - Left, - Right, -} - -impl State { - 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_pane: 0, - focused_pane: FocusedPane::None, - modifiers: keyboard::ModifiersState::default(), - }, - }, - first_pane, - ) - } - - pub fn len(&self) -> usize { - self.panes.len() - } - - pub fn get_mut(&mut self, pane: &Pane) -> Option<&mut T> { - self.panes.get_mut(pane) - } - - pub fn iter(&self) -> impl Iterator { - self.panes.iter() - } - - pub fn iter_mut(&mut self) -> impl Iterator { - self.panes.iter_mut() - } - - pub fn focused_pane(&self) -> Option { - match self.internal.focused_pane { - FocusedPane::Some { - pane, - focus: Focus::Idle, - } => Some(pane), - FocusedPane::Some { - focus: Focus::Dragging, - .. - } => None, - FocusedPane::None => None, - } - } - - pub fn adjacent_pane( - &self, - pane: &Pane, - direction: Direction, - ) -> Option { - 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) - } - - pub fn focus(&mut self, pane: &Pane) { - self.internal.focused_pane = FocusedPane::Some { - pane: *pane, - focus: Focus::Idle, - }; - } - - pub fn split_vertically(&mut self, pane: &Pane, state: T) -> Option { - self.split(Split::Vertical, pane, state) - } - - pub fn split_horizontally( - &mut self, - pane: &Pane, - state: T, - ) -> Option { - self.split(Split::Horizontal, pane, state) - } - - pub fn split( - &mut self, - kind: Split, - pane: &Pane, - state: T, - ) -> Option { - let node = self.internal.layout.find(pane)?; - - let new_pane = { - self.internal.last_pane = self.internal.last_pane.checked_add(1)?; - - Pane(self.internal.last_pane) - }; - - node.split(kind, new_pane); - - let _ = self.panes.insert(new_pane, state); - self.focus(&new_pane); - - Some(new_pane) - } - - 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); - } - } - }); - } - - pub fn close(&mut self, pane: &Pane) -> Option { - if let Some(sibling) = self.internal.layout.remove(pane) { - self.focus(&sibling); - self.panes.remove(pane) - } else { - None - } - } -} - -#[derive(Debug, Clone, Hash)] -enum Node { - Split { - kind: Split, - ratio: u32, - a: Box, - b: Box, - }, - Pane(Pane), -} - -impl Node { - 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 - } - } - } - } - - fn split(&mut self, kind: Split, new_pane: Pane) { - *self = Node::Split { - kind, - ratio: 500_000, - a: Box::new(self.clone()), - b: Box::new(Node::Pane(new_pane)), - }; - } - - fn update(&mut self, f: &impl Fn(&mut Node)) { - match self { - Node::Split { a, b, .. } => { - a.update(f); - b.update(f); - } - _ => {} - } - - f(self); - } - - fn remove(&mut self, pane: &Pane) -> Option { - 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 { - 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 - } - - fn pane(&self) -> Option { - match self { - Node::Split { .. } => None, - Node::Pane(pane) => Some(*pane), - } - } - - 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, - ) { - match self { - Node::Split { kind, ratio, a, b } => { - let ratio = *ratio as f32 / 1_000_000.0; - let (region_a, region_b) = - kind.apply(current, ratio, halved_spacing); - - a.compute_regions(halved_spacing, ®ion_a, regions); - b.compute_regions(halved_spacing, ®ion_b, regions); - } - Node::Pane(pane) => { - let _ = regions.insert(*pane, *current); - } - } - } -} - -#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] -pub enum Split { - Horizontal, - Vertical, -} - -impl Split { - fn apply( - &self, - rectangle: &Rectangle, - ratio: f32, - halved_spacing: f32, - ) -> (Rectangle, Rectangle) { - match self { - Split::Horizontal => { - let width_left = - (rectangle.width * ratio).round() - halved_spacing; - let width_right = rectangle.width - width_left - halved_spacing; - - ( - Rectangle { - width: width_left, - ..*rectangle - }, - Rectangle { - x: rectangle.x + width_left + halved_spacing, - width: width_right, - ..*rectangle - }, - ) - } - Split::Vertical => { - let height_top = - (rectangle.height * ratio).round() - halved_spacing; - let height_bottom = - rectangle.height - height_top - halved_spacing; - - ( - Rectangle { - height: height_top, - ..*rectangle - }, - Rectangle { - y: rectangle.y + height_top + halved_spacing, - height: height_bottom, - ..*rectangle - }, - ) - } - } - } -} - /// The renderer of a [`PaneGrid`]. /// /// Your [renderer] will need to implement this trait before being diff --git a/native/src/widget/pane_grid/direction.rs b/native/src/widget/pane_grid/direction.rs new file mode 100644 index 0000000000..0ee90557ad --- /dev/null +++ b/native/src/widget/pane_grid/direction.rs @@ -0,0 +1,7 @@ +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Direction { + Up, + Down, + Left, + Right, +} diff --git a/native/src/widget/pane_grid/node.rs b/native/src/widget/pane_grid/node.rs new file mode 100644 index 0000000000..a9aa7fdc6f --- /dev/null +++ b/native/src/widget/pane_grid/node.rs @@ -0,0 +1,128 @@ +use crate::{ + pane_grid::{Pane, Split}, + Rectangle, Size, +}; + +use std::collections::HashMap; + +#[derive(Debug, Clone, Hash)] +pub enum Node { + Split { + kind: Split, + ratio: u32, + a: Box, + b: Box, + }, + 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, kind: Split, new_pane: Pane) { + *self = Node::Split { + kind, + 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 remove(&mut self, pane: &Pane) -> Option { + 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 { + 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 pane(&self) -> Option { + 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, + ) { + match self { + Node::Split { kind, ratio, a, b } => { + let ratio = *ratio as f32 / 1_000_000.0; + let (region_a, region_b) = + kind.apply(current, ratio, halved_spacing); + + a.compute_regions(halved_spacing, ®ion_a, regions); + b.compute_regions(halved_spacing, ®ion_b, regions); + } + Node::Pane(pane) => { + let _ = regions.insert(*pane, *current); + } + } + } +} diff --git a/native/src/widget/pane_grid/pane.rs b/native/src/widget/pane_grid/pane.rs new file mode 100644 index 0000000000..cfca3b03ce --- /dev/null +++ b/native/src/widget/pane_grid/pane.rs @@ -0,0 +1,2 @@ +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct Pane(pub(super) usize); diff --git a/native/src/widget/pane_grid/split.rs b/native/src/widget/pane_grid/split.rs new file mode 100644 index 0000000000..ca9ed5e127 --- /dev/null +++ b/native/src/widget/pane_grid/split.rs @@ -0,0 +1,54 @@ +use crate::Rectangle; + +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] +pub enum Split { + Horizontal, + Vertical, +} + +impl Split { + pub(super) fn apply( + &self, + rectangle: &Rectangle, + ratio: f32, + halved_spacing: f32, + ) -> (Rectangle, Rectangle) { + match self { + Split::Horizontal => { + let width_left = + (rectangle.width * ratio).round() - halved_spacing; + let width_right = rectangle.width - width_left - halved_spacing; + + ( + Rectangle { + width: width_left, + ..*rectangle + }, + Rectangle { + x: rectangle.x + width_left + halved_spacing, + width: width_right, + ..*rectangle + }, + ) + } + Split::Vertical => { + let height_top = + (rectangle.height * ratio).round() - halved_spacing; + let height_bottom = + rectangle.height - height_top - halved_spacing; + + ( + Rectangle { + height: height_top, + ..*rectangle + }, + Rectangle { + y: rectangle.y + height_top + halved_spacing, + height: height_bottom, + ..*rectangle + }, + ) + } + } + } +} diff --git a/native/src/widget/pane_grid/state.rs b/native/src/widget/pane_grid/state.rs new file mode 100644 index 0000000000..130c7e34bc --- /dev/null +++ b/native/src/widget/pane_grid/state.rs @@ -0,0 +1,227 @@ +use crate::{ + input::keyboard, + pane_grid::{node::Node, Direction, Pane, Split}, + Hasher, Point, Rectangle, Size, +}; + +use std::collections::HashMap; + +#[derive(Debug)] +pub struct State { + pub(super) panes: HashMap, + pub(super) internal: Internal, + pub(super) modifiers: keyboard::ModifiersState, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Focus { + Idle, + Dragging, +} + +impl State { + 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_pane: 0, + focused_pane: FocusedPane::None, + }, + modifiers: keyboard::ModifiersState::default(), + }, + first_pane, + ) + } + + pub fn len(&self) -> usize { + self.panes.len() + } + + pub fn get_mut(&mut self, pane: &Pane) -> Option<&mut T> { + self.panes.get_mut(pane) + } + + pub fn iter(&self) -> impl Iterator { + self.panes.iter() + } + + pub fn iter_mut(&mut self) -> impl Iterator { + self.panes.iter_mut() + } + + pub fn focused_pane(&self) -> Option { + match self.internal.focused_pane { + FocusedPane::Some { + pane, + focus: Focus::Idle, + } => Some(pane), + FocusedPane::Some { + focus: Focus::Dragging, + .. + } => None, + FocusedPane::None => None, + } + } + + pub fn adjacent_pane( + &self, + pane: &Pane, + direction: Direction, + ) -> Option { + 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) + } + + pub fn focus(&mut self, pane: &Pane) { + self.internal.focus(pane); + } + + pub fn split_vertically(&mut self, pane: &Pane, state: T) -> Option { + self.split(Split::Vertical, pane, state) + } + + pub fn split_horizontally( + &mut self, + pane: &Pane, + state: T, + ) -> Option { + self.split(Split::Horizontal, pane, state) + } + + pub fn split( + &mut self, + kind: Split, + pane: &Pane, + state: T, + ) -> Option { + let node = self.internal.layout.find(pane)?; + + let new_pane = { + self.internal.last_pane = self.internal.last_pane.checked_add(1)?; + + Pane(self.internal.last_pane) + }; + + node.split(kind, new_pane); + + let _ = self.panes.insert(new_pane, state); + self.focus(&new_pane); + + Some(new_pane) + } + + 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); + } + } + }); + } + + pub fn close(&mut self, pane: &Pane) -> Option { + 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_pane: usize, + focused_pane: FocusedPane, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum FocusedPane { + None, + Some { pane: Pane, focus: Focus }, +} + +impl Internal { + pub fn focused(&self) -> FocusedPane { + self.focused_pane + } + pub fn dragged(&self) -> Option { + match self.focused_pane { + FocusedPane::Some { + pane, + focus: Focus::Dragging, + } => Some(pane), + _ => None, + } + } + + pub fn regions( + &self, + spacing: f32, + size: Size, + ) -> HashMap { + self.layout.regions(spacing, size) + } + + pub fn focus(&mut self, pane: &Pane) { + self.focused_pane = FocusedPane::Some { + pane: *pane, + focus: Focus::Idle, + }; + } + + pub fn drag(&mut self, pane: &Pane) { + self.focused_pane = FocusedPane::Some { + pane: *pane, + focus: Focus::Dragging, + }; + } + + pub fn unfocus(&mut self) { + self.focused_pane = FocusedPane::None; + } + + pub fn hash_layout(&self, hasher: &mut Hasher) { + use std::hash::Hash; + + self.layout.hash(hasher); + } +} From 00c2b55b569ea2ff2fc9de9bbf02475c6ede7e42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sat, 14 Mar 2020 06:26:09 +0100 Subject: [PATCH 26/53] Replace `FocusedPane` with `Action` in `pane_grid` --- native/src/widget/pane_grid.rs | 14 ++--- native/src/widget/pane_grid/node.rs | 8 ++- native/src/widget/pane_grid/state.rs | 80 +++++++++++++--------------- 3 files changed, 52 insertions(+), 50 deletions(-) diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 2272d32c35..e446b23519 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -36,17 +36,19 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { ) -> Element<'a, Message, Renderer>, ) -> Self { let elements = { - let focused_pane = state.internal.focused(); + let action = state.internal.action(); + let current_focus = action.focus(); state .panes .iter_mut() .map(move |(pane, pane_state)| { - let focus = match focused_pane { - state::FocusedPane::Some { - pane: focused_pane, - focus, - } if *pane == focused_pane => Some(focus), + let focus = match current_focus { + Some((focused_pane, focus)) + if *pane == focused_pane => + { + Some(focus) + } _ => None, }; diff --git a/native/src/widget/pane_grid/node.rs b/native/src/widget/pane_grid/node.rs index a9aa7fdc6f..744e3e17d5 100644 --- a/native/src/widget/pane_grid/node.rs +++ b/native/src/widget/pane_grid/node.rs @@ -8,6 +8,7 @@ use std::collections::HashMap; #[derive(Debug, Clone, Hash)] pub enum Node { Split { + id: usize, kind: Split, ratio: u32, a: Box, @@ -32,8 +33,9 @@ impl Node { } } - pub fn split(&mut self, kind: Split, new_pane: Pane) { + pub fn split(&mut self, id: usize, kind: Split, new_pane: Pane) { *self = Node::Split { + id, kind, ratio: 500_000, a: Box::new(self.clone()), @@ -112,7 +114,9 @@ impl Node { regions: &mut HashMap, ) { match self { - Node::Split { kind, ratio, a, b } => { + Node::Split { + kind, ratio, a, b, .. + } => { let ratio = *ratio as f32 / 1_000_000.0; let (region_a, region_b) = kind.apply(current, ratio, halved_spacing); diff --git a/native/src/widget/pane_grid/state.rs b/native/src/widget/pane_grid/state.rs index 130c7e34bc..61576a2924 100644 --- a/native/src/widget/pane_grid/state.rs +++ b/native/src/widget/pane_grid/state.rs @@ -31,8 +31,8 @@ impl State { panes, internal: Internal { layout: Node::Pane(first_pane), - last_pane: 0, - focused_pane: FocusedPane::None, + last_id: 0, + action: Action::Idle { focus: None }, }, modifiers: keyboard::ModifiersState::default(), }, @@ -56,25 +56,14 @@ impl State { self.panes.iter_mut() } - pub fn focused_pane(&self) -> Option { - match self.internal.focused_pane { - FocusedPane::Some { - pane, - focus: Focus::Idle, - } => Some(pane), - FocusedPane::Some { - focus: Focus::Dragging, - .. - } => None, - FocusedPane::None => None, + pub fn active(&self) -> Option { + match self.internal.action { + Action::Idle { focus } => focus, + _ => None, } } - pub fn adjacent_pane( - &self, - pane: &Pane, - direction: Direction, - ) -> Option { + pub fn adjacent(&self, pane: &Pane, direction: Direction) -> Option { let regions = self.internal.layout.regions(0.0, Size::new(4096.0, 4096.0)); @@ -130,12 +119,18 @@ impl State { let node = self.internal.layout.find(pane)?; let new_pane = { - self.internal.last_pane = self.internal.last_pane.checked_add(1)?; + self.internal.last_id = self.internal.last_id.checked_add(1)?; + + Pane(self.internal.last_id) + }; + + let split_id = { + self.internal.last_id = self.internal.last_id.checked_add(1)?; - Pane(self.internal.last_pane) + self.internal.last_id }; - node.split(kind, new_pane); + node.split(split_id, kind, new_pane); let _ = self.panes.insert(new_pane, state); self.focus(&new_pane); @@ -169,26 +164,33 @@ impl State { #[derive(Debug)] pub struct Internal { layout: Node, - last_pane: usize, - focused_pane: FocusedPane, + last_id: usize, + action: Action, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum FocusedPane { - None, - Some { pane: Pane, focus: Focus }, +pub enum Action { + Idle { focus: Option }, + Dragging { pane: Pane }, +} + +impl Action { + pub fn focus(&self) -> Option<(Pane, Focus)> { + match self { + Action::Idle { focus } => focus.map(|pane| (pane, Focus::Idle)), + Action::Dragging { pane } => Some((*pane, Focus::Dragging)), + } + } } impl Internal { - pub fn focused(&self) -> FocusedPane { - self.focused_pane + pub fn action(&self) -> Action { + self.action } + pub fn dragged(&self) -> Option { - match self.focused_pane { - FocusedPane::Some { - pane, - focus: Focus::Dragging, - } => Some(pane), + match self.action { + Action::Dragging { pane } => Some(pane), _ => None, } } @@ -202,21 +204,15 @@ impl Internal { } pub fn focus(&mut self, pane: &Pane) { - self.focused_pane = FocusedPane::Some { - pane: *pane, - focus: Focus::Idle, - }; + self.action = Action::Idle { focus: Some(*pane) }; } pub fn drag(&mut self, pane: &Pane) { - self.focused_pane = FocusedPane::Some { - pane: *pane, - focus: Focus::Dragging, - }; + self.action = Action::Dragging { pane: *pane }; } pub fn unfocus(&mut self) { - self.focused_pane = FocusedPane::None; + self.action = Action::Idle { focus: None }; } pub fn hash_layout(&self, hasher: &mut Hasher) { From a79603e4ca5e0cee46a737ef0b1af5c69ddb49b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sat, 14 Mar 2020 06:32:56 +0100 Subject: [PATCH 27/53] Rename `Split` to `Axis` --- native/src/widget/pane_grid.rs | 4 ++-- native/src/widget/pane_grid/{split.rs => axis.rs} | 10 +++++----- native/src/widget/pane_grid/node.rs | 12 ++++++------ native/src/widget/pane_grid/state.rs | 15 +++++---------- 4 files changed, 18 insertions(+), 23 deletions(-) rename native/src/widget/pane_grid/{split.rs => axis.rs} (92%) diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index e446b23519..12d0b09d41 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -1,12 +1,12 @@ +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::{ diff --git a/native/src/widget/pane_grid/split.rs b/native/src/widget/pane_grid/axis.rs similarity index 92% rename from native/src/widget/pane_grid/split.rs rename to native/src/widget/pane_grid/axis.rs index ca9ed5e127..375509b75a 100644 --- a/native/src/widget/pane_grid/split.rs +++ b/native/src/widget/pane_grid/axis.rs @@ -1,20 +1,20 @@ use crate::Rectangle; #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] -pub enum Split { +pub enum Axis { Horizontal, Vertical, } -impl Split { - pub(super) fn apply( +impl Axis { + pub(super) fn split( &self, rectangle: &Rectangle, ratio: f32, halved_spacing: f32, ) -> (Rectangle, Rectangle) { match self { - Split::Horizontal => { + Axis::Horizontal => { let width_left = (rectangle.width * ratio).round() - halved_spacing; let width_right = rectangle.width - width_left - halved_spacing; @@ -31,7 +31,7 @@ impl Split { }, ) } - Split::Vertical => { + Axis::Vertical => { let height_top = (rectangle.height * ratio).round() - halved_spacing; let height_bottom = diff --git a/native/src/widget/pane_grid/node.rs b/native/src/widget/pane_grid/node.rs index 744e3e17d5..aaf775d863 100644 --- a/native/src/widget/pane_grid/node.rs +++ b/native/src/widget/pane_grid/node.rs @@ -1,5 +1,5 @@ use crate::{ - pane_grid::{Pane, Split}, + pane_grid::{Axis, Pane}, Rectangle, Size, }; @@ -9,7 +9,7 @@ use std::collections::HashMap; pub enum Node { Split { id: usize, - kind: Split, + axis: Axis, ratio: u32, a: Box, b: Box, @@ -33,10 +33,10 @@ impl Node { } } - pub fn split(&mut self, id: usize, kind: Split, new_pane: Pane) { + pub fn split(&mut self, id: usize, axis: Axis, new_pane: Pane) { *self = Node::Split { id, - kind, + axis, ratio: 500_000, a: Box::new(self.clone()), b: Box::new(Node::Pane(new_pane)), @@ -115,11 +115,11 @@ impl Node { ) { match self { Node::Split { - kind, ratio, a, b, .. + axis, ratio, a, b, .. } => { let ratio = *ratio as f32 / 1_000_000.0; let (region_a, region_b) = - kind.apply(current, ratio, halved_spacing); + axis.split(current, ratio, halved_spacing); a.compute_regions(halved_spacing, ®ion_a, regions); b.compute_regions(halved_spacing, ®ion_b, regions); diff --git a/native/src/widget/pane_grid/state.rs b/native/src/widget/pane_grid/state.rs index 61576a2924..dc66e32a80 100644 --- a/native/src/widget/pane_grid/state.rs +++ b/native/src/widget/pane_grid/state.rs @@ -1,6 +1,6 @@ use crate::{ input::keyboard, - pane_grid::{node::Node, Direction, Pane, Split}, + pane_grid::{node::Node, Axis, Direction, Pane}, Hasher, Point, Rectangle, Size, }; @@ -99,7 +99,7 @@ impl State { } pub fn split_vertically(&mut self, pane: &Pane, state: T) -> Option { - self.split(Split::Vertical, pane, state) + self.split(Axis::Vertical, pane, state) } pub fn split_horizontally( @@ -107,15 +107,10 @@ impl State { pane: &Pane, state: T, ) -> Option { - self.split(Split::Horizontal, pane, state) + self.split(Axis::Horizontal, pane, state) } - pub fn split( - &mut self, - kind: Split, - pane: &Pane, - state: T, - ) -> Option { + pub fn split(&mut self, axis: Axis, pane: &Pane, state: T) -> Option { let node = self.internal.layout.find(pane)?; let new_pane = { @@ -130,7 +125,7 @@ impl State { self.internal.last_id }; - node.split(split_id, kind, new_pane); + node.split(split_id, axis, new_pane); let _ = self.panes.insert(new_pane, state); self.focus(&new_pane); From b55746b1e1d8a5ff759c86f52063fa6ce0c02a29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sat, 14 Mar 2020 06:33:17 +0100 Subject: [PATCH 28/53] Remove `PaneGrid::split_*` helpers We can use the `split` method directly instead. --- native/src/widget/pane_grid/state.rs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/native/src/widget/pane_grid/state.rs b/native/src/widget/pane_grid/state.rs index dc66e32a80..f46252c7f3 100644 --- a/native/src/widget/pane_grid/state.rs +++ b/native/src/widget/pane_grid/state.rs @@ -98,18 +98,6 @@ impl State { self.internal.focus(pane); } - pub fn split_vertically(&mut self, pane: &Pane, state: T) -> Option { - self.split(Axis::Vertical, pane, state) - } - - pub fn split_horizontally( - &mut self, - pane: &Pane, - state: T, - ) -> Option { - self.split(Axis::Horizontal, pane, state) - } - pub fn split(&mut self, axis: Axis, pane: &Pane, state: T) -> Option { let node = self.internal.layout.find(pane)?; From db441a64b18487f3f64bb4f99192548d7fac6893 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sat, 14 Mar 2020 06:35:43 +0100 Subject: [PATCH 29/53] Reintroduce `pane_grid::Split` as an identifier --- native/src/widget/pane_grid.rs | 2 ++ native/src/widget/pane_grid/node.rs | 6 +++--- native/src/widget/pane_grid/split.rs | 2 ++ native/src/widget/pane_grid/state.rs | 8 ++++---- 4 files changed, 11 insertions(+), 7 deletions(-) create mode 100644 native/src/widget/pane_grid/split.rs diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 12d0b09d41..68f32bc0cf 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -2,11 +2,13 @@ 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::{ diff --git a/native/src/widget/pane_grid/node.rs b/native/src/widget/pane_grid/node.rs index aaf775d863..08046956e8 100644 --- a/native/src/widget/pane_grid/node.rs +++ b/native/src/widget/pane_grid/node.rs @@ -1,5 +1,5 @@ use crate::{ - pane_grid::{Axis, Pane}, + pane_grid::{Axis, Pane, Split}, Rectangle, Size, }; @@ -8,7 +8,7 @@ use std::collections::HashMap; #[derive(Debug, Clone, Hash)] pub enum Node { Split { - id: usize, + id: Split, axis: Axis, ratio: u32, a: Box, @@ -33,7 +33,7 @@ impl Node { } } - pub fn split(&mut self, id: usize, axis: Axis, new_pane: Pane) { + pub fn split(&mut self, id: Split, axis: Axis, new_pane: Pane) { *self = Node::Split { id, axis, diff --git a/native/src/widget/pane_grid/split.rs b/native/src/widget/pane_grid/split.rs new file mode 100644 index 0000000000..c2dad98007 --- /dev/null +++ b/native/src/widget/pane_grid/split.rs @@ -0,0 +1,2 @@ +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct Split(pub(super) usize); diff --git a/native/src/widget/pane_grid/state.rs b/native/src/widget/pane_grid/state.rs index f46252c7f3..b0e571f0dc 100644 --- a/native/src/widget/pane_grid/state.rs +++ b/native/src/widget/pane_grid/state.rs @@ -1,6 +1,6 @@ use crate::{ input::keyboard, - pane_grid::{node::Node, Axis, Direction, Pane}, + pane_grid::{node::Node, Axis, Direction, Pane, Split}, Hasher, Point, Rectangle, Size, }; @@ -107,13 +107,13 @@ impl State { Pane(self.internal.last_id) }; - let split_id = { + let new_split = { self.internal.last_id = self.internal.last_id.checked_add(1)?; - self.internal.last_id + Split(self.internal.last_id) }; - node.split(split_id, axis, new_pane); + node.split(new_split, axis, new_pane); let _ = self.panes.insert(new_pane, state); self.focus(&new_pane); From f08cb4ad565799689d07bacc190fbe0436a63648 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sat, 14 Mar 2020 08:10:50 +0100 Subject: [PATCH 30/53] Implement mouse-based pane resizing for `PaneGrid` --- core/src/point.rs | 10 +++ native/src/mouse_cursor.rs | 6 ++ native/src/widget/pane_grid.rs | 116 ++++++++++++++++++++++++-- native/src/widget/pane_grid/node.rs | 67 +++++++++++++++ native/src/widget/pane_grid/state.rs | 65 +++++++++++++-- wgpu/src/renderer/widget/pane_grid.rs | 8 +- winit/src/conversion.rs | 4 + 7 files changed, 265 insertions(+), 11 deletions(-) diff --git a/core/src/point.rs b/core/src/point.rs index b55f5099c2..b855cd917f 100644 --- a/core/src/point.rs +++ b/core/src/point.rs @@ -22,6 +22,16 @@ impl Point { pub const fn new(x: f32, y: f32) -> Self { Self { x, y } } + + /// Computes the distance to another [`Point`]. + /// + /// [`Point`]: struct.Point.html + pub fn distance(&self, to: Point) -> f32 { + let a = self.x - to.x; + let b = self.y - to.y; + + f32::sqrt(a * a + b * b) + } } impl From<[f32; 2]> for Point { diff --git a/native/src/mouse_cursor.rs b/native/src/mouse_cursor.rs index c7297e0e08..0dad3edc72 100644 --- a/native/src/mouse_cursor.rs +++ b/native/src/mouse_cursor.rs @@ -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 { diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 68f32bc0cf..5229962d6d 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -14,7 +14,7 @@ pub use state::{Focus, State}; use crate::{ input::{keyboard, mouse, ButtonState}, layout, Clipboard, Element, Event, Hasher, Layout, Length, Point, Size, - Widget, + Vector, Widget, }; #[allow(missing_debug_implementations)] @@ -26,6 +26,7 @@ pub struct PaneGrid<'a, Message, Renderer> { height: Length, spacing: u16, on_drag: Option Message>>, + on_resize: Option Message>>, } impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { @@ -67,6 +68,7 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { height: Length::Fill, spacing: 0, on_drag: None, + on_resize: None, } } @@ -101,6 +103,14 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { self.on_drag = Some(Box::new(f)); self } + + pub fn on_resize( + mut self, + f: impl Fn(ResizeEvent) -> Message + 'static, + ) -> Self { + self.on_resize = Some(Box::new(f)); + self + } } #[derive(Debug, Clone, Copy)] @@ -110,6 +120,12 @@ pub enum DragEvent { Canceled { pane: Pane }, } +#[derive(Debug, Clone, Copy)] +pub struct ResizeEvent { + pub split: Split, + pub ratio: f32, +} + impl<'a, Message, Renderer> Widget for PaneGrid<'a, Message, Renderer> where @@ -178,7 +194,7 @@ where if let Some(((pane, _), _)) = clicked_region.next() { match &self.on_drag { Some(on_drag) if self.modifiers.alt => { - self.state.drag(pane); + self.state.pick_pane(pane); messages.push(on_drag(DragEvent::Picked { pane: *pane, @@ -193,7 +209,7 @@ where } } ButtonState::Released => { - if let Some(pane) = self.state.dragged() { + if let Some(pane) = self.state.picked_pane() { self.state.focus(&pane); if let Some(on_drag) = &self.on_drag { @@ -220,13 +236,101 @@ where } } }, + Event::Mouse(mouse::Event::Input { + button: mouse::Button::Right, + state, + }) if self.on_resize.is_some() + && self.state.picked_pane().is_none() + && self.modifiers.alt => + { + match state { + ButtonState::Pressed => { + let bounds = layout.bounds(); + + let splits = self.state.splits( + f32::from(self.spacing), + Size::new(bounds.width, bounds.height), + ); + + let mut sorted_splits: Vec<_> = splits.iter().collect(); + let offset = Vector::new(bounds.x, bounds.y); + + sorted_splits.sort_by_key( + |(_, (axis, rectangle, ratio))| { + let center = match axis { + Axis::Horizontal => Point::new( + rectangle.x + rectangle.width / 2.0, + rectangle.y + rectangle.height * ratio, + ), + + Axis::Vertical => Point::new( + rectangle.x + rectangle.width * ratio, + rectangle.y + rectangle.height / 2.0, + ), + }; + + cursor_position + .distance(center + offset) + .round() + as u32 + }, + ); + + if let Some((split, (axis, _, _))) = + sorted_splits.first() + { + self.state.pick_split(split, *axis); + } + } + ButtonState::Released => { + self.state.drop_split(); + } + } + } + Event::Mouse(mouse::Event::CursorMoved { .. }) => { + 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.x - bounds.x + + rectangle.x; + + (position / (rectangle.x + rectangle.width)) + .max(0.1) + .min(0.9) + } + Axis::Vertical => { + let position = cursor_position.y - bounds.y + + rectangle.y; + + (position + / (rectangle.y + rectangle.height)) + .max(0.1) + .min(0.9) + } + }; + + messages + .push(on_resize(ResizeEvent { split, ratio })); + } + } + } + } Event::Keyboard(keyboard::Event::Input { modifiers, .. }) => { *self.modifiers = modifiers; } _ => {} } - if self.state.dragged().is_none() { + if self.state.picked_pane().is_none() { { self.elements.iter_mut().zip(layout.children()).for_each( |((_, pane), layout)| { @@ -254,7 +358,8 @@ where renderer.draw( defaults, &self.elements, - self.state.dragged(), + self.state.picked_pane(), + self.state.picked_split().map(|(_, axis)| axis), layout, cursor_position, ) @@ -297,6 +402,7 @@ pub trait Renderer: crate::Renderer + Sized { defaults: &Self::Defaults, content: &[(Pane, Element<'_, Message, Self>)], dragging: Option, + resizing: Option, layout: Layout<'_>, cursor_position: Point, ) -> Self::Output; diff --git a/native/src/widget/pane_grid/node.rs b/native/src/widget/pane_grid/node.rs index 08046956e8..4d5970b87a 100644 --- a/native/src/widget/pane_grid/node.rs +++ b/native/src/widget/pane_grid/node.rs @@ -55,6 +55,25 @@ impl Node { 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 { match self { Node::Split { a, b, .. } => { @@ -93,6 +112,27 @@ impl Node { regions } + pub fn splits( + &self, + spacing: f32, + size: Size, + ) -> HashMap { + 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 { match self { Node::Split { .. } => None, @@ -129,4 +169,31 @@ impl Node { } } } + + fn compute_splits( + &self, + halved_spacing: f32, + current: &Rectangle, + splits: &mut HashMap, + ) { + 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, ®ion_a, splits); + b.compute_splits(halved_spacing, ®ion_b, splits); + } + Node::Pane(_) => {} + } + } } diff --git a/native/src/widget/pane_grid/state.rs b/native/src/widget/pane_grid/state.rs index b0e571f0dc..456ad78aba 100644 --- a/native/src/widget/pane_grid/state.rs +++ b/native/src/widget/pane_grid/state.rs @@ -134,6 +134,10 @@ impl State { }); } + pub fn resize(&mut self, split: &Split, percentage: f32) { + let _ = self.internal.layout.resize(split, percentage); + } + pub fn close(&mut self, pane: &Pane) -> Option { if let Some(sibling) = self.internal.layout.remove(pane) { self.focus(&sibling); @@ -153,14 +157,25 @@ pub struct Internal { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Action { - Idle { focus: Option }, - Dragging { pane: Pane }, + Idle { + focus: Option, + }, + Dragging { + pane: Pane, + }, + Resizing { + split: Split, + axis: Axis, + focus: Option, + }, } impl Action { pub fn focus(&self) -> Option<(Pane, Focus)> { match self { - Action::Idle { focus } => focus.map(|pane| (pane, Focus::Idle)), + Action::Idle { focus } | Action::Resizing { focus, .. } => { + focus.map(|pane| (pane, Focus::Idle)) + } Action::Dragging { pane } => Some((*pane, Focus::Dragging)), } } @@ -171,13 +186,20 @@ impl Internal { self.action } - pub fn dragged(&self) -> Option { + pub fn picked_pane(&self) -> Option { 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, @@ -186,14 +208,47 @@ impl Internal { self.layout.regions(spacing, size) } + pub fn splits( + &self, + spacing: f32, + size: Size, + ) -> HashMap { + self.layout.splits(spacing, size) + } + pub fn focus(&mut self, pane: &Pane) { self.action = Action::Idle { focus: Some(*pane) }; } - pub fn drag(&mut self, pane: &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 }; } diff --git a/wgpu/src/renderer/widget/pane_grid.rs b/wgpu/src/renderer/widget/pane_grid.rs index 8fb4a1a95f..a00b49ea9b 100644 --- a/wgpu/src/renderer/widget/pane_grid.rs +++ b/wgpu/src/renderer/widget/pane_grid.rs @@ -1,6 +1,6 @@ use crate::{Primitive, Renderer}; use iced_native::{ - pane_grid::{self, Pane}, + pane_grid::{self, Axis, Pane}, Element, Layout, MouseCursor, Point, Rectangle, Vector, }; @@ -10,6 +10,7 @@ impl pane_grid::Renderer for Renderer { defaults: &Self::Defaults, content: &[(Pane, Element<'_, Message, Self>)], dragging: Option, + resizing: Option, layout: Layout<'_>, cursor_position: Point, ) -> Self::Output { @@ -70,6 +71,11 @@ impl pane_grid::Renderer for Renderer { Primitive::Group { primitives }, if dragging.is_some() { MouseCursor::Grabbing + } else if let Some(axis) = resizing { + match axis { + Axis::Horizontal => MouseCursor::ResizingHorizontally, + Axis::Vertical => MouseCursor::ResizingVertically, + } } else { mouse_cursor }, diff --git a/winit/src/conversion.rs b/winit/src/conversion.rs index b6a0b64b4d..74852876da 100644 --- a/winit/src/conversion.rs +++ b/winit/src/conversion.rs @@ -116,6 +116,10 @@ pub fn mouse_cursor(mouse_cursor: MouseCursor) -> winit::window::CursorIcon { MouseCursor::Grab => winit::window::CursorIcon::Grab, MouseCursor::Grabbing => winit::window::CursorIcon::Grabbing, MouseCursor::Text => winit::window::CursorIcon::Text, + MouseCursor::ResizingHorizontally => { + winit::window::CursorIcon::EwResize + } + MouseCursor::ResizingVertically => winit::window::CursorIcon::NsResize, } } From eb5e2251bdb71c75e1da86b0f575cd0e13cafa6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sat, 14 Mar 2020 08:16:07 +0100 Subject: [PATCH 31/53] Trigger `PaneGrid` resize on click --- native/src/widget/pane_grid.rs | 82 +++++++++++++++++++--------------- 1 file changed, 47 insertions(+), 35 deletions(-) diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 5229962d6d..6214876463 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -111,6 +111,47 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { self.on_resize = Some(Box::new(f)); self } + + fn trigger_resize( + &mut self, + layout: Layout<'_>, + cursor_position: Point, + messages: &mut Vec, + ) { + 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.x - bounds.x + rectangle.x; + + (position / (rectangle.x + rectangle.width)) + .max(0.1) + .min(0.9) + } + Axis::Vertical => { + let position = + cursor_position.y - bounds.y + rectangle.y; + + (position / (rectangle.y + rectangle.height)) + .max(0.1) + .min(0.9) + } + }; + + messages.push(on_resize(ResizeEvent { split, ratio })); + } + } + } + } } #[derive(Debug, Clone, Copy)] @@ -280,6 +321,11 @@ where sorted_splits.first() { self.state.pick_split(split, *axis); + self.trigger_resize( + layout, + cursor_position, + messages, + ); } } ButtonState::Released => { @@ -288,41 +334,7 @@ where } } Event::Mouse(mouse::Event::CursorMoved { .. }) => { - 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.x - bounds.x - + rectangle.x; - - (position / (rectangle.x + rectangle.width)) - .max(0.1) - .min(0.9) - } - Axis::Vertical => { - let position = cursor_position.y - bounds.y - + rectangle.y; - - (position - / (rectangle.y + rectangle.height)) - .max(0.1) - .min(0.9) - } - }; - - messages - .push(on_resize(ResizeEvent { split, ratio })); - } - } - } + self.trigger_resize(layout, cursor_position, messages); } Event::Keyboard(keyboard::Event::Input { modifiers, .. }) => { *self.modifiers = modifiers; From ec334bdd362243a49237c1f07b601dd6b28ddc3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sat, 14 Mar 2020 09:00:57 +0100 Subject: [PATCH 32/53] Improve pane selection when resizing a `PaneGrid` --- native/src/widget/pane_grid.rs | 102 +++++++++++++------------- native/src/widget/pane_grid/axis.rs | 26 +++---- wgpu/src/renderer/widget/pane_grid.rs | 4 +- 3 files changed, 67 insertions(+), 65 deletions(-) diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 6214876463..0d4a440413 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -14,7 +14,7 @@ pub use state::{Focus, State}; use crate::{ input::{keyboard, mouse, ButtonState}, layout, Clipboard, Element, Event, Hasher, Layout, Length, Point, Size, - Vector, Widget, + Widget, }; #[allow(missing_debug_implementations)] @@ -131,17 +131,17 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { let ratio = match axis { Axis::Horizontal => { let position = - cursor_position.x - bounds.x + rectangle.x; + cursor_position.y - bounds.y + rectangle.y; - (position / (rectangle.x + rectangle.width)) + (position / (rectangle.y + rectangle.height)) .max(0.1) .min(0.9) } Axis::Vertical => { let position = - cursor_position.y - bounds.y + rectangle.y; + cursor_position.x - bounds.x + rectangle.x; - (position / (rectangle.y + rectangle.height)) + (position / (rectangle.x + rectangle.width)) .max(0.1) .min(0.9) } @@ -279,60 +279,62 @@ where }, Event::Mouse(mouse::Event::Input { button: mouse::Button::Right, - state, + state: ButtonState::Pressed, }) if self.on_resize.is_some() && self.state.picked_pane().is_none() && self.modifiers.alt => { - match state { - ButtonState::Pressed => { - let bounds = layout.bounds(); - - let splits = self.state.splits( - f32::from(self.spacing), - Size::new(bounds.width, bounds.height), - ); + let bounds = layout.bounds(); + let relative_cursor = Point::new( + cursor_position.x - bounds.x, + cursor_position.y - bounds.y, + ); - let mut sorted_splits: Vec<_> = splits.iter().collect(); - let offset = Vector::new(bounds.x, bounds.y); - - sorted_splits.sort_by_key( - |(_, (axis, rectangle, ratio))| { - let center = match axis { - Axis::Horizontal => Point::new( - rectangle.x + rectangle.width / 2.0, - rectangle.y + rectangle.height * ratio, - ), - - Axis::Vertical => Point::new( - rectangle.x + rectangle.width * ratio, - rectangle.y + rectangle.height / 2.0, - ), - }; - - cursor_position - .distance(center + offset) - .round() - as u32 - }, - ); + let splits = self.state.splits( + f32::from(self.spacing), + Size::new(bounds.width, bounds.height), + ); - if let Some((split, (axis, _, _))) = - sorted_splits.first() - { - self.state.pick_split(split, *axis); - self.trigger_resize( - layout, - cursor_position, - messages, - ); + 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 } - } - ButtonState::Released => { - self.state.drop_split(); - } + 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); } diff --git a/native/src/widget/pane_grid/axis.rs b/native/src/widget/pane_grid/axis.rs index 375509b75a..f8d53e093e 100644 --- a/native/src/widget/pane_grid/axis.rs +++ b/native/src/widget/pane_grid/axis.rs @@ -15,36 +15,36 @@ impl Axis { ) -> (Rectangle, Rectangle) { match self { Axis::Horizontal => { - let width_left = - (rectangle.width * ratio).round() - halved_spacing; - let width_right = rectangle.width - width_left - halved_spacing; + let height_top = + (rectangle.height * ratio).round() - halved_spacing; + let height_bottom = + rectangle.height - height_top - halved_spacing; ( Rectangle { - width: width_left, + height: height_top, ..*rectangle }, Rectangle { - x: rectangle.x + width_left + halved_spacing, - width: width_right, + y: rectangle.y + height_top + halved_spacing, + height: height_bottom, ..*rectangle }, ) } Axis::Vertical => { - let height_top = - (rectangle.height * ratio).round() - halved_spacing; - let height_bottom = - rectangle.height - height_top - halved_spacing; + let width_left = + (rectangle.width * ratio).round() - halved_spacing; + let width_right = rectangle.width - width_left - halved_spacing; ( Rectangle { - height: height_top, + width: width_left, ..*rectangle }, Rectangle { - y: rectangle.y + height_top + halved_spacing, - height: height_bottom, + x: rectangle.x + width_left + halved_spacing, + width: width_right, ..*rectangle }, ) diff --git a/wgpu/src/renderer/widget/pane_grid.rs b/wgpu/src/renderer/widget/pane_grid.rs index a00b49ea9b..741fe81432 100644 --- a/wgpu/src/renderer/widget/pane_grid.rs +++ b/wgpu/src/renderer/widget/pane_grid.rs @@ -73,8 +73,8 @@ impl pane_grid::Renderer for Renderer { MouseCursor::Grabbing } else if let Some(axis) = resizing { match axis { - Axis::Horizontal => MouseCursor::ResizingHorizontally, - Axis::Vertical => MouseCursor::ResizingVertically, + Axis::Horizontal => MouseCursor::ResizingVertically, + Axis::Vertical => MouseCursor::ResizingHorizontally, } } else { mouse_cursor From a373682fa4e8d57d66707faef1fb6b373f4297eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Sat, 14 Mar 2020 09:36:20 +0100 Subject: [PATCH 33/53] Fix ratio calculation on resize in `PaneGrid` --- native/src/widget/pane_grid.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 0d4a440413..7135efe40e 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -131,19 +131,15 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { let ratio = match axis { Axis::Horizontal => { let position = - cursor_position.y - bounds.y + rectangle.y; + cursor_position.y - bounds.y - rectangle.y; - (position / (rectangle.y + rectangle.height)) - .max(0.1) - .min(0.9) + (position / rectangle.height).max(0.1).min(0.9) } Axis::Vertical => { let position = - cursor_position.x - bounds.x + rectangle.x; + cursor_position.x - bounds.x - rectangle.x; - (position / (rectangle.x + rectangle.width)) - .max(0.1) - .min(0.9) + (position / rectangle.width).max(0.1).min(0.9) } }; From 21a4095a99e23d7302cb689c73970c886b0278b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 17 Mar 2020 04:15:17 +0100 Subject: [PATCH 34/53] Fix spacing calculation in `Axis::split` --- native/src/widget/pane_grid/axis.rs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/native/src/widget/pane_grid/axis.rs b/native/src/widget/pane_grid/axis.rs index f8d53e093e..a17d0c12ef 100644 --- a/native/src/widget/pane_grid/axis.rs +++ b/native/src/widget/pane_grid/axis.rs @@ -15,36 +15,33 @@ impl Axis { ) -> (Rectangle, Rectangle) { match self { Axis::Horizontal => { - let height_top = - (rectangle.height * ratio).round() - halved_spacing; - let height_bottom = - rectangle.height - height_top - halved_spacing; + let height_top = (rectangle.height * ratio).round(); + let height_bottom = rectangle.height - height_top; ( Rectangle { - height: height_top, + height: height_top - halved_spacing, ..*rectangle }, Rectangle { y: rectangle.y + height_top + halved_spacing, - height: height_bottom, + height: height_bottom - halved_spacing, ..*rectangle }, ) } Axis::Vertical => { - let width_left = - (rectangle.width * ratio).round() - halved_spacing; - let width_right = rectangle.width - width_left - halved_spacing; + let width_left = (rectangle.width * ratio).round(); + let width_right = rectangle.width - width_left; ( Rectangle { - width: width_left, + width: width_left - halved_spacing, ..*rectangle }, Rectangle { x: rectangle.x + width_left + halved_spacing, - width: width_right, + width: width_right - halved_spacing, ..*rectangle }, ) From 56ba6215a25fe90a50be8feebeb74031967e92b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 17 Mar 2020 04:23:28 +0100 Subject: [PATCH 35/53] Add simple `pane_grid` example --- Cargo.toml | 1 + examples/pane_grid/Cargo.toml | 9 ++ examples/pane_grid/README.md | 18 +++ examples/pane_grid/src/main.rs | 202 +++++++++++++++++++++++++++++++++ 4 files changed, 230 insertions(+) create mode 100644 examples/pane_grid/Cargo.toml create mode 100644 examples/pane_grid/README.md create mode 100644 examples/pane_grid/src/main.rs diff --git a/Cargo.toml b/Cargo.toml index 01231b70af..12b75aed82 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,6 +45,7 @@ members = [ "examples/geometry", "examples/integration", "examples/pokedex", + "examples/pane_grid", "examples/progress_bar", "examples/solar_system", "examples/stopwatch", diff --git a/examples/pane_grid/Cargo.toml b/examples/pane_grid/Cargo.toml new file mode 100644 index 0000000000..3ed912ac43 --- /dev/null +++ b/examples/pane_grid/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "pane_grid" +version = "0.1.0" +authors = ["Héctor Ramón Jiménez "] +edition = "2018" +publish = false + +[dependencies] +iced = { path = "../.." } diff --git a/examples/pane_grid/README.md b/examples/pane_grid/README.md new file mode 100644 index 0000000000..4d9fc5b979 --- /dev/null +++ b/examples/pane_grid/README.md @@ -0,0 +1,18 @@ +## Counter + +The classic counter example explained in the [`README`](../../README.md). + +The __[`main`]__ file contains all the code of the example. + + + +You can run it with `cargo run`: +``` +cargo run --package counter +``` + +[`main`]: src/main.rs diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs new file mode 100644 index 0000000000..09969630ba --- /dev/null +++ b/examples/pane_grid/src/main.rs @@ -0,0 +1,202 @@ +use iced::{ + button, pane_grid, scrollable, Align, Button, Column, Container, Element, + HorizontalAlignment, Length, PaneGrid, Sandbox, Scrollable, Settings, Text, +}; + +pub fn main() { + Example::run(Settings::default()) +} + +struct Example { + panes: pane_grid::State, + panes_created: usize, +} + +#[derive(Debug, Clone, Copy)] +enum Message { + Split(pane_grid::Axis, pane_grid::Pane), + SplitFocused(pane_grid::Axis), + Dragged(pane_grid::DragEvent), + Resized(pane_grid::ResizeEvent), + Close(pane_grid::Pane), +} + +impl Sandbox for Example { + type Message = Message; + + fn new() -> Self { + let (panes, _) = pane_grid::State::new(Content::new(0)); + + Example { + panes, + panes_created: 1, + } + } + + fn title(&self) -> String { + String::from("Pane grid - Iced") + } + + fn update(&mut self, message: Message) { + match message { + Message::Split(axis, pane) => { + let _ = self.panes.split( + axis, + &pane, + Content::new(self.panes_created), + ); + + self.panes_created += 1; + } + Message::SplitFocused(axis) => { + if let Some(pane) = self.panes.active() { + let _ = self.panes.split( + axis, + &pane, + Content::new(self.panes_created), + ); + + self.panes_created += 1; + } + } + Message::Resized(pane_grid::ResizeEvent { split, ratio }) => { + self.panes.resize(&split, ratio); + } + Message::Dragged(pane_grid::DragEvent::Dropped { + pane, + target, + }) => { + self.panes.swap(&pane, &target); + } + Message::Dragged(_) => {} + Message::Close(pane) => { + let _ = self.panes.close(&pane); + } + } + } + + fn view(&mut self) -> Element { + let total_panes = self.panes.len(); + + let pane_grid = + PaneGrid::new(&mut self.panes, |pane, content, focus| { + content.view(pane, focus, total_panes) + }) + .width(Length::Fill) + .height(Length::Fill) + .spacing(5) + .on_drag(Message::Dragged) + .on_resize(Message::Resized); + + Column::new() + .width(Length::Fill) + .height(Length::Fill) + .padding(10) + .push(pane_grid) + .into() + } +} + +struct Content { + id: usize, + scroll: scrollable::State, + split_horizontally: button::State, + split_vertically: button::State, + close: button::State, +} + +impl Content { + fn new(id: usize) -> Self { + Content { + id, + scroll: scrollable::State::new(), + split_horizontally: button::State::new(), + split_vertically: button::State::new(), + close: button::State::new(), + } + } + fn view( + &mut self, + pane: pane_grid::Pane, + focus: Option, + total_panes: usize, + ) -> Element { + let Content { + id, + scroll, + split_horizontally, + split_vertically, + close, + } = self; + + let button = |state, label, message| { + Button::new( + state, + Text::new(label) + .width(Length::Fill) + .horizontal_alignment(HorizontalAlignment::Center) + .size(16), + ) + .width(Length::Fill) + .on_press(message) + }; + + let mut controls = Column::new() + .spacing(5) + .max_width(150) + .push(button( + split_horizontally, + "Split horizontally", + Message::Split(pane_grid::Axis::Horizontal, pane), + )) + .push(button( + split_vertically, + "Split vertically", + Message::Split(pane_grid::Axis::Vertical, pane), + )); + + if total_panes > 1 { + controls = + controls.push(button(close, "Close", Message::Close(pane))); + } + + let content = Scrollable::new(scroll) + .width(Length::Fill) + .spacing(10) + .align_items(Align::Center) + .push(Text::new(format!("Pane {}", id)).size(30)) + .push(controls); + + Container::new(Column::new().padding(10).push(content)) + .width(Length::Fill) + .height(Length::Fill) + .center_y() + .style(style::Pane { + is_focused: focus.is_some(), + }) + .into() + } +} + +mod style { + use iced::{container, Background, Color}; + + pub struct Pane { + pub is_focused: bool, + } + + impl container::StyleSheet for Pane { + fn style(&self) -> container::Style { + container::Style { + background: Some(Background::Color(Color::WHITE)), + border_width: 1, + border_color: if self.is_focused { + Color::from_rgb8(0x25, 0x7A, 0xFD) + } else { + Color::BLACK + }, + ..Default::default() + } + } + } +} From a280dcda23c3c3432f12776b2fe69c4ed39cd99a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 17 Mar 2020 06:53:57 +0100 Subject: [PATCH 36/53] Add `PaneGrid::on_key_press` for hotkey logic --- native/src/widget/pane_grid.rs | 48 ++++++++++++++++++++++++++-- native/src/widget/pane_grid/state.rs | 7 ++++ 2 files changed, 52 insertions(+), 3 deletions(-) diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 7135efe40e..8410f95cc9 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -25,8 +25,10 @@ pub struct PaneGrid<'a, Message, Renderer> { width: Length, height: Length, spacing: u16, + modifier_keys: keyboard::ModifiersState, on_drag: Option Message>>, on_resize: Option Message>>, + on_key_press: Option Option>>, } impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { @@ -67,8 +69,13 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { 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, } } @@ -96,6 +103,14 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { self } + pub fn modifier_keys( + mut self, + modifier_keys: keyboard::ModifiersState, + ) -> Self { + self.modifier_keys = modifier_keys; + self + } + pub fn on_drag( mut self, f: impl Fn(DragEvent) -> Message + 'static, @@ -112,6 +127,14 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { self } + pub fn on_key_press( + mut self, + f: impl Fn(keyboard::KeyCode) -> Option + 'static, + ) -> Self { + self.on_key_press = Some(Box::new(f)); + self + } + fn trigger_resize( &mut self, layout: Layout<'_>, @@ -230,7 +253,9 @@ where if let Some(((pane, _), _)) = clicked_region.next() { match &self.on_drag { - Some(on_drag) if self.modifiers.alt => { + Some(on_drag) + if *self.modifiers == self.modifier_keys => + { self.state.pick_pane(pane); messages.push(on_drag(DragEvent::Picked { @@ -278,7 +303,7 @@ where state: ButtonState::Pressed, }) if self.on_resize.is_some() && self.state.picked_pane().is_none() - && self.modifiers.alt => + && *self.modifiers == self.modifier_keys => { let bounds = layout.bounds(); let relative_cursor = Point::new( @@ -334,7 +359,24 @@ where Event::Mouse(mouse::Event::CursorMoved { .. }) => { self.trigger_resize(layout, cursor_position, messages); } - Event::Keyboard(keyboard::Event::Input { modifiers, .. }) => { + 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.idle_pane() { + if modifiers == self.modifier_keys { + if let Some(message) = on_key_press(key_code) { + messages.push(message); + } + } + } + } + } + *self.modifiers = modifiers; } _ => {} diff --git a/native/src/widget/pane_grid/state.rs b/native/src/widget/pane_grid/state.rs index 456ad78aba..9103dcd0ce 100644 --- a/native/src/widget/pane_grid/state.rs +++ b/native/src/widget/pane_grid/state.rs @@ -186,6 +186,13 @@ impl Internal { self.action } + pub fn idle_pane(&self) -> Option { + match self.action { + Action::Idle { focus } => focus, + _ => None, + } + } + pub fn picked_pane(&self) -> Option { match self.action { Action::Dragging { pane } => Some(pane), From 6f9cf6c70d8ef01446dae4d093c6e8ff2c7e7708 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 17 Mar 2020 06:54:25 +0100 Subject: [PATCH 37/53] Implement hotkey logic in `pane_grid` example --- examples/pane_grid/Cargo.toml | 1 + examples/pane_grid/src/main.rs | 42 ++++++++++++++++++++++++++++++++-- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/examples/pane_grid/Cargo.toml b/examples/pane_grid/Cargo.toml index 3ed912ac43..fb160a8dfd 100644 --- a/examples/pane_grid/Cargo.toml +++ b/examples/pane_grid/Cargo.toml @@ -7,3 +7,4 @@ publish = false [dependencies] iced = { path = "../.." } +iced_native = { path = "../../native" } diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs index 09969630ba..7ab6839388 100644 --- a/examples/pane_grid/src/main.rs +++ b/examples/pane_grid/src/main.rs @@ -2,6 +2,7 @@ use iced::{ button, pane_grid, scrollable, Align, Button, Column, Container, Element, HorizontalAlignment, Length, PaneGrid, Sandbox, Scrollable, Settings, Text, }; +use iced_native::input::keyboard; pub fn main() { Example::run(Settings::default()) @@ -16,9 +17,11 @@ struct Example { enum Message { Split(pane_grid::Axis, pane_grid::Pane), SplitFocused(pane_grid::Axis), + FocusAdjacent(pane_grid::Direction), Dragged(pane_grid::DragEvent), Resized(pane_grid::ResizeEvent), Close(pane_grid::Pane), + CloseFocused, } impl Sandbox for Example { @@ -59,6 +62,15 @@ impl Sandbox for Example { self.panes_created += 1; } } + Message::FocusAdjacent(direction) => { + if let Some(pane) = self.panes.active() { + if let Some(adjacent) = + self.panes.adjacent(&pane, direction) + { + self.panes.focus(&adjacent); + } + } + } Message::Resized(pane_grid::ResizeEvent { split, ratio }) => { self.panes.resize(&split, ratio); } @@ -72,6 +84,11 @@ impl Sandbox for Example { Message::Close(pane) => { let _ = self.panes.close(&pane); } + Message::CloseFocused => { + if let Some(pane) = self.panes.active() { + let _ = self.panes.close(&pane); + } + } } } @@ -86,7 +103,8 @@ impl Sandbox for Example { .height(Length::Fill) .spacing(5) .on_drag(Message::Dragged) - .on_resize(Message::Resized); + .on_resize(Message::Resized) + .on_key_press(handle_hotkey); Column::new() .width(Length::Fill) @@ -97,6 +115,26 @@ impl Sandbox for Example { } } +fn handle_hotkey(key_code: keyboard::KeyCode) -> Option { + use keyboard::KeyCode; + use pane_grid::{Axis, Direction}; + + let direction = match key_code { + KeyCode::Up => Some(Direction::Up), + KeyCode::Down => Some(Direction::Down), + KeyCode::Left => Some(Direction::Left), + KeyCode::Right => Some(Direction::Right), + _ => None, + }; + + match key_code { + KeyCode::V => Some(Message::SplitFocused(Axis::Vertical)), + KeyCode::H => Some(Message::SplitFocused(Axis::Horizontal)), + KeyCode::W => Some(Message::CloseFocused), + _ => direction.map(Message::FocusAdjacent), + } +} + struct Content { id: usize, scroll: scrollable::State, @@ -189,7 +227,7 @@ mod style { fn style(&self) -> container::Style { container::Style { background: Some(Background::Color(Color::WHITE)), - border_width: 1, + border_width: if self.is_focused { 2 } else { 1 }, border_color: if self.is_focused { Color::from_rgb8(0x25, 0x7A, 0xFD) } else { From 1cd1582506810255394d2f9019597e9252bd8daa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 17 Mar 2020 07:16:54 +0100 Subject: [PATCH 38/53] Add `modifiers` to `KeyPressEvent` in `pane_grid` --- examples/pane_grid/src/main.rs | 6 +++--- native/src/widget/pane_grid.rs | 17 ++++++++++++++--- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs index 7ab6839388..461ffc304d 100644 --- a/examples/pane_grid/src/main.rs +++ b/examples/pane_grid/src/main.rs @@ -115,11 +115,11 @@ impl Sandbox for Example { } } -fn handle_hotkey(key_code: keyboard::KeyCode) -> Option { +fn handle_hotkey(event: pane_grid::KeyPressEvent) -> Option { use keyboard::KeyCode; use pane_grid::{Axis, Direction}; - let direction = match key_code { + let direction = match event.key_code { KeyCode::Up => Some(Direction::Up), KeyCode::Down => Some(Direction::Down), KeyCode::Left => Some(Direction::Left), @@ -127,7 +127,7 @@ fn handle_hotkey(key_code: keyboard::KeyCode) -> Option { _ => None, }; - match key_code { + match event.key_code { KeyCode::V => Some(Message::SplitFocused(Axis::Vertical)), KeyCode::H => Some(Message::SplitFocused(Axis::Horizontal)), KeyCode::W => Some(Message::CloseFocused), diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 8410f95cc9..5212a1472f 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -28,7 +28,7 @@ pub struct PaneGrid<'a, Message, Renderer> { modifier_keys: keyboard::ModifiersState, on_drag: Option Message>>, on_resize: Option Message>>, - on_key_press: Option Option>>, + on_key_press: Option Option>>, } impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { @@ -129,7 +129,7 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { pub fn on_key_press( mut self, - f: impl Fn(keyboard::KeyCode) -> Option + 'static, + f: impl Fn(KeyPressEvent) -> Option + 'static, ) -> Self { self.on_key_press = Some(Box::new(f)); self @@ -186,6 +186,12 @@ pub struct ResizeEvent { pub ratio: f32, } +#[derive(Debug, Clone, Copy)] +pub struct KeyPressEvent { + pub key_code: keyboard::KeyCode, + pub modifiers: keyboard::ModifiersState, +} + impl<'a, Message, Renderer> Widget for PaneGrid<'a, Message, Renderer> where @@ -369,7 +375,12 @@ where if state == ButtonState::Pressed { if let Some(_) = self.state.idle_pane() { if modifiers == self.modifier_keys { - if let Some(message) = on_key_press(key_code) { + if let Some(message) = + on_key_press(KeyPressEvent { + key_code, + modifiers, + }) + { messages.push(message); } } From 05beb878527b4d4e3141ca5ba09337d6ada858be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 17 Mar 2020 07:28:28 +0100 Subject: [PATCH 39/53] Move common keyboard types to `iced_core` Also expose them in `iced` through `iced_native` and `iced_web`. --- core/src/keyboard.rs | 6 ++++++ {native/src/input => core/src}/keyboard/key_code.rs | 0 {native/src/input => core/src}/keyboard/modifiers_state.rs | 0 core/src/lib.rs | 1 + examples/pane_grid/Cargo.toml | 1 - examples/pane_grid/src/main.rs | 6 +++--- native/src/input/keyboard.rs | 5 +---- src/keyboard.rs | 6 ++++++ src/lib.rs | 1 + web/src/lib.rs | 4 ++-- 10 files changed, 20 insertions(+), 10 deletions(-) create mode 100644 core/src/keyboard.rs rename {native/src/input => core/src}/keyboard/key_code.rs (100%) rename {native/src/input => core/src}/keyboard/modifiers_state.rs (100%) create mode 100644 src/keyboard.rs diff --git a/core/src/keyboard.rs b/core/src/keyboard.rs new file mode 100644 index 0000000000..d98b2989f8 --- /dev/null +++ b/core/src/keyboard.rs @@ -0,0 +1,6 @@ +//! Reuse basic keyboard types. +mod key_code; +mod modifiers_state; + +pub use key_code::KeyCode; +pub use modifiers_state::ModifiersState; diff --git a/native/src/input/keyboard/key_code.rs b/core/src/keyboard/key_code.rs similarity index 100% rename from native/src/input/keyboard/key_code.rs rename to core/src/keyboard/key_code.rs diff --git a/native/src/input/keyboard/modifiers_state.rs b/core/src/keyboard/modifiers_state.rs similarity index 100% rename from native/src/input/keyboard/modifiers_state.rs rename to core/src/keyboard/modifiers_state.rs diff --git a/core/src/lib.rs b/core/src/lib.rs index ea5e8b4365..c2887a0be2 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -14,6 +14,7 @@ #![deny(unused_results)] #![forbid(unsafe_code)] #![forbid(rust_2018_idioms)] +pub mod keyboard; mod align; mod background; diff --git a/examples/pane_grid/Cargo.toml b/examples/pane_grid/Cargo.toml index fb160a8dfd..3ed912ac43 100644 --- a/examples/pane_grid/Cargo.toml +++ b/examples/pane_grid/Cargo.toml @@ -7,4 +7,3 @@ publish = false [dependencies] iced = { path = "../.." } -iced_native = { path = "../../native" } diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs index 461ffc304d..9e6283ab26 100644 --- a/examples/pane_grid/src/main.rs +++ b/examples/pane_grid/src/main.rs @@ -1,8 +1,8 @@ use iced::{ - button, pane_grid, scrollable, Align, Button, Column, Container, Element, - HorizontalAlignment, Length, PaneGrid, Sandbox, Scrollable, Settings, Text, + button, keyboard, pane_grid, scrollable, Align, Button, Column, Container, + Element, HorizontalAlignment, Length, PaneGrid, Sandbox, Scrollable, + Settings, Text, }; -use iced_native::input::keyboard; pub fn main() { Example::run(Settings::default()) diff --git a/native/src/input/keyboard.rs b/native/src/input/keyboard.rs index 432e75ba68..928bf49279 100644 --- a/native/src/input/keyboard.rs +++ b/native/src/input/keyboard.rs @@ -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}; diff --git a/src/keyboard.rs b/src/keyboard.rs new file mode 100644 index 0000000000..181dd97486 --- /dev/null +++ b/src/keyboard.rs @@ -0,0 +1,6 @@ +//! Listen and react to keyboard events. +#[cfg(not(target_arch = "wasm32"))] +pub use iced_winit::input::keyboard::{KeyCode, ModifiersState}; + +#[cfg(target_arch = "wasm32")] +pub use iced_web::keyboard::{KeyCode, ModifiersState}; diff --git a/src/lib.rs b/src/lib.rs index d492db029f..aeec24c2c0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -183,6 +183,7 @@ mod element; mod sandbox; pub mod executor; +pub mod keyboard; pub mod settings; pub mod widget; pub mod window; diff --git a/web/src/lib.rs b/web/src/lib.rs index 258ad9e7b4..1de00545d4 100644 --- a/web/src/lib.rs +++ b/web/src/lib.rs @@ -73,8 +73,8 @@ pub use dodrio; pub use element::Element; pub use hasher::Hasher; pub use iced_core::{ - Align, Background, Color, Font, HorizontalAlignment, Length, Point, Size, - Vector, VerticalAlignment, + keyboard, Align, Background, Color, Font, HorizontalAlignment, Length, + Point, Size, Vector, VerticalAlignment, }; pub use iced_futures::{executor, futures, Command}; pub use subscription::Subscription; From 20b142e8e3a674f27f9c9429449002086708ce11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 18 Mar 2020 01:26:13 +0100 Subject: [PATCH 40/53] Make cursor unavailable when dragging panes --- wgpu/src/renderer/widget/pane_grid.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/wgpu/src/renderer/widget/pane_grid.rs b/wgpu/src/renderer/widget/pane_grid.rs index 741fe81432..2d201fec6b 100644 --- a/wgpu/src/renderer/widget/pane_grid.rs +++ b/wgpu/src/renderer/widget/pane_grid.rs @@ -14,6 +14,14 @@ impl pane_grid::Renderer for Renderer { layout: Layout<'_>, cursor_position: Point, ) -> Self::Output { + let pane_cursor_position = if dragging.is_some() { + // TODO: Remove once cursor availability is encoded in the type + // system + Point::new(-1.0, -1.0) + } else { + cursor_position + }; + let mut mouse_cursor = MouseCursor::OutOfBounds; let mut dragged_pane = None; @@ -23,7 +31,7 @@ impl pane_grid::Renderer for Renderer { .enumerate() .map(|(i, ((id, pane), layout))| { let (primitive, new_mouse_cursor) = - pane.draw(self, defaults, layout, cursor_position); + pane.draw(self, defaults, layout, pane_cursor_position); if new_mouse_cursor > mouse_cursor { mouse_cursor = new_mouse_cursor; From b8a035d2dae43f590f681686d856b8b22630141b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 18 Mar 2020 01:27:23 +0100 Subject: [PATCH 41/53] Add some styling to `pane_grid` buttons --- examples/pane_grid/src/main.rs | 51 +++++++++++++++++++++++++++++++--- 1 file changed, 47 insertions(+), 4 deletions(-) diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs index 9e6283ab26..c5dae01676 100644 --- a/examples/pane_grid/src/main.rs +++ b/examples/pane_grid/src/main.rs @@ -167,7 +167,7 @@ impl Content { close, } = self; - let button = |state, label, message| { + let button = |state, label, message, style| { Button::new( state, Text::new(label) @@ -176,7 +176,9 @@ impl Content { .size(16), ) .width(Length::Fill) + .padding(8) .on_press(message) + .style(style) }; let mut controls = Column::new() @@ -186,16 +188,22 @@ impl Content { split_horizontally, "Split horizontally", Message::Split(pane_grid::Axis::Horizontal, pane), + style::Button::Primary, )) .push(button( split_vertically, "Split vertically", Message::Split(pane_grid::Axis::Vertical, pane), + style::Button::Primary, )); if total_panes > 1 { - controls = - controls.push(button(close, "Close", Message::Close(pane))); + controls = controls.push(button( + close, + "Close", + Message::Close(pane), + style::Button::Destructive, + )); } let content = Scrollable::new(scroll) @@ -217,7 +225,7 @@ impl Content { } mod style { - use iced::{container, Background, Color}; + use iced::{button, container, Background, Color, Vector}; pub struct Pane { pub is_focused: bool, @@ -237,4 +245,39 @@ mod style { } } } + + pub enum Button { + Primary, + Destructive, + } + + impl button::StyleSheet for Button { + fn active(&self) -> button::Style { + let color = match self { + Button::Primary => Color::from_rgb8(0x25, 0x7A, 0xFD), + Button::Destructive => Color::from_rgb(0.8, 0.2, 0.2), + }; + + button::Style { + background: None, + border_color: color, + border_radius: 5, + border_width: 1, + shadow_offset: Vector::new(0.0, 0.0), + text_color: color, + ..button::Style::default() + } + } + + fn hovered(&self) -> button::Style { + let active = self.active(); + + button::Style { + background: Some(Background::Color(active.border_color)), + text_color: Color::WHITE, + border_width: 0, + ..active + } + } + } } From 36abf7457fdc5a50b53f7e9ae63b978f07fbcda4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 18 Mar 2020 05:53:41 +0100 Subject: [PATCH 42/53] Improve styling of `pane_grid` example --- examples/pane_grid/src/main.rs | 59 +++++++++++++++++++++++----------- 1 file changed, 41 insertions(+), 18 deletions(-) diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs index c5dae01676..dafc396c97 100644 --- a/examples/pane_grid/src/main.rs +++ b/examples/pane_grid/src/main.rs @@ -101,7 +101,7 @@ impl Sandbox for Example { }) .width(Length::Fill) .height(Length::Fill) - .spacing(5) + .spacing(10) .on_drag(Message::Dragged) .on_resize(Message::Resized) .on_key_press(handle_hotkey); @@ -213,7 +213,7 @@ impl Content { .push(Text::new(format!("Pane {}", id)).size(30)) .push(controls); - Container::new(Column::new().padding(10).push(content)) + Container::new(Column::new().padding(5).push(content)) .width(Length::Fill) .height(Length::Fill) .center_y() @@ -227,6 +227,24 @@ impl Content { mod style { use iced::{button, container, Background, Color, Vector}; + const SURFACE: Color = Color::from_rgb( + 0xF2 as f32 / 255.0, + 0xF3 as f32 / 255.0, + 0xF5 as f32 / 255.0, + ); + + const ACTIVE: Color = Color::from_rgb( + 0x72 as f32 / 255.0, + 0x89 as f32 / 255.0, + 0xDA as f32 / 255.0, + ); + + const HOVERED: Color = Color::from_rgb( + 0x67 as f32 / 255.0, + 0x7B as f32 / 255.0, + 0xC4 as f32 / 255.0, + ); + pub struct Pane { pub is_focused: bool, } @@ -234,12 +252,11 @@ mod style { impl container::StyleSheet for Pane { fn style(&self) -> container::Style { container::Style { - background: Some(Background::Color(Color::WHITE)), - border_width: if self.is_focused { 2 } else { 1 }, - border_color: if self.is_focused { - Color::from_rgb8(0x25, 0x7A, 0xFD) - } else { - Color::BLACK + background: Some(Background::Color(SURFACE)), + border_width: 2, + border_color: Color { + a: if self.is_focused { 1.0 } else { 0.3 }, + ..Color::BLACK }, ..Default::default() } @@ -253,18 +270,18 @@ mod style { impl button::StyleSheet for Button { fn active(&self) -> button::Style { - let color = match self { - Button::Primary => Color::from_rgb8(0x25, 0x7A, 0xFD), - Button::Destructive => Color::from_rgb(0.8, 0.2, 0.2), + let (background, text_color) = match self { + Button::Primary => (Some(ACTIVE), Color::WHITE), + Button::Destructive => { + (None, Color::from_rgb8(0xFF, 0x47, 0x47)) + } }; button::Style { - background: None, - border_color: color, + text_color, + background: background.map(Background::Color), border_radius: 5, - border_width: 1, shadow_offset: Vector::new(0.0, 0.0), - text_color: color, ..button::Style::default() } } @@ -272,10 +289,16 @@ mod style { fn hovered(&self) -> button::Style { let active = self.active(); + let background = match self { + Button::Primary => Some(HOVERED), + Button::Destructive => Some(Color { + a: 0.2, + ..active.text_color + }), + }; + button::Style { - background: Some(Background::Color(active.border_color)), - text_color: Color::WHITE, - border_width: 0, + background: background.map(Background::Color), ..active } } From eba2ded88a92479bee93b727b31f5f84899339cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 18 Mar 2020 06:35:55 +0100 Subject: [PATCH 43/53] Update `README` of examples --- examples/README.md | 1 + examples/pane_grid/README.md | 20 +++++++++++++++----- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/examples/README.md b/examples/README.md index 04399b932f..a76737053f 100644 --- a/examples/README.md +++ b/examples/README.md @@ -76,6 +76,7 @@ A bunch of simpler examples exist: - [`events`](events), a log of native events displayed using a conditional `Subscription`. - [`geometry`](geometry), a custom widget showcasing how to draw geometry with the `Mesh2D` primitive in [`iced_wgpu`](../wgpu). - [`integration`](integration), a demonstration of how to integrate Iced in an existing graphical application. +- [`pane_grid`](pane_grid), a grid of panes that can be split, resized, and reorganized. - [`pokedex`](pokedex), an application that displays a random Pokédex entry (sprite included!) by using the [PokéAPI]. - [`progress_bar`](progress_bar), a simple progress bar that can be filled by using a slider. - [`solar_system`](solar_system), an animated solar system drawn using the `Canvas` widget and showcasing how to compose different transforms. diff --git a/examples/pane_grid/README.md b/examples/pane_grid/README.md index 4d9fc5b979..3653fc5b71 100644 --- a/examples/pane_grid/README.md +++ b/examples/pane_grid/README.md @@ -1,18 +1,28 @@ -## Counter +## Pane grid -The classic counter example explained in the [`README`](../../README.md). +A grid of panes that can be split, resized, and reorganized. + +This example showcases the `PaneGrid` widget, which features: + +* Vertical and horizontal splits +* Tracking of the last active pane +* Mouse-based resizing +* Drag and drop to reorganize panes +* Hotkey support +* Configurable modifier keys +* API to perform actions programmatically (`split`, `swap`, `resize`, etc.) The __[`main`]__ file contains all the code of the example. You can run it with `cargo run`: ``` -cargo run --package counter +cargo run --package pane_grid ``` [`main`]: src/main.rs From 50b02d41a01ad66e08045b320a30a0f5d76ee2f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Wed, 18 Mar 2020 07:10:36 +0100 Subject: [PATCH 44/53] Check only for partial match of modifier keys --- core/src/keyboard/modifiers_state.rs | 17 ++++++++++++++++- native/src/widget/pane_grid.rs | 8 +++++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/core/src/keyboard/modifiers_state.rs b/core/src/keyboard/modifiers_state.rs index 3058c06536..0cfc6d691f 100644 --- a/core/src/keyboard/modifiers_state.rs +++ b/core/src/keyboard/modifiers_state.rs @@ -1,5 +1,5 @@ /// The current state of the keyboard modifiers. -#[derive(Debug, Clone, Copy, PartialEq, Default)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub struct ModifiersState { /// Whether a shift key is pressed pub shift: bool, @@ -13,3 +13,18 @@ pub struct ModifiersState { /// Whether a logo key is pressed (e.g. windows key, command key...) pub logo: bool, } + +impl ModifiersState { + /// Returns true if the current [`ModifiersState`] has at least the same + /// modifiers enabled as the given value, and false otherwise. + /// + /// [`ModifiersState`]: struct.ModifiersState.html + pub fn matches(&self, modifiers: ModifiersState) -> bool { + let shift = !modifiers.shift || modifiers.shift && self.shift; + let control = !modifiers.control || modifiers.control && self.control; + let alt = !modifiers.alt || modifiers.alt && self.alt; + let logo = !modifiers.logo || modifiers.logo && self.logo; + + shift && control && alt && logo + } +} diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 5212a1472f..a2e4ebaa2e 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -260,7 +260,9 @@ where if let Some(((pane, _), _)) = clicked_region.next() { match &self.on_drag { Some(on_drag) - if *self.modifiers == self.modifier_keys => + if self + .modifiers + .matches(self.modifier_keys) => { self.state.pick_pane(pane); @@ -309,7 +311,7 @@ where state: ButtonState::Pressed, }) if self.on_resize.is_some() && self.state.picked_pane().is_none() - && *self.modifiers == self.modifier_keys => + && self.modifiers.matches(self.modifier_keys) => { let bounds = layout.bounds(); let relative_cursor = Point::new( @@ -374,7 +376,7 @@ where // TODO: Discard when event is captured if state == ButtonState::Pressed { if let Some(_) = self.state.idle_pane() { - if modifiers == self.modifier_keys { + if modifiers.matches(self.modifier_keys) { if let Some(message) = on_key_press(KeyPressEvent { key_code, From a820b8ce7b192a496a1679a43d6fe4603dfc954b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 19 Mar 2020 08:21:23 +0100 Subject: [PATCH 45/53] Rename `PaneGrid::modifiers` to `pressed_modifiers` --- native/src/widget/pane_grid.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index a2e4ebaa2e..3e61642e53 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -20,7 +20,7 @@ use crate::{ #[allow(missing_debug_implementations)] pub struct PaneGrid<'a, Message, Renderer> { state: &'a mut state::Internal, - modifiers: &'a mut keyboard::ModifiersState, + pressed_modifiers: &'a mut keyboard::ModifiersState, elements: Vec<(Pane, Element<'a, Message, Renderer>)>, width: Length, height: Length, @@ -64,7 +64,7 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { Self { state: &mut state.internal, - modifiers: &mut state.modifiers, + pressed_modifiers: &mut state.modifiers, elements, width: Length::Fill, height: Length::Fill, @@ -261,7 +261,7 @@ where match &self.on_drag { Some(on_drag) if self - .modifiers + .pressed_modifiers .matches(self.modifier_keys) => { self.state.pick_pane(pane); @@ -311,7 +311,7 @@ where state: ButtonState::Pressed, }) if self.on_resize.is_some() && self.state.picked_pane().is_none() - && self.modifiers.matches(self.modifier_keys) => + && self.pressed_modifiers.matches(self.modifier_keys) => { let bounds = layout.bounds(); let relative_cursor = Point::new( @@ -390,7 +390,7 @@ where } } - *self.modifiers = modifiers; + *self.pressed_modifiers = modifiers; } _ => {} } From bd74c4e577de01b48064c7a01541ca2ad6d9ae16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 19 Mar 2020 09:30:54 +0100 Subject: [PATCH 46/53] Write documentation for `pane_grid` --- native/src/lib.rs | 6 +- native/src/widget/pane_grid.rs | 160 ++++++++++++++++++++++- native/src/widget/pane_grid/axis.rs | 3 + native/src/widget/pane_grid/direction.rs | 5 + native/src/widget/pane_grid/pane.rs | 3 + native/src/widget/pane_grid/split.rs | 3 + native/src/widget/pane_grid/state.rs | 104 ++++++++++++++- 7 files changed, 273 insertions(+), 11 deletions(-) diff --git a/native/src/lib.rs b/native/src/lib.rs index 4551a98299..d17dd918a4 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -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. //! @@ -34,7 +34,7 @@ //! [`window::Renderer`]: window/trait.Renderer.html //! [`UserInterface`]: struct.UserInterface.html //! [renderer]: renderer/index.html -//#![deny(missing_docs)] +#![deny(missing_docs)] #![deny(missing_debug_implementations)] #![deny(unused_results)] #![forbid(unsafe_code)] diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 3e61642e53..d33573cab9 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -1,3 +1,6 @@ +//! 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; @@ -17,6 +20,57 @@ use crate::{ 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, @@ -32,6 +86,13 @@ pub struct PaneGrid<'a, Message, Renderer> { } 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 pub fn new( state: &'a mut State, view: impl Fn( @@ -81,7 +142,7 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { /// Sets the width of the [`PaneGrid`]. /// - /// [`PaneGrid`]: struct.Column.html + /// [`PaneGrid`]: struct.PaneGrid.html pub fn width(mut self, width: Length) -> Self { self.width = width; self @@ -89,7 +150,7 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { /// Sets the height of the [`PaneGrid`]. /// - /// [`PaneGrid`]: struct.Column.html + /// [`PaneGrid`]: struct.PaneGrid.html pub fn height(mut self, height: Length) -> Self { self.height = height; self @@ -97,12 +158,20 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { /// Sets the spacing _between_ the panes of the [`PaneGrid`]. /// - /// [`PaneGrid`]: struct.Column.html + /// [`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, @@ -111,6 +180,12 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { 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, @@ -119,6 +194,12 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { 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, @@ -127,6 +208,23 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { 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 function 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 + 'static, @@ -173,22 +271,72 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { } } +/// An event produced during a drag and drop interaction of a [`PaneGrid`]. +/// +/// [`PaneGrid`]: struct.PaneGrid.html #[derive(Debug, Clone, Copy)] pub enum DragEvent { - Picked { pane: Pane }, - Dropped { pane: Pane, target: Pane }, - Canceled { pane: Pane }, + /// 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. 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. 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, } diff --git a/native/src/widget/pane_grid/axis.rs b/native/src/widget/pane_grid/axis.rs index a17d0c12ef..f0e3f362f8 100644 --- a/native/src/widget/pane_grid/axis.rs +++ b/native/src/widget/pane_grid/axis.rs @@ -1,8 +1,11 @@ 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, } diff --git a/native/src/widget/pane_grid/direction.rs b/native/src/widget/pane_grid/direction.rs index 0ee90557ad..b31a87370b 100644 --- a/native/src/widget/pane_grid/direction.rs +++ b/native/src/widget/pane_grid/direction.rs @@ -1,7 +1,12 @@ +/// A four cardinal direction. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Direction { + /// ↑ Up, + /// ↓ Down, + /// ← Left, + /// → Right, } diff --git a/native/src/widget/pane_grid/pane.rs b/native/src/widget/pane_grid/pane.rs index cfca3b03ce..f9866407e8 100644 --- a/native/src/widget/pane_grid/pane.rs +++ b/native/src/widget/pane_grid/pane.rs @@ -1,2 +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); diff --git a/native/src/widget/pane_grid/split.rs b/native/src/widget/pane_grid/split.rs index c2dad98007..d020c51087 100644 --- a/native/src/widget/pane_grid/split.rs +++ b/native/src/widget/pane_grid/split.rs @@ -1,2 +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); diff --git a/native/src/widget/pane_grid/state.rs b/native/src/widget/pane_grid/state.rs index 9103dcd0ce..6c80cacc67 100644 --- a/native/src/widget/pane_grid/state.rs +++ b/native/src/widget/pane_grid/state.rs @@ -6,6 +6,20 @@ use crate::{ 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 +/// [`State`]: struct.State.html +/// [`Pane`]: struct.Pane.html #[derive(Debug)] pub struct State { pub(super) panes: HashMap, @@ -13,13 +27,28 @@ pub struct State { pub(super) modifiers: keyboard::ModifiersState, } +/// The current focus of a [`Pane`]. #[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 State { + /// 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); @@ -40,22 +69,42 @@ impl State { ) } + /// 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 { 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 { 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 { match self.internal.action { Action::Idle { focus } => focus, @@ -63,6 +112,27 @@ impl State { } } + /// 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 { let regions = self.internal.layout.regions(0.0, Size::new(4096.0, 4096.0)); @@ -94,10 +164,18 @@ impl State { 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 { let node = self.internal.layout.find(pane)?; @@ -121,6 +199,14 @@ impl State { 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 { .. } => {} @@ -134,10 +220,24 @@ impl State { }); } - pub fn resize(&mut self, split: &Split, percentage: f32) { - let _ = self.internal.layout.resize(split, percentage); + /// 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 { if let Some(sibling) = self.internal.layout.remove(pane) { self.focus(&sibling); From bb898fa2e277d46642feec397efd753f133a0aae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 19 Mar 2020 09:37:13 +0100 Subject: [PATCH 47/53] Create `PaneGrid` alias in `iced_wgpu` --- src/widget.rs | 7 ++++--- wgpu/src/widget.rs | 3 +++ wgpu/src/widget/pane_grid.rs | 17 +++++++++++++++++ 3 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 wgpu/src/widget/pane_grid.rs diff --git a/src/widget.rs b/src/widget.rs index cb099a65b6..91ea1ed427 100644 --- a/src/widget.rs +++ b/src/widget.rs @@ -30,13 +30,14 @@ mod platform { pub use iced_winit::svg::{Handle, Svg}; } - pub use iced_winit::{pane_grid, PaneGrid, Text}; + pub use iced_winit::Text; #[doc(no_inline)] pub use { button::Button, checkbox::Checkbox, container::Container, image::Image, - progress_bar::ProgressBar, radio::Radio, scrollable::Scrollable, - slider::Slider, svg::Svg, text_input::TextInput, + pane_grid::PaneGrid, progress_bar::ProgressBar, radio::Radio, + scrollable::Scrollable, slider::Slider, svg::Svg, + text_input::TextInput, }; /// A container that distributes its contents vertically. diff --git a/wgpu/src/widget.rs b/wgpu/src/widget.rs index 73cce7e269..b39f2d9184 100644 --- a/wgpu/src/widget.rs +++ b/wgpu/src/widget.rs @@ -10,6 +10,7 @@ pub mod button; pub mod checkbox; pub mod container; +pub mod pane_grid; pub mod progress_bar; pub mod radio; pub mod scrollable; @@ -23,6 +24,8 @@ pub use checkbox::Checkbox; #[doc(no_inline)] pub use container::Container; #[doc(no_inline)] +pub use pane_grid::PaneGrid; +#[doc(no_inline)] pub use progress_bar::ProgressBar; #[doc(no_inline)] pub use radio::Radio; diff --git a/wgpu/src/widget/pane_grid.rs b/wgpu/src/widget/pane_grid.rs new file mode 100644 index 0000000000..7bc2f7c52a --- /dev/null +++ b/wgpu/src/widget/pane_grid.rs @@ -0,0 +1,17 @@ +//! 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) +use crate::Renderer; + +pub use iced_native::pane_grid::{ + Axis, Direction, DragEvent, Focus, KeyPressEvent, Pane, ResizeEvent, Split, + State, +}; + +/// 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 is an alias of an `iced_native` pane grid with an `iced_wgpu::Renderer`. +pub type PaneGrid<'a, Message> = iced_native::PaneGrid<'a, Message, Renderer>; From 420275793e04b41254bacdaedd8ca60fb2ffe63f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Thu, 19 Mar 2020 09:43:36 +0100 Subject: [PATCH 48/53] Fix minor documentation issues in `pane_grid` --- native/src/widget/pane_grid.rs | 7 ++++++- native/src/widget/pane_grid/state.rs | 5 ++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index d33573cab9..5ced6610fe 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -93,6 +93,7 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { /// /// [`PaneGrid`]: struct.PaneGrid.html /// [`State`]: struct.State.html + /// [`Pane`]: struct.Pane.html pub fn new( state: &'a mut State, view: impl Fn( @@ -219,7 +220,7 @@ impl<'a, Message, Renderer> PaneGrid<'a, Message, Renderer> { /// If the function returns `None`, the key press event will be discarded /// without producing any message. /// - /// This function is particularly useful to implement hotkey interactions. + /// 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. /// @@ -319,12 +320,16 @@ pub enum DragEvent { #[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, } diff --git a/native/src/widget/pane_grid/state.rs b/native/src/widget/pane_grid/state.rs index 6c80cacc67..0e528d90f3 100644 --- a/native/src/widget/pane_grid/state.rs +++ b/native/src/widget/pane_grid/state.rs @@ -18,8 +18,9 @@ use std::collections::HashMap; /// /// [`PaneGrid`]: struct.PaneGrid.html /// [`PaneGrid::new`]: struct.PaneGrid.html#method.new -/// [`State`]: struct.State.html /// [`Pane`]: struct.Pane.html +/// [`Split`]: struct.Split.html +/// [`State`]: struct.State.html #[derive(Debug)] pub struct State { pub(super) panes: HashMap, @@ -28,6 +29,8 @@ pub struct State { } /// The current focus of a [`Pane`]. +/// +/// [`Pane`]: struct.Pane.html #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Focus { /// The [`Pane`] is just focused. From 18f016cba70bf59095ae65ce0e289d80a548ae58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Fri, 20 Mar 2020 04:08:18 +0100 Subject: [PATCH 49/53] Use `f32::hypot` in `Point::distance` --- core/src/point.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/point.rs b/core/src/point.rs index b855cd917f..43ee21432d 100644 --- a/core/src/point.rs +++ b/core/src/point.rs @@ -30,7 +30,7 @@ impl Point { let a = self.x - to.x; let b = self.y - to.y; - f32::sqrt(a * a + b * b) + a.hypot(b) } } From 31aaf207d6772e2ef332bf523cde262cac118d1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Fri, 20 Mar 2020 04:10:58 +0100 Subject: [PATCH 50/53] Remove redundant check in `ModifiersState::matches` --- core/src/keyboard/modifiers_state.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/src/keyboard/modifiers_state.rs b/core/src/keyboard/modifiers_state.rs index 0cfc6d691f..4d24266fab 100644 --- a/core/src/keyboard/modifiers_state.rs +++ b/core/src/keyboard/modifiers_state.rs @@ -20,10 +20,10 @@ impl ModifiersState { /// /// [`ModifiersState`]: struct.ModifiersState.html pub fn matches(&self, modifiers: ModifiersState) -> bool { - let shift = !modifiers.shift || modifiers.shift && self.shift; - let control = !modifiers.control || modifiers.control && self.control; - let alt = !modifiers.alt || modifiers.alt && self.alt; - let logo = !modifiers.logo || modifiers.logo && self.logo; + let shift = !modifiers.shift || self.shift; + let control = !modifiers.control || self.control; + let alt = !modifiers.alt || self.alt; + let logo = !modifiers.logo || self.logo; shift && control && alt && logo } From 33f33ed4e32932175f1a77a0d8b59b81380ccf74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Fri, 20 Mar 2020 11:53:08 +0100 Subject: [PATCH 51/53] Check cursor is in-bounds before resizing panes --- native/src/widget/pane_grid.rs | 83 ++++++++++++++++++---------------- 1 file changed, 44 insertions(+), 39 deletions(-) diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 5ced6610fe..5b609b567a 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -467,48 +467,53 @@ where && self.pressed_modifiers.matches(self.modifier_keys) => { let bounds = layout.bounds(); - 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(), - }; + 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 - }); + 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); + 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 { From cfc2b55e05a3dc20eae71088d0475f82e34ea36b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Fri, 20 Mar 2020 11:54:42 +0100 Subject: [PATCH 52/53] Rename `Internal::idle_pane` to `active_pane` --- native/src/widget/pane_grid.rs | 2 +- native/src/widget/pane_grid/state.rs | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 5b609b567a..7e547ccb41 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -533,7 +533,7 @@ where 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.idle_pane() { + if let Some(_) = self.state.active_pane() { if modifiers.matches(self.modifier_keys) { if let Some(message) = on_key_press(KeyPressEvent { diff --git a/native/src/widget/pane_grid/state.rs b/native/src/widget/pane_grid/state.rs index 0e528d90f3..0a8b8419d8 100644 --- a/native/src/widget/pane_grid/state.rs +++ b/native/src/widget/pane_grid/state.rs @@ -109,10 +109,7 @@ impl State { /// [`Pane`]: struct.Pane.html /// [`State`]: struct.State.html pub fn active(&self) -> Option { - match self.internal.action { - Action::Idle { focus } => focus, - _ => None, - } + self.internal.active_pane() } /// Returns the adjacent [`Pane`] of another [`Pane`] in the given @@ -289,7 +286,7 @@ impl Internal { self.action } - pub fn idle_pane(&self) -> Option { + pub fn active_pane(&self) -> Option { match self.action { Action::Idle { focus } => focus, _ => None, From fb744a338c1b7566a3db9a3d24c03729b4858217 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Fri, 20 Mar 2020 11:56:39 +0100 Subject: [PATCH 53/53] Fix links in `pane_grid` documentation --- native/src/widget/column.rs | 2 +- native/src/widget/pane_grid.rs | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/native/src/widget/column.rs b/native/src/widget/column.rs index a7a6f242d5..b1adc6e39a 100644 --- a/native/src/widget/column.rs +++ b/native/src/widget/column.rs @@ -219,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( &mut self, diff --git a/native/src/widget/pane_grid.rs b/native/src/widget/pane_grid.rs index 7e547ccb41..a88f591acd 100644 --- a/native/src/widget/pane_grid.rs +++ b/native/src/widget/pane_grid.rs @@ -615,10 +615,12 @@ pub trait Renderer: crate::Renderer + Sized { /// 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 /// - /// [`Column`]: struct.Row.html + /// [`PaneGrid`]: struct.PaneGrid.html + /// [`Pane`]: struct.Pane.html /// [`Layout`]: ../layout/struct.Layout.html fn draw( &mut self,