diff options
Diffstat (limited to 'examples')
-rw-r--r-- | examples/arc/src/main.rs | 14 | ||||
-rw-r--r-- | examples/cached/Cargo.toml | 10 | ||||
-rw-r--r-- | examples/cached/src/main.rs | 139 | ||||
-rw-r--r-- | examples/clock/src/main.rs | 54 | ||||
-rw-r--r-- | examples/game_of_life/src/main.rs | 14 | ||||
-rw-r--r-- | examples/geometry/Cargo.toml | 11 | ||||
-rw-r--r-- | examples/geometry/README.md | 18 | ||||
-rw-r--r-- | examples/geometry/src/main.rs | 217 | ||||
-rw-r--r-- | examples/integration_wgpu/src/main.rs | 2 | ||||
-rw-r--r-- | examples/lazy/Cargo.toml | 10 | ||||
-rw-r--r-- | examples/lazy/src/main.rs | 139 | ||||
-rw-r--r-- | examples/modern_art/Cargo.toml | 10 | ||||
-rw-r--r-- | examples/modern_art/src/main.rs | 140 | ||||
-rw-r--r-- | examples/multitouch/Cargo.toml | 12 | ||||
-rw-r--r-- | examples/multitouch/src/main.rs | 200 | ||||
-rw-r--r-- | examples/scrollable/src/main.rs | 23 | ||||
-rw-r--r-- | examples/solar_system/src/main.rs | 45 | ||||
-rw-r--r-- | examples/styling/src/main.rs | 57 | ||||
-rw-r--r-- | examples/todos/Cargo.toml | 2 | ||||
-rw-r--r-- | examples/todos/src/main.rs | 6 | ||||
-rw-r--r-- | examples/websocket/Cargo.toml | 2 | ||||
-rw-r--r-- | examples/websocket/src/main.rs | 5 |
22 files changed, 800 insertions, 330 deletions
diff --git a/examples/arc/src/main.rs b/examples/arc/src/main.rs index 0c619dc9..7b6ea0e1 100644 --- a/examples/arc/src/main.rs +++ b/examples/arc/src/main.rs @@ -2,7 +2,7 @@ use std::{f32::consts::PI, time::Instant}; use iced::executor; use iced::widget::canvas::{ - self, Cache, Canvas, Cursor, Geometry, Path, Stroke, + self, stroke, Cache, Canvas, Cursor, Geometry, Path, Stroke, }; use iced::{ Application, Command, Element, Length, Point, Rectangle, Settings, @@ -52,11 +52,6 @@ impl Application for Arc { Command::none() } - fn subscription(&self) -> Subscription<Message> { - iced::time::every(std::time::Duration::from_millis(10)) - .map(|_| Message::Tick) - } - fn view(&self) -> Element<Message> { Canvas::new(self) .width(Length::Fill) @@ -67,6 +62,11 @@ impl Application for Arc { fn theme(&self) -> Theme { Theme::Dark } + + fn subscription(&self) -> Subscription<Message> { + iced::time::every(std::time::Duration::from_millis(10)) + .map(|_| Message::Tick) + } } impl<Message> canvas::Program<Message> for Arc { @@ -114,7 +114,7 @@ impl<Message> canvas::Program<Message> for Arc { frame.stroke( &path, Stroke { - color: palette.text, + style: stroke::Style::Solid(palette.text), width: 10.0, ..Stroke::default() }, diff --git a/examples/cached/Cargo.toml b/examples/cached/Cargo.toml new file mode 100644 index 00000000..2c7edde2 --- /dev/null +++ b/examples/cached/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "cached" +version = "0.1.0" +authors = ["Nick Senger <dev@nsenger.com>"] +edition = "2021" +publish = false + +[dependencies] +iced = { path = "../..", features = ["debug"] } +iced_lazy = { path = "../../lazy" } diff --git a/examples/cached/src/main.rs b/examples/cached/src/main.rs new file mode 100644 index 00000000..8845b874 --- /dev/null +++ b/examples/cached/src/main.rs @@ -0,0 +1,139 @@ +use iced::theme; +use iced::widget::{ + button, column, horizontal_space, row, scrollable, text, text_input, +}; +use iced::{Element, Length, Sandbox, Settings}; +use iced_lazy::lazy; + +use std::collections::HashSet; + +pub fn main() -> iced::Result { + App::run(Settings::default()) +} + +struct App { + options: HashSet<String>, + input: String, + order: Order, +} + +impl Default for App { + fn default() -> Self { + Self { + options: ["Foo", "Bar", "Baz", "Qux", "Corge", "Waldo", "Fred"] + .into_iter() + .map(ToString::to_string) + .collect(), + input: Default::default(), + order: Order::Ascending, + } + } +} + +#[derive(Debug, Clone)] +enum Message { + InputChanged(String), + ToggleOrder, + DeleteOption(String), + AddOption(String), +} + +impl Sandbox for App { + type Message = Message; + + fn new() -> Self { + Self::default() + } + + fn title(&self) -> String { + String::from("Cached - Iced") + } + + fn update(&mut self, message: Message) { + match message { + Message::InputChanged(input) => { + self.input = input; + } + Message::ToggleOrder => { + self.order = match self.order { + Order::Ascending => Order::Descending, + Order::Descending => Order::Ascending, + } + } + Message::AddOption(option) => { + self.options.insert(option); + self.input.clear(); + } + Message::DeleteOption(option) => { + self.options.remove(&option); + } + } + } + + fn view(&self) -> Element<Message> { + let options = lazy((&self.order, self.options.len()), || { + let mut options: Vec<_> = self.options.iter().collect(); + + options.sort_by(|a, b| match self.order { + Order::Ascending => a.to_lowercase().cmp(&b.to_lowercase()), + Order::Descending => b.to_lowercase().cmp(&a.to_lowercase()), + }); + + column( + options + .into_iter() + .map(|option| { + row![ + text(option), + horizontal_space(Length::Fill), + button("Delete") + .on_press(Message::DeleteOption( + option.to_string(), + ),) + .style(theme::Button::Destructive) + ] + .into() + }) + .collect(), + ) + .spacing(10) + }); + + column![ + scrollable(options).height(Length::Fill), + row![ + text_input( + "Add a new option", + &self.input, + Message::InputChanged, + ) + .on_submit(Message::AddOption(self.input.clone())), + button(text(format!("Toggle Order ({})", self.order))) + .on_press(Message::ToggleOrder) + ] + .spacing(10) + ] + .spacing(20) + .padding(20) + .into() + } +} + +#[derive(Debug, Hash)] +enum Order { + Ascending, + Descending, +} + +impl std::fmt::Display for Order { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Self::Ascending => "Ascending", + Self::Descending => "Descending", + } + ) + } +} diff --git a/examples/clock/src/main.rs b/examples/clock/src/main.rs index 8818fb54..a389c54f 100644 --- a/examples/clock/src/main.rs +++ b/examples/clock/src/main.rs @@ -1,5 +1,7 @@ use iced::executor; -use iced::widget::canvas::{Cache, Cursor, Geometry, LineCap, Path, Stroke}; +use iced::widget::canvas::{ + stroke, Cache, Cursor, Geometry, LineCap, Path, Stroke, +}; use iced::widget::{canvas, container}; use iced::{ Application, Color, Command, Element, Length, Point, Rectangle, Settings, @@ -24,9 +26,9 @@ enum Message { } impl Application for Clock { + type Executor = executor::Default; type Message = Message; type Theme = Theme; - type Executor = executor::Default; type Flags = (); fn new(_flags: ()) -> (Self, Command<Message>) { @@ -59,15 +61,6 @@ impl Application for Clock { Command::none() } - fn subscription(&self) -> Subscription<Message> { - iced::time::every(std::time::Duration::from_millis(500)).map(|_| { - Message::Tick( - time::OffsetDateTime::now_local() - .unwrap_or_else(|_| time::OffsetDateTime::now_utc()), - ) - }) - } - fn view(&self) -> Element<Message> { let canvas = canvas(self as &Self) .width(Length::Fill) @@ -79,6 +72,15 @@ impl Application for Clock { .padding(20) .into() } + + fn subscription(&self) -> Subscription<Message> { + iced::time::every(std::time::Duration::from_millis(500)).map(|_| { + Message::Tick( + time::OffsetDateTime::now_local() + .unwrap_or_else(|_| time::OffsetDateTime::now_utc()), + ) + }) + } } impl<Message> canvas::Program<Message> for Clock { @@ -104,33 +106,41 @@ impl<Message> canvas::Program<Message> for Clock { let long_hand = Path::line(Point::ORIGIN, Point::new(0.0, -0.8 * radius)); - let thin_stroke = Stroke { - width: radius / 100.0, - color: Color::WHITE, - line_cap: LineCap::Round, - ..Stroke::default() + let width = radius / 100.0; + + let thin_stroke = || -> Stroke { + Stroke { + width, + style: stroke::Style::Solid(Color::WHITE), + line_cap: LineCap::Round, + ..Stroke::default() + } }; - let wide_stroke = Stroke { - width: thin_stroke.width * 3.0, - ..thin_stroke + let wide_stroke = || -> Stroke { + Stroke { + width: width * 3.0, + style: stroke::Style::Solid(Color::WHITE), + line_cap: LineCap::Round, + ..Stroke::default() + } }; frame.translate(Vector::new(center.x, center.y)); frame.with_save(|frame| { frame.rotate(hand_rotation(self.now.hour(), 12)); - frame.stroke(&short_hand, wide_stroke); + frame.stroke(&short_hand, wide_stroke()); }); frame.with_save(|frame| { frame.rotate(hand_rotation(self.now.minute(), 60)); - frame.stroke(&long_hand, wide_stroke); + frame.stroke(&long_hand, wide_stroke()); }); frame.with_save(|frame| { frame.rotate(hand_rotation(self.now.second(), 60)); - frame.stroke(&long_hand, thin_stroke); + frame.stroke(&long_hand, thin_stroke()); }) }); diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index a2030275..2a8b3721 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -204,6 +204,7 @@ fn view_controls<'a>( mod grid { use crate::Preset; + use iced::touch; use iced::widget::canvas; use iced::widget::canvas::event::{self, Event}; use iced::widget::canvas::{ @@ -423,6 +424,19 @@ mod grid { }; match event { + Event::Touch(touch::Event::FingerMoved { .. }) => { + let message = { + *interaction = if is_populated { + Interaction::Erasing + } else { + Interaction::Drawing + }; + + populate.or(unpopulate) + }; + + (event::Status::Captured, message) + } Event::Mouse(mouse_event) => match mouse_event { mouse::Event::ButtonPressed(button) => { let message = match button { diff --git a/examples/geometry/Cargo.toml b/examples/geometry/Cargo.toml deleted file mode 100644 index 22ede0e0..00000000 --- a/examples/geometry/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "geometry" -version = "0.1.0" -authors = ["Héctor Ramón Jiménez <hector0193@gmail.com>"] -edition = "2021" -publish = false - -[dependencies] -iced = { path = "../.." } -iced_native = { path = "../../native" } -iced_graphics = { path = "../../graphics" } diff --git a/examples/geometry/README.md b/examples/geometry/README.md deleted file mode 100644 index 4d5c81cb..00000000 --- a/examples/geometry/README.md +++ /dev/null @@ -1,18 +0,0 @@ -## Geometry - -A custom widget showcasing how to draw geometry with the `Mesh2D` primitive in [`iced_wgpu`](../../wgpu). - -The __[`main`]__ file contains all the code of the example. - -<div align="center"> - <a href="https://gfycat.com/activeunfitkangaroo"> - <img src="https://thumbs.gfycat.com/ActiveUnfitKangaroo-small.gif"> - </a> -</div> - -You can run it with `cargo run`: -``` -cargo run --package geometry -``` - -[`main`]: src/main.rs diff --git a/examples/geometry/src/main.rs b/examples/geometry/src/main.rs deleted file mode 100644 index d8b99ab3..00000000 --- a/examples/geometry/src/main.rs +++ /dev/null @@ -1,217 +0,0 @@ -//! This example showcases a simple native custom widget that renders using -//! arbitrary low-level geometry. -mod rainbow { - // For now, to implement a custom native widget you will need to add - // `iced_native` and `iced_wgpu` to your dependencies. - // - // Then, you simply need to define your widget type and implement the - // `iced_native::Widget` trait with the `iced_wgpu::Renderer`. - // - // 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::renderer::{self, Renderer}; - use iced_graphics::{Backend, Primitive}; - - use iced_native::widget::{self, Widget}; - use iced_native::{ - layout, Element, Layout, Length, Point, Rectangle, Size, Vector, - }; - - #[derive(Default)] - pub struct Rainbow; - - impl Rainbow { - pub fn new() -> Self { - Self - } - } - - pub fn rainbow() -> Rainbow { - Rainbow - } - - impl<Message, B, T> Widget<Message, Renderer<B, T>> for Rainbow - where - B: Backend, - { - fn width(&self) -> Length { - Length::Fill - } - - fn height(&self) -> Length { - Length::Shrink - } - - fn layout( - &self, - _renderer: &Renderer<B, T>, - limits: &layout::Limits, - ) -> layout::Node { - let size = limits.width(Length::Fill).resolve(Size::ZERO); - - layout::Node::new(Size::new(size.width, size.width)) - } - - fn draw( - &self, - _tree: &widget::Tree, - renderer: &mut Renderer<B, T>, - _theme: &T, - _style: &renderer::Style, - layout: Layout<'_>, - cursor_position: Point, - _viewport: &Rectangle, - ) { - use iced_graphics::triangle::{Mesh2D, Vertex2D}; - use iced_native::Renderer as _; - - let b = layout.bounds(); - - // R O Y G B I V - let color_r = [1.0, 0.0, 0.0, 1.0]; - let color_o = [1.0, 0.5, 0.0, 1.0]; - let color_y = [1.0, 1.0, 0.0, 1.0]; - let color_g = [0.0, 1.0, 0.0, 1.0]; - let color_gb = [0.0, 1.0, 0.5, 1.0]; - let color_b = [0.0, 0.2, 1.0, 1.0]; - let color_i = [0.5, 0.0, 1.0, 1.0]; - let color_v = [0.75, 0.0, 0.5, 1.0]; - - let posn_center = { - if b.contains(cursor_position) { - [cursor_position.x - b.x, cursor_position.y - b.y] - } else { - [b.width / 2.0, b.height / 2.0] - } - }; - - let posn_tl = [0.0, 0.0]; - let posn_t = [b.width / 2.0, 0.0]; - let posn_tr = [b.width, 0.0]; - let posn_r = [b.width, b.height / 2.0]; - let posn_br = [b.width, b.height]; - let posn_b = [(b.width / 2.0), b.height]; - let posn_bl = [0.0, b.height]; - let posn_l = [0.0, b.height / 2.0]; - - let mesh = Primitive::Mesh2D { - size: b.size(), - buffers: Mesh2D { - vertices: vec![ - Vertex2D { - position: posn_center, - color: [1.0, 1.0, 1.0, 1.0], - }, - Vertex2D { - position: posn_tl, - color: color_r, - }, - Vertex2D { - position: posn_t, - color: color_o, - }, - Vertex2D { - position: posn_tr, - color: color_y, - }, - Vertex2D { - position: posn_r, - color: color_g, - }, - Vertex2D { - position: posn_br, - color: color_gb, - }, - Vertex2D { - position: posn_b, - color: color_b, - }, - Vertex2D { - position: posn_bl, - color: color_i, - }, - Vertex2D { - position: posn_l, - color: color_v, - }, - ], - indices: vec![ - 0, 1, 2, // TL - 0, 2, 3, // T - 0, 3, 4, // TR - 0, 4, 5, // R - 0, 5, 6, // BR - 0, 6, 7, // B - 0, 7, 8, // BL - 0, 8, 1, // L - ], - }, - }; - - renderer.with_translation(Vector::new(b.x, b.y), |renderer| { - renderer.draw_primitive(mesh); - }); - } - } - - impl<'a, Message, B, T> From<Rainbow> for Element<'a, Message, Renderer<B, T>> - where - B: Backend, - { - fn from(rainbow: Rainbow) -> Self { - Self::new(rainbow) - } - } -} - -use iced::widget::{column, container, scrollable}; -use iced::{Alignment, Element, Length, Sandbox, Settings}; -use rainbow::rainbow; - -pub fn main() -> iced::Result { - Example::run(Settings::default()) -} - -struct Example; - -impl Sandbox for Example { - type Message = (); - - fn new() -> Self { - Example - } - - fn title(&self) -> String { - String::from("Custom 2D geometry - Iced") - } - - fn update(&mut self, _: ()) {} - - fn view(&self) -> Element<()> { - let content = column![ - rainbow(), - "In this example we draw a custom widget Rainbow, using \ - the Mesh2D primitive. This primitive supplies a list of \ - triangles, expressed as vertices and indices.", - "Move your cursor over it, and see the center vertex \ - follow you!", - "Every Vertex2D defines its own color. You could use the \ - Mesh2D primitive to render virtually any two-dimensional \ - geometry for your widget.", - ] - .padding(20) - .spacing(20) - .max_width(500) - .align_items(Alignment::Start); - - let scrollable = - scrollable(container(content).width(Length::Fill).center_x()); - - container(scrollable) - .width(Length::Fill) - .height(Length::Fill) - .center_y() - .into() - } -} diff --git a/examples/integration_wgpu/src/main.rs b/examples/integration_wgpu/src/main.rs index 69d46c3e..70f9a48b 100644 --- a/examples/integration_wgpu/src/main.rs +++ b/examples/integration_wgpu/src/main.rs @@ -119,6 +119,7 @@ pub fn main() { width: physical_size.width, height: physical_size.height, present_mode: wgpu::PresentMode::AutoVsync, + alpha_mode: wgpu::CompositeAlphaMode::Auto, }, ); @@ -213,6 +214,7 @@ pub fn main() { width: size.width, height: size.height, present_mode: wgpu::PresentMode::AutoVsync, + alpha_mode: wgpu::CompositeAlphaMode::Auto }, ); diff --git a/examples/lazy/Cargo.toml b/examples/lazy/Cargo.toml new file mode 100644 index 00000000..79255c25 --- /dev/null +++ b/examples/lazy/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "lazy" +version = "0.1.0" +authors = ["Nick Senger <dev@nsenger.com>"] +edition = "2021" +publish = false + +[dependencies] +iced = { path = "../..", features = ["debug"] } +iced_lazy = { path = "../../lazy" } diff --git a/examples/lazy/src/main.rs b/examples/lazy/src/main.rs new file mode 100644 index 00000000..8845b874 --- /dev/null +++ b/examples/lazy/src/main.rs @@ -0,0 +1,139 @@ +use iced::theme; +use iced::widget::{ + button, column, horizontal_space, row, scrollable, text, text_input, +}; +use iced::{Element, Length, Sandbox, Settings}; +use iced_lazy::lazy; + +use std::collections::HashSet; + +pub fn main() -> iced::Result { + App::run(Settings::default()) +} + +struct App { + options: HashSet<String>, + input: String, + order: Order, +} + +impl Default for App { + fn default() -> Self { + Self { + options: ["Foo", "Bar", "Baz", "Qux", "Corge", "Waldo", "Fred"] + .into_iter() + .map(ToString::to_string) + .collect(), + input: Default::default(), + order: Order::Ascending, + } + } +} + +#[derive(Debug, Clone)] +enum Message { + InputChanged(String), + ToggleOrder, + DeleteOption(String), + AddOption(String), +} + +impl Sandbox for App { + type Message = Message; + + fn new() -> Self { + Self::default() + } + + fn title(&self) -> String { + String::from("Cached - Iced") + } + + fn update(&mut self, message: Message) { + match message { + Message::InputChanged(input) => { + self.input = input; + } + Message::ToggleOrder => { + self.order = match self.order { + Order::Ascending => Order::Descending, + Order::Descending => Order::Ascending, + } + } + Message::AddOption(option) => { + self.options.insert(option); + self.input.clear(); + } + Message::DeleteOption(option) => { + self.options.remove(&option); + } + } + } + + fn view(&self) -> Element<Message> { + let options = lazy((&self.order, self.options.len()), || { + let mut options: Vec<_> = self.options.iter().collect(); + + options.sort_by(|a, b| match self.order { + Order::Ascending => a.to_lowercase().cmp(&b.to_lowercase()), + Order::Descending => b.to_lowercase().cmp(&a.to_lowercase()), + }); + + column( + options + .into_iter() + .map(|option| { + row![ + text(option), + horizontal_space(Length::Fill), + button("Delete") + .on_press(Message::DeleteOption( + option.to_string(), + ),) + .style(theme::Button::Destructive) + ] + .into() + }) + .collect(), + ) + .spacing(10) + }); + + column![ + scrollable(options).height(Length::Fill), + row![ + text_input( + "Add a new option", + &self.input, + Message::InputChanged, + ) + .on_submit(Message::AddOption(self.input.clone())), + button(text(format!("Toggle Order ({})", self.order))) + .on_press(Message::ToggleOrder) + ] + .spacing(10) + ] + .spacing(20) + .padding(20) + .into() + } +} + +#[derive(Debug, Hash)] +enum Order { + Ascending, + Descending, +} + +impl std::fmt::Display for Order { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + match self { + Self::Ascending => "Ascending", + Self::Descending => "Descending", + } + ) + } +} diff --git a/examples/modern_art/Cargo.toml b/examples/modern_art/Cargo.toml new file mode 100644 index 00000000..a48361ae --- /dev/null +++ b/examples/modern_art/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "modern_art" +version = "0.1.0" +authors = ["Bingus <shankern@protonmail.com>"] +edition = "2021" +publish = false + +[dependencies] +iced = { path = "../..", features = ["canvas", "tokio", "debug"] } +rand = "0.8.5" diff --git a/examples/modern_art/src/main.rs b/examples/modern_art/src/main.rs new file mode 100644 index 00000000..0dd21c74 --- /dev/null +++ b/examples/modern_art/src/main.rs @@ -0,0 +1,140 @@ +use iced::widget::canvas::{ + self, gradient::Location, gradient::Position, Cache, Canvas, Cursor, Frame, + Geometry, Gradient, +}; +use iced::{ + executor, Application, Color, Command, Element, Length, Point, Rectangle, + Renderer, Settings, Size, Theme, +}; +use rand::{thread_rng, Rng}; + +fn main() -> iced::Result { + ModernArt::run(Settings { + antialiasing: true, + ..Settings::default() + }) +} + +#[derive(Debug, Clone, Copy)] +enum Message {} + +struct ModernArt { + cache: Cache, +} + +impl Application for ModernArt { + type Executor = executor::Default; + type Message = Message; + type Theme = Theme; + type Flags = (); + + fn new(_flags: Self::Flags) -> (Self, Command<Self::Message>) { + ( + ModernArt { + cache: Default::default(), + }, + Command::none(), + ) + } + + fn title(&self) -> String { + String::from("Modern Art") + } + + fn update(&mut self, _message: Message) -> Command<Message> { + Command::none() + } + + fn view(&self) -> Element<'_, Self::Message, Renderer<Self::Theme>> { + Canvas::new(self) + .width(Length::Fill) + .height(Length::Fill) + .into() + } +} + +impl<Message> canvas::Program<Message> for ModernArt { + type State = (); + + fn draw( + &self, + _state: &Self::State, + _theme: &Theme, + bounds: Rectangle, + _cursor: Cursor, + ) -> Vec<Geometry> { + let geometry = self.cache.draw(bounds.size(), |frame| { + let num_squares = thread_rng().gen_range(0..1200); + + let mut i = 0; + while i <= num_squares { + generate_box(frame, bounds.size()); + i += 1; + } + }); + + vec![geometry] + } +} + +fn random_direction() -> Location { + match thread_rng().gen_range(0..8) { + 0 => Location::TopLeft, + 1 => Location::Top, + 2 => Location::TopRight, + 3 => Location::Right, + 4 => Location::BottomRight, + 5 => Location::Bottom, + 6 => Location::BottomLeft, + 7 => Location::Left, + _ => Location::TopLeft, + } +} + +fn generate_box(frame: &mut Frame, bounds: Size) -> bool { + let solid = rand::random::<bool>(); + + let random_color = || -> Color { + Color::from_rgb( + thread_rng().gen_range(0.0..1.0), + thread_rng().gen_range(0.0..1.0), + thread_rng().gen_range(0.0..1.0), + ) + }; + + let gradient = |top_left: Point, size: Size| -> Gradient { + let mut builder = Gradient::linear(Position::Relative { + top_left, + size, + start: random_direction(), + end: random_direction(), + }); + let stops = thread_rng().gen_range(1..15u32); + + let mut i = 0; + while i <= stops { + builder = builder.add_stop(i as f32 / stops as f32, random_color()); + i += 1; + } + + builder.build().unwrap() + }; + + let top_left = Point::new( + thread_rng().gen_range(0.0..bounds.width), + thread_rng().gen_range(0.0..bounds.height), + ); + + let size = Size::new( + thread_rng().gen_range(50.0..200.0), + thread_rng().gen_range(50.0..200.0), + ); + + if solid { + frame.fill_rectangle(top_left, size, random_color()); + } else { + frame.fill_rectangle(top_left, size, gradient(top_left, size)); + }; + + solid +} diff --git a/examples/multitouch/Cargo.toml b/examples/multitouch/Cargo.toml new file mode 100644 index 00000000..f7c8c145 --- /dev/null +++ b/examples/multitouch/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "multitouch" +version = "0.1.0" +authors = ["Artur Sapek <artur@kraken.com>"] +edition = "2021" +publish = false + +[dependencies] +iced = { path = "../..", features = ["canvas", "tokio", "debug"] } +tokio = { version = "1.0", features = ["sync"] } +env_logger = "0.9" +voronator = "0.2" diff --git a/examples/multitouch/src/main.rs b/examples/multitouch/src/main.rs new file mode 100644 index 00000000..f5faae0f --- /dev/null +++ b/examples/multitouch/src/main.rs @@ -0,0 +1,200 @@ +//! This example shows how to use touch events in `Canvas` to draw +//! a circle around each fingertip. This only works on touch-enabled +//! computers like Microsoft Surface. +use iced::widget::canvas::event; +use iced::widget::canvas::stroke::{self, Stroke}; +use iced::widget::canvas::{self, Canvas, Cursor, Geometry}; +use iced::{ + executor, touch, window, Application, Color, Command, Element, Length, + Point, Rectangle, Settings, Subscription, Theme, +}; + +use std::collections::HashMap; + +pub fn main() -> iced::Result { + env_logger::builder().format_timestamp(None).init(); + + Multitouch::run(Settings { + antialiasing: true, + window: window::Settings { + position: window::Position::Centered, + ..window::Settings::default() + }, + ..Settings::default() + }) +} + +struct Multitouch { + state: State, +} + +#[derive(Debug)] +struct State { + cache: canvas::Cache, + fingers: HashMap<touch::Finger, Point>, +} + +impl State { + fn new() -> Self { + Self { + cache: canvas::Cache::new(), + fingers: HashMap::new(), + } + } +} + +#[derive(Debug)] +enum Message { + FingerPressed { id: touch::Finger, position: Point }, + FingerLifted { id: touch::Finger }, +} + +impl Application for Multitouch { + type Executor = executor::Default; + type Message = Message; + type Theme = Theme; + type Flags = (); + + fn new(_flags: ()) -> (Self, Command<Message>) { + ( + Multitouch { + state: State::new(), + }, + Command::none(), + ) + } + + fn title(&self) -> String { + String::from("Multitouch - Iced") + } + + fn update(&mut self, message: Message) -> Command<Message> { + match message { + Message::FingerPressed { id, position } => { + self.state.fingers.insert(id, position); + self.state.cache.clear(); + } + Message::FingerLifted { id } => { + self.state.fingers.remove(&id); + self.state.cache.clear(); + } + } + + Command::none() + } + + fn subscription(&self) -> Subscription<Message> { + Subscription::none() + } + + fn view(&self) -> Element<Message> { + Canvas::new(&self.state) + .width(Length::Fill) + .height(Length::Fill) + .into() + } +} + +impl canvas::Program<Message> for State { + type State = (); + + fn update( + &self, + _state: &mut Self::State, + event: event::Event, + _bounds: Rectangle, + _cursor: Cursor, + ) -> (event::Status, Option<Message>) { + match event { + event::Event::Touch(touch_event) => match touch_event { + touch::Event::FingerPressed { id, position } + | touch::Event::FingerMoved { id, position } => ( + event::Status::Captured, + Some(Message::FingerPressed { id, position }), + ), + touch::Event::FingerLifted { id, .. } + | touch::Event::FingerLost { id, .. } => ( + event::Status::Captured, + Some(Message::FingerLifted { id }), + ), + }, + _ => (event::Status::Ignored, None), + } + } + + fn draw( + &self, + _state: &Self::State, + _theme: &Theme, + bounds: Rectangle, + _cursor: Cursor, + ) -> Vec<Geometry> { + let fingerweb = self.cache.draw(bounds.size(), |frame| { + if self.fingers.len() < 2 { + return; + } + + // Collect tuples of (id, point); + let mut zones: Vec<(u64, Point)> = + self.fingers.iter().map(|(id, pt)| (id.0, *pt)).collect(); + + // Sort by ID + zones.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap()); + + // Generate sorted list of points + let vpoints: Vec<(f64, f64)> = zones + .iter() + .map(|(_, p)| (f64::from(p.x), f64::from(p.y))) + .collect(); + + let diagram: voronator::VoronoiDiagram< + voronator::delaunator::Point, + > = voronator::VoronoiDiagram::from_tuple( + &(0.0, 0.0), + &(700.0, 700.0), + &vpoints, + ) + .expect("Generate Voronoi diagram"); + + for (cell, zone) in diagram.cells().iter().zip(zones) { + let mut builder = canvas::path::Builder::new(); + + for (index, p) in cell.points().iter().enumerate() { + let p = Point::new(p.x as f32, p.y as f32); + + match index { + 0 => builder.move_to(p), + _ => builder.line_to(p), + } + } + + let path = builder.build(); + + let color_r = (10 % zone.0) as f32 / 20.0; + let color_g = (10 % (zone.0 + 8)) as f32 / 20.0; + let color_b = (10 % (zone.0 + 3)) as f32 / 20.0; + + frame.fill( + &path, + Color { + r: color_r, + g: color_g, + b: color_b, + a: 1.0, + }, + ); + + frame.stroke( + &path, + Stroke { + style: stroke::Style::Solid(Color::BLACK), + width: 3.0, + ..Stroke::default() + }, + ); + } + }); + + vec![fingerweb] + } +} diff --git a/examples/scrollable/src/main.rs b/examples/scrollable/src/main.rs index b7b3dedc..6eba34e2 100644 --- a/examples/scrollable/src/main.rs +++ b/examples/scrollable/src/main.rs @@ -14,9 +14,15 @@ struct ScrollableDemo { variants: Vec<Variant>, } +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +enum ThemeType { + Light, + Dark, +} + #[derive(Debug, Clone)] enum Message { - ThemeChanged(Theme), + ThemeChanged(ThemeType), ScrollToTop(usize), ScrollToBottom(usize), Scrolled(usize, f32), @@ -45,7 +51,10 @@ impl Application for ScrollableDemo { fn update(&mut self, message: Message) -> Command<Message> { match message { Message::ThemeChanged(theme) => { - self.theme = theme; + self.theme = match theme { + ThemeType::Light => Theme::Light, + ThemeType::Dark => Theme::Dark, + }; Command::none() } @@ -78,17 +87,15 @@ impl Application for ScrollableDemo { } fn view(&self) -> Element<Message> { - let ScrollableDemo { - theme, variants, .. - } = self; + let ScrollableDemo { variants, .. } = self; - let choose_theme = [Theme::Light, Theme::Dark].iter().fold( + let choose_theme = [ThemeType::Light, ThemeType::Dark].iter().fold( column!["Choose a theme:"].spacing(10), |column, option| { column.push(radio( format!("{:?}", option), *option, - Some(*theme), + Some(*option), Message::ThemeChanged, )) }, @@ -198,7 +205,7 @@ impl Application for ScrollableDemo { } fn theme(&self) -> Theme { - self.theme + self.theme.clone() } } diff --git a/examples/solar_system/src/main.rs b/examples/solar_system/src/main.rs index c59d73a8..56787a99 100644 --- a/examples/solar_system/src/main.rs +++ b/examples/solar_system/src/main.rs @@ -11,7 +11,9 @@ use iced::executor; use iced::theme::{self, Theme}; use iced::time; use iced::widget::canvas; -use iced::widget::canvas::{Cursor, Path, Stroke}; +use iced::widget::canvas::gradient::{self, Gradient}; +use iced::widget::canvas::stroke::{self, Stroke}; +use iced::widget::canvas::{Cursor, Path}; use iced::window; use iced::{ Application, Color, Command, Element, Length, Point, Rectangle, Settings, @@ -37,9 +39,9 @@ enum Message { } impl Application for SolarSystem { + type Executor = executor::Default; type Message = Message; type Theme = Theme; - type Executor = executor::Default; type Flags = (); fn new(_flags: ()) -> (Self, Command<Message>) { @@ -65,10 +67,6 @@ impl Application for SolarSystem { Command::none() } - fn subscription(&self) -> Subscription<Message> { - time::every(std::time::Duration::from_millis(10)).map(Message::Tick) - } - fn view(&self) -> Element<Message> { canvas(&self.state) .width(Length::Fill) @@ -86,6 +84,10 @@ impl Application for SolarSystem { text_color: Color::WHITE, }) } + + fn subscription(&self) -> Subscription<Message> { + time::every(time::Duration::from_millis(10)).map(Message::Tick) + } } #[derive(Debug)] @@ -178,8 +180,10 @@ impl<Message> canvas::Program<Message> for State { frame.stroke( &orbit, Stroke { + style: stroke::Style::Solid(Color::from_rgba8( + 0, 153, 255, 0.1, + )), width: 1.0, - color: Color::from_rgba8(0, 153, 255, 0.1), line_dash: canvas::LineDash { offset: 0, segments: &[3.0, 6.0], @@ -198,15 +202,18 @@ impl<Message> canvas::Program<Message> for State { frame.translate(Vector::new(Self::ORBIT_RADIUS, 0.0)); let earth = Path::circle(Point::ORIGIN, Self::EARTH_RADIUS); - let shadow = Path::rectangle( - Point::new(0.0, -Self::EARTH_RADIUS), - Size::new( - Self::EARTH_RADIUS * 4.0, - Self::EARTH_RADIUS * 2.0, - ), - ); - frame.fill(&earth, Color::from_rgb8(0x6B, 0x93, 0xD6)); + let earth_fill = + Gradient::linear(gradient::Position::Absolute { + start: Point::new(-Self::EARTH_RADIUS, 0.0), + end: Point::new(Self::EARTH_RADIUS, 0.0), + }) + .add_stop(0.2, Color::from_rgb(0.15, 0.50, 1.0)) + .add_stop(0.8, Color::from_rgb(0.0, 0.20, 0.47)) + .build() + .expect("Build Earth fill gradient"); + + frame.fill(&earth, earth_fill); frame.with_save(|frame| { frame.rotate(rotation * 10.0); @@ -215,14 +222,6 @@ impl<Message> canvas::Program<Message> for State { let moon = Path::circle(Point::ORIGIN, Self::MOON_RADIUS); frame.fill(&moon, Color::WHITE); }); - - frame.fill( - &shadow, - Color { - a: 0.7, - ..Color::BLACK - }, - ); }); }); diff --git a/examples/styling/src/main.rs b/examples/styling/src/main.rs index cda53e87..e16860ad 100644 --- a/examples/styling/src/main.rs +++ b/examples/styling/src/main.rs @@ -1,9 +1,10 @@ +use iced::theme::{self, Theme}; use iced::widget::{ button, checkbox, column, container, horizontal_rule, progress_bar, radio, row, scrollable, slider, text, text_input, toggler, vertical_rule, vertical_space, }; -use iced::{Alignment, Element, Length, Sandbox, Settings, Theme}; +use iced::{Alignment, Color, Element, Length, Sandbox, Settings}; pub fn main() -> iced::Result { Styling::run(Settings::default()) @@ -18,9 +19,16 @@ struct Styling { toggler_value: bool, } +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +enum ThemeType { + Light, + Dark, + Custom, +} + #[derive(Debug, Clone)] enum Message { - ThemeChanged(Theme), + ThemeChanged(ThemeType), InputChanged(String), ButtonPressed, SliderChanged(f32), @@ -41,7 +49,19 @@ impl Sandbox for Styling { fn update(&mut self, message: Message) { match message { - Message::ThemeChanged(theme) => self.theme = theme, + Message::ThemeChanged(theme) => { + self.theme = match theme { + ThemeType::Light => Theme::Light, + ThemeType::Dark => Theme::Dark, + ThemeType::Custom => Theme::custom(theme::Palette { + background: Color::from_rgb(1.0, 0.9, 1.0), + text: Color::BLACK, + primary: Color::from_rgb(0.5, 0.5, 0.0), + success: Color::from_rgb(0.0, 1.0, 0.0), + danger: Color::from_rgb(1.0, 0.0, 0.0), + }), + } + } Message::InputChanged(value) => self.input_value = value, Message::ButtonPressed => {} Message::SliderChanged(value) => self.slider_value = value, @@ -51,17 +71,24 @@ impl Sandbox for Styling { } fn view(&self) -> Element<Message> { - let choose_theme = [Theme::Light, Theme::Dark].iter().fold( - column![text("Choose a theme:")].spacing(10), - |column, theme| { - column.push(radio( - format!("{:?}", theme), - *theme, - Some(self.theme), - Message::ThemeChanged, - )) - }, - ); + let choose_theme = + [ThemeType::Light, ThemeType::Dark, ThemeType::Custom] + .iter() + .fold( + column![text("Choose a theme:")].spacing(10), + |column, theme| { + column.push(radio( + format!("{:?}", theme), + *theme, + Some(match self.theme { + Theme::Light => ThemeType::Light, + Theme::Dark => ThemeType::Dark, + Theme::Custom { .. } => ThemeType::Custom, + }), + Message::ThemeChanged, + )) + }, + ); let text_input = text_input( "Type something...", @@ -132,6 +159,6 @@ impl Sandbox for Styling { } fn theme(&self) -> Theme { - self.theme + self.theme.clone() } } diff --git a/examples/todos/Cargo.toml b/examples/todos/Cargo.toml index 2326ffc6..7ad4d558 100644 --- a/examples/todos/Cargo.toml +++ b/examples/todos/Cargo.toml @@ -9,7 +9,7 @@ publish = false iced = { path = "../..", features = ["async-std", "debug"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -lazy_static = "1.4" +once_cell = "1.15" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] async-std = "1.0" diff --git a/examples/todos/src/main.rs b/examples/todos/src/main.rs index bddc0e71..be48ae8c 100644 --- a/examples/todos/src/main.rs +++ b/examples/todos/src/main.rs @@ -11,12 +11,10 @@ use iced::window; use iced::{Application, Element}; use iced::{Color, Command, Font, Length, Settings, Subscription}; -use lazy_static::lazy_static; +use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; -lazy_static! { - static ref INPUT_ID: text_input::Id = text_input::Id::unique(); -} +static INPUT_ID: Lazy<text_input::Id> = Lazy::new(text_input::Id::unique); pub fn main() -> iced::Result { Todos::run(Settings { diff --git a/examples/websocket/Cargo.toml b/examples/websocket/Cargo.toml index 3981f699..c25f067b 100644 --- a/examples/websocket/Cargo.toml +++ b/examples/websocket/Cargo.toml @@ -9,7 +9,7 @@ publish = false iced = { path = "../..", features = ["tokio", "debug"] } iced_native = { path = "../../native" } iced_futures = { path = "../../futures" } -lazy_static = "1.4" +once_cell = "1.15" [dependencies.async-tungstenite] version = "0.16" diff --git a/examples/websocket/src/main.rs b/examples/websocket/src/main.rs index 3902e04c..ff2929da 100644 --- a/examples/websocket/src/main.rs +++ b/examples/websocket/src/main.rs @@ -8,6 +8,7 @@ use iced::widget::{ use iced::{ Application, Color, Command, Element, Length, Settings, Subscription, Theme, }; +use once_cell::sync::Lazy; pub fn main() -> iced::Result { WebSocket::run(Settings::default()) @@ -165,6 +166,4 @@ impl Default for State { } } -lazy_static::lazy_static! { - static ref MESSAGE_LOG: scrollable::Id = scrollable::Id::unique(); -} +static MESSAGE_LOG: Lazy<scrollable::Id> = Lazy::new(scrollable::Id::unique); |