diff options
author | 2020-03-04 04:10:26 +0100 | |
---|---|---|
committer | 2020-03-04 04:10:26 +0100 | |
commit | 012b4adec7a87331b2d75f6bc5d2a0189dcd7ec5 (patch) | |
tree | ce668596d734fe9fe04e1fa40cb932cd3e1c1789 | |
parent | 02091267bf38d4da5efab57e9b63504804b3a468 (diff) | |
download | iced-012b4adec7a87331b2d75f6bc5d2a0189dcd7ec5.tar.gz iced-012b4adec7a87331b2d75f6bc5d2a0189dcd7ec5.tar.bz2 iced-012b4adec7a87331b2d75f6bc5d2a0189dcd7ec5.zip |
Draft `Panes` widget and `panes` example
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | examples/clock/Cargo.toml | 3 | ||||
-rw-r--r-- | examples/clock/README.md | 4 | ||||
-rw-r--r-- | examples/clock/src/lib.rs | 187 | ||||
-rw-r--r-- | examples/clock/src/main.rs | 188 | ||||
-rw-r--r-- | examples/panes/Cargo.toml | 11 | ||||
-rw-r--r-- | examples/panes/README.md | 18 | ||||
-rw-r--r-- | examples/panes/src/main.rs | 95 | ||||
-rw-r--r-- | examples/stopwatch/src/lib.rs | 204 | ||||
-rw-r--r-- | examples/stopwatch/src/main.rs | 204 | ||||
-rw-r--r-- | native/src/element.rs | 2 | ||||
-rw-r--r-- | native/src/lib.rs | 2 | ||||
-rw-r--r-- | native/src/widget.rs | 3 | ||||
-rw-r--r-- | native/src/widget/panes.rs | 238 | ||||
-rw-r--r-- | src/widget.rs | 2 | ||||
-rw-r--r-- | wgpu/src/renderer/widget.rs | 1 | ||||
-rw-r--r-- | wgpu/src/renderer/widget/panes.rs | 34 |
17 files changed, 801 insertions, 396 deletions
@@ -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 308cbfbb..ab771405 100644 --- a/examples/clock/Cargo.toml +++ b/examples/clock/Cargo.toml @@ -5,9 +5,6 @@ authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] 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. <div align="center"> <img src="https://user-images.githubusercontent.com/518289/74716344-a3e6b300-522e-11ea-8aea-3cc0a5100a2e.gif"> @@ -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<LocalTime>, +} + +#[derive(Debug, Clone, Copy)] +pub enum Message { + Tick(chrono::DateTime<chrono::Local>), +} + +impl Application for Clock { + type Executor = executor::Default; + type Message = Message; + + fn new() -> (Self, Command<Message>) { + ( + 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<Message> { + 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<Message> { + time::every(std::time::Duration::from_millis(500)).map(Message::Tick) + } + + fn view(&mut self) -> Element<Message> { + 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<chrono::DateTime<chrono::Local>> for LocalTime { + fn from(date_time: chrono::DateTime<chrono::Local>) -> 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<chrono::DateTime<chrono::Local>> { + iced::Subscription::from_recipe(Every(duration)) + } + + struct Every(std::time::Duration); + + impl<H, I> iced_native::subscription::Recipe<H, I> for Every + where + H: std::hash::Hasher, + { + type Output = chrono::DateTime<chrono::Local>; + + fn hash(&self, state: &mut H) { + use std::hash::Hash; + + std::any::TypeId::of::<Self>().hash(state); + self.0.hash(state); + } + + fn stream( + self: Box<Self>, + _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<LocalTime>, -} - -#[derive(Debug, Clone, Copy)] -enum Message { - Tick(chrono::DateTime<chrono::Local>), -} - -impl Application for Clock { - type Executor = executor::Default; - type Message = Message; - - fn new() -> (Self, Command<Message>) { - ( - 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<Message> { - 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<Message> { - time::every(std::time::Duration::from_millis(500)).map(Message::Tick) - } - - fn view(&mut self) -> Element<Message> { - 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<chrono::DateTime<chrono::Local>> for LocalTime { - fn from(date_time: chrono::DateTime<chrono::Local>) -> 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<chrono::DateTime<chrono::Local>> { - iced::Subscription::from_recipe(Every(duration)) - } - - struct Every(std::time::Duration); - - impl<H, I> iced_native::subscription::Recipe<H, I> for Every - where - H: std::hash::Hasher, - { - type Output = chrono::DateTime<chrono::Local>; - - fn hash(&self, state: &mut H) { - use std::hash::Hash; - - std::any::TypeId::of::<Self>().hash(state); - self.0.hash(state); - } - - fn stream( - self: Box<Self>, - _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 <hector0193@gmail.com>"] +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. + +<div align="center"> + <a href="https://gfycat.com/fairdeadcatbird"> + <img src="https://thumbs.gfycat.com/FairDeadCatbird-small.gif"> + </a> +</div> + +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<Example>, +} + +#[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<Message>) { + 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<Message> { + 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<Message> { + 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<Message> { + 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<Message>) { + ( + 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<Message> { + 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<Message> { + match self.state { + State::Idle => Subscription::none(), + State::Ticking { .. } => { + time::every(Duration::from_millis(10)).map(Message::Tick) + } + } + } + + fn view(&mut self) -> Element<Message> { + 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<std::time::Instant> { + iced::Subscription::from_recipe(Every(duration)) + } + + struct Every(std::time::Duration); + + impl<H, I> iced_native::subscription::Recipe<H, I> 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::<Self>().hash(state); + self.0.hash(state); + } + + fn stream( + self: Box<Self>, + _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<Message>) { - ( - 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<Message> { - 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<Message> { - match self.state { - State::Idle => Subscription::none(), - State::Ticking { .. } => { - time::every(Duration::from_millis(10)).map(Message::Tick) - } - } - } - - fn view(&mut self) -> Element<Message> { - 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<std::time::Instant> { - iced::Subscription::from_recipe(Every(duration)) - } - - struct Every(std::time::Duration); - - impl<H, I> iced_native::subscription::Recipe<H, I> 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::<Self>().hash(state); - self.0.hash(state); - } - - fn stream( - self: Box<Self>, - _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 276f7614..4e7c7fc6 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 e4e7baee..4551a982 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 f9424b02..d97e836c 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 00000000..d69d251e --- /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<Element<'a, Message, Renderer>>, + width: Length, + height: Length, +} + +impl<'a, Message, Renderer> Panes<'a, Message, Renderer> { + pub fn new<T>( + state: &'a mut State<T>, + 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<Message, Renderer> + 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::<Panes<'_, Message, Renderer>>().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<T> { + panes: HashMap<Pane, T>, + internal: Internal, +} + +#[derive(Debug)] +struct Internal { + layout: Node, + last_pane: usize, + focused_pane: Option<Pane>, +} + +impl<T> State<T> { + 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<Item = (Pane, &T)> { + self.panes.iter().map(|(pane, state)| (*pane, state)) + } + + pub fn iter_mut(&mut self) -> impl Iterator<Item = (Pane, &mut T)> { + self.panes.iter_mut().map(|(pane, state)| (*pane, state)) + } + + pub fn focused_pane(&self) -> Option<Pane> { + 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<Pane> { + 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<Pane> { + 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<Node>, + b: Box<Node>, + }, + 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<Message>( + &mut self, + defaults: &Self::Defaults, + content: &[Element<'_, Message, Self>], + layout: Layout<'_>, + cursor_position: Point, + ) -> Self::Output; +} + +impl<'a, Message, Renderer> From<Panes<'a, Message, Renderer>> + 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 7d3a1cef..de3301fa 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 84f908e7..9a46552e 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 00000000..67f92f52 --- /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<Message>( + &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, + ) + } +} |