turtle 1.0.0-rc.3

Learn the Rust language by creating animated drawings!
Documentation
use std::cell::RefCell;
use std::fmt::Debug;
use std::rc::Rc;
use std::path::Path;

use crate::state::DrawingState;
use crate::turtle_window::TurtleWindow;
use crate::{Color, Event, Point};

/// Represents a size
///
/// A `Size` can be converted from either a tuple or array. These forms are often more ergonomic
/// than using the `Size` struct on its own. The [`set_size()`](struct.Drawing.html#method.set_size)
/// method accepts either form (without needing to use `into()`). See that method's documentation
/// for more information.
///
/// ```rust
/// # use turtle::Size;
/// assert_eq!(Size {width: 640, height: 480}, (640, 480).into());
/// assert_eq!(Size {width: 640, height: 480}, [640, 480].into());
/// ```
///
/// You can access the `width` and `height` fields directly on any `Size` struct.
///
/// ```rust
/// # use turtle::Size;
/// let size: Size = (800, 600).into();
/// assert_eq!(size.width, 800);
/// assert_eq!(size.height, 600);
/// ```
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Size {
    /// The width in pixels
    pub width: u32,
    /// The height in pixels
    pub height: u32,
}

impl From<(u32, u32)> for Size {
    fn from(size: (u32, u32)) -> Self {
        Self {
            width: size.0,
            height: size.1,
        }
    }
}

impl From<[u32; 2]> for Size {
    fn from(size: [u32; 2]) -> Self {
        Self {
            width: size[0],
            height: size[1],
        }
    }
}

/// Represents the drawing that the turtle is creating
///
/// This struct is usually accessed using the [`drawing()`](struct.Turtle.html#method.drawing)
/// and [`drawing_mut()`](struct.Turtle.html#method.drawing_mut) methods on the
/// [`Turtle` struct](struct.Turtle.html).
pub struct Drawing {
    window: Rc<RefCell<TurtleWindow>>,
}

impl Drawing {
    pub(crate) fn with_window(window: Rc<RefCell<TurtleWindow>>) -> Self {
        Self { window }
    }

    /// Returns the title of the drawing
    ///
    /// ```rust
    /// # use turtle::*;
    /// # let mut turtle = Turtle::new();
    /// turtle.drawing_mut().set_title("Hello, world!");
    /// assert_eq!(&*turtle.drawing().title(), "Hello, world!");
    /// ```
    pub fn title(&self) -> String {
        self.window.borrow().fetch_drawing().title
    }

    /// Sets the title of the drawing to the given text
    ///
    /// # Example
    ///
    /// ```rust,no_run
    /// use turtle::Turtle;
    ///
    /// fn main() {
    ///     let mut turtle = Turtle::new();
    ///     turtle.drawing_mut().set_title("My Fancy Title! - Yay!");
    /// }
    /// ```
    ///
    /// This will produce the following:
    ///
    /// ![turtle set title](https://github.com/sunjay/turtle/raw/9240f8890d1032a0033ec5c5338a10ffa942dc21/docs/assets/images/docs/changed_title.png)
    pub fn set_title(&mut self, title: &str) {
        self.window
            .borrow_mut()
            .with_drawing_mut(|drawing| drawing.title = title.to_owned());
    }

    /// Returns the color of the background.
    ///
    /// ```rust
    /// # use turtle::*;
    /// # let mut turtle = Turtle::new();
    /// turtle.drawing_mut().set_background_color("purple");
    /// assert_eq!(turtle.drawing().background_color(), "purple".into());
    /// ```
    ///
    /// See the [`color` module](color/index.html) for more information about colors.
    pub fn background_color(&self) -> Color {
        self.window.borrow().fetch_drawing().background
    }

    /// Sets the color of the background to the given color.
    ///
    /// Any type that can be converted into a color can be passed into this function.
    /// See the [`color` module](color/index.html) for more information.
    ///
    /// # Example
    ///
    /// ```rust,no_run
    /// use turtle::Turtle;
    ///
    /// fn main() {
    ///     let mut turtle = Turtle::new();
    ///     turtle.drawing_mut().set_background_color("orange");
    /// }
    /// ```
    ///
    /// This will produce the following:
    ///
    /// ![turtle background](https://github.com/sunjay/turtle/raw/9240f8890d1032a0033ec5c5338a10ffa942dc21/docs/assets/images/docs/orange_background.png)
    pub fn set_background_color<C: Into<Color> + Copy + Debug>(&mut self, color: C) {
        let bg_color = color.into();
        assert!(
            bg_color.is_valid(),
            "Invalid color: {:?}. See the color module documentation for more information.",
            color
        );
        self.window.borrow_mut().with_drawing_mut(|drawing| drawing.background = bg_color);
    }

