diff options
author | 2021-12-05 20:03:07 +0800 | |
---|---|---|
committer | 2022-09-23 17:41:34 +0200 | |
commit | 79d045cfe1c22cda9cfa6a359322360b0ca97cd8 (patch) | |
tree | 4e42b44e980bcaf55591e6553429d959b8f1a696 | |
parent | 7420ea7a6b80663cad178c1238c5b756232a087f (diff) | |
download | iced-79d045cfe1c22cda9cfa6a359322360b0ca97cd8.tar.gz iced-79d045cfe1c22cda9cfa6a359322360b0ca97cd8.tar.bz2 iced-79d045cfe1c22cda9cfa6a359322360b0ca97cd8.zip |
Implement sierpinski-triangle example
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | examples/README.md | 1 | ||||
-rw-r--r-- | examples/sierpinski_triangle/Cargo.toml | 10 | ||||
-rw-r--r-- | examples/sierpinski_triangle/README.md | 16 | ||||
-rw-r--r-- | examples/sierpinski_triangle/src/main.rs | 193 |
5 files changed, 221 insertions, 0 deletions
@@ -78,6 +78,7 @@ members = [ "examples/progress_bar", "examples/qr_code", "examples/scrollable", + "examples/sierpinski_triangle", "examples/solar_system", "examples/stopwatch", "examples/styling", diff --git a/examples/README.md b/examples/README.md index 2b4919df..bb15dc2e 100644 --- a/examples/README.md +++ b/examples/README.md @@ -100,6 +100,7 @@ A bunch of simpler examples exist: - [`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. +- [`sierpinski_triangle`](sierpinski_triangle), a [sierpiński triangle](https://en.wikipedia.org/wiki/Sierpi%C5%84ski_triangle) Emulator, use `Canvas` and `Slider`. - [`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. diff --git a/examples/sierpinski_triangle/Cargo.toml b/examples/sierpinski_triangle/Cargo.toml new file mode 100644 index 00000000..39d45f64 --- /dev/null +++ b/examples/sierpinski_triangle/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "sierpinski_triangle" +version = "0.1.0" +authors = ["xkenmon <xkenmon@gmail.com>"] +edition = "2018" +publish = false + +[dependencies] +iced = { path = "../..", features = ["canvas", "debug"] } +rand = "0.8.4" diff --git a/examples/sierpinski_triangle/README.md b/examples/sierpinski_triangle/README.md new file mode 100644 index 00000000..9fd18257 --- /dev/null +++ b/examples/sierpinski_triangle/README.md @@ -0,0 +1,16 @@ +## Sierpinski Triangle Emulator + +A simple [Sierpiński triangle](https://en.wikipedia.org/wiki/Sierpi%C5%84ski_triangle) Emulator, use canvas and slider. + +Left-click add fixed point, right-click remove fixed point. + +<div align="center"> + <a href="https://gfycat.com/flippantrectangularechidna"> + <img src="https://thumbs.gfycat.com/FlippantRectangularEchidna-size_restricted.gif"> + </a> +</div> + +You can run with cargo: +``` +cargo run --package sierpinski_triangle +```
\ No newline at end of file diff --git a/examples/sierpinski_triangle/src/main.rs b/examples/sierpinski_triangle/src/main.rs new file mode 100644 index 00000000..1d25d171 --- /dev/null +++ b/examples/sierpinski_triangle/src/main.rs @@ -0,0 +1,193 @@ +use std::fmt::Debug; + +use iced::executor; +use iced::widget::canvas::event::{self, Event}; +use iced::widget::canvas::{self, Canvas}; +use iced::widget::{column, row, slider, text}; +use iced::{ + Application, Color, Command, Length, Point, Rectangle, Settings, Size, + Theme, +}; + +use rand::Rng; + +fn main() -> iced::Result { + SierpinskiEmulator::run(Settings { + antialiasing: true, + ..Settings::default() + }) +} + +#[derive(Debug)] +struct SierpinskiEmulator { + graph: SierpinskiGraph, +} + +#[derive(Debug, Clone)] +pub enum Message { + IterationSet(i32), + PointAdded(Point), + PointRemoved, +} + +impl Application for SierpinskiEmulator { + type Executor = executor::Default; + type Message = Message; + type Theme = Theme; + type Flags = (); + + fn new(_flags: Self::Flags) -> (Self, iced::Command<Self::Message>) { + let emulator = SierpinskiEmulator { + graph: SierpinskiGraph::new(), + }; + (emulator, Command::none()) + } + + fn title(&self) -> String { + "Sierpinski Triangle Emulator".to_string() + } + + fn update( + &mut self, + message: Self::Message, + ) -> iced::Command<Self::Message> { + match message { + Message::IterationSet(cur_iter) => { + self.graph.iteration = cur_iter; + } + Message::PointAdded(point) => { + self.graph.fix_points.push(point); + self.graph.random_points.clear(); + } + Message::PointRemoved => { + self.graph.fix_points.pop(); + self.graph.random_points.clear(); + } + } + + self.graph.redraw(); + + Command::none() + } + + fn view(&self) -> iced::Element<'_, Self::Message> { + column![ + Canvas::new(&self.graph) + .width(Length::Fill) + .height(Length::Fill), + row![ + text(format!("Iteration: {:?}", self.graph.iteration)), + slider(0..=10000, self.graph.iteration, Message::IterationSet) + .width(Length::Fill) + ] + .padding(10) + .spacing(20), + ] + .width(Length::Fill) + .align_items(iced::Alignment::Center) + .into() + } +} + +#[derive(Default, Debug)] +struct SierpinskiGraph { + iteration: i32, + fix_points: Vec<Point>, + random_points: Vec<Point>, + cache: canvas::Cache, +} + +impl canvas::Program<Message> for SierpinskiGraph { + type State = (); + + fn update( + &self, + _state: &mut Self::State, + event: Event, + bounds: Rectangle, + cursor: canvas::Cursor, + ) -> (event::Status, Option<Message>) { + let cursor_position = + if let Some(position) = cursor.position_in(&bounds) { + position + } else { + return (event::Status::Ignored, None); + }; + + match event { + Event::Mouse(mouse_event) => { + let message = match mouse_event { + iced::mouse::Event::ButtonPressed( + iced::mouse::Button::Left, + ) => Some(Message::PointAdded(cursor_position)), + iced::mouse::Event::ButtonPressed( + iced::mouse::Button::Right, + ) => Some(Message::PointRemoved), + _ => None, + }; + (event::Status::Captured, message) + } + _ => (event::Status::Ignored, None), + } + } + + fn draw( + &self, + _state: &Self::State, + _theme: &Theme, + bounds: Rectangle, + _cursor: canvas::Cursor, + ) -> Vec<canvas::Geometry> { + let geom = self.cache.draw(bounds.size(), |frame| { + frame.stroke( + &canvas::Path::rectangle(Point::ORIGIN, frame.size()), + canvas::Stroke::default(), + ); + + if self.fix_points.is_empty() { + return; + } + + let mut last = None; + + for _ in 0..self.iteration { + let p = self.gen_rand_point(last); + let path = canvas::Path::rectangle(p, Size::new(1_f32, 1_f32)); + + frame.stroke(&path, canvas::Stroke::default()); + + last = Some(p); + } + + self.fix_points.iter().for_each(|p| { + let path = canvas::Path::circle(*p, 5.0); + frame.fill(&path, Color::from_rgb8(0x12, 0x93, 0xD8)); + }); + }); + + vec![geom] + } +} + +impl SierpinskiGraph { + fn new() -> SierpinskiGraph { + SierpinskiGraph::default() + } + + fn redraw(&mut self) { + self.cache.clear(); + } + + fn gen_rand_point(&self, last: Option<Point>) -> Point { + let dest_point_idx = + rand::thread_rng().gen_range(0..self.fix_points.len()); + + let dest_point = self.fix_points[dest_point_idx]; + let cur_point = last.or_else(|| Some(self.fix_points[0])).unwrap(); + + Point::new( + (dest_point.x + cur_point.x) / 2_f32, + (dest_point.y + cur_point.y) / 2_f32, + ) + } +} |