From 012b4adec7a87331b2d75f6bc5d2a0189dcd7ec5 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 4 Mar 2020 04:10:26 +0100 Subject: Draft `Panes` widget and `panes` example --- 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 +---------------------------------------- 9 files changed, 521 insertions(+), 393 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 (limited to 'examples') diff --git a/examples/clock/Cargo.toml b/examples/clock/Cargo.toml index 308cbfbb..ab771405 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 17509180..a87edad0 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 00000000..229ba8fe --- /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 d8266f06..454ff4f0 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 00000000..174d2cde --- /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 00000000..4d9fc5b9 --- /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 00000000..c1bf991a --- /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 00000000..0219470b --- /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 d84c4817..adcdffe4 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() - } - } - } -} -- cgit From d7f32d47ba352616328f72323cad18351b326ae8 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 4 Mar 2020 22:01:57 +0100 Subject: Compute `panes` regions and focus on click --- examples/panes/src/main.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'examples') diff --git a/examples/panes/src/main.rs b/examples/panes/src/main.rs index c1bf991a..65db2b40 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() } -- cgit From cc310f71cc11dca8fb0d144181b75a68ed3eb82a Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 4 Mar 2020 22:31:59 +0100 Subject: Add split hotkeys to `panes` example --- examples/panes/Cargo.toml | 1 + examples/panes/src/main.rs | 97 ++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 87 insertions(+), 11 deletions(-) (limited to 'examples') diff --git a/examples/panes/Cargo.toml b/examples/panes/Cargo.toml index 174d2cde..dc94cc2c 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 65db2b40..50b21fc5 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() + } + } +} -- cgit From a6531c840b97b1d30af5153c01fda69d09f43a08 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 5 Mar 2020 02:08:53 +0100 Subject: Implement `Subscription::with` --- examples/panes/src/main.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) (limited to 'examples') diff --git a/examples/panes/src/main.rs b/examples/panes/src/main.rs index 50b21fc5..b34ce205 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), + ) + } } })); -- cgit From 15fad17f373c0aeb023a879f5e38440fdd944eca Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Thu, 5 Mar 2020 03:12:45 +0100 Subject: Implement `panes::State::close` --- examples/panes/src/main.rs | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'examples') diff --git a/examples/panes/src/main.rs b/examples/panes/src/main.rs index b34ce205..34206a2c 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, ]) } -- cgit From 6151c528241d0a6ece88e6e664df1b50f8174ecb Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 10 Mar 2020 02:57:13 +0100 Subject: Rename `Panes` widget to `PaneGrid` --- examples/pane_grid/Cargo.toml | 12 +++ examples/pane_grid/README.md | 18 +++++ examples/pane_grid/src/main.rs | 179 +++++++++++++++++++++++++++++++++++++++++ examples/panes/Cargo.toml | 12 --- examples/panes/README.md | 18 ----- examples/panes/src/main.rs | 179 ----------------------------------------- 6 files changed, 209 insertions(+), 209 deletions(-) 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 delete mode 100644 examples/panes/Cargo.toml delete mode 100644 examples/panes/README.md delete mode 100644 examples/panes/src/main.rs (limited to 'examples') diff --git a/examples/pane_grid/Cargo.toml b/examples/pane_grid/Cargo.toml new file mode 100644 index 00000000..6d8573bd --- /dev/null +++ b/examples/pane_grid/Cargo.toml @@ -0,0 +1,12 @@ +[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 new file mode 100644 index 00000000..4d9fc5b9 --- /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 00000000..b103afc8 --- /dev/null +++ b/examples/pane_grid/src/main.rs @@ -0,0 +1,179 @@ +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/panes/Cargo.toml b/examples/panes/Cargo.toml deleted file mode 100644 index dc94cc2c..00000000 --- a/examples/panes/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "panes" -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/panes/README.md b/examples/panes/README.md deleted file mode 100644 index 4d9fc5b9..00000000 --- a/examples/panes/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/panes/src/main.rs b/examples/panes/src/main.rs deleted file mode 100644 index 34206a2c..00000000 --- a/examples/panes/src/main.rs +++ /dev/null @@ -1,179 +0,0 @@ -use iced::{ - panes, Application, Command, Element, Panes, 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: 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), - Split(panes::Split), - Close, -} - -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)); - - (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(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, - ]) - } - - 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() - } -} - -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() - } - } -} -- cgit From df6e3f8da921a98c9a1e75a1790885a07485ee45 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 11 Mar 2020 23:25:00 +0100 Subject: Expose `pane_grid::Focus` for state-based styling --- examples/pane_grid/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'examples') diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs index b103afc8..eb8aaa51 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)), -- cgit From 858c086eeee4284a5a7b3e49cb3c112d204e53c3 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Sat, 14 Mar 2020 03:39:20 +0100 Subject: Remove `pane_grid` example for now It's too contrived. I will work on something simpler. --- 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 ++++++++++++++++++++++++++++++++++++++++- 9 files changed, 393 insertions(+), 606 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 (limited to 'examples') diff --git a/examples/clock/Cargo.toml b/examples/clock/Cargo.toml index ab771405..308cbfbb 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 a87edad0..17509180 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 229ba8fe..00000000 --- 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 454ff4f0..d8266f06 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 6d8573bd..00000000 --- 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 4d9fc5b9..00000000 --- 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 eb8aaa51..00000000 --- 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 0219470b..00000000 --- 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 adcdffe4..d84c4817 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() + } + } + } +} -- cgit From 56ba6215a25fe90a50be8feebeb74031967e92b0 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 17 Mar 2020 04:23:28 +0100 Subject: Add simple `pane_grid` example --- examples/pane_grid/Cargo.toml | 9 ++ examples/pane_grid/README.md | 18 ++++ examples/pane_grid/src/main.rs | 202 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 229 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 (limited to 'examples') diff --git a/examples/pane_grid/Cargo.toml b/examples/pane_grid/Cargo.toml new file mode 100644 index 00000000..3ed912ac --- /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 00000000..4d9fc5b9 --- /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 00000000..09969630 --- /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() + } + } + } +} -- cgit From 6f9cf6c70d8ef01446dae4d093c6e8ff2c7e7708 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 17 Mar 2020 06:54:25 +0100 Subject: 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(-) (limited to 'examples') diff --git a/examples/pane_grid/Cargo.toml b/examples/pane_grid/Cargo.toml index 3ed912ac..fb160a8d 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 09969630..7ab68393 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 { -- cgit From 1cd1582506810255394d2f9019597e9252bd8daa Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 17 Mar 2020 07:16:54 +0100 Subject: Add `modifiers` to `KeyPressEvent` in `pane_grid` --- examples/pane_grid/src/main.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'examples') diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs index 7ab68393..461ffc30 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), -- cgit From 05beb878527b4d4e3141ca5ba09337d6ada858be Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Tue, 17 Mar 2020 07:28:28 +0100 Subject: Move common keyboard types to `iced_core` Also expose them in `iced` through `iced_native` and `iced_web`. --- examples/pane_grid/Cargo.toml | 1 - examples/pane_grid/src/main.rs | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) (limited to 'examples') diff --git a/examples/pane_grid/Cargo.toml b/examples/pane_grid/Cargo.toml index fb160a8d..3ed912ac 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 461ffc30..9e6283ab 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()) -- cgit From b8a035d2dae43f590f681686d856b8b22630141b Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 18 Mar 2020 01:27:23 +0100 Subject: Add some styling to `pane_grid` buttons --- examples/pane_grid/src/main.rs | 51 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 47 insertions(+), 4 deletions(-) (limited to 'examples') diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs index 9e6283ab..c5dae016 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 + } + } + } } -- cgit From 36abf7457fdc5a50b53f7e9ae63b978f07fbcda4 Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 18 Mar 2020 05:53:41 +0100 Subject: Improve styling of `pane_grid` example --- examples/pane_grid/src/main.rs | 59 +++++++++++++++++++++++++++++------------- 1 file changed, 41 insertions(+), 18 deletions(-) (limited to 'examples') diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs index c5dae016..dafc396c 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 } } -- cgit From eba2ded88a92479bee93b727b31f5f84899339cf Mon Sep 17 00:00:00 2001 From: Héctor Ramón Jiménez Date: Wed, 18 Mar 2020 06:35:55 +0100 Subject: Update `README` of examples --- examples/README.md | 1 + examples/pane_grid/README.md | 20 +++++++++++++++----- 2 files changed, 16 insertions(+), 5 deletions(-) (limited to 'examples') diff --git a/examples/README.md b/examples/README.md index 04399b93..a7673705 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 4d9fc5b9..3653fc5b 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 -- cgit