    /// Returns the center of the drawing
    ///
    /// ```rust
    /// # use turtle::*;
    /// let mut turtle = Turtle::new();
    /// assert_eq!(turtle.drawing().center(), Point {x: 0.0, y: 0.0});
    /// turtle.drawing_mut().set_center([4.0, 3.0]);
    /// assert_eq!(turtle.drawing().center(), Point {x: 4.0, y: 3.0});
    /// ```
    pub fn center(&self) -> Point {
        self.window.borrow().fetch_drawing().center
    }

    /// Sets the center of the drawing to the given point. See the [`Point` struct](struct.Point.html)
    /// documentation for more information.
    ///
    /// The center represents the offset from the center of the viewport at which to draw the
    /// drawing. The default center is (0, 0) which means that the drawing is centered at the
    /// middle of the viewport.
    ///
    /// Use this method to move the canvas that the turtle is drawing on.
    ///
    /// # Example
    ///
    /// ```rust,no_run
    /// use turtle::Turtle;
    ///
    /// fn main() {
    ///     let mut turtle = Turtle::new();
    ///
    ///     for _ in 0..360 {
    ///         // Move forward three steps
    ///         turtle.forward(3.0);
    ///         // Rotate to the right (clockwise) by 1 degree
    ///         turtle.right(1.0);
    ///     }
    ///
    ///     turtle.wait_for_click();
    ///     turtle.drawing_mut().set_center([50.0, 100.0]);
    /// }
    /// ```
    ///
    /// This will produce the following:
    ///
    /// ![turtle center middle](https://github.com/sunjay/turtle/raw/9240f8890d1032a0033ec5c5338a10ffa942dc21/docs/assets/images/docs/circle.png)
    ///
    /// Once you click on the window:
    ///
    /// ![turtle center offset](https://github.com/sunjay/turtle/raw/9240f8890d1032a0033ec5c5338a10ffa942dc21/docs/assets/images/docs/circle_offset_center.png)
    pub fn set_center<P: Into<Point>>(&mut self, center: P) {
        let center = center.into();
        if !center.is_finite() {
            return;
        }
        self.window.borrow_mut().with_drawing_mut(|drawing| drawing.center = center);
    }

    /// Resets the center of the drawing back to its initial value
    ///
    /// ```rust
    /// # use turtle::*;
    /// let mut turtle = Turtle::new();
    /// let default_center = turtle.drawing().center();
    /// turtle.drawing_mut().set_center([400.0, -30.0]);
    /// assert_ne!(turtle.drawing().center(), default_center);
    /// turtle.drawing_mut().reset_center();
    /// assert_eq!(turtle.drawing().center(), default_center);
    /// ```
    pub fn reset_center(&mut self) {
        let default = DrawingState::default();
        self.set_center(default.center);
    }

    /// Returns the size of the drawing
    ///
    /// ```rust
    /// # use turtle::*;
    /// # let mut turtle = Turtle::new();
    /// # assert_eq!(turtle.drawing().size(), Size {width: 800, height: 600});
    /// // 1080p is 1920x1080
    /// turtle.drawing_mut().set_size((1920, 1080));
    /// let size = turtle.drawing().size();
    /// assert_eq!(size.width, 1920);
    /// assert_eq!(size.height, 1080);
    /// ```
    pub fn size(&self) -> Size {
        let drawing = self.window.borrow().fetch_drawing();
        Size {
            width: drawing.width,
            height: drawing.height,
        }
    }

