diff --git a/examples/beats.rs b/examples/beats.rs new file mode 100644 index 00000000..18846e3b --- /dev/null +++ b/examples/beats.rs @@ -0,0 +1,43 @@ +extern crate turtle; +use turtle::{Point, Turtle}; + +fn main() { + let mut turtle = Turtle::new(); + let init_pos: Point = turtle.position(); + + let _radius_outer = 250.0; + turtle.set_pen_color("#fe0000"); + turtle.pen_up(); + turtle.go_to((init_pos.x - (_radius_outer), init_pos.y)); + turtle.pen_down(); + turtle.set_fill_color("#fe0000"); + turtle.begin_fill(); + turtle.arc(_radius_outer, None, None); + turtle.end_fill(); + + let _radius_mid = 115.0; + turtle.pen_up(); + turtle.go_to((init_pos.x.round() - (_radius_mid), init_pos.y)); + turtle.pen_down(); + turtle.set_fill_color("white"); + turtle.set_pen_color("white"); + turtle.begin_fill(); + turtle.arc(_radius_mid, None, None); + turtle.end_fill(); + + let _radius_inner = 60.0; + turtle.pen_up(); + turtle.go_to((init_pos.x - (_radius_inner), init_pos.y)); + turtle.pen_down(); + turtle.set_fill_color("#fe0000"); + turtle.begin_fill(); + turtle.arc(_radius_inner, None, None); + turtle.end_fill(); + + let _size = 25.0; + turtle.pen_up(); + turtle.go_to((init_pos.x - (_radius_mid) + _size - 2.0, init_pos.y)); + turtle.pen_down(); + turtle.set_pen_size(_size); + turtle.forward(250.0); +} diff --git a/examples/dragon_circle.rs b/examples/dragon_circle.rs new file mode 100644 index 00000000..c61c56a6 --- /dev/null +++ b/examples/dragon_circle.rs @@ -0,0 +1,29 @@ +extern crate turtle; +use turtle::Turtle; + +enum Turn { + Left, + Right, +} + +// Shameless copy from https://gist.github.com/fogleman/006e4069348fa163b8ae + +fn turn(i: i64) -> Turn { + let left = (((i & -i) << 1) & i) != 0; + if left { + return Turn::Left; + } else { + return Turn::Right; + } +} + +fn main() { + let mut turtle = Turtle::new(); + turtle.set_speed("instant"); + for i in 1..100000 { + match turn(i) { + Turn::Left => turtle.arc(-4.0, Some(90.0), Some(36)), + Turn::Right => turtle.arc(4.0, Some(90.0), Some(36)), + } + } +} diff --git a/src/async_turtle.rs b/src/async_turtle.rs index 27a7227c..74db9ee2 100644 --- a/src/async_turtle.rs +++ b/src/async_turtle.rs @@ -1,11 +1,12 @@ +use std::f64::consts::PI; use std::fmt::Debug; use tokio::time; -use crate::radians::{self, Radians}; use crate::ipc_protocol::{ProtocolClient, RotationDirection}; +use crate::radians::{self, Radians}; use crate::renderer_server::TurtleId; -use crate::{Turtle, Color, Point, Speed}; +use crate::{Color, Point, Speed, Turtle}; /// Any distance value (positive or negative) pub type Distance = f64; @@ -58,8 +59,7 @@ impl AsyncTurtle { // of many programs that use the turtle crate. crate::start(); - let client = ProtocolClient::new().await - .expect("unable to create renderer client"); + let client = ProtocolClient::new().await.expect("unable to create renderer client"); Self::with_client(client).await } @@ -68,7 +68,7 @@ impl AsyncTurtle { let id = client.create_turtle().await; let angle_unit = AngleUnit::Degrees; - Self {client, id, angle_unit} + Self { client, id, angle_unit } } pub async fn forward(&mut self, distance: Distance) { @@ -87,7 +87,9 @@ impl AsyncTurtle { pub async fn left(&mut self, angle: Angle) { let angle = self.angle_unit.to_radians(angle); - self.client.rotate_in_place(self.id, angle, RotationDirection::Counterclockwise).await + self.client + .rotate_in_place(self.id, angle, RotationDirection::Counterclockwise) + .await } pub async fn wait(&mut self, secs: f64) { @@ -101,6 +103,27 @@ impl AsyncTurtle { time::delay_for(time::Duration::from_millis((secs * 1000.0) as u64)).await } + pub async fn arc(&mut self, radius: Distance, extent: Option, steps: Option) { + let angle = extent.unwrap_or(360.0); + + // Arc Length = radius * angle in radians + let angle_rad = 2.0 * PI * angle.abs() / 360.0; + let arc_length = radius.abs() * angle_rad; + + let step_count = steps.unwrap_or(((arc_length / 4.0).round() + 1.0) as i32); + let step_length = arc_length / step_count as f64; + let step_angle = angle / step_count as f64; + + for _ in 0..step_count { + self.forward(step_length).await; + if radius < 0.0 { + self.left(step_angle).await; + } else { + self.right(step_angle).await; + } + } + } + pub fn into_sync(self) -> Turtle { self.into() } @@ -122,13 +145,13 @@ impl AsyncTurtle { } pub async fn set_x(&mut self, x: f64) { - let Point {x: _, y} = self.position().await; - self.go_to(Point {x, y}).await + let Point { x: _, y } = self.position().await; + self.go_to(Point { x, y }).await } pub async fn set_y(&mut self, y: f64) { - let Point {x, y: _} = self.position().await; - self.go_to(Point {x, y}).await + let Point { x, y: _ } = self.position().await; + self.go_to(Point { x, y }).await } pub async fn home(&mut self) { @@ -155,7 +178,9 @@ impl AsyncTurtle { // Formula from: https://stackoverflow.com/a/24234924/551904 let angle = angle - radians::TWO_PI * ((angle + radians::PI) / radians::TWO_PI).floor(); - self.client.rotate_in_place(self.id, angle, RotationDirection::Counterclockwise).await + self.client + .rotate_in_place(self.id, angle, RotationDirection::Counterclockwise) + .await } pub fn is_using_degrees(&self) -> bool { @@ -291,13 +316,15 @@ impl AsyncTurtle { angle }; - self.client.rotate_in_place(self.id, angle, RotationDirection::Counterclockwise).await + self.client + .rotate_in_place(self.id, angle, RotationDirection::Counterclockwise) + .await } pub async fn wait_for_click(&mut self) { use crate::{ + event::{MouseButton::LeftButton, PressedState::Pressed}, Event::MouseButton, - event::{PressedState::Pressed, MouseButton::LeftButton}, }; loop { diff --git a/src/turtle.rs b/src/turtle.rs index eeede870..065ea46e 100644 --- a/src/turtle.rs +++ b/src/turtle.rs @@ -1,8 +1,8 @@ use std::fmt::Debug; -use crate::{Color, Point, Speed, Distance, Angle}; use crate::async_turtle::AsyncTurtle; use crate::sync_runtime::block_on; +use crate::{Angle, Color, Distance, Point, Speed}; /// A turtle with a pen attached to its tail /// @@ -26,7 +26,7 @@ impl Default for Turtle { impl From for Turtle { fn from(turtle: AsyncTurtle) -> Self { - Self {turtle} + Self { turtle } } } @@ -199,6 +199,46 @@ impl Turtle { block_on(self.turtle.wait(secs)) } + /// Instructs the turtle to draw an arc by the given angle for a given radius in the given step count from the current position of the turtle. + /// + /// The `radius` parameter is a floation point number that represents the radius of the arc + /// that you want to draw. If a negative `radius` is passed its absolute value is + /// considered for drawing the arc. + /// + /// The `extent` parameter is a floating point number that represents how much you want the + /// turtle to rotate. If the `extent` parameter is `None` then it is defaulted to 360. If + /// a negative `extent` is passed the arc is drawn in the opposite direction. + /// + /// the `steps` parameter is an integer that represent over how many steps you want the turtle to draw. If the `steps` parameter + /// is `None` then it is defaulted to the arc_length / 4.0. + /// + /// #Example + /// + /// ```rust + /// # use turtle::*; + /// # let mut turtle = Turtle::new(); + /// // Turtle will draw an arc of 90 in a clockwise direction + /// turtle.arc(100.0, Some(90.0), None); + /// + /// // Turtle will draw an arc of 90 in a anti-clockwise direction + /// turtle.arc(-100.0, Some(90.0), None); + /// + /// // Turtle will draw an arc of 90 in a clockwise direction + /// turtle.arc(-100.0, Some(-90.0), None); + /// + /// //Turtle will draw an arc of 90 in a anti-clockwise direction + /// turtle.arc(-100.0, Some(90.0), None); + /// + /// // Turtle will draw an arc of 90 in a clockwise direction in 10 steps + /// turtle.arc(100.0, Some(90.0), Some(10)); + /// + /// // Turtle will draw an arc of 90 in a clockwise direction in 100 steps + /// turtle.arc(100.0, Some(90.0), Some(100)); + /// ``` + pub fn arc(&mut self, radius: Distance, extent: Option, steps: Option) { + block_on(self.turtle.arc(radius, extent, steps)) + } + pub(crate) fn into_async(self) -> AsyncTurtle { self.turtle } @@ -964,7 +1004,9 @@ mod tests { } #[test] - #[should_panic(expected = "Invalid color: Color { red: NaN, green: 0.0, blue: 0.0, alpha: 0.0 }. See the color module documentation for more information.")] + #[should_panic( + expected = "Invalid color: Color { red: NaN, green: 0.0, blue: 0.0, alpha: 0.0 }. See the color module documentation for more information." + )] fn rejects_invalid_pen_color() { let mut turtle = Turtle::new(); turtle.set_pen_color(Color { @@ -976,7 +1018,9 @@ mod tests { } #[test] - #[should_panic(expected = "Invalid color: Color { red: NaN, green: 0.0, blue: 0.0, alpha: 0.0 }. See the color module documentation for more information.")] + #[should_panic( + expected = "Invalid color: Color { red: NaN, green: 0.0, blue: 0.0, alpha: 0.0 }. See the color module documentation for more information." + )] fn rejects_invalid_fill_color() { let mut turtle = Turtle::new(); turtle.set_fill_color(Color {