diff options
Diffstat (limited to 'examples')
46 files changed, 1564 insertions, 520 deletions
diff --git a/examples/README.md b/examples/README.md index 8e1b781f..10c28cf5 100644 --- a/examples/README.md +++ b/examples/README.md @@ -100,8 +100,10 @@ A bunch of simpler examples exist: - [`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. +- [`pick_list`](pick_list), a dropdown list of selectable options. - [`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. +- [`scrollable`](scrollable), a showcase of the various scrollbar width options. - [`solar_system`](solar_system), an animated solar system drawn using the `Canvas` widget and showcasing how to compose different transforms. - [`stopwatch`](stopwatch), a watch with start/stop and reset buttons showcasing how to listen to time. - [`svg`](svg), an application that renders the [Ghostscript Tiger] by leveraging the `Svg` widget. @@ -116,7 +118,7 @@ cargo run --package <example> [Ghostscript Tiger]: https://commons.wikimedia.org/wiki/File:Ghostscript_Tiger.svg ## [Coffee] -Since [Iced was born in May], it has been powering the user interfaces in +Since [Iced was born in May 2019], it has been powering the user interfaces in [Coffee], an experimental 2D game engine. @@ -126,6 +128,6 @@ Since [Iced was born in May], it has been powering the user interfaces in </a> </div> -[Iced was born in May]: https://github.com/hecrj/coffee/pull/35 +[Iced was born in May 2019]: https://github.com/hecrj/coffee/pull/35 [`ui` module]: https://docs.rs/coffee/0.3.2/coffee/ui/index.html [Coffee]: https://github.com/hecrj/coffee diff --git a/examples/bezier_tool/src/main.rs b/examples/bezier_tool/src/main.rs index fe41e1b2..97832e01 100644 --- a/examples/bezier_tool/src/main.rs +++ b/examples/bezier_tool/src/main.rs @@ -3,11 +3,11 @@ use iced::{ button, Align, Button, Column, Element, Length, Sandbox, Settings, Text, }; -pub fn main() { +pub fn main() -> iced::Result { Example::run(Settings { antialiasing: true, ..Settings::default() - }); + }) } #[derive(Default)] @@ -69,7 +69,8 @@ impl Sandbox for Example { mod bezier { use iced::{ - canvas::{self, Canvas, Cursor, Event, Frame, Geometry, Path, Stroke}, + canvas::event::{self, Event}, + canvas::{self, Canvas, Cursor, Frame, Geometry, Path, Stroke}, mouse, Element, Length, Point, Rectangle, }; @@ -109,40 +110,51 @@ mod bezier { event: Event, bounds: Rectangle, cursor: Cursor, - ) -> Option<Curve> { - let cursor_position = cursor.position_in(&bounds)?; + ) -> (event::Status, Option<Curve>) { + let cursor_position = + if let Some(position) = cursor.position_in(&bounds) { + position + } else { + return (event::Status::Ignored, None); + }; match event { - Event::Mouse(mouse_event) => match mouse_event { - mouse::Event::ButtonPressed(mouse::Button::Left) => { - match self.state.pending { - None => { - self.state.pending = Some(Pending::One { - from: cursor_position, - }); - None - } - Some(Pending::One { from }) => { - self.state.pending = Some(Pending::Two { - from, - to: cursor_position, - }); - - None - } - Some(Pending::Two { from, to }) => { - self.state.pending = None; - - Some(Curve { - from, - to, - control: cursor_position, - }) + Event::Mouse(mouse_event) => { + let message = match mouse_event { + mouse::Event::ButtonPressed(mouse::Button::Left) => { + match self.state.pending { + None => { + self.state.pending = Some(Pending::One { + from: cursor_position, + }); + + None + } + Some(Pending::One { from }) => { + self.state.pending = Some(Pending::Two { + from, + to: cursor_position, + }); + + None + } + Some(Pending::Two { from, to }) => { + self.state.pending = None; + + Some(Curve { + from, + to, + control: cursor_position, + }) + } } } - } - _ => None, - }, + _ => None, + }; + + (event::Status::Captured, message) + } + _ => (event::Status::Ignored, None), } } diff --git a/examples/clock/src/main.rs b/examples/clock/src/main.rs index 9c583c78..b317ac00 100644 --- a/examples/clock/src/main.rs +++ b/examples/clock/src/main.rs @@ -4,7 +4,7 @@ use iced::{ Point, Rectangle, Settings, Subscription, Vector, }; -pub fn main() { +pub fn main() -> iced::Result { Clock::run(Settings { antialiasing: true, ..Settings::default() diff --git a/examples/color_palette/src/main.rs b/examples/color_palette/src/main.rs index cec6ac79..bb2c61cb 100644 --- a/examples/color_palette/src/main.rs +++ b/examples/color_palette/src/main.rs @@ -8,7 +8,7 @@ use palette::{self, Hsl, Limited, Srgb}; use std::marker::PhantomData; use std::ops::RangeInclusive; -pub fn main() { +pub fn main() -> iced::Result { ColorPalette::run(Settings { antialiasing: true, ..Settings::default() @@ -269,7 +269,7 @@ struct ColorPicker<C: ColorSpace> { trait ColorSpace: Sized { const LABEL: &'static str; - const COMPONENT_RANGES: [RangeInclusive<f32>; 3]; + const COMPONENT_RANGES: [RangeInclusive<f64>; 3]; fn new(a: f32, b: f32, c: f32) -> Self; @@ -284,13 +284,25 @@ impl<C: 'static + ColorSpace + Copy> ColorPicker<C> { let [s1, s2, s3] = &mut self.sliders; let [cr1, cr2, cr3] = C::COMPONENT_RANGES; + fn slider<C: Clone>( + state: &mut slider::State, + range: RangeInclusive<f64>, + component: f32, + update: impl Fn(f32) -> C + 'static, + ) -> Slider<f64, C> { + Slider::new(state, range, f64::from(component), move |v| { + update(v as f32) + }) + .step(0.01) + } + Row::new() .spacing(10) .align_items(Align::Center) .push(Text::new(C::LABEL).width(Length::Units(50))) - .push(Slider::new(s1, cr1, c1, move |v| C::new(v, c2, c3))) - .push(Slider::new(s2, cr2, c2, move |v| C::new(c1, v, c3))) - .push(Slider::new(s3, cr3, c3, move |v| C::new(c1, c2, v))) + .push(slider(s1, cr1, c1, move |v| C::new(v, c2, c3))) + .push(slider(s2, cr2, c2, move |v| C::new(c1, v, c3))) + .push(slider(s3, cr3, c3, move |v| C::new(c1, c2, v))) .push( Text::new(color.to_string()) .width(Length::Units(185)) @@ -302,7 +314,7 @@ impl<C: 'static + ColorSpace + Copy> ColorPicker<C> { impl ColorSpace for Color { const LABEL: &'static str = "RGB"; - const COMPONENT_RANGES: [RangeInclusive<f32>; 3] = + const COMPONENT_RANGES: [RangeInclusive<f64>; 3] = [0.0..=1.0, 0.0..=1.0, 0.0..=1.0]; fn new(r: f32, g: f32, b: f32) -> Self { @@ -325,7 +337,7 @@ impl ColorSpace for Color { impl ColorSpace for palette::Hsl { const LABEL: &'static str = "HSL"; - const COMPONENT_RANGES: [RangeInclusive<f32>; 3] = + const COMPONENT_RANGES: [RangeInclusive<f64>; 3] = [0.0..=360.0, 0.0..=1.0, 0.0..=1.0]; fn new(hue: f32, saturation: f32, lightness: f32) -> Self { @@ -356,7 +368,7 @@ impl ColorSpace for palette::Hsl { impl ColorSpace for palette::Hsv { const LABEL: &'static str = "HSV"; - const COMPONENT_RANGES: [RangeInclusive<f32>; 3] = + const COMPONENT_RANGES: [RangeInclusive<f64>; 3] = [0.0..=360.0, 0.0..=1.0, 0.0..=1.0]; fn new(hue: f32, saturation: f32, value: f32) -> Self { @@ -379,7 +391,7 @@ impl ColorSpace for palette::Hsv { impl ColorSpace for palette::Hwb { const LABEL: &'static str = "HWB"; - const COMPONENT_RANGES: [RangeInclusive<f32>; 3] = + const COMPONENT_RANGES: [RangeInclusive<f64>; 3] = [0.0..=360.0, 0.0..=1.0, 0.0..=1.0]; fn new(hue: f32, whiteness: f32, blackness: f32) -> Self { @@ -410,7 +422,7 @@ impl ColorSpace for palette::Hwb { impl ColorSpace for palette::Lab { const LABEL: &'static str = "Lab"; - const COMPONENT_RANGES: [RangeInclusive<f32>; 3] = + const COMPONENT_RANGES: [RangeInclusive<f64>; 3] = [0.0..=100.0, -128.0..=127.0, -128.0..=127.0]; fn new(l: f32, a: f32, b: f32) -> Self { @@ -428,7 +440,7 @@ impl ColorSpace for palette::Lab { impl ColorSpace for palette::Lch { const LABEL: &'static str = "Lch"; - const COMPONENT_RANGES: [RangeInclusive<f32>; 3] = + const COMPONENT_RANGES: [RangeInclusive<f64>; 3] = [0.0..=100.0, 0.0..=128.0, 0.0..=360.0]; fn new(l: f32, chroma: f32, hue: f32) -> Self { diff --git a/examples/counter/src/main.rs b/examples/counter/src/main.rs index bde0ea94..e0b2ebd6 100644 --- a/examples/counter/src/main.rs +++ b/examples/counter/src/main.rs @@ -1,6 +1,6 @@ use iced::{button, Align, Button, Column, Element, Sandbox, Settings, Text}; -pub fn main() { +pub fn main() -> iced::Result { Counter::run(Settings::default()) } diff --git a/examples/custom_widget/Cargo.toml b/examples/custom_widget/Cargo.toml index 30747dc0..3942538d 100644 --- a/examples/custom_widget/Cargo.toml +++ b/examples/custom_widget/Cargo.toml @@ -8,4 +8,4 @@ publish = false [dependencies] iced = { path = "../.." } iced_native = { path = "../../native" } -iced_wgpu = { path = "../../wgpu" } +iced_graphics = { path = "../../graphics" } diff --git a/examples/custom_widget/src/main.rs b/examples/custom_widget/src/main.rs index f096fb54..36f468c7 100644 --- a/examples/custom_widget/src/main.rs +++ b/examples/custom_widget/src/main.rs @@ -9,23 +9,26 @@ mod circle { // Of course, you can choose to make the implementation renderer-agnostic, // if you wish to, by creating your own `Renderer` trait, which could be // implemented by `iced_wgpu` and other renderers. + use iced_graphics::{Backend, Defaults, Primitive, Renderer}; use iced_native::{ layout, mouse, Background, Color, Element, Hasher, Layout, Length, - Point, Size, Widget, + Point, Rectangle, Size, Widget, }; - use iced_wgpu::{Defaults, Primitive, Renderer}; pub struct Circle { - radius: u16, + radius: f32, } impl Circle { - pub fn new(radius: u16) -> Self { + pub fn new(radius: f32) -> Self { Self { radius } } } - impl<Message> Widget<Message, Renderer> for Circle { + impl<Message, B> Widget<Message, Renderer<B>> for Circle + where + B: Backend, + { fn width(&self) -> Length { Length::Shrink } @@ -36,34 +39,32 @@ mod circle { fn layout( &self, - _renderer: &Renderer, + _renderer: &Renderer<B>, _limits: &layout::Limits, ) -> layout::Node { - layout::Node::new(Size::new( - f32::from(self.radius) * 2.0, - f32::from(self.radius) * 2.0, - )) + layout::Node::new(Size::new(self.radius * 2.0, self.radius * 2.0)) } fn hash_layout(&self, state: &mut Hasher) { use std::hash::Hash; - self.radius.hash(state); + self.radius.to_bits().hash(state); } fn draw( &self, - _renderer: &mut Renderer, + _renderer: &mut Renderer<B>, _defaults: &Defaults, layout: Layout<'_>, _cursor_position: Point, + _viewport: &Rectangle, ) -> (Primitive, mouse::Interaction) { ( Primitive::Quad { bounds: layout.bounds(), background: Background::Color(Color::BLACK), border_radius: self.radius, - border_width: 0, + border_width: 0.0, border_color: Color::TRANSPARENT, }, mouse::Interaction::default(), @@ -71,8 +72,11 @@ mod circle { } } - impl<'a, Message> Into<Element<'a, Message, Renderer>> for Circle { - fn into(self) -> Element<'a, Message, Renderer> { + impl<'a, Message, B> Into<Element<'a, Message, Renderer<B>>> for Circle + where + B: Backend, + { + fn into(self) -> Element<'a, Message, Renderer<B>> { Element::new(self) } } @@ -84,12 +88,12 @@ use iced::{ Slider, Text, }; -pub fn main() { +pub fn main() -> iced::Result { Example::run(Settings::default()) } struct Example { - radius: u16, + radius: f32, slider: slider::State, } @@ -103,7 +107,7 @@ impl Sandbox for Example { fn new() -> Self { Example { - radius: 50, + radius: 50.0, slider: slider::State::new(), } } @@ -115,7 +119,7 @@ impl Sandbox for Example { fn update(&mut self, message: Message) { match message { Message::RadiusChanged(radius) => { - self.radius = radius.round() as u16; + self.radius = radius; } } } @@ -127,13 +131,16 @@ impl Sandbox for Example { .max_width(500) .align_items(Align::Center) .push(Circle::new(self.radius)) - .push(Text::new(format!("Radius: {}", self.radius.to_string()))) - .push(Slider::new( - &mut self.slider, - 1.0..=100.0, - f32::from(self.radius), - Message::RadiusChanged, - )); + .push(Text::new(format!("Radius: {:.2}", self.radius))) + .push( + Slider::new( + &mut self.slider, + 1.0..=100.0, + self.radius, + Message::RadiusChanged, + ) + .step(0.01), + ); Container::new(content) .width(Length::Fill) diff --git a/examples/download_progress/Cargo.toml b/examples/download_progress/Cargo.toml index 34e6a132..4b05e7dc 100644 --- a/examples/download_progress/Cargo.toml +++ b/examples/download_progress/Cargo.toml @@ -6,7 +6,7 @@ edition = "2018" publish = false [dependencies] -iced = { path = "../..", features = ["tokio"] } +iced = { path = "../..", features = ["tokio_old"] } iced_native = { path = "../../native" } iced_futures = { path = "../../futures" } reqwest = "0.10" diff --git a/examples/download_progress/src/main.rs b/examples/download_progress/src/main.rs index c37ae678..77b01354 100644 --- a/examples/download_progress/src/main.rs +++ b/examples/download_progress/src/main.rs @@ -5,7 +5,7 @@ use iced::{ mod download; -pub fn main() { +pub fn main() -> iced::Result { Example::run(Settings::default()) } diff --git a/examples/events/src/main.rs b/examples/events/src/main.rs index 066fc230..6eba6aad 100644 --- a/examples/events/src/main.rs +++ b/examples/events/src/main.rs @@ -3,7 +3,7 @@ use iced::{ Element, Length, Settings, Subscription, Text, }; -pub fn main() { +pub fn main() -> iced::Result { Events::run(Settings::default()) } diff --git a/examples/game_of_life/Cargo.toml b/examples/game_of_life/Cargo.toml index b9bb7f2a..9c4172c4 100644 --- a/examples/game_of_life/Cargo.toml +++ b/examples/game_of_life/Cargo.toml @@ -7,6 +7,6 @@ publish = false [dependencies] iced = { path = "../..", features = ["canvas", "tokio", "debug"] } -tokio = { version = "0.2", features = ["blocking"] } +tokio = { version = "0.3", features = ["sync"] } itertools = "0.9" rustc-hash = "1.1" diff --git a/examples/game_of_life/README.md b/examples/game_of_life/README.md index 1aeb1455..aa39201c 100644 --- a/examples/game_of_life/README.md +++ b/examples/game_of_life/README.md @@ -7,8 +7,8 @@ It runs a simulation in a background thread while allowing interaction with a `C The __[`main`]__ file contains the relevant code of the example. <div align="center"> - <a href="https://gfycat.com/briefaccurateaardvark"> - <img src="https://thumbs.gfycat.com/BriefAccurateAardvark-size_restricted.gif"> + <a href="https://gfycat.com/WhichPaltryChick"> + <img src="https://thumbs.gfycat.com/WhichPaltryChick-size_restricted.gif"> </a> </div> diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index 080d55c0..e18bd6e0 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -1,18 +1,22 @@ //! This example showcases an interactive version of the Game of Life, invented //! by John Conway. It leverages a `Canvas` together with other widgets. +mod preset; mod style; use grid::Grid; +use iced::button::{self, Button}; +use iced::executor; +use iced::pick_list::{self, PickList}; +use iced::slider::{self, Slider}; +use iced::time; use iced::{ - button::{self, Button}, - executor, - slider::{self, Slider}, - time, Align, Application, Checkbox, Column, Command, Container, Element, - Length, Row, Settings, Subscription, Text, + Align, Application, Checkbox, Column, Command, Container, Element, Length, + Row, Settings, Subscription, Text, }; +use preset::Preset; use std::time::{Duration, Instant}; -pub fn main() { +pub fn main() -> iced::Result { GameOfLife::run(Settings { antialiasing: true, ..Settings::default() @@ -27,17 +31,19 @@ struct GameOfLife { queued_ticks: usize, speed: usize, next_speed: Option<usize>, + version: usize, } #[derive(Debug, Clone)] enum Message { - Grid(grid::Message), + Grid(grid::Message, usize), Tick(Instant), TogglePlayback, ToggleGrid(bool), Next, Clear, SpeedChanged(f32), + PresetPicked(Preset), } impl Application for GameOfLife { @@ -48,7 +54,7 @@ impl Application for GameOfLife { fn new(_flags: ()) -> (Self, Command<Message>) { ( Self { - speed: 1, + speed: 5, ..Self::default() }, Command::none(), @@ -61,8 +67,10 @@ impl Application for GameOfLife { fn update(&mut self, message: Message) -> Command<Message> { match message { - Message::Grid(message) => { - self.grid.update(message); + Message::Grid(message, version) => { + if version == self.version { + self.grid.update(message); + } } Message::Tick(_) | Message::Next => { self.queued_ticks = (self.queued_ticks + 1).min(self.speed); @@ -74,7 +82,11 @@ impl Application for GameOfLife { self.queued_ticks = 0; - return Command::perform(task, Message::Grid); + let version = self.version; + + return Command::perform(task, move |message| { + Message::Grid(message, version) + }); } } Message::TogglePlayback => { @@ -85,6 +97,7 @@ impl Application for GameOfLife { } Message::Clear => { self.grid.clear(); + self.version += 1; } Message::SpeedChanged(speed) => { if self.is_playing { @@ -93,6 +106,10 @@ impl Application for GameOfLife { self.speed = speed.round() as usize; } } + Message::PresetPicked(new_preset) => { + self.grid = Grid::from_preset(new_preset); + self.version += 1; + } } Command::none() @@ -108,15 +125,21 @@ impl Application for GameOfLife { } fn view(&mut self) -> Element<Message> { + let version = self.version; let selected_speed = self.next_speed.unwrap_or(self.speed); let controls = self.controls.view( self.is_playing, self.grid.are_lines_visible(), selected_speed, + self.grid.preset(), ); let content = Column::new() - .push(self.grid.view().map(Message::Grid)) + .push( + self.grid + .view() + .map(move |message| Message::Grid(message, version)), + ) .push(controls); Container::new(content) @@ -128,10 +151,10 @@ impl Application for GameOfLife { } mod grid { + use crate::Preset; use iced::{ - canvas::{ - self, Cache, Canvas, Cursor, Event, Frame, Geometry, Path, Text, - }, + canvas::event::{self, Event}, + canvas::{self, Cache, Canvas, Cursor, Frame, Geometry, Path, Text}, mouse, Color, Element, HorizontalAlignment, Length, Point, Rectangle, Size, Vector, VerticalAlignment, }; @@ -142,6 +165,7 @@ mod grid { pub struct Grid { state: State, + preset: Preset, interaction: Interaction, life_cache: Cache, grid_cache: Cache, @@ -150,7 +174,6 @@ mod grid { show_lines: bool, last_tick_duration: Duration, last_queued_ticks: usize, - version: usize, } #[derive(Debug, Clone)] @@ -160,7 +183,6 @@ mod grid { Ticked { result: Result<Life, TickError>, tick_duration: Duration, - version: usize, }, } @@ -171,8 +193,24 @@ mod grid { impl Default for Grid { fn default() -> Self { + Self::from_preset(Preset::default()) + } + } + + impl Grid { + const MIN_SCALING: f32 = 0.1; + const MAX_SCALING: f32 = 2.0; + + pub fn from_preset(preset: Preset) -> Self { Self { - state: State::default(), + state: State::with_life( + preset + .life() + .into_iter() + .map(|(i, j)| Cell { i, j }) + .collect(), + ), + preset, interaction: Interaction::None, life_cache: Cache::default(), grid_cache: Cache::default(), @@ -181,20 +219,13 @@ mod grid { show_lines: true, last_tick_duration: Duration::default(), last_queued_ticks: 0, - version: 0, } } - } - - impl Grid { - const MIN_SCALING: f32 = 0.1; - const MAX_SCALING: f32 = 2.0; pub fn tick( &mut self, amount: usize, ) -> Option<impl Future<Output = Message>> { - let version = self.version; let tick = self.state.tick(amount)?; self.last_queued_ticks = amount; @@ -206,7 +237,6 @@ mod grid { Message::Ticked { result, - version, tick_duration, } }) @@ -217,16 +247,19 @@ mod grid { Message::Populate(cell) => { self.state.populate(cell); self.life_cache.clear(); + + self.preset = Preset::Custom; } Message::Unpopulate(cell) => { self.state.unpopulate(&cell); self.life_cache.clear(); + + self.preset = Preset::Custom; } Message::Ticked { result: Ok(life), - version, tick_duration, - } if version == self.version => { + } => { self.state.update(life); self.life_cache.clear(); @@ -237,7 +270,6 @@ mod grid { } => { dbg!(error); } - Message::Ticked { .. } => {} } } @@ -250,11 +282,15 @@ mod grid { pub fn clear(&mut self) { self.state = State::default(); - self.version += 1; + self.preset = Preset::Custom; self.life_cache.clear(); } + pub fn preset(&self) -> Preset { + self.preset + } + pub fn toggle_lines(&mut self, enabled: bool) { self.show_lines = enabled; } @@ -291,12 +327,18 @@ mod grid { event: Event, bounds: Rectangle, cursor: Cursor, - ) -> Option<Message> { + ) -> (event::Status, Option<Message>) { if let Event::Mouse(mouse::Event::ButtonReleased(_)) = event { self.interaction = Interaction::None; } - let cursor_position = cursor.position_in(&bounds)?; + let cursor_position = + if let Some(position) = cursor.position_in(&bounds) { + position + } else { + return (event::Status::Ignored, None); + }; + let cell = Cell::at(self.project(cursor_position, bounds.size())); let is_populated = self.state.contains(&cell); @@ -308,28 +350,32 @@ mod grid { match event { Event::Mouse(mouse_event) => match mouse_event { - mouse::Event::ButtonPressed(button) => match button { - mouse::Button::Left => { - self.interaction = if is_populated { - Interaction::Erasing - } else { - Interaction::Drawing - }; - - populate.or(unpopulate) - } - mouse::Button::Right => { - self.interaction = Interaction::Panning { - translation: self.translation, - start: cursor_position, - }; + mouse::Event::ButtonPressed(button) => { + let message = match button { + mouse::Button::Left => { + self.interaction = if is_populated { + Interaction::Erasing + } else { + Interaction::Drawing + }; + + populate.or(unpopulate) + } + mouse::Button::Right => { + self.interaction = Interaction::Panning { + translation: self.translation, + start: cursor_position, + }; - None - } - _ => None, - }, + None + } + _ => None, + }; + + (event::Status::Captured, message) + } mouse::Event::CursorMoved { .. } => { - match self.interaction { + let message = match self.interaction { Interaction::Drawing => populate, Interaction::Erasing => unpopulate, Interaction::Panning { translation, start } => { @@ -343,7 +389,14 @@ mod grid { None } _ => None, - } + }; + + let event_status = match self.interaction { + Interaction::None => event::Status::Ignored, + _ => event::Status::Captured, + }; + + (event_status, message) } mouse::Event::WheelScrolled { delta } => match delta { mouse::ScrollDelta::Lines { y, .. } @@ -376,11 +429,12 @@ mod grid { self.grid_cache.clear(); } - None + (event::Status::Captured, None) } }, - _ => None, + _ => (event::Status::Ignored, None), }, + _ => (event::Status::Ignored, None), } } @@ -533,6 +587,13 @@ mod grid { } impl State { + pub fn with_life(life: Life) -> Self { + Self { + life, + ..Self::default() + } + } + fn cell_count(&self) -> usize { self.life.len() + self.births.len() } @@ -647,6 +708,14 @@ mod grid { } } + impl std::iter::FromIterator<Cell> for Life { + fn from_iter<I: IntoIterator<Item = Cell>>(iter: I) -> Self { + Life { + cells: iter.into_iter().collect(), + } + } + } + impl std::fmt::Debug for Life { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Life") @@ -741,6 +810,7 @@ struct Controls { next_button: button::State, clear_button: button::State, speed_slider: slider::State, + preset_list: pick_list::State<Preset>, } impl Controls { @@ -749,6 +819,7 @@ impl Controls { is_playing: bool, is_grid_enabled: bool, speed: usize, + preset: Preset, ) -> Element<'a, Message> { let playback_controls = Row::new() .spacing(10) @@ -794,6 +865,17 @@ impl Controls { .text_size(16), ) .push( + PickList::new( + &mut self.preset_list, + preset::ALL, + Some(preset), + Message::PresetPicked, + ) + .padding(8) + .text_size(16) + .style(style::PickList), + ) + .push( Button::new(&mut self.clear_button, Text::new("Clear")) .on_press(Message::Clear) .style(style::Clear), diff --git a/examples/game_of_life/src/preset.rs b/examples/game_of_life/src/preset.rs new file mode 100644 index 00000000..05157b6a --- /dev/null +++ b/examples/game_of_life/src/preset.rs @@ -0,0 +1,142 @@ +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Preset { + Custom, + XKCD, + Glider, + SmallExploder, + Exploder, + TenCellRow, + LightweightSpaceship, + Tumbler, + GliderGun, + Acorn, +} + +pub static ALL: &[Preset] = &[ + Preset::Custom, + Preset::XKCD, + Preset::Glider, + Preset::SmallExploder, + Preset::Exploder, + Preset::TenCellRow, + Preset::LightweightSpaceship, + Preset::Tumbler, + Preset::GliderGun, + Preset::Acorn, +]; + +impl Preset { + pub fn life(self) -> Vec<(isize, isize)> { + #[rustfmt::skip] + let cells = match self { + Preset::Custom => vec![], + Preset::XKCD => vec![ + " xxx ", + " x x ", + " x x ", + " x ", + "x xxx ", + " x x x ", + " x x", + " x x ", + " x x ", + ], + Preset::Glider => vec![ + " x ", + " x", + "xxx" + ], + Preset::SmallExploder => vec![ + " x ", + "xxx", + "x x", + " x ", + ], + Preset::Exploder => vec![ + "x x x", + "x x", + "x x", + "x x", + "x x x", + ], + Preset::TenCellRow => vec![ + "xxxxxxxxxx", + ], + Preset::LightweightSpaceship => vec![ + " xxxxx", + "x x", + " x", + "x x ", + ], + Preset::Tumbler => vec![ + " xx xx ", + " xx xx ", + " x x ", + "x x x x", + "x x x x", + "xx xx", + ], + Preset::GliderGun => vec![ + " x ", + " x x ", + " xx xx xx", + " x x xx xx", + "xx x x xx ", + "xx x x xx x x ", + " x x x ", + " x x ", + " xx ", + ], + Preset::Acorn => vec![ + " x ", + " x ", + "xx xxx", + ], + }; + + let start_row = -(cells.len() as isize / 2); + + cells + .into_iter() + .enumerate() + .flat_map(|(i, cells)| { + let start_column = -(cells.len() as isize / 2); + + cells + .chars() + .enumerate() + .filter(|(_, c)| !c.is_whitespace()) + .map(move |(j, _)| { + (start_row + i as isize, start_column + j as isize) + }) + }) + .collect() + } +} + +impl Default for Preset { + fn default() -> Preset { + Preset::XKCD + } +} + +impl std::fmt::Display for Preset { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Preset::Custom => "Custom", + Preset::XKCD => "xkcd #2293", + Preset::Glider => "Glider", + Preset::SmallExploder => "Small Exploder", + Preset::Exploder => "Exploder", + Preset::TenCellRow => "10 Cell Row", + Preset::LightweightSpaceship => "Lightweight spaceship", + Preset::Tumbler => "Tumbler", + Preset::GliderGun => "Gosper Glider Gun", + Preset::Acorn => "Acorn", + } + ) + } +} diff --git a/examples/game_of_life/src/style.rs b/examples/game_of_life/src/style.rs index d59569f2..6605826f 100644 --- a/examples/game_of_life/src/style.rs +++ b/examples/game_of_life/src/style.rs @@ -1,4 +1,4 @@ -use iced::{button, container, slider, Background, Color}; +use iced::{button, container, pick_list, slider, Background, Color}; const ACTIVE: Color = Color::from_rgb( 0x72 as f32 / 255.0, @@ -18,6 +18,12 @@ const HOVERED: Color = Color::from_rgb( 0xC4 as f32 / 255.0, ); +const BACKGROUND: Color = Color::from_rgb( + 0x2F as f32 / 255.0, + 0x31 as f32 / 255.0, + 0x36 as f32 / 255.0, +); + pub struct Container; impl container::StyleSheet for Container { @@ -38,7 +44,7 @@ impl button::StyleSheet for Button { fn active(&self) -> button::Style { button::Style { background: Some(Background::Color(ACTIVE)), - border_radius: 3, + border_radius: 3.0, text_color: Color::WHITE, ..button::Style::default() } @@ -54,7 +60,7 @@ impl button::StyleSheet for Button { fn pressed(&self) -> button::Style { button::Style { - border_width: 1, + border_width: 1.0, border_color: Color::WHITE, ..self.hovered() } @@ -67,7 +73,7 @@ impl button::StyleSheet for Clear { fn active(&self) -> button::Style { button::Style { background: Some(Background::Color(DESTRUCTIVE)), - border_radius: 3, + border_radius: 3.0, text_color: Color::WHITE, ..button::Style::default() } @@ -86,7 +92,7 @@ impl button::StyleSheet for Clear { fn pressed(&self) -> button::Style { button::Style { - border_width: 1, + border_width: 1.0, border_color: Color::WHITE, ..self.hovered() } @@ -100,9 +106,9 @@ impl slider::StyleSheet for Slider { slider::Style { rail_colors: (ACTIVE, Color { a: 0.1, ..ACTIVE }), handle: slider::Handle { - shape: slider::HandleShape::Circle { radius: 9 }, + shape: slider::HandleShape::Circle { radius: 9.0 }, color: ACTIVE, - border_width: 0, + border_width: 0.0, border_color: Color::TRANSPARENT, }, } @@ -132,3 +138,51 @@ impl slider::StyleSheet for Slider { } } } + +pub struct PickList; + +impl pick_list::StyleSheet for PickList { + fn menu(&self) -> pick_list::Menu { + pick_list::Menu { + text_color: Color::WHITE, + background: BACKGROUND.into(), + border_width: 1.0, + border_color: Color { + a: 0.7, + ..Color::BLACK + }, + selected_background: Color { + a: 0.5, + ..Color::BLACK + } + .into(), + selected_text_color: Color::WHITE, + } + } + + fn active(&self) -> pick_list::Style { + pick_list::Style { + text_color: Color::WHITE, + background: BACKGROUND.into(), + border_width: 1.0, + border_color: Color { + a: 0.6, + ..Color::BLACK + }, + border_radius: 2.0, + icon_size: 0.5, + } + } + + fn hovered(&self) -> pick_list::Style { + let active = self.active(); + + pick_list::Style { + border_color: Color { + a: 0.9, + ..Color::BLACK + }, + ..active + } + } +} diff --git a/examples/geometry/Cargo.toml b/examples/geometry/Cargo.toml index 9df52454..34eec4fb 100644 --- a/examples/geometry/Cargo.toml +++ b/examples/geometry/Cargo.toml @@ -8,4 +8,4 @@ publish = false [dependencies] iced = { path = "../.." } iced_native = { path = "../../native" } -iced_wgpu = { path = "../../wgpu" } +iced_graphics = { path = "../../graphics" } diff --git a/examples/geometry/src/main.rs b/examples/geometry/src/main.rs index aabe6b21..f650b2c1 100644 --- a/examples/geometry/src/main.rs +++ b/examples/geometry/src/main.rs @@ -10,13 +10,13 @@ mod rainbow { // Of course, you can choose to make the implementation renderer-agnostic, // if you wish to, by creating your own `Renderer` trait, which could be // implemented by `iced_wgpu` and other renderers. - use iced_native::{ - layout, mouse, Element, Hasher, Layout, Length, Point, Size, Vector, - Widget, - }; - use iced_wgpu::{ + use iced_graphics::{ triangle::{Mesh2D, Vertex2D}, - Defaults, Primitive, Renderer, + Backend, Defaults, Primitive, Renderer, + }; + use iced_native::{ + layout, mouse, Element, Hasher, Layout, Length, Point, Rectangle, Size, + Vector, Widget, }; pub struct Rainbow; @@ -27,7 +27,10 @@ mod rainbow { } } - impl<Message> Widget<Message, Renderer> for Rainbow { + impl<Message, B> Widget<Message, Renderer<B>> for Rainbow + where + B: Backend, + { fn width(&self) -> Length { Length::Fill } @@ -38,7 +41,7 @@ mod rainbow { fn layout( &self, - _renderer: &Renderer, + _renderer: &Renderer<B>, limits: &layout::Limits, ) -> layout::Node { let size = limits.width(Length::Fill).resolve(Size::ZERO); @@ -50,10 +53,11 @@ mod rainbow { fn draw( &self, - _renderer: &mut Renderer, + _renderer: &mut Renderer<B>, _defaults: &Defaults, layout: Layout<'_>, cursor_position: Point, + _viewport: &Rectangle, ) -> (Primitive, mouse::Interaction) { let b = layout.bounds(); @@ -146,8 +150,11 @@ mod rainbow { } } - impl<'a, Message> Into<Element<'a, Message, Renderer>> for Rainbow { - fn into(self) -> Element<'a, Message, Renderer> { + impl<'a, Message, B> Into<Element<'a, Message, Renderer<B>>> for Rainbow + where + B: Backend, + { + fn into(self) -> Element<'a, Message, Renderer<B>> { Element::new(self) } } @@ -159,7 +166,7 @@ use iced::{ }; use rainbow::Rainbow; -pub fn main() { +pub fn main() -> iced::Result { Example::run(Settings::default()) } diff --git a/examples/integration/Cargo.toml b/examples/integration/Cargo.toml index afc2c791..4515502f 100644 --- a/examples/integration/Cargo.toml +++ b/examples/integration/Cargo.toml @@ -8,4 +8,4 @@ publish = false [dependencies] iced_winit = { path = "../../winit" } iced_wgpu = { path = "../../wgpu" } -env_logger = "0.7" +env_logger = "0.8" diff --git a/examples/integration/src/controls.rs b/examples/integration/src/controls.rs index 0999336b..824f9f53 100644 --- a/examples/integration/src/controls.rs +++ b/examples/integration/src/controls.rs @@ -1,15 +1,15 @@ -use crate::Scene; - use iced_wgpu::Renderer; use iced_winit::{ - slider, Align, Color, Column, Element, Length, Row, Slider, Text, + slider, Align, Color, Column, Command, Element, Length, Program, Row, + Slider, Text, }; pub struct Controls { + background_color: Color, sliders: [slider::State; 3], } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum Message { BackgroundColorChanged(Color), } @@ -17,58 +17,64 @@ pub enum Message { impl Controls { pub fn new() -> Controls { Controls { + background_color: Color::BLACK, sliders: Default::default(), } } - pub fn update(&self, message: Message, scene: &mut Scene) { + pub fn background_color(&self) -> Color { + self.background_color + } +} + +impl Program for Controls { + type Renderer = Renderer; + type Message = Message; + + fn update(&mut self, message: Message) -> Command<Message> { match message { Message::BackgroundColorChanged(color) => { - scene.background_color = color; + self.background_color = color; } } + + Command::none() } - pub fn view(&mut self, scene: &Scene) -> Element<Message, Renderer> { + fn view(&mut self) -> Element<Message, Renderer> { let [r, g, b] = &mut self.sliders; - let background_color = scene.background_color; + let background_color = self.background_color; let sliders = Row::new() .width(Length::Units(500)) .spacing(20) - .push(Slider::new( - r, - 0.0..=1.0, - scene.background_color.r, - move |r| { + .push( + Slider::new(r, 0.0..=1.0, background_color.r, move |r| { Message::BackgroundColorChanged(Color { r, ..background_color }) - }, - )) - .push(Slider::new( - g, - 0.0..=1.0, - scene.background_color.g, - move |g| { + }) + .step(0.01), + ) + .push( + Slider::new(g, 0.0..=1.0, background_color.g, move |g| { Message::BackgroundColorChanged(Color { g, ..background_color }) - }, - )) - .push(Slider::new( - b, - 0.0..=1.0, - scene.background_color.b, - move |b| { + }) + .step(0.01), + ) + .push( + Slider::new(b, 0.0..=1.0, background_color.b, move |b| { Message::BackgroundColorChanged(Color { b, ..background_color }) - }, - )); + }) + .step(0.01), + ); Row::new() .width(Length::Fill) diff --git a/examples/integration/src/main.rs b/examples/integration/src/main.rs index 92d2fa8d..9b52f3a5 100644 --- a/examples/integration/src/main.rs +++ b/examples/integration/src/main.rs @@ -4,14 +4,12 @@ mod scene; use controls::Controls; use scene::Scene; -use iced_wgpu::{ - wgpu, window::SwapChain, Primitive, Renderer, Settings, Target, -}; -use iced_winit::{ - futures, mouse, winit, Cache, Clipboard, Size, UserInterface, -}; +use iced_wgpu::{wgpu, Backend, Renderer, Settings, Viewport}; +use iced_winit::{conversion, futures, program, winit, Debug, Size}; +use futures::task::SpawnExt; use winit::{ + dpi::PhysicalPosition, event::{Event, ModifiersState, WindowEvent}, event_loop::{ControlFlow, EventLoop}, }; @@ -22,32 +20,39 @@ pub fn main() { // Initialize winit let event_loop = EventLoop::new(); let window = winit::window::Window::new(&event_loop).unwrap(); - let mut logical_size = - window.inner_size().to_logical(window.scale_factor()); + + let physical_size = window.inner_size(); + let mut viewport = Viewport::with_physical_size( + Size::new(physical_size.width, physical_size.height), + window.scale_factor(), + ); + let mut cursor_position = PhysicalPosition::new(-1.0, -1.0); let mut modifiers = ModifiersState::default(); - // Initialize WGPU + // Initialize wgpu + let instance = wgpu::Instance::new(wgpu::BackendBit::PRIMARY); + let surface = unsafe { instance.create_surface(&window) }; - let surface = wgpu::Surface::create(&window); let (mut device, queue) = futures::executor::block_on(async { - let adapter = wgpu::Adapter::request( - &wgpu::RequestAdapterOptions { + let adapter = instance + .request_adapter(&wgpu::RequestAdapterOptions { power_preference: wgpu::PowerPreference::Default, compatible_surface: Some(&surface), - }, - wgpu::BackendBit::PRIMARY, - ) - .await - .expect("Request adapter"); + }) + .await + .expect("Request adapter"); adapter - .request_device(&wgpu::DeviceDescriptor { - extensions: wgpu::Extensions { - anisotropic_filtering: false, + .request_device( + &wgpu::DeviceDescriptor { + features: wgpu::Features::empty(), + limits: wgpu::Limits::default(), + shader_validation: false, }, - limits: wgpu::Limits::default(), - }) + None, + ) .await + .expect("Request device") }); let format = wgpu::TextureFormat::Bgra8UnormSrgb; @@ -55,20 +60,39 @@ pub fn main() { let mut swap_chain = { let size = window.inner_size(); - SwapChain::new(&device, &surface, format, size.width, size.height) + device.create_swap_chain( + &surface, + &wgpu::SwapChainDescriptor { + usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT, + format: format, + width: size.width, + height: size.height, + present_mode: wgpu::PresentMode::Mailbox, + }, + ) }; let mut resized = false; - // Initialize iced - let mut events = Vec::new(); - let mut cache = Some(Cache::default()); - let mut renderer = Renderer::new(&mut device, Settings::default()); - let mut output = (Primitive::None, mouse::Interaction::default()); - let clipboard = Clipboard::new(&window); + // Initialize staging belt and local pool + let mut staging_belt = wgpu::util::StagingBelt::new(5 * 1024); + let mut local_pool = futures::executor::LocalPool::new(); // Initialize scene and GUI controls - let mut scene = Scene::new(&device); - let mut controls = Controls::new(); + let scene = Scene::new(&mut device); + let controls = Controls::new(); + + // Initialize iced + let mut debug = Debug::new(); + let mut renderer = + Renderer::new(Backend::new(&mut device, Settings::default())); + + let mut state = program::State::new( + controls, + viewport.logical_size(), + conversion::cursor_position(cursor_position, viewport.scale_factor()), + &mut renderer, + &mut debug, + ); // Run event loop event_loop.run(move |event, _, control_flow| { @@ -78,18 +102,23 @@ pub fn main() { match event { Event::WindowEvent { event, .. } => { match event { + WindowEvent::CursorMoved { position, .. } => { + cursor_position = position; + } WindowEvent::ModifiersChanged(new_modifiers) => { modifiers = new_modifiers; } WindowEvent::Resized(new_size) => { - logical_size = - new_size.to_logical(window.scale_factor()); + viewport = Viewport::with_physical_size( + Size::new(new_size.width, new_size.height), + window.scale_factor(), + ); + resized = true; } WindowEvent::CloseRequested => { *control_flow = ControlFlow::Exit; } - _ => {} } @@ -99,117 +128,95 @@ pub fn main() { window.scale_factor(), modifiers, ) { - events.push(event); + state.queue_event(event); } } Event::MainEventsCleared => { - // If no relevant events happened, we can simply skip this - if events.is_empty() { - return; - } - - // We need to: - // 1. Process events of our user interface. - // 2. Update state as a result of any interaction. - // 3. Generate a new output for our renderer. - - // First, we build our user interface. - let mut user_interface = UserInterface::build( - controls.view(&scene), - Size::new(logical_size.width, logical_size.height), - cache.take().unwrap(), - &mut renderer, - ); - - // Then, we process the events, obtaining messages in return. - let messages = user_interface.update( - events.drain(..), - clipboard.as_ref().map(|c| c as _), - &renderer, - ); - - let user_interface = if messages.is_empty() { - // If there are no messages, no interactions we care about have - // happened. We can simply leave our user interface as it is. - user_interface - } else { - // If there are messages, we need to update our state - // accordingly and rebuild our user interface. - // We can only do this if we drop our user interface first - // by turning it into its cache. - cache = Some(user_interface.into_cache()); - - // In this example, `Controls` is the only part that cares - // about messages, so updating our state is pretty - // straightforward. - for message in messages { - controls.update(message, &mut scene); - } - - // Once the state has been changed, we rebuild our updated - // user interface. - UserInterface::build( - controls.view(&scene), - Size::new(logical_size.width, logical_size.height), - cache.take().unwrap(), + // If there are events pending + if !state.is_queue_empty() { + // We update iced + let _ = state.update( + viewport.logical_size(), + conversion::cursor_position( + cursor_position, + viewport.scale_factor(), + ), + None, &mut renderer, - ) - }; - - // Finally, we just need to draw a new output for our renderer, - output = user_interface.draw(&mut renderer); - - // update our cache, - cache = Some(user_interface.into_cache()); + &mut debug, + ); - // and request a redraw - window.request_redraw(); + // and request a redraw + window.request_redraw(); + } } Event::RedrawRequested(_) => { if resized { let size = window.inner_size(); - swap_chain = SwapChain::new( - &device, + swap_chain = device.create_swap_chain( &surface, - format, - size.width, - size.height, + &wgpu::SwapChainDescriptor { + usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT, + format: format, + width: size.width, + height: size.height, + present_mode: wgpu::PresentMode::Mailbox, + }, ); + + resized = false; } - let (frame, viewport) = - swap_chain.next_frame().expect("Next frame"); + let frame = swap_chain.get_current_frame().expect("Next frame"); let mut encoder = device.create_command_encoder( &wgpu::CommandEncoderDescriptor { label: None }, ); - // We draw the scene first - scene.draw(&mut encoder, &frame.view); + let program = state.program(); + + { + // We clear the frame + let mut render_pass = scene.clear( + &frame.output.view, + &mut encoder, + program.background_color(), + ); + + // Draw the scene + scene.draw(&mut render_pass); + } // And then iced on top - let mouse_interaction = renderer.draw( + let mouse_interaction = renderer.backend_mut().draw( &mut device, + &mut staging_belt, &mut encoder, - Target { - texture: &frame.view, - viewport, - }, - &output, - window.scale_factor(), - &["Some debug information!"], + &frame.output.view, + &viewport, + state.primitive(), + &debug.overlay(), ); // Then we submit the work - queue.submit(&[encoder.finish()]); + staging_belt.finish(); + queue.submit(Some(encoder.finish())); - // And update the mouse cursor + // Update the mouse cursor window.set_cursor_icon( iced_winit::conversion::mouse_interaction( mouse_interaction, ), ); + + // And recall staging buffers + local_pool + .spawner() + .spawn(staging_belt.recall()) + .expect("Recall staging buffers"); + + local_pool.run_until_stalled(); } _ => {} } diff --git a/examples/integration/src/scene.rs b/examples/integration/src/scene.rs index 22c6812a..03a338c6 100644 --- a/examples/integration/src/scene.rs +++ b/examples/integration/src/scene.rs @@ -2,91 +2,68 @@ use iced_wgpu::wgpu; use iced_winit::Color; pub struct Scene { - pub background_color: Color, pipeline: wgpu::RenderPipeline, - bind_group: wgpu::BindGroup, } impl Scene { pub fn new(device: &wgpu::Device) -> Scene { - let (pipeline, bind_group) = build_pipeline(device); + let pipeline = build_pipeline(device); - Scene { - background_color: Color::BLACK, - pipeline, - bind_group, - } + Scene { pipeline } } - pub fn draw( + pub fn clear<'a>( &self, - encoder: &mut wgpu::CommandEncoder, - target: &wgpu::TextureView, - ) { - let mut rpass = - encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - color_attachments: &[ - wgpu::RenderPassColorAttachmentDescriptor { - attachment: target, - resolve_target: None, - load_op: wgpu::LoadOp::Clear, - store_op: wgpu::StoreOp::Store, - clear_color: { - let [r, g, b, a] = - self.background_color.into_linear(); + target: &'a wgpu::TextureView, + encoder: &'a mut wgpu::CommandEncoder, + background_color: Color, + ) -> wgpu::RenderPass<'a> { + encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor { + attachment: target, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear({ + let [r, g, b, a] = background_color.into_linear(); - wgpu::Color { - r: r as f64, - g: g as f64, - b: b as f64, - a: a as f64, - } - }, - }, - ], - depth_stencil_attachment: None, - }); + wgpu::Color { + r: r as f64, + g: g as f64, + b: b as f64, + a: a as f64, + } + }), + store: true, + }, + }], + depth_stencil_attachment: None, + }) + } - rpass.set_pipeline(&self.pipeline); - rpass.set_bind_group(0, &self.bind_group, &[]); - rpass.draw(0..3, 0..1); + pub fn draw<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) { + render_pass.set_pipeline(&self.pipeline); + render_pass.draw(0..3, 0..1); } } -fn build_pipeline( - device: &wgpu::Device, -) -> (wgpu::RenderPipeline, wgpu::BindGroup) { - let vs = include_bytes!("shader/vert.spv"); - let fs = include_bytes!("shader/frag.spv"); - - let vs_module = device.create_shader_module( - &wgpu::read_spirv(std::io::Cursor::new(&vs[..])).unwrap(), - ); +fn build_pipeline(device: &wgpu::Device) -> wgpu::RenderPipeline { + let vs_module = + device.create_shader_module(wgpu::include_spirv!("shader/vert.spv")); - let fs_module = device.create_shader_module( - &wgpu::read_spirv(std::io::Cursor::new(&fs[..])).unwrap(), - ); - - let bind_group_layout = - device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: None, - bindings: &[], - }); - - let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { - label: None, - layout: &bind_group_layout, - bindings: &[], - }); + let fs_module = + device.create_shader_module(wgpu::include_spirv!("shader/frag.spv")); let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - bind_group_layouts: &[&bind_group_layout], + label: None, + push_constant_ranges: &[], + bind_group_layouts: &[], }); let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - layout: &pipeline_layout, + label: None, + layout: Some(&pipeline_layout), vertex_stage: wgpu::ProgrammableStageDescriptor { module: &vs_module, entry_point: "main", @@ -98,9 +75,7 @@ fn build_pipeline( rasterization_state: Some(wgpu::RasterizationStateDescriptor { front_face: wgpu::FrontFace::Ccw, cull_mode: wgpu::CullMode::None, - depth_bias: 0, - depth_bias_slope_scale: 0.0, - depth_bias_clamp: 0.0, + ..Default::default() }), primitive_topology: wgpu::PrimitiveTopology::TriangleList, color_states: &[wgpu::ColorStateDescriptor { @@ -119,5 +94,5 @@ fn build_pipeline( alpha_to_coverage_enabled: false, }); - (pipeline, bind_group) + pipeline } diff --git a/examples/pane_grid/Cargo.toml b/examples/pane_grid/Cargo.toml index 3ed912ac..e489f210 100644 --- a/examples/pane_grid/Cargo.toml +++ b/examples/pane_grid/Cargo.toml @@ -6,4 +6,5 @@ edition = "2018" publish = false [dependencies] -iced = { path = "../.." } +iced = { path = "../..", features = ["debug"] } +iced_native = { path = "../../native" } diff --git a/examples/pane_grid/README.md b/examples/pane_grid/README.md index 3653fc5b..a4cfcb7d 100644 --- a/examples/pane_grid/README.md +++ b/examples/pane_grid/README.md @@ -15,8 +15,8 @@ This example showcases the `PaneGrid` widget, which features: The __[`main`]__ file contains all the code of the example. <div align="center"> - <a href="https://gfycat.com/mixedflatjellyfish"> - <img src="https://thumbs.gfycat.com/MixedFlatJellyfish-small.gif"> + <a href="https://gfycat.com/frailfreshairedaleterrier"> + <img src="https://thumbs.gfycat.com/FrailFreshAiredaleterrier-small.gif"> </a> </div> diff --git a/examples/pane_grid/src/main.rs b/examples/pane_grid/src/main.rs index b4bbd68f..3c3256cf 100644 --- a/examples/pane_grid/src/main.rs +++ b/examples/pane_grid/src/main.rs @@ -1,16 +1,18 @@ use iced::{ - button, keyboard, pane_grid, scrollable, Align, Button, Column, Container, - Element, HorizontalAlignment, Length, PaneGrid, Sandbox, Scrollable, - Settings, Text, + button, executor, keyboard, pane_grid, scrollable, Align, Application, + Button, Column, Command, Container, Element, HorizontalAlignment, Length, + PaneGrid, Scrollable, Settings, Subscription, Text, }; +use iced_native::{event, subscription, Event}; -pub fn main() { +pub fn main() -> iced::Result { Example::run(Settings::default()) } struct Example { panes: pane_grid::State<Content>, panes_created: usize, + focus: Option<pane_grid::Pane>, } #[derive(Debug, Clone, Copy)] @@ -18,59 +20,77 @@ enum Message { Split(pane_grid::Axis, pane_grid::Pane), SplitFocused(pane_grid::Axis), FocusAdjacent(pane_grid::Direction), + Clicked(pane_grid::Pane), Dragged(pane_grid::DragEvent), Resized(pane_grid::ResizeEvent), Close(pane_grid::Pane), CloseFocused, } -impl Sandbox for Example { +impl Application for Example { type Message = Message; + type Executor = executor::Default; + type Flags = (); - fn new() -> Self { + fn new(_flags: ()) -> (Self, Command<Message>) { let (panes, _) = pane_grid::State::new(Content::new(0)); - Example { - panes, - panes_created: 1, - } + ( + Example { + panes, + panes_created: 1, + focus: None, + }, + Command::none(), + ) } fn title(&self) -> String { String::from("Pane grid - Iced") } - fn update(&mut self, message: Message) { + fn update(&mut self, message: Message) -> Command<Message> { match message { Message::Split(axis, pane) => { - let _ = self.panes.split( + let result = self.panes.split( axis, &pane, Content::new(self.panes_created), ); + if let Some((pane, _)) = result { + self.focus = Some(pane); + } + self.panes_created += 1; } Message::SplitFocused(axis) => { - if let Some(pane) = self.panes.active() { - let _ = self.panes.split( + if let Some(pane) = self.focus { + let result = self.panes.split( axis, &pane, Content::new(self.panes_created), ); + if let Some((pane, _)) = result { + self.focus = Some(pane); + } + self.panes_created += 1; } } Message::FocusAdjacent(direction) => { - if let Some(pane) = self.panes.active() { + if let Some(pane) = self.focus { if let Some(adjacent) = self.panes.adjacent(&pane, direction) { - self.panes.focus(&adjacent); + self.focus = Some(adjacent); } } } + Message::Clicked(pane) => { + self.focus = Some(pane); + } Message::Resized(pane_grid::ResizeEvent { split, ratio }) => { self.panes.resize(&split, ratio); } @@ -82,29 +102,60 @@ impl Sandbox for Example { } Message::Dragged(_) => {} Message::Close(pane) => { - let _ = self.panes.close(&pane); + if let Some((_, sibling)) = self.panes.close(&pane) { + self.focus = Some(sibling); + } } Message::CloseFocused => { - if let Some(pane) = self.panes.active() { - let _ = self.panes.close(&pane); + if let Some(pane) = self.focus { + if let Some((_, sibling)) = self.panes.close(&pane) { + self.focus = Some(sibling); + } } } } + + Command::none() + } + + fn subscription(&self) -> Subscription<Message> { + subscription::events_with(|event, status| { + if let event::Status::Captured = status { + return None; + } + + match event { + Event::Keyboard(keyboard::Event::KeyPressed { + modifiers, + key_code, + }) if modifiers.is_command_pressed() => handle_hotkey(key_code), + _ => None, + } + }) } fn view(&mut self) -> Element<Message> { + let focus = self.focus; 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(10) - .on_drag(Message::Dragged) - .on_resize(Message::Resized) - .on_key_press(handle_hotkey); + let pane_grid = PaneGrid::new(&mut self.panes, |pane, content| { + let is_focused = focus == Some(pane); + + let title_bar = + pane_grid::TitleBar::new(format!("Pane {}", content.id)) + .padding(10) + .style(style::TitleBar { is_focused }); + + pane_grid::Content::new(content.view(pane, total_panes)) + .title_bar(title_bar) + .style(style::Pane { is_focused }) + }) + .width(Length::Fill) + .height(Length::Fill) + .spacing(10) + .on_click(Message::Clicked) + .on_drag(Message::Dragged) + .on_resize(10, Message::Resized); Container::new(pane_grid) .width(Length::Fill) @@ -114,11 +165,11 @@ impl Sandbox for Example { } } -fn handle_hotkey(event: pane_grid::KeyPressEvent) -> Option<Message> { +fn handle_hotkey(key_code: keyboard::KeyCode) -> Option<Message> { use keyboard::KeyCode; use pane_grid::{Axis, Direction}; - let direction = match event.key_code { + let direction = match key_code { KeyCode::Up => Some(Direction::Up), KeyCode::Down => Some(Direction::Down), KeyCode::Left => Some(Direction::Left), @@ -126,7 +177,7 @@ fn handle_hotkey(event: pane_grid::KeyPressEvent) -> Option<Message> { _ => None, }; - match event.key_code { + match key_code { KeyCode::V => Some(Message::SplitFocused(Axis::Vertical)), KeyCode::H => Some(Message::SplitFocused(Axis::Horizontal)), KeyCode::W => Some(Message::CloseFocused), @@ -155,15 +206,14 @@ impl Content { fn view( &mut self, pane: pane_grid::Pane, - focus: Option<pane_grid::Focus>, total_panes: usize, ) -> Element<Message> { let Content { - id, scroll, split_horizontally, split_vertically, close, + .. } = self; let button = |state, label, message, style| { @@ -209,7 +259,6 @@ impl Content { .width(Length::Fill) .spacing(10) .align_items(Align::Center) - .push(Text::new(format!("Pane {}", id)).size(30)) .push(controls); Container::new(content) @@ -217,9 +266,6 @@ impl Content { .height(Length::Fill) .padding(5) .center_y() - .style(style::Pane { - is_focused: focus.is_some(), - }) .into() } } @@ -245,6 +291,25 @@ mod style { 0xC4 as f32 / 255.0, ); + pub struct TitleBar { + pub is_focused: bool, + } + + impl container::StyleSheet for TitleBar { + fn style(&self) -> container::Style { + let pane = Pane { + is_focused: self.is_focused, + } + .style(); + + container::Style { + text_color: Some(Color::WHITE), + background: Some(pane.border_color.into()), + ..Default::default() + } + } + } + pub struct Pane { pub is_focused: bool, } @@ -253,10 +318,11 @@ mod style { fn style(&self) -> container::Style { container::Style { background: Some(Background::Color(SURFACE)), - border_width: 2, - border_color: Color { - a: if self.is_focused { 1.0 } else { 0.3 }, - ..Color::BLACK + border_width: 2.0, + border_color: if self.is_focused { + Color::BLACK + } else { + Color::from_rgb(0.7, 0.7, 0.7) }, ..Default::default() } @@ -280,7 +346,7 @@ mod style { button::Style { text_color, background: background.map(Background::Color), - border_radius: 5, + border_radius: 5.0, shadow_offset: Vector::new(0.0, 0.0), ..button::Style::default() } diff --git a/examples/pick_list/Cargo.toml b/examples/pick_list/Cargo.toml new file mode 100644 index 00000000..a87d7217 --- /dev/null +++ b/examples/pick_list/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "pick_list" +version = "0.1.0" +authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] +edition = "2018" +publish = false + +[dependencies] +iced = { path = "../..", features = ["debug"] } diff --git a/examples/pick_list/README.md b/examples/pick_list/README.md new file mode 100644 index 00000000..6dc80bf4 --- /dev/null +++ b/examples/pick_list/README.md @@ -0,0 +1,18 @@ +## Pick-list + +A dropdown list of selectable options. + +It displays and positions an overlay based on the window position of the widget. + +The __[`main`]__ file contains all the code of the example. + +<div align="center"> + <img src="https://user-images.githubusercontent.com/518289/87125075-2c232e80-c28a-11ea-95c2-769c610b8843.gif"> +</div> + +You can run it with `cargo run`: +``` +cargo run --package pick_list +``` + +[`main`]: src/main.rs diff --git a/examples/pick_list/src/main.rs b/examples/pick_list/src/main.rs new file mode 100644 index 00000000..68662602 --- /dev/null +++ b/examples/pick_list/src/main.rs @@ -0,0 +1,113 @@ +use iced::{ + pick_list, scrollable, Align, Container, Element, Length, PickList, + Sandbox, Scrollable, Settings, Space, Text, +}; + +pub fn main() -> iced::Result { + Example::run(Settings::default()) +} + +#[derive(Default)] +struct Example { + scroll: scrollable::State, + pick_list: pick_list::State<Language>, + selected_language: Language, +} + +#[derive(Debug, Clone, Copy)] +enum Message { + LanguageSelected(Language), +} + +impl Sandbox for Example { + type Message = Message; + + fn new() -> Self { + Self::default() + } + + fn title(&self) -> String { + String::from("Pick list - Iced") + } + + fn update(&mut self, message: Message) { + match message { + Message::LanguageSelected(language) => { + self.selected_language = language; + } + } + } + + fn view(&mut self) -> Element<Message> { + let pick_list = PickList::new( + &mut self.pick_list, + &Language::ALL[..], + Some(self.selected_language), + Message::LanguageSelected, + ); + + let mut content = Scrollable::new(&mut self.scroll) + .width(Length::Fill) + .align_items(Align::Center) + .spacing(10) + .push(Space::with_height(Length::Units(600))) + .push(Text::new("Which is your favorite language?")) + .push(pick_list); + + content = content.push(Space::with_height(Length::Units(600))); + + Container::new(content) + .width(Length::Fill) + .height(Length::Fill) + .center_x() + .center_y() + .into() + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Language { + Rust, + Elm, + Ruby, + Haskell, + C, + Javascript, + Other, +} + +impl Language { + const ALL: [Language; 7] = [ + Language::C, + Language::Elm, + Language::Ruby, + Language::Haskell, + Language::Rust, + Language::Javascript, + Language::Other, + ]; +} + +impl Default for Language { + fn default() -> Language { + Language::Rust + } +} + +impl std::fmt::Display for Language { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Language::Rust => "Rust", + Language::Elm => "Elm", + Language::Ruby => "Ruby", + Language::Haskell => "Haskell", + Language::C => "C", + Language::Javascript => "Javascript", + Language::Other => "Some other language", + } + ) + } +} diff --git a/examples/pokedex/Cargo.toml b/examples/pokedex/Cargo.toml index 94320086..05e73992 100644 --- a/examples/pokedex/Cargo.toml +++ b/examples/pokedex/Cargo.toml @@ -6,7 +6,7 @@ edition = "2018" publish = false [dependencies] -iced = { path = "../..", features = ["image", "debug", "tokio"] } +iced = { path = "../..", features = ["image", "debug", "tokio_old"] } serde_json = "1.0" [dependencies.serde] diff --git a/examples/pokedex/src/main.rs b/examples/pokedex/src/main.rs index e7afa8f5..187e5dee 100644 --- a/examples/pokedex/src/main.rs +++ b/examples/pokedex/src/main.rs @@ -3,7 +3,7 @@ use iced::{ Container, Element, Image, Length, Row, Settings, Text, }; -pub fn main() { +pub fn main() -> iced::Result { Pokedex::run(Settings::default()) } @@ -251,7 +251,7 @@ mod style { background: Some(Background::Color(match self { Button::Primary => Color::from_rgb(0.11, 0.42, 0.87), })), - border_radius: 12, + border_radius: 12.0, shadow_offset: Vector::new(1.0, 1.0), text_color: Color::WHITE, ..button::Style::default() diff --git a/examples/progress_bar/src/main.rs b/examples/progress_bar/src/main.rs index 43b09928..c9a8e798 100644 --- a/examples/progress_bar/src/main.rs +++ b/examples/progress_bar/src/main.rs @@ -1,6 +1,6 @@ use iced::{slider, Column, Element, ProgressBar, Sandbox, Settings, Slider}; -pub fn main() { +pub fn main() -> iced::Result { Progress::run(Settings::default()) } @@ -36,12 +36,15 @@ impl Sandbox for Progress { Column::new() .padding(20) .push(ProgressBar::new(0.0..=100.0, self.value)) - .push(Slider::new( - &mut self.progress_bar_slider, - 0.0..=100.0, - self.value, - Message::SliderChanged, - )) + .push( + Slider::new( + &mut self.progress_bar_slider, + 0.0..=100.0, + self.value, + Message::SliderChanged, + ) + .step(0.01), + ) .into() } } diff --git a/examples/qr_code/Cargo.toml b/examples/qr_code/Cargo.toml new file mode 100644 index 00000000..7f2d4e42 --- /dev/null +++ b/examples/qr_code/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "qr_code" +version = "0.1.0" +authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] +edition = "2018" +publish = false + +[dependencies] +iced = { path = "../..", features = ["qr_code"] } diff --git a/examples/qr_code/README.md b/examples/qr_code/README.md new file mode 100644 index 00000000..2dd89c26 --- /dev/null +++ b/examples/qr_code/README.md @@ -0,0 +1,18 @@ +## QR Code Generator + +A basic QR code generator that showcases the `QRCode` widget. + +The __[`main`]__ file contains all the code of the example. + +<div align="center"> + <a href="https://gfycat.com/heavyexhaustedaracari"> + <img src="https://thumbs.gfycat.com/HeavyExhaustedAracari-size_restricted.gif"> + </a> +</div> + +You can run it with `cargo run`: +``` +cargo run --package qr_code +``` + +[`main`]: src/main.rs diff --git a/examples/qr_code/src/main.rs b/examples/qr_code/src/main.rs new file mode 100644 index 00000000..37b4855d --- /dev/null +++ b/examples/qr_code/src/main.rs @@ -0,0 +1,81 @@ +use iced::qr_code::{self, QRCode}; +use iced::text_input::{self, TextInput}; +use iced::{ + Align, Column, Container, Element, Length, Sandbox, Settings, Text, +}; + +pub fn main() -> iced::Result { + QRGenerator::run(Settings::default()) +} + +#[derive(Default)] +struct QRGenerator { + data: String, + input: text_input::State, + qr_code: Option<qr_code::State>, +} + +#[derive(Debug, Clone)] +enum Message { + DataChanged(String), +} + +impl Sandbox for QRGenerator { + type Message = Message; + + fn new() -> Self { + QRGenerator { + qr_code: qr_code::State::new("").ok(), + ..Self::default() + } + } + + fn title(&self) -> String { + String::from("QR Code Generator - Iced") + } + + fn update(&mut self, message: Message) { + match message { + Message::DataChanged(mut data) => { + data.truncate(100); + + self.qr_code = qr_code::State::new(&data).ok(); + self.data = data; + } + } + } + + fn view(&mut self) -> Element<Message> { + let title = Text::new("QR Code Generator") + .size(70) + .color([0.5, 0.5, 0.5]); + + let input = TextInput::new( + &mut self.input, + "Type the data of your QR code here...", + &self.data, + Message::DataChanged, + ) + .size(30) + .padding(15); + + let mut content = Column::new() + .width(Length::Units(700)) + .spacing(20) + .align_items(Align::Center) + .push(title) + .push(input); + + if let Some(qr_code) = self.qr_code.as_mut() { + content = content.push(QRCode::new(qr_code).cell_size(10)); + } + + Container::new(content) + .width(Length::Fill) + .height(Length::Fill) + .padding(20) + .center_x() + .center_y() + .into() + } +} diff --git a/examples/scrollable/Cargo.toml b/examples/scrollable/Cargo.toml new file mode 100644 index 00000000..12753fb6 --- /dev/null +++ b/examples/scrollable/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "scrollable" +version = "0.1.0" +authors = ["Clark Moody <clark@clarkmoody.com>"] +edition = "2018" +publish = false + +[dependencies] +iced = { path = "../.." } diff --git a/examples/scrollable/README.md b/examples/scrollable/README.md new file mode 100644 index 00000000..ed0e31b5 --- /dev/null +++ b/examples/scrollable/README.md @@ -0,0 +1,15 @@ +# Scrollable +An example showcasing the various size and style options for the Scrollable. + +All the example code is located in the __[`main`](src/main.rs)__ file. + +<div align="center"> + <a href="./screenshot.png"> + <img src="./screenshot.png" height="640px"> + </a> +</div> + +You can run it with `cargo run`: +``` +cargo run --package scrollable +``` diff --git a/examples/scrollable/screenshot.png b/examples/scrollable/screenshot.png Binary files differnew file mode 100644 index 00000000..2d800251 --- /dev/null +++ b/examples/scrollable/screenshot.png diff --git a/examples/scrollable/src/main.rs b/examples/scrollable/src/main.rs new file mode 100644 index 00000000..8dd2e20c --- /dev/null +++ b/examples/scrollable/src/main.rs @@ -0,0 +1,184 @@ +mod style; + +use iced::{ + scrollable, Column, Container, Element, Length, Radio, Row, Rule, Sandbox, + Scrollable, Settings, Space, Text, +}; + +pub fn main() -> iced::Result { + ScrollableDemo::run(Settings::default()) +} + +struct ScrollableDemo { + theme: style::Theme, + variants: Vec<Variant>, +} + +#[derive(Debug, Clone)] +enum Message { + ThemeChanged(style::Theme), +} + +impl Sandbox for ScrollableDemo { + type Message = Message; + + fn new() -> Self { + ScrollableDemo { + theme: Default::default(), + variants: Variant::all(), + } + } + + fn title(&self) -> String { + String::from("Scrollable - Iced") + } + + fn update(&mut self, message: Message) { + match message { + Message::ThemeChanged(theme) => self.theme = theme, + } + } + + fn view(&mut self) -> Element<Message> { + let ScrollableDemo { + theme, variants, .. + } = self; + + let choose_theme = style::Theme::ALL.iter().fold( + Column::new().spacing(10).push(Text::new("Choose a theme:")), + |column, option| { + column.push( + Radio::new( + *option, + &format!("{:?}", option), + Some(*theme), + Message::ThemeChanged, + ) + .style(*theme), + ) + }, + ); + + let scrollable_row = Row::with_children( + variants + .iter_mut() + .map(|variant| { + let mut scrollable = Scrollable::new(&mut variant.state) + .padding(10) + .width(Length::Fill) + .height(Length::Fill) + .style(*theme) + .push(Text::new(variant.title)); + + if let Some(scrollbar_width) = variant.scrollbar_width { + scrollable = scrollable + .scrollbar_width(scrollbar_width) + .push(Text::new(format!( + "scrollbar_width: {:?}", + scrollbar_width + ))); + } + + if let Some(scrollbar_margin) = variant.scrollbar_margin { + scrollable = scrollable + .scrollbar_margin(scrollbar_margin) + .push(Text::new(format!( + "scrollbar_margin: {:?}", + scrollbar_margin + ))); + } + + if let Some(scroller_width) = variant.scroller_width { + scrollable = scrollable + .scroller_width(scroller_width) + .push(Text::new(format!( + "scroller_width: {:?}", + scroller_width + ))); + } + + scrollable = scrollable + .push(Space::with_height(Length::Units(100))) + .push(Text::new( + "Some content that should wrap within the \ + scrollable. Let's output a lot of short words, so \ + that we'll make sure to see how wrapping works \ + with these scrollbars.", + )) + .push(Space::with_height(Length::Units(1200))) + .push(Text::new("Middle")) + .push(Space::with_height(Length::Units(1200))) + .push(Text::new("The End.")); + + Container::new(scrollable) + .width(Length::Fill) + .height(Length::Fill) + .style(*theme) + .into() + }) + .collect(), + ) + .spacing(20) + .width(Length::Fill) + .height(Length::Fill); + + let content = Column::new() + .spacing(20) + .padding(20) + .push(choose_theme) + .push(Rule::horizontal(20).style(self.theme)) + .push(scrollable_row); + + Container::new(content) + .width(Length::Fill) + .height(Length::Fill) + .center_x() + .center_y() + .style(self.theme) + .into() + } +} + +/// A version of a scrollable +struct Variant { + title: &'static str, + state: scrollable::State, + scrollbar_width: Option<u16>, + scrollbar_margin: Option<u16>, + scroller_width: Option<u16>, +} + +impl Variant { + pub fn all() -> Vec<Self> { + vec![ + Self { + title: "Default Scrollbar", + state: scrollable::State::new(), + scrollbar_width: None, + scrollbar_margin: None, + scroller_width: None, + }, + Self { + title: "Slimmed & Margin", + state: scrollable::State::new(), + scrollbar_width: Some(4), + scrollbar_margin: Some(3), + scroller_width: Some(4), + }, + Self { + title: "Wide Scroller", + state: scrollable::State::new(), + scrollbar_width: Some(4), + scrollbar_margin: None, + scroller_width: Some(10), + }, + Self { + title: "Narrow Scroller", + state: scrollable::State::new(), + scrollbar_width: Some(10), + scrollbar_margin: None, + scroller_width: Some(4), + }, + ] + } +} diff --git a/examples/scrollable/src/style.rs b/examples/scrollable/src/style.rs new file mode 100644 index 00000000..ae449141 --- /dev/null +++ b/examples/scrollable/src/style.rs @@ -0,0 +1,190 @@ +use iced::{container, radio, rule, scrollable}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Theme { + Light, + Dark, +} + +impl Theme { + pub const ALL: [Theme; 2] = [Theme::Light, Theme::Dark]; +} + +impl Default for Theme { + fn default() -> Theme { + Theme::Light + } +} + +impl From<Theme> for Box<dyn container::StyleSheet> { + fn from(theme: Theme) -> Self { + match theme { + Theme::Light => Default::default(), + Theme::Dark => dark::Container.into(), + } + } +} + +impl From<Theme> for Box<dyn radio::StyleSheet> { + fn from(theme: Theme) -> Self { + match theme { + Theme::Light => Default::default(), + Theme::Dark => dark::Radio.into(), + } + } +} + +impl From<Theme> for Box<dyn scrollable::StyleSheet> { + fn from(theme: Theme) -> Self { + match theme { + Theme::Light => Default::default(), + Theme::Dark => dark::Scrollable.into(), + } + } +} + +impl From<Theme> for Box<dyn rule::StyleSheet> { + fn from(theme: Theme) -> Self { + match theme { + Theme::Light => Default::default(), + Theme::Dark => dark::Rule.into(), + } + } +} + +mod dark { + use iced::{container, radio, rule, scrollable, Color}; + + const BACKGROUND: Color = Color::from_rgb( + 0x36 as f32 / 255.0, + 0x39 as f32 / 255.0, + 0x3F as f32 / 255.0, + ); + + const SURFACE: Color = Color::from_rgb( + 0x40 as f32 / 255.0, + 0x44 as f32 / 255.0, + 0x4B as f32 / 255.0, + ); + + const ACCENT: Color = Color::from_rgb( + 0x6F as f32 / 255.0, + 0xFF as f32 / 255.0, + 0xE9 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 SCROLLBAR: Color = Color::from_rgb( + 0x2E as f32 / 255.0, + 0x33 as f32 / 255.0, + 0x38 as f32 / 255.0, + ); + + const SCROLLER: Color = Color::from_rgb( + 0x20 as f32 / 255.0, + 0x22 as f32 / 255.0, + 0x25 as f32 / 255.0, + ); + + pub struct Container; + + impl container::StyleSheet for Container { + fn style(&self) -> container::Style { + container::Style { + background: Color { + a: 0.99, + ..BACKGROUND + } + .into(), + text_color: Color::WHITE.into(), + ..container::Style::default() + } + } + } + + pub struct Radio; + + impl radio::StyleSheet for Radio { + fn active(&self) -> radio::Style { + radio::Style { + background: SURFACE.into(), + dot_color: ACTIVE, + border_width: 1.0, + border_color: ACTIVE, + } + } + + fn hovered(&self) -> radio::Style { + radio::Style { + background: Color { a: 0.5, ..SURFACE }.into(), + ..self.active() + } + } + } + + pub struct Scrollable; + + impl scrollable::StyleSheet for Scrollable { + fn active(&self) -> scrollable::Scrollbar { + scrollable::Scrollbar { + background: Color { + a: 0.8, + ..SCROLLBAR + } + .into(), + border_radius: 2.0, + border_width: 0.0, + border_color: Color::TRANSPARENT, + scroller: scrollable::Scroller { + color: Color { a: 0.7, ..SCROLLER }, + border_radius: 2.0, + border_width: 0.0, + border_color: Color::TRANSPARENT, + }, + } + } + + fn hovered(&self) -> scrollable::Scrollbar { + let active = self.active(); + + scrollable::Scrollbar { + background: SCROLLBAR.into(), + scroller: scrollable::Scroller { + color: SCROLLER, + ..active.scroller + }, + ..active + } + } + + fn dragging(&self) -> scrollable::Scrollbar { + let hovered = self.hovered(); + + scrollable::Scrollbar { + scroller: scrollable::Scroller { + color: ACCENT, + ..hovered.scroller + }, + ..hovered + } + } + } + + pub struct Rule; + + impl rule::StyleSheet for Rule { + fn style(&self) -> rule::Style { + rule::Style { + color: SURFACE, + width: 2, + radius: 1.0, + fill_mode: rule::FillMode::Percent(30.0), + } + } + } +} diff --git a/examples/solar_system/src/main.rs b/examples/solar_system/src/main.rs index 98bd3b21..6a2de736 100644 --- a/examples/solar_system/src/main.rs +++ b/examples/solar_system/src/main.rs @@ -14,7 +14,7 @@ use iced::{ use std::time::Instant; -pub fn main() { +pub fn main() -> iced::Result { SolarSystem::run(Settings { antialiasing: true, ..Settings::default() diff --git a/examples/stopwatch/src/main.rs b/examples/stopwatch/src/main.rs index 9de6d39e..983cf3e6 100644 --- a/examples/stopwatch/src/main.rs +++ b/examples/stopwatch/src/main.rs @@ -5,7 +5,7 @@ use iced::{ }; use std::time::{Duration, Instant}; -pub fn main() { +pub fn main() -> iced::Result { Stopwatch::run(Settings::default()) } @@ -161,7 +161,7 @@ mod style { 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, + border_radius: 12.0, shadow_offset: Vector::new(1.0, 1.0), text_color: Color::WHITE, ..button::Style::default() diff --git a/examples/styling/src/main.rs b/examples/styling/src/main.rs index 63ab9d62..8975fd9a 100644 --- a/examples/styling/src/main.rs +++ b/examples/styling/src/main.rs @@ -1,10 +1,10 @@ use iced::{ button, scrollable, slider, text_input, Align, Button, Checkbox, Column, - Container, Element, Length, ProgressBar, Radio, Row, Sandbox, Scrollable, - Settings, Slider, Space, Text, TextInput, + Container, Element, Length, ProgressBar, Radio, Row, Rule, Sandbox, + Scrollable, Settings, Slider, Space, Text, TextInput, }; -pub fn main() { +pub fn main() -> iced::Result { Styling::run(Settings::default()) } @@ -113,14 +113,17 @@ impl Sandbox for Styling { .padding(20) .max_width(600) .push(choose_theme) + .push(Rule::horizontal(38).style(self.theme)) .push(Row::new().spacing(10).push(text_input).push(button)) .push(slider) .push(progress_bar) .push( Row::new() .spacing(10) + .height(Length::Units(100)) .align_items(Align::Center) .push(scrollable) + .push(Rule::vertical(38).style(self.theme)) .push(checkbox), ); @@ -136,8 +139,8 @@ impl Sandbox for Styling { mod style { use iced::{ - button, checkbox, container, progress_bar, radio, scrollable, slider, - text_input, + button, checkbox, container, progress_bar, radio, rule, scrollable, + slider, text_input, }; #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -228,18 +231,25 @@ mod style { } } + impl From<Theme> for Box<dyn rule::StyleSheet> { + fn from(theme: Theme) -> Self { + match theme { + Theme::Light => Default::default(), + Theme::Dark => dark::Rule.into(), + } + } + } + mod light { - use iced::{button, Background, Color, Vector}; + use iced::{button, Color, Vector}; pub struct Button; impl button::StyleSheet for Button { fn active(&self) -> button::Style { button::Style { - background: Some(Background::Color(Color::from_rgb( - 0.11, 0.42, 0.87, - ))), - border_radius: 12, + background: Color::from_rgb(0.11, 0.42, 0.87).into(), + border_radius: 12.0, shadow_offset: Vector::new(1.0, 1.0), text_color: Color::from_rgb8(0xEE, 0xEE, 0xEE), ..button::Style::default() @@ -258,8 +268,8 @@ mod style { mod dark { use iced::{ - button, checkbox, container, progress_bar, radio, scrollable, - slider, text_input, Background, Color, + button, checkbox, container, progress_bar, radio, rule, scrollable, + slider, text_input, Color, }; const SURFACE: Color = Color::from_rgb( @@ -291,10 +301,8 @@ mod style { impl container::StyleSheet for Container { fn style(&self) -> container::Style { container::Style { - background: Some(Background::Color(Color::from_rgb8( - 0x36, 0x39, 0x3F, - ))), - text_color: Some(Color::WHITE), + background: Color::from_rgb8(0x36, 0x39, 0x3F).into(), + text_color: Color::WHITE.into(), ..container::Style::default() } } @@ -305,16 +313,16 @@ mod style { impl radio::StyleSheet for Radio { fn active(&self) -> radio::Style { radio::Style { - background: Background::Color(SURFACE), + background: SURFACE.into(), dot_color: ACTIVE, - border_width: 1, + border_width: 1.0, border_color: ACTIVE, } } fn hovered(&self) -> radio::Style { radio::Style { - background: Background::Color(Color { a: 0.5, ..SURFACE }), + background: Color { a: 0.5, ..SURFACE }.into(), ..self.active() } } @@ -325,16 +333,16 @@ mod style { impl text_input::StyleSheet for TextInput { fn active(&self) -> text_input::Style { text_input::Style { - background: Background::Color(SURFACE), - border_radius: 2, - border_width: 0, + background: SURFACE.into(), + border_radius: 2.0, + border_width: 0.0, border_color: Color::TRANSPARENT, } } fn focused(&self) -> text_input::Style { text_input::Style { - border_width: 1, + border_width: 1.0, border_color: ACCENT, ..self.active() } @@ -342,7 +350,7 @@ mod style { fn hovered(&self) -> text_input::Style { text_input::Style { - border_width: 1, + border_width: 1.0, border_color: Color { a: 0.3, ..ACCENT }, ..self.focused() } @@ -366,8 +374,8 @@ mod style { impl button::StyleSheet for Button { fn active(&self) -> button::Style { button::Style { - background: Some(Background::Color(ACTIVE)), - border_radius: 3, + background: ACTIVE.into(), + border_radius: 3.0, text_color: Color::WHITE, ..button::Style::default() } @@ -375,7 +383,7 @@ mod style { fn hovered(&self) -> button::Style { button::Style { - background: Some(Background::Color(HOVERED)), + background: HOVERED.into(), text_color: Color::WHITE, ..self.active() } @@ -383,7 +391,7 @@ mod style { fn pressed(&self) -> button::Style { button::Style { - border_width: 1, + border_width: 1.0, border_color: Color::WHITE, ..self.hovered() } @@ -395,14 +403,14 @@ mod style { impl scrollable::StyleSheet for Scrollable { fn active(&self) -> scrollable::Scrollbar { scrollable::Scrollbar { - background: Some(Background::Color(SURFACE)), - border_radius: 2, - border_width: 0, + background: SURFACE.into(), + border_radius: 2.0, + border_width: 0.0, border_color: Color::TRANSPARENT, scroller: scrollable::Scroller { color: ACTIVE, - border_radius: 2, - border_width: 0, + border_radius: 2.0, + border_width: 0.0, border_color: Color::TRANSPARENT, }, } @@ -412,10 +420,7 @@ mod style { let active = self.active(); scrollable::Scrollbar { - background: Some(Background::Color(Color { - a: 0.5, - ..SURFACE - })), + background: Color { a: 0.5, ..SURFACE }.into(), scroller: scrollable::Scroller { color: HOVERED, ..active.scroller @@ -444,9 +449,9 @@ mod style { slider::Style { rail_colors: (ACTIVE, Color { a: 0.1, ..ACTIVE }), handle: slider::Handle { - shape: slider::HandleShape::Circle { radius: 9 }, + shape: slider::HandleShape::Circle { radius: 9.0 }, color: ACTIVE, - border_width: 0, + border_width: 0.0, border_color: Color::TRANSPARENT, }, } @@ -482,9 +487,9 @@ mod style { impl progress_bar::StyleSheet for ProgressBar { fn style(&self) -> progress_bar::Style { progress_bar::Style { - background: Background::Color(SURFACE), - bar: Background::Color(ACTIVE), - border_radius: 10, + background: SURFACE.into(), + bar: ACTIVE.into(), + border_radius: 10.0, } } } @@ -494,27 +499,38 @@ mod style { impl checkbox::StyleSheet for Checkbox { fn active(&self, is_checked: bool) -> checkbox::Style { checkbox::Style { - background: Background::Color(if is_checked { - ACTIVE - } else { - SURFACE - }), + background: if is_checked { ACTIVE } else { SURFACE } + .into(), checkmark_color: Color::WHITE, - border_radius: 2, - border_width: 1, + border_radius: 2.0, + border_width: 1.0, border_color: ACTIVE, } } fn hovered(&self, is_checked: bool) -> checkbox::Style { checkbox::Style { - background: Background::Color(Color { + background: Color { a: 0.8, ..if is_checked { ACTIVE } else { SURFACE } - }), + } + .into(), ..self.active(is_checked) } } } + + pub struct Rule; + + impl rule::StyleSheet for Rule { + fn style(&self) -> rule::Style { + rule::Style { + color: SURFACE, + width: 2, + radius: 1.0, + fill_mode: rule::FillMode::Padded(15), + } + } + } } } diff --git a/examples/svg/src/main.rs b/examples/svg/src/main.rs index e19eeca2..8707fa3b 100644 --- a/examples/svg/src/main.rs +++ b/examples/svg/src/main.rs @@ -1,6 +1,6 @@ use iced::{Container, Element, Length, Sandbox, Settings, Svg}; -pub fn main() { +pub fn main() -> iced::Result { Tiger::run(Settings::default()) } diff --git a/examples/todos/Cargo.toml b/examples/todos/Cargo.toml index f945cde5..c8926c33 100644 --- a/examples/todos/Cargo.toml +++ b/examples/todos/Cargo.toml @@ -6,13 +6,13 @@ edition = "2018" publish = false [dependencies] -iced = { path = "../..", features = ["async-std"] } +iced = { path = "../..", features = ["async-std", "debug"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] async-std = "1.0" -directories = "2.0" +directories-next = "2.0" [target.'cfg(target_arch = "wasm32")'.dependencies] web-sys = { version = "0.3", features = ["Window", "Storage"] } diff --git a/examples/todos/src/main.rs b/examples/todos/src/main.rs index c9cbcc69..ccee2703 100644 --- a/examples/todos/src/main.rs +++ b/examples/todos/src/main.rs @@ -5,7 +5,7 @@ use iced::{ }; use serde::{Deserialize, Serialize}; -pub fn main() { +pub fn main() -> iced::Result { Todos::run(Settings::default()) } @@ -425,7 +425,7 @@ impl Filter { } } -fn loading_message() -> Element<'static, Message> { +fn loading_message<'a>() -> Element<'a, Message> { Container::new( Text::new("Loading...") .horizontal_alignment(HorizontalAlignment::Center) @@ -437,7 +437,7 @@ fn loading_message() -> Element<'static, Message> { .into() } -fn empty_message(message: &str) -> Element<'static, Message> { +fn empty_message<'a>(message: &str) -> Element<'a, Message> { Container::new( Text::new(message) .width(Length::Fill) @@ -499,7 +499,7 @@ enum SaveError { impl SavedState { fn path() -> std::path::PathBuf { let mut path = if let Some(project_dirs) = - directories::ProjectDirs::from("rs", "Iced", "Todos") + directories_next::ProjectDirs::from("rs", "Iced", "Todos") { project_dirs.data_dir().into() } else { @@ -611,7 +611,7 @@ mod style { background: Some(Background::Color( Color::from_rgb(0.2, 0.2, 0.7), )), - border_radius: 10, + border_radius: 10.0, text_color: Color::WHITE, ..button::Style::default() } @@ -627,7 +627,7 @@ mod style { background: Some(Background::Color(Color::from_rgb( 0.8, 0.2, 0.2, ))), - border_radius: 5, + border_radius: 5.0, text_color: Color::WHITE, shadow_offset: Vector::new(1.0, 1.0), ..button::Style::default() diff --git a/examples/tour/Cargo.toml b/examples/tour/Cargo.toml index 96749e90..bc7fac11 100644 --- a/examples/tour/Cargo.toml +++ b/examples/tour/Cargo.toml @@ -7,4 +7,4 @@ publish = false [dependencies] iced = { path = "../..", features = ["image", "debug"] } -env_logger = "0.7" +env_logger = "0.8" diff --git a/examples/tour/src/main.rs b/examples/tour/src/main.rs index c9678b9d..e8755d39 100644 --- a/examples/tour/src/main.rs +++ b/examples/tour/src/main.rs @@ -4,7 +4,7 @@ use iced::{ Sandbox, Scrollable, Settings, Slider, Space, Text, TextInput, }; -pub fn main() { +pub fn main() -> iced::Result { env_logger::init(); Tour::run(Settings::default()) @@ -190,7 +190,7 @@ enum Step { Welcome, Slider { state: slider::State, - value: u16, + value: u8, }, RowsAndColumns { layout: Layout, @@ -222,13 +222,13 @@ enum Step { #[derive(Debug, Clone)] pub enum StepMessage { - SliderChanged(f32), + SliderChanged(u8), LayoutChanged(Layout), - SpacingChanged(f32), - TextSizeChanged(f32), + SpacingChanged(u16), + TextSizeChanged(u16), TextColorChanged(Color), LanguageSelected(Language), - ImageWidthChanged(f32), + ImageWidthChanged(u16), InputChanged(String), ToggleSecureInput(bool), DebugToggled(bool), @@ -249,12 +249,12 @@ impl<'a> Step { } StepMessage::SliderChanged(new_value) => { if let Step::Slider { value, .. } = self { - *value = new_value.round() as u16; + *value = new_value; } } StepMessage::TextSizeChanged(new_size) => { if let Step::Text { size, .. } = self { - *size = new_size.round() as u16; + *size = new_size; } } StepMessage::TextColorChanged(new_color) => { @@ -269,12 +269,12 @@ impl<'a> Step { } StepMessage::SpacingChanged(new_spacing) => { if let Step::RowsAndColumns { spacing, .. } = self { - *spacing = new_spacing.round() as u16; + *spacing = new_spacing; } } StepMessage::ImageWidthChanged(new_width) => { if let Step::Image { width, .. } = self { - *width = new_width.round() as u16; + *width = new_width; } } StepMessage::InputChanged(new_value) => { @@ -384,7 +384,7 @@ impl<'a> Step { fn slider( state: &'a mut slider::State, - value: u16, + value: u8, ) -> Column<'a, StepMessage> { Self::container("Slider") .push(Text::new( @@ -397,8 +397,8 @@ impl<'a> Step { )) .push(Slider::new( state, - 0.0..=100.0, - value as f32, + 0..=100, + value, StepMessage::SliderChanged, )) .push( @@ -444,8 +444,8 @@ impl<'a> Step { .spacing(10) .push(Slider::new( spacing_slider, - 0.0..=80.0, - spacing as f32, + 0..=80, + spacing, StepMessage::SpacingChanged, )) .push( @@ -486,30 +486,25 @@ impl<'a> Step { ) .push(Slider::new( size_slider, - 10.0..=70.0, - size as f32, + 10..=70, + size, StepMessage::TextSizeChanged, )); let [red, green, blue] = color_sliders; + + let color_sliders = Row::new() + .spacing(10) + .push(color_slider(red, color.r, move |r| Color { r, ..color })) + .push(color_slider(green, color.g, move |g| Color { g, ..color })) + .push(color_slider(blue, color.b, move |b| Color { b, ..color })); + let color_section = Column::new() .padding(20) .spacing(20) .push(Text::new("And its color:")) .push(Text::new(&format!("{:?}", color)).color(color)) - .push( - Row::new() - .spacing(10) - .push(Slider::new(red, 0.0..=1.0, color.r, move |r| { - StepMessage::TextColorChanged(Color { r, ..color }) - })) - .push(Slider::new(green, 0.0..=1.0, color.g, move |g| { - StepMessage::TextColorChanged(Color { g, ..color }) - })) - .push(Slider::new(blue, 0.0..=1.0, color.b, move |b| { - StepMessage::TextColorChanged(Color { b, ..color }) - })), - ); + .push(color_sliders); Self::container("Text") .push(Text::new( @@ -559,8 +554,8 @@ impl<'a> Step { .push(ferris(width)) .push(Slider::new( slider, - 100.0..=500.0, - width as f32, + 100..=500, + width, StepMessage::ImageWidthChanged, )) .push( @@ -694,7 +689,7 @@ fn ferris<'a>(width: u16) -> Container<'a, StepMessage> { .center_x() } -fn button<'a, Message>( +fn button<'a, Message: Clone>( state: &'a mut button::State, label: &str, ) -> Button<'a, Message> { @@ -706,6 +701,17 @@ fn button<'a, Message>( .min_width(100) } +fn color_slider( + state: &mut slider::State, + component: f32, + update: impl Fn(f32) -> Color + 'static, +) -> Slider<f64, StepMessage> { + Slider::new(state, 0.0..=1.0, f64::from(component), move |c| { + StepMessage::TextColorChanged(update(c as f32)) + }) + .step(0.01) +} + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Language { Rust, @@ -763,7 +769,7 @@ mod style { Button::Primary => Color::from_rgb(0.11, 0.42, 0.87), Button::Secondary => Color::from_rgb(0.5, 0.5, 0.5), })), - border_radius: 12, + border_radius: 12.0, shadow_offset: Vector::new(1.0, 1.0), text_color: Color::from_rgb8(0xEE, 0xEE, 0xEE), ..button::Style::default() |