    /// Sets the size of the drawing to the given width and height.
    ///
    /// You can specify the size as any value that can be converted into a
    /// [`Size` struct](struct.Size.html). That means that any of the following would work:
    ///
    /// ```rust
    /// # use turtle::Turtle;
    /// # let mut turtle = Turtle::new();
    /// // These are all equivalent to each other
    /// turtle.drawing_mut().set_size((640, 480));
    /// # assert_eq!(turtle.drawing().size(), Size {width: 640, height: 480});
    /// turtle.drawing_mut().set_size([640, 480]);
    /// # assert_eq!(turtle.drawing().size(), Size {width: 640, height: 480});
    ///
    /// // It recommended that you use the options above instead of the following
    /// use turtle::Size; // Size must be imported
    /// turtle.drawing_mut().set_size(Size {width: 640, height: 480});
    /// # assert_eq!(turtle.drawing().size(), Size {width: 640, height: 480});
    /// ```
    ///
    /// # Example
    ///
    /// ```rust,no_run
    /// use turtle::Turtle;
    ///
    /// fn main() {
    ///     let mut turtle = Turtle::new();
    ///
    ///     for _ in 0..360 {
    ///         // Move forward three steps
    ///         turtle.forward(3.0);
    ///         // Rotate to the right (clockwise) by 1 degree
    ///         turtle.right(1.0);
    ///     }
    ///
    ///     turtle.wait_for_click();
    ///     turtle.drawing_mut().set_size((300, 300));
    /// }
    /// ```
    ///
    /// This will produce the following:
    ///
    /// ![turtle default size](https://github.com/sunjay/turtle/raw/9240f8890d1032a0033ec5c5338a10ffa942dc21/docs/assets/images/docs/circle.png)
    ///
    /// Once you click on the window:
    ///
    /// ![turtle small drawing](https://github.com/sunjay/turtle/raw/9240f8890d1032a0033ec5c5338a10ffa942dc21/docs/assets/images/docs/small_drawing.png)
    ///
    /// Notice that the center of the drawing stays the same. To control that, see
    /// [`set_center()`](struct.Drawing.html#method.set_center).
    pub fn set_size<S: Into<Size>>(&mut self, size: S) {
        let size = size.into();
        self.window.borrow_mut().with_drawing_mut(|drawing| {
            drawing.width = size.width;
            drawing.height = size.height;
        });
    }

    /// Resets the size of the drawing back to its initial value
    ///
    /// ```rust
    /// # use turtle::*;
    /// let mut turtle = Turtle::new();
    /// let default_size = turtle.drawing().size();
    ///
    /// turtle.drawing_mut().set_size([920, 680]);
    /// assert_ne!(turtle.drawing().size(), default_size);
    ///
    /// turtle.drawing_mut().reset_size();
    /// assert_eq!(turtle.drawing().size(), default_size);
    /// ```
    pub fn reset_size(&mut self) {
        let default = DrawingState::default();
        self.set_size((default.width, default.height));
    }

    /// Returns true if the drawing is currently maximized.
    ///
    /// Note: Even if you set the drawing to the width and height of the current display, it won't
    /// be maximized unless [`maximize()`](struct.Drawing.html#method.maximize) is called.
    ///
    /// ```rust
    /// # use turtle::*;
    /// let mut turtle = Turtle::new();
    /// assert_eq!(turtle.drawing().is_maximized(), false);
    ///
    /// turtle.drawing_mut().maximize();
    /// assert_eq!(turtle.drawing().is_maximized(), true);
    ///
    /// turtle.drawing_mut().unmaximize();
    /// assert_eq!(turtle.drawing().is_maximized(), false);
    ///
    /// // Calling the same method again doesn't change the result
    /// turtle.drawing_mut().unmaximize();
    /// assert_eq!(turtle.drawing().is_maximized(), false);
    /// ```
    ///
    /// # Unstable
    ///
    /// **This method is currently unstable and unreliable.**
    /// ([GitHub Issue](https://github.com/sunjay/turtle/issues/49))
    ///
    /// Unfortunately, we cannot currently detect when the window is maximized using the maximize
    /// button on the window. This method is reliable until that button is pressed. Since there is
    /// no way to tell when that is, treat the value returned from this method as unreliable and
    /// potentially inaccurate.
    #[cfg(feature = "unstable")]
    pub fn is_maximized(&self) -> bool {
        self.window.borrow().fetch_drawing().maximized
    }

    /// Maximizes the size of the drawing so that it takes up the entire display.
    ///
    /// If the drawing is already maximized, this method does nothing.
    ///
    /// ```rust
    /// # use turtle::*;
    /// # let mut turtle = Turtle::new();
    /// turtle.drawing_mut().maximize();
    /// assert_eq!(turtle.drawing().is_maximized(), true);
    /// // Calling this method again does nothing
    /// turtle.drawing_mut().maximize();
    /// assert_eq!(turtle.drawing().is_maximized(), true);
    /// ```
    ///
    /// # Unstable
    ///
    /// **This method is currently unstable and unreliable.**
    /// ([GitHub Issue](https://github.com/sunjay/turtle/issues/49))
    ///
    /// Unfortunately, we cannot currently detect when the window is maximized using the maximize
    /// button on the window. This method is reliable until that button is pressed. Since there is
    /// no way to tell when that is, treat the value returned from this method as unreliable and
    /// potentially inaccurate.
    ///
    /// It is usually okay to use this method right when the turtle is created, but don't rely on
    /// it after that because by then the user may have pressed the maximize button on the window.
    #[cfg(feature = "unstable")]
    pub fn maximize(&mut self) {
        self.window.borrow_mut().with_drawing_mut(|drawing| drawing.maximized = true);
    }

    /// Returns the size of the drawing to its value before it was maximized
    ///
    /// If the drawing is already unmaximized, this method does nothing.
    ///
    ///
    /// ```rust
    /// # use turtle::*;
    /// # let mut turtle = Turtle::new();
    /// turtle.drawing_mut().maximize();
    /// assert_eq!(turtle.drawing().is_maximized(), true);
    /// turtle.drawing_mut().unmaximize();
    /// assert_eq!(turtle.drawing().is_maximized(), false);
    /// // Calling this again does nothing because the drawing is already unmaximized
    /// turtle.drawing_mut().unmaximize();
    /// assert_eq!(turtle.drawing().is_maximized(), false);
    /// ```
    ///
    /// # Unstable
    ///
    /// **This method is currently unstable and unreliable.**
    /// ([GitHub Issue](https://github.com/sunjay/turtle/issues/49))
    ///
    /// Unfortunately, we cannot currently detect when the window is maximized using the maximize
    /// button on the window. This method is reliable until that button is pressed. Since there is
    /// no way to tell when that is, treat the value returned from this method as unreliable and
    /// potentially inaccurate.
    #[cfg(feature = "unstable")]
    pub fn unmaximize(&mut self) {
        self.window.borrow_mut().with_drawing_mut(|drawing| drawing.maximized = false);
    }

    /// Returns true if the drawing is currently full screen.
    ///
    /// ```rust
    /// # use turtle::*;
    /// let mut turtle = Turtle::new();
    /// assert_eq!(turtle.drawing().is_fullscreen(), false);
    ///
    /// turtle.drawing_mut().enter_fullscreen();
    /// assert_eq!(turtle.drawing().is_fullscreen(), true);
    ///
    /// turtle.drawing_mut().exit_fullscreen();
    /// assert_eq!(turtle.drawing().is_fullscreen(), false);
    ///
    /// // Calling the same method again doesn't change the result
    /// turtle.drawing_mut().exit_fullscreen();
    /// assert_eq!(turtle.drawing().is_fullscreen(), false);
    /// ```
    pub fn is_fullscreen(&self) -> bool {
        self.window.borrow().fetch_drawing().fullscreen
    }

    /// Makes the drawing take up the entire screen hiding everything else that would have been
    /// shown on screen otherwise.
    ///
    /// If the drawing is already fullscreen, this method does nothing.
    ///
    /// ```rust
    /// # use turtle::*;
    /// # let mut turtle = Turtle::new();
    /// turtle.drawing_mut().enter_fullscreen();
    /// assert_eq!(turtle.drawing().is_fullscreen(), true);
    /// // Calling this method again does nothing
    /// turtle.drawing_mut().enter_fullscreen();
    /// assert_eq!(turtle.drawing().is_fullscreen(), true);
    /// ```
    pub fn enter_fullscreen(&mut self) {
        self.window.borrow_mut().with_drawing_mut(|drawing| drawing.fullscreen = true);
    }

    /// Returns the size of the drawing to its value before it became fullscreen.
    ///
    /// If the drawing is already not fullscreen, this method does nothing.
    ///
    ///
    /// ```rust
    /// # use turtle::*;
    /// # let mut turtle = Turtle::new();
    /// turtle.drawing_mut().enter_fullscreen();
    /// assert_eq!(turtle.drawing().is_fullscreen(), true);
    /// turtle.drawing_mut().exit_fullscreen();
    /// assert_eq!(turtle.drawing().is_fullscreen(), false);
    /// // Calling this again does nothing because the drawing is already not fullscreen
    /// turtle.drawing_mut().exit_fullscreen();
    /// assert_eq!(turtle.drawing().is_fullscreen(), false);
    /// ```
    pub fn exit_fullscreen(&mut self) {
        self.window.borrow_mut().with_drawing_mut(|drawing| drawing.fullscreen = false);
    }

    /// Returns the next event (if any). Returns `None` if there are no events to be processed at
    /// the current moment. This **does not** mean that there will never be events later on as the
    /// application continues to run.
    ///
    /// See the [`Event` enum](event/enum.Event.html) for the complete list of events that you can
    /// handle in your applications.
    ///
    /// # Example
    ///
    /// To use this advanced method, you need to create what is known as an "event loop". An "event
    /// loop" is any loop that handles the events generated by the application. The reason that it
    /// is important to create a loop like this is because events in Turtle are "polled". That
    /// means that every time an event happens, it is placed in a queue (a list) until you ask to
    /// look at it. If you do not check for events continuously, there is a chance that the events
    /// you ask for from `poll_event()` will be outdated.
    ///
    /// Even if you do not use every kind of event, you should aim to poll events using this method
    /// until there are none left to poll. If you do not poll events for a significant amount of
    /// time during your application, favor the events that come later as you poll since those will
    /// be the most recent. This can happen if you run many animations between loop iterations.
    ///
    /// See the [`examples/`](https://github.com/sunjay/turtle/raw/master/examples) directory in
    /// the source code of this library for more examples of how to use events.
    ///
    /// The following is an example of a basic event loop. Notice that it uses two loops. One to
    /// move the turtle continuously, and another to handle all the events available at a given
    /// moment. If it suits your purposes, you may also just use a single loop to handle events
    /// and move the turtle from within that loop. This example is of a more complex case where
    /// it really matters that the most recent information is taken into consideration before any
    /// further movements take place.
    ///
    /// ```rust,no_run
    /// use turtle::Turtle;
    /// use turtle::event::Key::{Left, Right};
    /// use turtle::Event::KeyPressed;
    ///
    /// fn main() {
    ///     let mut turtle = Turtle::new();
    ///
    ///     loop {
    ///         turtle.forward(1.0);
    ///
    ///         while let Some(event) = turtle.drawing_mut().poll_event() {
    ///             match event {
    ///                 KeyPressed(key) => match key {
    ///                     Left => {
    ///                         turtle.set_speed(8);
    ///                         for _ in 0..20 {
    ///                             turtle.forward(1.0);
    ///                             turtle.left(4.5);
    ///                         }
    ///                         turtle.set_speed(4);
    ///                     },
    ///                     Right => {
    ///                         turtle.set_speed(8);
    ///                         for _ in 0..20 {
    ///                             turtle.forward(1.0);
    ///                             turtle.right(4.5);
    ///                         }
    ///                         turtle.set_speed(4);
    ///                     },
    ///                     _ => {},
    ///                 },
    ///                 _ => {},
    ///             }
    ///         }
    ///     }
    /// }
    /// ```
    pub fn poll_event(&mut self) -> Option<Event> {
        self.window.borrow_mut().poll_event()
    }

    /// Saves the current drawings in SVG format at the location specified by `path`.
    ///
    /// ```rust
    /// use turtle::{Turtle, Color};
    ///
    /// fn main() {
    ///     let mut turtle = Turtle::new();
    ///     turtle.drawing_mut().set_background_color("pink");
    ///
    ///     for i in 0..36 {
    ///         let base_color: Color = if i % 2 == 0 {
    ///             "red".into()
    ///         } else {
    ///             "white".into()
    ///         };
    ///         turtle.set_fill_color(base_color.with_alpha(1.0 - i as f64 / 54.0));
    ///         turtle.begin_fill();
    ///         square(&mut turtle);
    ///         turtle.end_fill();
    ///         turtle.right(10.0);
    ///     }
    ///    turtle.hide();
    ///    turtle.drawing().save_svg("squares.svg");
    /// }
    ///
    /// fn square(turtle: &mut Turtle) {
    ///     for _ in 0..4 {
    ///         turtle.forward(200.0);
    ///         turtle.right(90.0);
    ///     }
    /// }
    /// ```
    ///
    /// This will produce the following image in the current directory under the name `squares.svg`:
    ///
    /// ![squares](https://raw.githubusercontent.com/sunjay/turtle/master/docs/assets/images/docs/squares.svg?sanitize=true)
    pub fn save_svg<P: AsRef<Path>>(&self, path: P) {
        self.window.borrow().save_svg(path)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    use crate::turtle::*;

    #[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."
    )]
    fn rejects_invalid_background_color() {
        let mut turtle = Turtle::new();
        turtle.drawing_mut().set_background_color(Color {
            red: ::std::f64::NAN,
            green: 0.0,
            blue: 0.0,
            alpha: 0.0,
        });
    }
}