<commit_before>use std::collections::vec_deque::{IntoIter, VecDeque};
use std::error;
use std::fmt::{self, Debug, Formatter};
use {Command, Error, Merger};

/// A record of commands.
///
/// The `Record` works mostly like a `Stack`, but it stores the commands
/// instead of returning them when undoing. This means it can roll the
/// receivers state backwards and forwards by using the undo and redo methods.
/// In addition, the `Record` has an internal state that is either clean or dirty.
/// A clean state means that the `Record` does not have any `Command`s to redo,
/// while a dirty state means that it does. The user can give the `Record` a function
/// that is called each time the state changes by using the `config` constructor.
///
/// # Examples
/// ```
/// use std::error::Error;
/// use undo::{Command, Record};
///
/// struct Add(char);
///
/// impl Command<String> for Add {
///     fn redo(&mut self, s: &mut String) -> Result<(), Box<Error>> {
///         s.push(self.0);
///         Ok(())
///     }
///
///     fn undo(&mut self, s: &mut String) -> Result<(), Box<Error>> {
///         self.0 = s.pop().ok_or("`String` is unexpectedly empty")?;
///         Ok(())
///     }
/// }
///
/// fn foo() -> Result<(), Box<Error>> {
///     let mut record = Record::default();
///
///     record.push(Add('a'))?;
///     record.push(Add('b'))?;
///     record.push(Add('c'))?;
///
///     assert_eq!(record.as_receiver(), "abc");
///
///     record.undo().unwrap()?;
///     record.undo().unwrap()?;
///     record.undo().unwrap()?;
///
///     assert_eq!(record.as_receiver(), "");
///
///     record.redo().unwrap()?;
///     record.redo().unwrap()?;
///     record.redo().unwrap()?;
///
///     assert_eq!(record.into_receiver(), "abc");
///
///     Ok(())
/// }
/// # foo().unwrap();
/// ```
#[derive(Default)]
pub struct Record<'a, R> {
    commands: VecDeque<Box<Command<R>>>,
    receiver: R,
    idx: usize,
    limit: Option<usize>,
    state_handle: Option<Box<FnMut(bool) + 'a>>,
}

impl<'a, R> Record<'a, R> {
    /// Returns a new `Record`.
    #[inline]
    pub fn new<T: Into<R>>(receiver: T) -> Record<'a, R> {
        Record {
            commands: VecDeque::new(),
            receiver: receiver.into(),
            idx: 0,
            limit: None,
            state_handle: None,
        }
    }

    /// Returns a configurator for a `Record`.
    ///
    /// # Examples
    /// ```
    /// # use std::error::Error;
    /// # use undo::{Command, Record};
    /// # struct Add(char);
    /// # impl Command<String> for Add {
    /// #     fn redo(&mut self, s: &mut String) -> Result<(), Box<Error>> {
    /// #         s.push(self.0);
    /// #         Ok(())
    /// #     }
    /// #     fn undo(&mut self, s: &mut String) -> Result<(), Box<Error>> {
    /// #         self.0 = s.pop().ok_or("`String` is unexpectedly empty")?;
    /// #         Ok(())
    /// #     }
    /// # }
    /// # fn foo() -> Result<(), Box<Error>> {
    /// let mut record = Record::config("")
    ///     .capacity(2)
    ///     .limit(2)
    ///     .create();
    ///
    /// record.push(Add('a'))?;
    /// record.push(Add('b'))?;
    /// record.push(Add('c'))?; // 'a' is removed from the record since limit is 2.
    ///
    /// assert_eq!(record.as_receiver(), "abc");
    ///
    /// record.undo().unwrap()?;
    /// record.undo().unwrap()?;
    /// assert!(record.undo().is_none());
    ///
    /// assert_eq!(record.into_receiver(), "a");
    /// # Ok(())
    /// # }
    /// # foo().unwrap();
    /// ```
    #[inline]
    pub fn config<T: Into<R>>(receiver: T) -> Config<'a, R> {
        Config {
            receiver: receiver.into(),
            capacity: 0,
            limit: None,
            state_handle: None,
        }
    }

    /// Returns the limit of the `Record`, or `None` if it has no limit.
    #[inline]
    pub fn limit(&self) -> Option<usize> {
        self.limit
    }

    /// Returns the number of commands in the `Record`.
    #[inline]
    pub fn len(&self) -> usize {
        self.commands.len()
    }

    /// Returns `true` if the `Record` is empty.
    #[inline]
    pub fn is_empty(&self) -> bool {
        self.commands.is_empty()
    }

    /// Returns `true` if the state of the stack is clean, `false` otherwise.
    #[inline]
    pub fn is_clean(&self) -> bool {
        self.idx == self.commands.len()
    }

    /// Returns `true` if the state of the stack is dirty, `false` otherwise.
    #[inline]
    pub fn is_dirty(&self) -> bool {
        !self.is_clean()
    }

    /// Returns a reference to the `receiver`.
    #[inline]
    pub fn as_receiver(&self) -> &R {
        &self.receiver
    }

    /// Consumes the `Record`, returning the `receiver`.
    #[inline]
    pub fn into_receiver(self) -> R {
        self.receiver
    }

    /// Pushes `cmd` to the top of the `Record` and executes its [`redo`] method.
    /// The command is merged with the previous top command if [`merge`] does not return `None`.
    ///
    /// All commands above the active one are removed from the stack and returned as an iterator.
    ///
    /// # Errors
    /// If an error occur when executing [`redo`] the error is returned together with the command,
    /// and the state of the stack is left unchanged.
    ///
    /// # Examples
    /// ```
    /// # use std::error::Error;
    /// # use undo::{Command, Record};
    /// # struct Add(char);
    /// # impl Command<String> for Add {
    /// #     fn redo(&mut self, s: &mut String) -> Result<(), Box<Error>> {
    /// #         s.push(self.0);
    /// #         Ok(())
    /// #     }
    /// #     fn undo(&mut self, s: &mut String) -> Result<(), Box<Error>> {
    /// #         self.0 = s.pop().ok_or("`String` is unexpectedly empty")?;
    /// #         Ok(())
    /// #     }
    /// # }
    /// # fn foo() -> Result<(), Box<Error>> {
    /// let mut record = Record::default();
    ///
    /// record.push(Add('a'))?;
    /// record.push(Add('b'))?;
    /// record.push(Add('c'))?;
    ///
    /// assert_eq!(record.as_receiver(), "abc");
    ///
    /// record.undo().unwrap()?;
    /// record.undo().unwrap()?;
    /// let mut bc = record.push(Add('e'))?;
    ///
    /// assert_eq!(record.into_receiver(), "ae");
    /// assert!(bc.next().is_some());
    /// assert!(bc.next().is_some());
    /// assert!(bc.next().is_none());
    /// # Ok(())
    /// # }
    /// # foo().unwrap();
    /// ```
    ///
    /// [`redo`]: trait.Command.html#tymethod.redo
    #[inline]
    pub fn push<C>(&mut self, mut cmd: C) -> Result<Commands<R>, Error<R>>
    where
        C: Command<R> + 'static,
        R: 'static,
    {
        let is_dirty = self.is_dirty();
        let len = self.idx;
        match cmd.redo(&mut self.receiver) {
            Ok(_) => {
                // Pop off all elements after len from record.
                let iter = self.commands.split_off(len).into_iter();
                debug_assert_eq!(len, self.len());

                match (cmd.id(), self.commands.back().and_then(|last| last.id())) {
                    (Some(id1), Some(id2)) if id1 == id2 => {
                        // Merge the command with the one on the top of the stack.
                        let cmd = Merger {
                            cmd1: self.commands.pop_back().unwrap(),
                            cmd2: Box::new(cmd),
                        };
                        self.commands.push_back(Box::new(cmd));
                    }
                    _ => {
                        match self.limit {
                            Some(limit) if len == limit => {
                                self.commands.pop_front();
                            }
                            _ => self.idx += 1,
                        }
                        self.commands.push_back(Box::new(cmd));
                    }
                }

                debug_assert_eq!(self.idx, self.len());
                // Record is always clean after a push, check if it was dirty before.
                if is_dirty {
                    if let Some(ref mut f) = self.state_handle {
                        f(true);
                    }
                }
                Ok(Commands(iter))
            }
            Err(e) => Err(Error(Box::new(cmd), e)),
        }
    }

    /// Calls the [`redo`] method for the active `Command` and sets the next one as the new
    /// active one.
    ///
    /// # Errors
    /// If an error occur when executing [`redo`] the
    /// error is returned and the state is left unchanged.
    ///
    /// [`redo`]: trait.Command.html#tymethod.redo
    #[inline]
    pub fn redo(&mut self) -> Option<Result<(), Box<error::Error>>> {
        if self.idx < self.commands.len() {
            let is_dirty = self.is_dirty();
            match self.commands[self.idx].redo(&mut self.receiver) {
                Ok(_) => {
                    self.idx += 1;
                    // Check if record went from dirty to clean.
                    if is_dirty && self.is_clean() {
                        if let Some(ref mut f) = self.state_handle {
                            f(true);
                        }
                    }
                    Some(Ok(()))
                }
                Err(e) => Some(Err(e)),
            }
        } else {
            None
        }
    }

    /// Calls the [`undo`] method for the active `Command` and sets the previous one as the
    /// new active one.
    ///
    /// # Errors
    /// If an error occur when executing [`undo`] the
    /// error is returned and the state is left unchanged.
    ///
    /// [`undo`]: trait.Command.html#tymethod.undo
    #[inline]
    pub fn undo(&mut self) -> Option<Result<(), Box<error::Error>>> {
        if self.idx > 0 {
            let is_clean = self.is_clean();
            match self.commands[self.idx - 1].undo(&mut self.receiver) {
                Ok(_) => {
                    self.idx -= 1;
                    // Check if record went from clean to dirty.
                    if is_clean && self.is_dirty() {
                        if let Some(ref mut f) = self.state_handle {
                            f(false);
                        }
                    }
                    Some(Ok(()))
                }
                Err(e) => Some(Err(e)),
            }
        } else {
            None
        }
    }
}

impl<'a, R> AsRef<R> for Record<'a, R> {
    #[inline]
    fn as_ref(&self) -> &R {
        self.as_receiver()
    }
}

impl<'a, R: Debug> Debug for Record<'a, R> {
    #[inline]
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        f.debug_struct("Record")
            .field("commands", &self.commands)
            .field("receiver", &self.receiver)
            .field("idx", &self.idx)
            .field("limit", &self.limit)
            .finish()
    }
}

/// Iterator over `Command`s.
#[derive(Debug)]
pub struct Commands<R>(IntoIter<Box<Command<R>>>);

impl<R> Iterator for Commands<R> {
    type Item = Box<Command<R>>;

    #[inline]
    fn next(&mut self) -> Option<Self::Item> {
        self.0.next()
    }
}

/// Configurator for `Record`.
pub struct Config<'a, R> {
    receiver: R,
    capacity: usize,
    limit: Option<usize>,
    state_handle: Option<Box<FnMut(bool) + 'a>>,
}

impl<'a, R> Config<'a, R> {
    /// Sets the specified [capacity] for the stack.
    ///
    /// [capacity]: https://doc.rust-lang.org/std/vec/struct.Vec.html#capacity-and-reallocation
    #[inline]
    pub fn capacity(mut self, capacity: usize) -> Config<'a, R> {
        self.capacity = capacity;
        self
    }

    /// Sets a limit on how many `Command`s can be stored in the stack.
    /// If this limit is reached it will start popping of commands at the bottom of the stack when
    /// pushing new commands on to the stack. No limit is set by default which means it may grow
    /// indefinitely.
    #[inline]
    pub fn limit(mut self, limit: usize) -> Config<'a, R> {
        self.limit = if limit == 0 { None } else { Some(limit) };
        self
    }

    /// Sets what should happen when the state changes.
    /// By default the `Record` does nothing when the state changes.
    ///
    /// # Examples
    /// ```
    /// # use std::cell::Cell;
    /// # use std::error::Error;
    /// # use undo::{Command, Record};
    /// # struct Add(char);
    /// # impl Command<String> for Add {
    /// #     fn redo(&mut self, s: &mut String) -> Result<(), Box<Error>> {
    /// #         s.push(self.0);
    /// #         Ok(())
    /// #     }
    /// #     fn undo(&mut self, s: &mut String) -> Result<(), Box<Error>> {
    /// #         self.0 = s.pop().ok_or("`String` is unexpectedly empty")?;
    /// #         Ok(())
    /// #     }
    /// # }
    /// # fn foo() -> Result<(), Box<Error>> {
    /// let x = Cell::new(0);
    /// let mut record = Record::config("")
    ///     .state_handle(|is_clean| {
    ///         if is_clean {
    ///             x.set(1);
    ///         } else {
    ///             x.set(2);
    ///         }
    ///     })
    ///     .create();
    ///
    /// assert_eq!(x.get(), 0);
    /// record.push(Add('a'))?;
    /// assert_eq!(x.get(), 0);
    /// record.undo().unwrap()?;
    /// assert_eq!(x.get(), 2);
    /// record.redo().unwrap()?;
    /// assert_eq!(x.get(), 1);
    /// # Ok(())
    /// # }
    /// # foo().unwrap();
    /// ```
    #[inline]
    pub fn state_handle<F>(mut self, f: F) -> Config<'a, R>
    where
        F: FnMut(bool) + 'a,
    {
        self.state_handle = Some(Box::new(f));
        self
    }

    /// Creates the `Record`.
    #[inline]
    pub fn create(self) -> Record<'a, R> {
        Record {
            commands: VecDeque::with_capacity(self.capacity),
            receiver: self.receiver,
            idx: 0,
            limit: self.limit,
            state_handle: self.state_handle,
        }
    }
}

impl<'a, R: Debug> Debug for Config<'a, R> {
    #[inline]
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        f.debug_struct("Config")
            .field("receiver", &self.receiver)
            .field("capacity", &self.capacity)
            .field("limit", &self.limit)
            .finish()
    }
}
<commit_msg>Add Send and Sync to Record<commit_after>use std::collections::vec_deque::{IntoIter, VecDeque};
use std::error;
use std::fmt::{self, Debug, Formatter};
use {Command, Error, Merger};

/// A record of commands.
///
/// The `Record` works mostly like a `Stack`, but it stores the commands
/// instead of returning them when undoing. This means it can roll the
/// receivers state backwards and forwards by using the undo and redo methods.
/// In addition, the `Record` has an internal state that is either clean or dirty.
/// A clean state means that the `Record` does not have any `Command`s to redo,
/// while a dirty state means that it does. The user can give the `Record` a function
/// that is called each time the state changes by using the `config` constructor.
///
/// # Examples
/// ```
/// use std::error::Error;
/// use undo::{Command, Record};
///
/// struct Add(char);
///
/// impl Command<String> for Add {
///     fn redo(&mut self, s: &mut String) -> Result<(), Box<Error>> {
///         s.push(self.0);
///         Ok(())
///     }
///
///     fn undo(&mut self, s: &mut String) -> Result<(), Box<Error>> {
///         self.0 = s.pop().ok_or("`String` is unexpectedly empty")?;
///         Ok(())
///     }
/// }
///
/// fn foo() -> Result<(), Box<Error>> {
///     let mut record = Record::default();
///
///     record.push(Add('a'))?;
///     record.push(Add('b'))?;
///     record.push(Add('c'))?;
///
///     assert_eq!(record.as_receiver(), "abc");
///
///     record.undo().unwrap()?;
///     record.undo().unwrap()?;
///     record.undo().unwrap()?;
///
///     assert_eq!(record.as_receiver(), "");
///
///     record.redo().unwrap()?;
///     record.redo().unwrap()?;
///     record.redo().unwrap()?;
///
///     assert_eq!(record.into_receiver(), "abc");
///
///     Ok(())
/// }
/// # foo().unwrap();
/// ```
#[derive(Default)]
pub struct Record<'a, R> {
    commands: VecDeque<Box<Command<R>>>,
    receiver: R,
    idx: usize,
    limit: Option<usize>,
    state_handle: Option<Box<FnMut(bool) + Send + Sync + 'a>>,
}

impl<'a, R> Record<'a, R> {
    /// Returns a new `Record`.
    #[inline]
    pub fn new<T: Into<R>>(receiver: T) -> Record<'a, R> {
        Record {
            commands: VecDeque::new(),
            receiver: receiver.into(),
            idx: 0,
            limit: None,
            state_handle: None,
        }
    }

    /// Returns a configurator for a `Record`.
    ///
    /// # Examples
    /// ```
    /// # use std::error::Error;
    /// # use undo::{Command, Record};
    /// # struct Add(char);
    /// # impl Command<String> for Add {
    /// #     fn redo(&mut self, s: &mut String) -> Result<(), Box<Error>> {
    /// #         s.push(self.0);
    /// #         Ok(())
    /// #     }
    /// #     fn undo(&mut self, s: &mut String) -> Result<(), Box<Error>> {
    /// #         self.0 = s.pop().ok_or("`String` is unexpectedly empty")?;
    /// #         Ok(())
    /// #     }
    /// # }
    /// # fn foo() -> Result<(), Box<Error>> {
    /// let mut record = Record::configure("")
    ///     .capacity(2)
    ///     .limit(2)
    ///     .create();
    ///
    /// record.push(Add('a'))?;
    /// record.push(Add('b'))?;
    /// record.push(Add('c'))?; // 'a' is removed from the record since limit is 2.
    ///
    /// assert_eq!(record.as_receiver(), "abc");
    ///
    /// record.undo().unwrap()?;
    /// record.undo().unwrap()?;
    /// assert!(record.undo().is_none());
    ///
    /// assert_eq!(record.into_receiver(), "a");
    /// # Ok(())
    /// # }
    /// # foo().unwrap();
    /// ```
    #[inline]
    pub fn configure<T: Into<R>>(receiver: T) -> Config<'a, R> {
        Config {
            receiver: receiver.into(),
            capacity: 0,
            limit: None,
            state_handle: None,
        }
    }

    /// Returns the limit of the `Record`, or `None` if it has no limit.
    #[inline]
    pub fn limit(&self) -> Option<usize> {
        self.limit
    }

    /// Returns the number of commands in the `Record`.
    #[inline]
    pub fn len(&self) -> usize {
        self.commands.len()
    }

    /// Returns `true` if the `Record` is empty.
    #[inline]
    pub fn is_empty(&self) -> bool {
        self.commands.is_empty()
    }

    /// Returns `true` if the state of the stack is clean, `false` otherwise.
    #[inline]
    pub fn is_clean(&self) -> bool {
        self.idx == self.commands.len()
    }

    /// Returns `true` if the state of the stack is dirty, `false` otherwise.
    #[inline]
    pub fn is_dirty(&self) -> bool {
        !self.is_clean()
    }

    /// Returns a reference to the `receiver`.
    #[inline]
    pub fn as_receiver(&self) -> &R {
        &self.receiver
    }

    /// Consumes the `Record`, returning the `receiver`.
    #[inline]
    pub fn into_receiver(self) -> R {
        self.receiver
    }

    /// Pushes `cmd` to the top of the `Record` and executes its [`redo`] method.
    /// The command is merged with the previous top command if [`merge`] does not return `None`.
    ///
    /// All commands above the active one are removed from the stack and returned as an iterator.
    ///
    /// # Errors
    /// If an error occur when executing [`redo`] the error is returned together with the command,
    /// and the state of the stack is left unchanged.
    ///
    /// # Examples
    /// ```
    /// # use std::error::Error;
    /// # use undo::{Command, Record};
    /// # struct Add(char);
    /// # impl Command<String> for Add {
    /// #     fn redo(&mut self, s: &mut String) -> Result<(), Box<Error>> {
    /// #         s.push(self.0);
    /// #         Ok(())
    /// #     }
    /// #     fn undo(&mut self, s: &mut String) -> Result<(), Box<Error>> {
    /// #         self.0 = s.pop().ok_or("`String` is unexpectedly empty")?;
    /// #         Ok(())
    /// #     }
    /// # }
    /// # fn foo() -> Result<(), Box<Error>> {
    /// let mut record = Record::default();
    ///
    /// record.push(Add('a'))?;
    /// record.push(Add('b'))?;
    /// record.push(Add('c'))?;
    ///
    /// assert_eq!(record.as_receiver(), "abc");
    ///
    /// record.undo().unwrap()?;
    /// record.undo().unwrap()?;
    /// let mut bc = record.push(Add('e'))?;
    ///
    /// assert_eq!(record.into_receiver(), "ae");
    /// assert!(bc.next().is_some());
    /// assert!(bc.next().is_some());
    /// assert!(bc.next().is_none());
    /// # Ok(())
    /// # }
    /// # foo().unwrap();
    /// ```
    ///
    /// [`redo`]: trait.Command.html#tymethod.redo
    #[inline]
    pub fn push<C>(&mut self, mut cmd: C) -> Result<Commands<R>, Error<R>>
    where
        C: Command<R> + 'static,
        R: 'static,
    {
        let is_dirty = self.is_dirty();
        let len = self.idx;
        match cmd.redo(&mut self.receiver) {
            Ok(_) => {
                // Pop off all elements after len from record.
                let iter = self.commands.split_off(len).into_iter();
                debug_assert_eq!(len, self.len());

                match (cmd.id(), self.commands.back().and_then(|last| last.id())) {
                    (Some(id1), Some(id2)) if id1 == id2 => {
                        // Merge the command with the one on the top of the stack.
                        let cmd = Merger {
                            cmd1: self.commands.pop_back().unwrap(),
                            cmd2: Box::new(cmd),
                        };
                        self.commands.push_back(Box::new(cmd));
                    }
                    _ => {
                        match self.limit {
                            Some(limit) if len == limit => {
                                self.commands.pop_front();
                            }
                            _ => self.idx += 1,
                        }
                        self.commands.push_back(Box::new(cmd));
                    }
                }

                debug_assert_eq!(self.idx, self.len());
                // Record is always clean after a push, check if it was dirty before.
                if is_dirty {
                    if let Some(ref mut f) = self.state_handle {
                        f(true);
                    }
                }
                Ok(Commands(iter))
            }
            Err(e) => Err(Error(Box::new(cmd), e)),
        }
    }

    /// Calls the [`redo`] method for the active `Command` and sets the next one as the new
    /// active one.
    ///
    /// # Errors
    /// If an error occur when executing [`redo`] the
    /// error is returned and the state is left unchanged.
    ///
    /// [`redo`]: trait.Command.html#tymethod.redo
    #[inline]
    pub fn redo(&mut self) -> Option<Result<(), Box<error::Error>>> {
        if self.idx < self.commands.len() {
            let is_dirty = self.is_dirty();
            match self.commands[self.idx].redo(&mut self.receiver) {
                Ok(_) => {
                    self.idx += 1;
                    // Check if record went from dirty to clean.
                    if is_dirty && self.is_clean() {
                        if let Some(ref mut f) = self.state_handle {
                            f(true);
                        }
                    }
                    Some(Ok(()))
                }
                Err(e) => Some(Err(e)),
            }
        } else {
            None
        }
    }

    /// Calls the [`undo`] method for the active `Command` and sets the previous one as the
    /// new active one.
    ///
    /// # Errors
    /// If an error occur when executing [`undo`] the
    /// error is returned and the state is left unchanged.
    ///
    /// [`undo`]: trait.Command.html#tymethod.undo
    #[inline]
    pub fn undo(&mut self) -> Option<Result<(), Box<error::Error>>> {
        if self.idx > 0 {
            let is_clean = self.is_clean();
            match self.commands[self.idx - 1].undo(&mut self.receiver) {
                Ok(_) => {
                    self.idx -= 1;
                    // Check if record went from clean to dirty.
                    if is_clean && self.is_dirty() {
                        if let Some(ref mut f) = self.state_handle {
                            f(false);
                        }
                    }
                    Some(Ok(()))
                }
                Err(e) => Some(Err(e)),
            }
        } else {
            None
        }
    }
}

impl<'a, R> AsRef<R> for Record<'a, R> {
    #[inline]
    fn as_ref(&self) -> &R {
        self.as_receiver()
    }
}

impl<'a, R: Debug> Debug for Record<'a, R> {
    #[inline]
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        f.debug_struct("Record")
            .field("commands", &self.commands)
            .field("receiver", &self.receiver)
            .field("idx", &self.idx)
            .field("limit", &self.limit)
            .finish()
    }
}

/// Iterator over `Command`s.
#[derive(Debug)]
pub struct Commands<R>(IntoIter<Box<Command<R>>>);

impl<R> Iterator for Commands<R> {
    type Item = Box<Command<R>>;

    #[inline]
    fn next(&mut self) -> Option<Self::Item> {
        self.0.next()
    }
}

/// Configurator for `Record`.
pub struct Config<'a, R> {
    receiver: R,
    capacity: usize,
    limit: Option<usize>,
    state_handle: Option<Box<FnMut(bool) + Send + Sync + 'a>>,
}

impl<'a, R> Config<'a, R> {
    /// Sets the specified [capacity] for the stack.
    ///
    /// [capacity]: https://doc.rust-lang.org/std/vec/struct.Vec.html#capacity-and-reallocation
    #[inline]
    pub fn capacity(mut self, capacity: usize) -> Config<'a, R> {
        self.capacity = capacity;
        self
    }

    /// Sets a limit on how many `Command`s can be stored in the stack.
    /// If this limit is reached it will start popping of commands at the bottom of the stack when
    /// pushing new commands on to the stack. No limit is set by default which means it may grow
    /// indefinitely.
    #[inline]
    pub fn limit(mut self, limit: usize) -> Config<'a, R> {
        self.limit = if limit == 0 { None } else { Some(limit) };
        self
    }

    /// Sets what should happen when the state changes.
    /// By default the `Record` does nothing when the state changes.
    ///
    /// # Examples
    /// ```
    /// # use std::error::Error;
    /// # use undo::Record;
    /// # fn foo() -> Result<(), Box<Error>> {
    /// let mut x = 0;
    /// Record::<String>::configure("")
    ///     .state_handle(|is_clean| {
    ///         if is_clean {
    ///             x = 1;
    ///         } else {
    ///             x = 2;
    ///         }
    ///     })
    ///     .create();
    /// # Ok(())
    /// # }
    /// # foo().unwrap();
    /// ```
    #[inline]
    pub fn state_handle<F>(mut self, f: F) -> Config<'a, R>
    where
        F: FnMut(bool) + Send + Sync + 'a,
    {
        self.state_handle = Some(Box::new(f));
        self
    }

    /// Creates the `Record`.
    #[inline]
    pub fn create(self) -> Record<'a, R> {
        Record {
            commands: VecDeque::with_capacity(self.capacity),
            receiver: self.receiver,
            idx: 0,
            limit: self.limit,
            state_handle: self.state_handle,
        }
    }
}

impl<'a, R: Debug> Debug for Config<'a, R> {
    #[inline]
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        f.debug_struct("Config")
            .field("receiver", &self.receiver)
            .field("capacity", &self.capacity)
            .field("limit", &self.limit)
            .finish()
    }
}
<|endoftext|>


<commit_before>// Copyright 2018 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use crate::code_block::CodeBlock;
use crate::errors::{CompilationError, Error};
use crate::EvalContext;
use json;
use regex::Regex;
use std;
use std::fs;
use std::path::{Path, PathBuf};

fn shared_object_name_from_crate_name(crate_name: &str) -> String {
    if cfg!(target_os = "macos") {
        format!("lib{}.dylib", crate_name)
    } else if cfg!(target_os = "windows") {
        format!("{}.dll", crate_name)
    } else {
        format!("lib{}.so", crate_name)
    }
}

fn create_dir(dir: &Path) -> Result<(), Error> {
    if let Err(err) = fs::create_dir_all(dir) {
        bail!("Error creating directory '{:?}': {}", dir, err);
    }
    Ok(())
}

fn write_file(dir: &Path, basename: &str, contents: &str) -> Result<(), Error> {
    create_dir(dir)?;
    let filename = dir.join(basename);
    if let Err(err) = fs::write(&filename, contents) {
        bail!("Error writing '{:?}': {}", filename, err);
    }
    Ok(())
}

pub(crate) struct Module {
    pub(crate) tmpdir: PathBuf,
    build_num: i32,
}

const CRATE_NAME: &str = "ctx";

impl Module {
    pub(crate) fn new(tmpdir: PathBuf) -> Result<Module, Error> {
        let module = Module {
            tmpdir,
            build_num: 0,
        };
        Ok(module)
    }

    pub(crate) fn deps_dir(&self) -> PathBuf {
        self.target_dir().join("debug").join("deps")
    }

    fn target_dir(&self) -> PathBuf {
        self.tmpdir.join("target")
    }

    fn so_path(&self) -> PathBuf {
        self.deps_dir()
            .join(shared_object_name_from_crate_name(CRATE_NAME))
    }

    fn src_dir(&self) -> PathBuf {
        self.tmpdir.join("src")
    }

    pub(crate) fn crate_dir(&self) -> &Path {
        &self.tmpdir
    }

    // Writes Cargo.toml. Should be called before compile.
    pub(crate) fn write_cargo_toml(&self, eval_context: &EvalContext) -> Result<(), Error> {
        write_file(
            self.crate_dir(),
            "Cargo.toml",
            &self.get_cargo_toml_contents(eval_context),
        )
    }

    pub(crate) fn compile(&mut self, code_block: &CodeBlock) -> Result<SoFile, Error> {
        write_file(&self.src_dir(), "lib.rs", &code_block.to_string())?;

        // Our compiler errors should all be in JSON format, but for errors from Cargo errors, we
        // need to add explicit matching for those errors that we expect we might see.
        lazy_static! {
            static ref KNOWN_NON_JSON_ERRORS: Regex =
                Regex::new("(error: no matching package named)").unwrap();
        }

        let mut command = std::process::Command::new("cargo");
        command
            .env("CARGO_TARGET_DIR", &self.target_dir())
            .arg("rustc")
            .arg("--message-format=json")
            .arg("--")
            .arg("-C")
            .arg("prefer-dynamic")
            .current_dir(self.crate_dir());
        let cargo_output = command.output()?;
        if !cargo_output.status.success() {
            let stderr = String::from_utf8_lossy(&cargo_output.stderr);
            let stdout = String::from_utf8_lossy(&cargo_output.stdout);
            let mut non_json_error = None;
            let errors: Vec<CompilationError> = stderr
                .lines()
                .chain(stdout.lines())
                .filter_map(|line| {
                    json::parse(&line)
                        .ok()
                        .and_then(|json| CompilationError::opt_new(json, code_block))
                        .or_else(|| {
                            if KNOWN_NON_JSON_ERRORS.is_match(line) {
                                non_json_error = Some(line);
                            }
                            None
                        })
                })
                .collect();
            if errors.is_empty() {
                if let Some(error) = non_json_error {
                    bail!(Error::Message(error.to_owned()));
                } else {
                    bail!(Error::Message(format!(
                        "Compilation failed, but no parsable errors were found. STDERR:\n\
                         {}\nSTDOUT:{}\n",
                        stderr, stdout
                    )));
                }
            } else {
                bail!(Error::CompilationErrors(errors));
            }
        }
        self.build_num += 1;
        let copied_so_file = self
            .deps_dir()
            .join(shared_object_name_from_crate_name(&format!(
                "code_{}",
                self.build_num
            )));
        // Every time we compile, the output file is the same. We need to
        // renamed it so that we have a unique filename, otherwise we wouldn't
        // be able to load the result of the next compilation. Also, on Windows,
        // a loaded dll gets locked, so we couldn't even compile a second time
        // if we didn't load a different file.
        if let Err(err) = fs::rename(self.so_path(), &copied_so_file) {
            bail!(
                "Error copying '{:?}' to '{:?}': {}",
                self.so_path(),
                copied_so_file,
                err
            );
        }
        Ok(SoFile {
            path: copied_so_file,
        })
    }

    fn get_cargo_toml_contents(&self, eval_context: &EvalContext) -> String {
        let crate_imports = eval_context.format_cargo_deps();
        format!(
            r#"
[package]
name = "{}"
version = "1.0.0"
edition = "2018"

[lib]
crate-type = ["cdylib"]

[profile.dev]
opt-level = {}
debug = false
rpath = true
lto = false
debug-assertions = true
codegen-units = 16
panic = 'unwind'
incremental = true
overflow-checks = true

[dependencies]
{}
"#,
            CRATE_NAME,
            eval_context.opt_level(),
            crate_imports
        )
    }
}

pub(crate) struct SoFile {
    pub(crate) path: PathBuf,
}
<commit_msg>Copy dll on Windows instead of renaming it, otherwise it seems that something subsequently tries to delete a file that isn't there and fails. Hopefully fixes #56<commit_after>// Copyright 2018 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use crate::code_block::CodeBlock;
use crate::errors::{CompilationError, Error};
use crate::EvalContext;
use json;
use regex::Regex;
use std;
use std::fs;
use std::path::{Path, PathBuf};

fn shared_object_name_from_crate_name(crate_name: &str) -> String {
    if cfg!(target_os = "macos") {
        format!("lib{}.dylib", crate_name)
    } else if cfg!(target_os = "windows") {
        format!("{}.dll", crate_name)
    } else {
        format!("lib{}.so", crate_name)
    }
}

fn create_dir(dir: &Path) -> Result<(), Error> {
    if let Err(err) = fs::create_dir_all(dir) {
        bail!("Error creating directory '{:?}': {}", dir, err);
    }
    Ok(())
}

fn write_file(dir: &Path, basename: &str, contents: &str) -> Result<(), Error> {
    create_dir(dir)?;
    let filename = dir.join(basename);
    if let Err(err) = fs::write(&filename, contents) {
        bail!("Error writing '{:?}': {}", filename, err);
    }
    Ok(())
}

/// On Mac, if we copy the dylib, we get intermittent failures where we end up
/// with the previous version of the file when we shouldn't. On windows, if
/// rename the file, we get errors subsequently when something (perhaps the
/// Windows linker) tries to delete the file that it expects to still be there.
/// On Linux either renaming or copying works, but renaming should be more
/// efficient, so we do that.
#[cfg(windows)]
fn rename_or_copy_so_file(src: &Path, dest: &Path) -> Result<(), Error> {
    if let Err(err) = fs::copy(src, dest) {
        bail!("Error copying '{:?}' to '{:?}': {}", src, dest, err);
    }
    Ok(())
}

#[cfg(not(windows))]
fn rename_or_copy_so_file(src: &Path, dest: &Path) -> Result<(), Error> {
    if let Err(err) = fs::rename(src, dest) {
        bail!("Error renaming '{:?}' to '{:?}': {}", src, dest, err);
    }
    Ok(())
}

pub(crate) struct Module {
    pub(crate) tmpdir: PathBuf,
    build_num: i32,
}

const CRATE_NAME: &str = "ctx";

impl Module {
    pub(crate) fn new(tmpdir: PathBuf) -> Result<Module, Error> {
        let module = Module {
            tmpdir,
            build_num: 0,
        };
        Ok(module)
    }

    pub(crate) fn deps_dir(&self) -> PathBuf {
        self.target_dir().join("debug").join("deps")
    }

    fn target_dir(&self) -> PathBuf {
        self.tmpdir.join("target")
    }

    fn so_path(&self) -> PathBuf {
        self.deps_dir()
            .join(shared_object_name_from_crate_name(CRATE_NAME))
    }

    fn src_dir(&self) -> PathBuf {
        self.tmpdir.join("src")
    }

    pub(crate) fn crate_dir(&self) -> &Path {
        &self.tmpdir
    }

    // Writes Cargo.toml. Should be called before compile.
    pub(crate) fn write_cargo_toml(&self, eval_context: &EvalContext) -> Result<(), Error> {
        write_file(
            self.crate_dir(),
            "Cargo.toml",
            &self.get_cargo_toml_contents(eval_context),
        )
    }

    pub(crate) fn compile(&mut self, code_block: &CodeBlock) -> Result<SoFile, Error> {
        write_file(&self.src_dir(), "lib.rs", &code_block.to_string())?;

        // Our compiler errors should all be in JSON format, but for errors from Cargo errors, we
        // need to add explicit matching for those errors that we expect we might see.
        lazy_static! {
            static ref KNOWN_NON_JSON_ERRORS: Regex =
                Regex::new("(error: no matching package named)").unwrap();
        }

        let mut command = std::process::Command::new("cargo");
        command
            .env("CARGO_TARGET_DIR", &self.target_dir())
            .arg("rustc")
            .arg("--message-format=json")
            .arg("--")
            .arg("-C")
            .arg("prefer-dynamic")
            .current_dir(self.crate_dir());
        let cargo_output = command.output()?;
        if !cargo_output.status.success() {
            let stderr = String::from_utf8_lossy(&cargo_output.stderr);
            let stdout = String::from_utf8_lossy(&cargo_output.stdout);
            let mut non_json_error = None;
            let errors: Vec<CompilationError> = stderr
                .lines()
                .chain(stdout.lines())
                .filter_map(|line| {
                    json::parse(&line)
                        .ok()
                        .and_then(|json| CompilationError::opt_new(json, code_block))
                        .or_else(|| {
                            if KNOWN_NON_JSON_ERRORS.is_match(line) {
                                non_json_error = Some(line);
                            }
                            None
                        })
                })
                .collect();
            if errors.is_empty() {
                if let Some(error) = non_json_error {
                    bail!(Error::Message(error.to_owned()));
                } else {
                    bail!(Error::Message(format!(
                        "Compilation failed, but no parsable errors were found. STDERR:\n\
                         {}\nSTDOUT:{}\n",
                        stderr, stdout
                    )));
                }
            } else {
                bail!(Error::CompilationErrors(errors));
            }
        }
        self.build_num += 1;
        let copied_so_file = self
            .deps_dir()
            .join(shared_object_name_from_crate_name(&format!(
                "code_{}",
                self.build_num
            )));
        // Every time we compile, the output file is the same. We need to
        // renamed it so that we have a unique filename, otherwise we wouldn't
        // be able to load the result of the next compilation. Also, on Windows,
        // a loaded dll gets locked, so we couldn't even compile a second time
        // if we didn't load a different file.
        rename_or_copy_so_file(&self.so_path(), &copied_so_file)?;
        Ok(SoFile {
            path: copied_so_file,
        })
    }

    fn get_cargo_toml_contents(&self, eval_context: &EvalContext) -> String {
        let crate_imports = eval_context.format_cargo_deps();
        format!(
            r#"
[package]
name = "{}"
version = "1.0.0"
edition = "2018"

[lib]
crate-type = ["cdylib"]

[profile.dev]
opt-level = {}
debug = false
rpath = true
lto = false
debug-assertions = true
codegen-units = 16
panic = 'unwind'
incremental = true
overflow-checks = true

[dependencies]
{}
"#,
            CRATE_NAME,
            eval_context.opt_level(),
            crate_imports
        )
    }
}

pub(crate) struct SoFile {
    pub(crate) path: PathBuf,
}
<|endoftext|>


<commit_before>// This file is part of sokoban-rs
// Copyright 2015 Sébastien Watteau
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::path::Path;
use sdl2::rect::Rect;
use sdl2::pixels::{Color, PixelFormatEnum};
use sdl2::render::{Renderer, Texture};
use sdl2_image::LoadTexture;
use sdl2_ttf::Font;

use super::game::{Level, Position, Direction};

/// The Drawer struct is responsible for drawing the game onto the screen.
pub struct Drawer<'a> {
    /// The underlying SDL renderer
    renderer: Renderer<'a>,
    /// The texture containing all the tiles
    texture: Texture,
    /// The font used to display text
    font: Font,
    /// The size of the screen in pixels
    screen_size: (u32, u32),
    /// The height of the status bar
    bar_height: u32,
    /// The color of the status bar
    bar_color: Color,
    /// The color of the text in the status bar
    bar_text_color: Color,
}

/// Represents a location for text in the status bar
enum StatusBarLocation {
    FlushLeft,
    FlushRight,
}

impl<'a> Drawer<'a> {
    /// Creates a new Drawer instance.
    pub fn new(renderer: Renderer<'a>) -> Drawer {
        let path = Path::new("assets/image/tileset.png");
        let texture = renderer.load_texture(path).unwrap();
        let font = Font::from_file(Path::new("assets/font/RujisHandwritingFontv.2.0.ttf"), 20).unwrap();
        let screen_size = renderer.window().unwrap().drawable_size();
        Drawer {
            renderer: renderer,
            texture: texture,
            font: font,
            screen_size: screen_size,
            bar_height: 32,
            bar_color: Color::RGBA(20, 20, 20, 255),
            bar_text_color: Color::RGBA(255, 192, 0, 255),
        }
    }

    /// Draws a level onto the screen.
    pub fn draw(&mut self, level: &Level) {
        // Draw a full-size image onto an off-screen buffer
        let fullsize = self.get_rendering_size(&level);
        let _ = self.renderer.render_target()
            .expect("Render targets are not supported")
            .create_and_set(PixelFormatEnum::RGBA8888, fullsize);

        self.draw_fullsize(level);

        // Copy onto the screen with appropriate scaling
        let final_rect = self.get_centered_image_rect(self.get_scaled_rendering_size(&level));
        let texture = self.renderer.render_target()
            .unwrap()
            .reset()
            .unwrap_or_else(|err| panic!("Could not reset to the default render target: {}", err))
            .unwrap_or_else(|| panic!("Could not get the offscreen texture"));

        self.renderer.clear();
        self.renderer.copy(&texture, Some(Rect::new_unwrap(0, 0, fullsize.0, fullsize.1)), final_rect);

        self.draw_status_bar(&level);

        self.renderer.present();
    }

    /// Draws a full-size image of the given level onto the current render target.
    fn draw_fullsize(&mut self, level: &Level) {
        let (cols, rows) = level.extents();
        self.renderer.set_draw_color(Color::RGB(0, 0, 0));
        self.renderer.clear();

        for j in (0..rows) {
            for i in (0..cols) {
                let pos = Position::new(j, i);
                let (x, y) = Tile::get_coordinates(&pos);

                // First draw the floor tiles
                if level.is_square(&pos) {
                    self.draw_tile(Tile::Square, x, y);
                } else {
                    self.draw_tile(Tile::Floor, x, y);
                }

                // Add the shadows
                let flags = get_shadow_flags(&level, &pos);
                for f in &[N_EDGE, S_EDGE, E_EDGE, W_EDGE, NE_CORNER, NW_CORNER, SE_CORNER, SW_CORNER] {
                    if flags.contains(*f) {
                        self.draw_tile(Tile::Shadow(*f), x, y);
                    }
                }

                // Draw the other items
                let z = y - Tile::offset();
                if level.is_wall(&pos) {
                    self.draw_tile(Tile::Wall, x, z);
                }
                if level.is_box(&pos) {
                    self.draw_tile(Tile::Rock, x, z);
                }
                if level.is_player(&pos) {
                    self.draw_tile(Tile::Player, x, z);
                }
            }
        }
    }

    /// Draws the status bar
    fn draw_status_bar(&mut self, level: &Level) {
        let prev_color = self.renderer.draw_color();
        self.renderer.set_draw_color(self.bar_color);
        let rect = Rect::new_unwrap(0, (self.screen_size.1 - self.bar_height) as i32, self.screen_size.0, self.bar_height);
        self.renderer.fill_rect(rect);
        self.renderer.set_draw_color(prev_color);

        // Draw the number of moves
        let s = format!("# moves: {}", level.get_steps());
        self.draw_status_text(&s, StatusBarLocation::FlushLeft);

        // Draw the level's title
        self.draw_status_text(level.title(), StatusBarLocation::FlushRight);
    }

    /// Draws text in the status bar
    fn draw_status_text(&mut self, text: &str, location: StatusBarLocation) {
        let surface = self.font.render_str_blended(text, self.bar_text_color).unwrap();
        let texture = self.renderer.create_texture_from_surface(&surface).unwrap();
        let margin = 4;
        let (w, h) = {
            let q = texture.query();
            (q.width, q.height)
        };
        let (x, y) = match location {
            StatusBarLocation::FlushLeft => {
                (margin as i32, (self.screen_size.1 - margin - h) as i32)
            },
            StatusBarLocation::FlushRight => {
                ((self.screen_size.0 - margin - w) as i32, (self.screen_size.1 - margin - h) as i32)
            },
        };
        self.renderer.copy(&texture, None, Some(Rect::new_unwrap(x, y, w, h)));
    }

    /// Draws a tile at the given coordinates.
    fn draw_tile(&mut self, tile: Tile, x: i32, y: i32) {
        let (col, row) = tile.location().unwrap_or_else(|| {
            panic!("No image for this tile: {:?}", tile);
        });
        let tile_rect = self.get_tile_rect(col, row);
        self.renderer.copy(&self.texture, tile_rect, Some(Rect::new_unwrap(x, y, Tile::width(), Tile::height())));
    }

    /// Returns the Rect of the tile located at the given row and column in the texture.
    fn get_tile_rect(&self, col: u32, row: u32) -> Option<Rect> {
        let x = (col * Tile::width()) as i32;
        let y = (row * Tile::height()) as i32;
        Some(Rect::new_unwrap(x, y, Tile::width(), Tile::height()))
    }

    /// Returns the full size needed to draw the given level.
    fn get_rendering_size(&self, level: &Level) -> (u32, u32) {
        let (w, h) = level.extents();
        let width = w as u32 * Tile::width();
        let height = if h > 0 {
            Tile::height() + (h - 1) as u32 * Tile::effective_height()
        } else {
            0
        };

        (width, height)
    }

    /// Returns the size of the drawing scaled to fit onto the screen.
    fn get_scaled_rendering_size(&self, level: &Level) -> (u32, u32) {
        let render_size = self.get_rendering_size(&level);
        let width_ratio = (self.screen_size.0 as f64) / (render_size.0 as f64);
        let h = self.screen_size.1 - self.bar_height;
        let height_ratio = (h as f64) / (render_size.1 as f64);
        let ratio = f64::min(1.0, f64::min(width_ratio, height_ratio));

        let scale = |sz: u32| {
            (ratio * (sz as f64)).floor() as u32
        };

        (scale(render_size.0), scale(render_size.1))
    }

    /// Returns the Rect of an image of given dimensions so that it's centered on the screen.
    fn get_centered_image_rect(&self, img_size: (u32, u32)) -> Option<Rect> {
        let x = (self.screen_size.0 - img_size.0) as i32 / 2;
        let y = (self.screen_size.1 - self.bar_height - img_size.1) as i32 / 2;
        Some(Rect::new_unwrap(x, y, img_size.0, img_size.1))
    }
}

/// Represents a kind of tile.
#[derive(Debug)]
enum Tile {
    /// Standard floor tile
    Floor,
    /// Wall tile
    Wall,
    /// Rock tile
    Rock,
    /// Target square tile
    Square,
    /// Player tile
    Player,
    /// Shadow tile
    Shadow(ShadowFlags),
}

impl Tile {
    /// Returns the location of the tile in the tileset texture.
    pub fn location(&self) -> Option<(u32, u32)> {
        match *self {
           Tile::Floor => Some((0, 0)),
           Tile::Wall => Some((0, 2)),
           Tile::Rock => Some((2, 0)),
           Tile::Square => Some((1, 0)),
           Tile::Player => Some((3, 0)),
           Tile::Shadow(N_EDGE) => Some((4, 0)),
           Tile::Shadow(S_EDGE) => Some((5, 0)),
           Tile::Shadow(E_EDGE) => Some((0, 1)),
           Tile::Shadow(W_EDGE) => Some((1, 1)),
           Tile::Shadow(NE_CORNER) => Some((2, 1)),
           Tile::Shadow(NW_CORNER) => Some((3, 1)),
           Tile::Shadow(SE_CORNER) => Some((4, 1)),
           Tile::Shadow(SW_CORNER) => Some((5, 1)),
           Tile::Shadow(ShadowFlags { .. }) => None,
        }
    }

    /// Returns the width of a tile.
    pub fn width() -> u32 {
        101
    }

    /// Returns the height of a tile.
    pub fn height() -> u32 {
        171
    }

    /// Returns the effective height of a tile (used for stacking)
    pub fn effective_height() -> u32 {
        83
    }

    /// Returns the offset need to draw items on the floor.
    pub fn offset() -> i32 {
        40
    }

    /// Returns the top-left corner coordinates of the tile corresponding
    /// to the given position.
    fn get_coordinates(pos: &Position) -> (i32, i32) {
        let x = Self::width() as i32 * pos.column();
        let y = Self::effective_height() as i32 * pos.row();
        (x, y)
    }
}

bitflags!(
    /// Represents the different kind of shadows that can be cast
    /// onto a floor tile.
    flags ShadowFlags: i32 {
        /// North edge
        const N_EDGE = 0x1,
        /// South edge
        const S_EDGE = 0x2,
        /// East edge
        const E_EDGE = 0x4,
        /// West edge
        const W_EDGE = 0x8,
        /// North East corner
        const NE_CORNER = 0x10,
        /// North West corner
        const NW_CORNER = 0x20,
        /// South East corner
        const SE_CORNER = 0x40,
        /// South West corner
        const SW_CORNER = 0x80,
    }
);

/// Returns the shadow flags for a particular position in the given level.
fn get_shadow_flags(level: &Level, pos: &Position) -> ShadowFlags {
    let north = pos.neighbor(Direction::Up);
    let south = pos.neighbor(Direction::Down);
    let west = pos.neighbor(Direction::Left);
    let east = pos.neighbor(Direction::Right);

    let mut flags = ShadowFlags::empty();
    if level.is_wall(&north) {
        flags = flags | N_EDGE;
    }
    if level.is_wall(&south) {
        flags = flags | S_EDGE;
    }
    if level.is_wall(&west) {
        flags = flags | W_EDGE;
    }
    if level.is_wall(&east) {
        flags = flags | E_EDGE;
    }
    if level.is_wall(&north.neighbor(Direction::Right)) && !flags.intersects(N_EDGE | E_EDGE) {
        flags = flags | NE_CORNER;
    }
    if level.is_wall(&north.neighbor(Direction::Left)) && !flags.intersects(N_EDGE | W_EDGE) {
        flags = flags | NW_CORNER;
    }
    if level.is_wall(&south.neighbor(Direction::Right)) && !flags.intersects(S_EDGE | E_EDGE) {
        flags = flags | SE_CORNER;
    }
    if level.is_wall(&south.neighbor(Direction::Left)) && !flags.intersects(S_EDGE | W_EDGE) {
        flags = flags | SW_CORNER;
    }
    flags
}

<commit_msg>Added a TileSet trait.<commit_after>// This file is part of sokoban-rs
// Copyright 2015 Sébastien Watteau
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::path::Path;
use sdl2::rect::Rect;
use sdl2::pixels::{Color, PixelFormatEnum};
use sdl2::render::{Renderer, Texture};
use sdl2_image::LoadTexture;
use sdl2_ttf::Font;

use super::game::{Level, Position, Direction};

/// The Drawer struct is responsible for drawing the game onto the screen.
pub struct Drawer<'a> {
    /// The underlying SDL renderer
    renderer: Renderer<'a>,
    /// The texture containing all the tiles
    texture: Texture,
    tileset: Box<TileSet>,
    /// The font used to display text
    font: Font,
    /// The size of the screen in pixels
    screen_size: (u32, u32),
    /// The height of the status bar
    bar_height: u32,
    /// The color of the status bar
    bar_color: Color,
    /// The color of the text in the status bar
    bar_text_color: Color,
}

/// Represents a location for text in the status bar
enum StatusBarLocation {
    FlushLeft,
    FlushRight,
}

impl<'a> Drawer<'a> {
    /// Creates a new Drawer instance.
    pub fn new(renderer: Renderer<'a>) -> Drawer {
        let path = Path::new("assets/image/tileset.png");
        let texture = renderer.load_texture(path).unwrap();
        let font = Font::from_file(Path::new("assets/font/RujisHandwritingFontv.2.0.ttf"), 20).unwrap();
        let screen_size = renderer.window().unwrap().drawable_size();
        Drawer {
            renderer: renderer,
            texture: texture,
            tileset: Box::new(BigTileSet),
            font: font,
            screen_size: screen_size,
            bar_height: 32,
            bar_color: Color::RGBA(20, 20, 20, 255),
            bar_text_color: Color::RGBA(255, 192, 0, 255),
        }
    }

    /// Draws a level onto the screen.
    pub fn draw(&mut self, level: &Level) {
        // Draw a full-size image onto an off-screen buffer
        let fullsize = self.get_rendering_size(&level);
        let _ = self.renderer.render_target()
            .expect("Render targets are not supported")
            .create_and_set(PixelFormatEnum::RGBA8888, fullsize);

        self.draw_fullsize(level);

        // Copy onto the screen with appropriate scaling
        let final_rect = self.get_centered_image_rect(self.get_scaled_rendering_size(&level));
        let texture = self.renderer.render_target()
            .unwrap()
            .reset()
            .unwrap_or_else(|err| panic!("Could not reset to the default render target: {}", err))
            .unwrap_or_else(|| panic!("Could not get the offscreen texture"));

        self.renderer.clear();
        self.renderer.copy(&texture, Some(Rect::new_unwrap(0, 0, fullsize.0, fullsize.1)), final_rect);

        self.draw_status_bar(&level);

        self.renderer.present();
    }

    /// Draws a full-size image of the given level onto the current render target.
    fn draw_fullsize(&mut self, level: &Level) {
        let (cols, rows) = level.extents();
        self.renderer.set_draw_color(Color::RGB(0, 0, 0));
        self.renderer.clear();

        for j in (0..rows) {
            for i in (0..cols) {
                let pos = Position::new(j, i);
                let (x, y) = self.tileset.get_coordinates(&pos);

                // First draw the floor tiles
                if level.is_square(&pos) {
                    self.draw_tile(Tile::Square, x, y);
                } else {
                    self.draw_tile(Tile::Floor, x, y);
                }

                // Add the shadows
                let flags = get_shadow_flags(&level, &pos);
                for f in &[N_EDGE, S_EDGE, E_EDGE, W_EDGE, NE_CORNER, NW_CORNER, SE_CORNER, SW_CORNER] {
                    if flags.contains(*f) {
                        self.draw_tile(Tile::Shadow(*f), x, y);
                    }
                }

                // Draw the other items
                let z = y - self.tileset.tile_offset();
                if level.is_wall(&pos) {
                    self.draw_tile(Tile::Wall, x, z);
                }
                if level.is_box(&pos) {
                    self.draw_tile(Tile::Rock, x, z);
                }
                if level.is_player(&pos) {
                    self.draw_tile(Tile::Player, x, z);
                }
            }
        }
    }

    /// Draws the status bar
    fn draw_status_bar(&mut self, level: &Level) {
        let prev_color = self.renderer.draw_color();
        self.renderer.set_draw_color(self.bar_color);
        let rect = Rect::new_unwrap(0, (self.screen_size.1 - self.bar_height) as i32, self.screen_size.0, self.bar_height);
        self.renderer.fill_rect(rect);
        self.renderer.set_draw_color(prev_color);

        // Draw the number of moves
        let s = format!("# moves: {}", level.get_steps());
        self.draw_status_text(&s, StatusBarLocation::FlushLeft);

        // Draw the level's title
        self.draw_status_text(level.title(), StatusBarLocation::FlushRight);
    }

    /// Draws text in the status bar
    fn draw_status_text(&mut self, text: &str, location: StatusBarLocation) {
        let surface = self.font.render_str_blended(text, self.bar_text_color).unwrap();
        let texture = self.renderer.create_texture_from_surface(&surface).unwrap();
        let margin = 4;
        let (w, h) = {
            let q = texture.query();
            (q.width, q.height)
        };
        let (x, y) = match location {
            StatusBarLocation::FlushLeft => {
                (margin as i32, (self.screen_size.1 - margin - h) as i32)
            },
            StatusBarLocation::FlushRight => {
                ((self.screen_size.0 - margin - w) as i32, (self.screen_size.1 - margin - h) as i32)
            },
        };
        self.renderer.copy(&texture, None, Some(Rect::new_unwrap(x, y, w, h)));
    }

    /// Draws a tile at the given coordinates.
    fn draw_tile(&mut self, tile: Tile, x: i32, y: i32) {
        let (col, row) = self.tileset.location(tile).unwrap_or_else(|| {
            panic!("No image for this tile: {:?}", tile);
        });
        let tile_rect = self.get_tile_rect(col, row);
        self.renderer.copy(&self.texture, tile_rect, Some(Rect::new_unwrap(x, y, self.tileset.tile_width(), self.tileset.tile_height())));
    }

    /// Returns the Rect of the tile located at the given row and column in the texture.
    fn get_tile_rect(&self, col: u32, row: u32) -> Option<Rect> {
        let (w, h) = (self.tileset.tile_width(), self.tileset.tile_height());
        let x = (col * w) as i32;
        let y = (row * h) as i32;
        Some(Rect::new_unwrap(x, y, w, h))
    }

    /// Returns the full size needed to draw the given level.
    fn get_rendering_size(&self, level: &Level) -> (u32, u32) {
        let (w, h) = level.extents();
        let width = w as u32 * self.tileset.tile_width();
        let height = if h > 0 {
            self.tileset.tile_height() + (h - 1) as u32 * self.tileset.tile_effective_height()
        } else {
            0
        };

        (width, height)
    }

    /// Returns the size of the drawing scaled to fit onto the screen.
    fn get_scaled_rendering_size(&self, level: &Level) -> (u32, u32) {
        let render_size = self.get_rendering_size(&level);
        let width_ratio = (self.screen_size.0 as f64) / (render_size.0 as f64);
        let h = self.screen_size.1 - self.bar_height;
        let height_ratio = (h as f64) / (render_size.1 as f64);
        let ratio = f64::min(1.0, f64::min(width_ratio, height_ratio));

        let scale = |sz: u32| {
            (ratio * (sz as f64)).floor() as u32
        };

        (scale(render_size.0), scale(render_size.1))
    }

    /// Returns the Rect of an image of given dimensions so that it's centered on the screen.
    fn get_centered_image_rect(&self, img_size: (u32, u32)) -> Option<Rect> {
        let x = (self.screen_size.0 - img_size.0) as i32 / 2;
        let y = (self.screen_size.1 - self.bar_height - img_size.1) as i32 / 2;
        Some(Rect::new_unwrap(x, y, img_size.0, img_size.1))
    }
}

/// Represents a kind of tile.
#[derive(Copy, Clone, Debug)]
enum Tile {
    /// Standard floor tile
    Floor,
    /// Wall tile
    Wall,
    /// Rock tile
    Rock,
    /// Target square tile
    Square,
    /// Player tile
    Player,
    /// Shadow tile
    Shadow(ShadowFlags),
}

/// Holds information about a tileset.
trait TileSet {
    /// Returns the width of a tile.
    fn tile_width(&self) -> u32;

    /// Returns the height of a tile.
    fn tile_height(&self) -> u32;

    /// Returns the effective height of a tile (used for stacking)
    fn tile_effective_height(&self) -> u32;

    /// Returns the offset need to draw items on the floor.
    fn tile_offset(&self) -> i32;

    /// Returns the location of the tile in the tileset texture.
    fn location(&self, tile: Tile) -> Option<(u32, u32)> {
        match tile {
           Tile::Floor => Some((0, 0)),
           Tile::Wall => Some((0, 2)),
           Tile::Rock => Some((2, 0)),
           Tile::Square => Some((1, 0)),
           Tile::Player => Some((3, 0)),
           Tile::Shadow(N_EDGE) => Some((4, 0)),
           Tile::Shadow(S_EDGE) => Some((5, 0)),
           Tile::Shadow(E_EDGE) => Some((0, 1)),
           Tile::Shadow(W_EDGE) => Some((1, 1)),
           Tile::Shadow(NE_CORNER) => Some((2, 1)),
           Tile::Shadow(NW_CORNER) => Some((3, 1)),
           Tile::Shadow(SE_CORNER) => Some((4, 1)),
           Tile::Shadow(SW_CORNER) => Some((5, 1)),
           Tile::Shadow(ShadowFlags { .. }) => None,
        }
    }

    /// Returns the top-left corner coordinates of the tile corresponding
    /// to the given position.
    fn get_coordinates(&self, pos: &Position) -> (i32, i32) {
        let x = self.tile_width() as i32 * pos.column();
        let y = self.tile_effective_height() as i32 * pos.row();
        (x, y)
    }
}

/// Holds information about the big tileset.
struct BigTileSet;

impl TileSet for BigTileSet {
    fn tile_width(&self) -> u32 { 101 }
    fn tile_height(&self) -> u32 { 171 }
    fn tile_effective_height(&self) -> u32 { 83 }
    fn tile_offset(&self) -> i32 { 40 }
}

/// Holds information about the small tileset.
struct SmallTileSet;

impl TileSet for SmallTileSet {
    fn tile_width(&self) -> u32 { 50 }
    fn tile_height(&self) -> u32 { 85 }
    fn tile_effective_height(&self) -> u32 { 41 }
    fn tile_offset(&self) -> i32 { 20 }
}

bitflags!(
    /// Represents the different kind of shadows that can be cast
    /// onto a floor tile.
    flags ShadowFlags: i32 {
        /// North edge
        const N_EDGE = 0x1,
        /// South edge
        const S_EDGE = 0x2,
        /// East edge
        const E_EDGE = 0x4,
        /// West edge
        const W_EDGE = 0x8,
        /// North East corner
        const NE_CORNER = 0x10,
        /// North West corner
        const NW_CORNER = 0x20,
        /// South East corner
        const SE_CORNER = 0x40,
        /// South West corner
        const SW_CORNER = 0x80,
    }
);

/// Returns the shadow flags for a particular position in the given level.
fn get_shadow_flags(level: &Level, pos: &Position) -> ShadowFlags {
    let north = pos.neighbor(Direction::Up);
    let south = pos.neighbor(Direction::Down);
    let west = pos.neighbor(Direction::Left);
    let east = pos.neighbor(Direction::Right);

    let mut flags = ShadowFlags::empty();
    if level.is_wall(&north) {
        flags = flags | N_EDGE;
    }
    if level.is_wall(&south) {
        flags = flags | S_EDGE;
    }
    if level.is_wall(&west) {
        flags = flags | W_EDGE;
    }
    if level.is_wall(&east) {
        flags = flags | E_EDGE;
    }
    if level.is_wall(&north.neighbor(Direction::Right)) && !flags.intersects(N_EDGE | E_EDGE) {
        flags = flags | NE_CORNER;
    }
    if level.is_wall(&north.neighbor(Direction::Left)) && !flags.intersects(N_EDGE | W_EDGE) {
        flags = flags | NW_CORNER;
    }
    if level.is_wall(&south.neighbor(Direction::Right)) && !flags.intersects(S_EDGE | E_EDGE) {
        flags = flags | SE_CORNER;
    }
    if level.is_wall(&south.neighbor(Direction::Left)) && !flags.intersects(S_EDGE | W_EDGE) {
        flags = flags | SW_CORNER;
    }
    flags
}

<|endoftext|>


<commit_before>#[macro_use] extern crate conrod;
extern crate find_folder;
extern crate glutin_window;
extern crate graphics;
extern crate rebind;
extern crate piston;
extern crate opengl_graphics;
extern crate viewport;

use conrod::{
    Background,
    Color,
    Colorable,
    Frameable,
    Labelable,
    Positionable,
    Sizeable,
    Theme,
    Toggle,
    Ui,
    Widget,
};
use conrod::color::{black, grayscale, green, red};
use glutin_window::GlutinWindow;
use graphics::*;
use opengl_graphics::{GlGraphics, OpenGL};
use opengl_graphics::glyph_cache::GlyphCache;
use piston::event_loop::{EventMap, Events};
use piston::input::{
    Event,
    Input,
    Motion,
    RenderArgs,
    RenderEvent,
    UpdateArgs,
    UpdateEvent
};
use piston::window::{Window, WindowSettings};
use rebind::{Action, InputTranslator, RebindBuilder, Translated};
use std::cell::RefCell;
use std::rc::Rc;

type RcWindow = Rc<RefCell<GlutinWindow>>;
type RcGraphics = Rc<RefCell<GlGraphics>>;
type RcUi = Rc<RefCell<Ui<GlyphCache<'static>>>>;

struct App {
    window: RcWindow,
    graphics: RcGraphics,
    ui: RcUi,
    translator: InputTranslator<CharacterAction>,
    character: Character,
    cursor: VirtualCursor,
    bg_color: Color
}

impl App {
    fn input(&mut self, input: &Input) {
        if let Some(t) = self.translator.translate(input) {
            match t {
                Translated::Press(action) => {
                    const CHARACTER_WALK_SPEED_INCREMENT: f64 = 400.0;
                    match action {
                        CharacterAction::Jump => {
                            println!("You jumped! Yay!");
                        },
                        CharacterAction::MoveLeft => {
                            if self.character.current_velocity[0] > (self.character.max_velocity[0] * -1.0) {
                                self.character.current_velocity[0] -= CHARACTER_WALK_SPEED_INCREMENT;
                            }
                        },
                        CharacterAction::MoveRight => {
                            if self.character.current_velocity[0] < self.character.max_velocity[0] {
                                self.character.current_velocity[0] += CHARACTER_WALK_SPEED_INCREMENT;
                            }
                        }
                    }
                },
                Translated::Release(_) => {
                    self.character.current_velocity = [0.0, 0.0];
                },
                Translated::Move(Motion::MouseCursor(x, y)) => {
                    self.cursor.position = [x, y];
                },
                _ => { }
            }
        }
    }

    fn update(&mut self, args: &UpdateArgs) {
        // we need to pass the window to update (and set the size here) because using
        // the update event from the window events queue is currently broken.
        self.translator.set_size(self.window.borrow().size());

        // update the character's velocity
        let ctl = self.character.topleft;
        let v = self.character.current_velocity;

        self.character.topleft = [ctl[0] + (v[0] * args.dt), ctl[1] + (v[1] * args.dt)];
    }

    fn render(&mut self, args: &RenderArgs) {
        let mut gl_graphics = self.graphics.borrow_mut();
        let ui = &mut *self.ui.borrow_mut();
        let mut rebind = self.translator.clone().into_rebind();

        // declare widget ids
        widget_ids! {
            X_INVERT_TOGGLE,
            Y_INVERT_TOGGLE
        }

        // draw the ui
        gl_graphics.draw(args.viewport(), |c, gl| {
            Background::new().color(self.bg_color).set(ui);

            Toggle::new(rebind.get_x_motion_inverted())
                .xy(-350.0, 260.0)
                .dimensions(80.0, 40.0)
                .color(grayscale(0.4))
                .frame(1.0)
                .label(&"Invert X")
                .label_color(self.bg_color.plain_contrast())
                .label_font_size(16)
                .react(|b| rebind.set_x_motion_inverted(b))
                .set(X_INVERT_TOGGLE, ui);

            Toggle::new(rebind.get_y_motion_inverted())
                .xy(-350.0, 200.0)
                .dimensions(80.0, 40.0)
                .color(grayscale(0.4))
                .frame(1.0)
                .label(&"Invert Y")
                .label_color(self.bg_color.plain_contrast())
                .label_font_size(16)
                .react(|b| rebind.set_y_motion_inverted(b))
                .set(Y_INVERT_TOGGLE, ui);

            ui.draw(c, gl);
        });

        // draw the character
        {
            let square = rectangle::square(self.character.topleft[0],
                                           self.character.topleft[1],
                                           self.character.size);

            gl_graphics.draw(args.viewport(), |c, gl| rectangle(self.character.color.to_fsa(),
                                                                square,
                                                                c.transform,
                                                                gl));
        }

        // draw the cursor dot
        {
            let dot = ellipse::circle(self.cursor.position[0],
                                      self.cursor.position[1],
                                      self.cursor.size);

            gl_graphics.draw(args.viewport(), |c, gl| ellipse(self.cursor.color.to_fsa(),
                                                              dot,
                                                              c.transform,
                                                              gl));
        }

        self.translator = rebind.into();
    }
}

struct Character {
    color: Color,
    topleft: [f64; 2],
    current_velocity: [f64; 2],
    max_velocity: [f64; 2],
    size: f64
}

impl Character {
    fn new(col: Color, tl: [f64; 2], sz: f64) -> Self {
        Character {
            color: col,
            topleft: tl,
            current_velocity: [0.0, 0.0],
            max_velocity: [800.0, 0.0],
            size: sz
        }
    }
}

struct VirtualCursor {
    position: [f64; 2],
    color: Color,
    size: f64
}

impl VirtualCursor {
    fn new() -> Self {
        VirtualCursor {
            position: [0.0, 0.0],
            color: green(),
            size: 5.0
        }
    }
}

#[derive(Copy, Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
enum CharacterAction {
    Jump,
    MoveLeft,
    MoveRight
}

impl Action for CharacterAction { }

fn main() {
    const OPENGL: OpenGL = OpenGL::V3_2;
    const WINDOW_SIZE: (u32, u32) = (800, 600);

    let window = WindowSettings::new("rebind-example", WINDOW_SIZE)
        .exit_on_esc(true)
        .fullscreen(false)
        .opengl(OPENGL)
        .vsync(true)
        .build()
        .ok()
        .expect("Could not create main window");

    let gl_graphics = GlGraphics::new(OPENGL);

    let translator = {
        use piston::input::keyboard::Key;
        use piston::input::Button::Keyboard;
        RebindBuilder::new(WINDOW_SIZE)
            .with_action_mapping(Keyboard(Key::Space), CharacterAction::Jump)
            .with_action_mapping(Keyboard(Key::Left),  CharacterAction::MoveLeft)
            .with_action_mapping(Keyboard(Key::A),     CharacterAction::MoveLeft)
            .with_action_mapping(Keyboard(Key::Right), CharacterAction::MoveRight)
            .with_action_mapping(Keyboard(Key::D),     CharacterAction::MoveRight)
            .build_translator()
    };

    let character = {
        const INITIAL_CHARACTER_POS: [f64; 2] = [WINDOW_SIZE.0 as f64 / 20.0,
                                                 WINDOW_SIZE.1 as f64 * 0.85];
        Character::new(red(), INITIAL_CHARACTER_POS, 50.0)
    };

    let ui = {
        let glyph_cache = {
            let font_path = find_folder::Search::ParentsThenKids(3, 3)
                .for_folder("assets")
                .ok()
                .expect("Could not find assets folder")
                .join("fonts/NotoSans/NotoSans-Regular.ttf");

            GlyphCache::new(&font_path)
                .ok()
                .expect("Could not find font file within assets folder")
        };

        Ui::new(glyph_cache, Theme::default())
    };

    let mut app = App {
        window: Rc::new(RefCell::new(window)),
        graphics: Rc::new(RefCell::new(gl_graphics)),
        ui: Rc::new(RefCell::new(ui)),
        translator: translator,
        character: character,
        cursor: VirtualCursor::new(),
        bg_color: black()
    };

    for e in app.window.clone().events() {
        app.ui.borrow_mut().handle_event(&e);
        match e {
            Event::Input(i) => { app.input(&i); },
            Event::Update(u) => { app.update(&u); },
            Event::Render(r) => { app.render(&r); },
            _ => { }
        }
    }
}<commit_msg>Add explanation comment about why the translator is cloned<commit_after>#[macro_use] extern crate conrod;
extern crate find_folder;
extern crate glutin_window;
extern crate graphics;
extern crate rebind;
extern crate piston;
extern crate opengl_graphics;
extern crate viewport;

use conrod::{
    Background,
    Color,
    Colorable,
    Frameable,
    Labelable,
    Positionable,
    Sizeable,
    Theme,
    Toggle,
    Ui,
    Widget,
};
use conrod::color::{black, grayscale, green, red};
use glutin_window::GlutinWindow;
use graphics::*;
use opengl_graphics::{GlGraphics, OpenGL};
use opengl_graphics::glyph_cache::GlyphCache;
use piston::event_loop::{EventMap, Events};
use piston::input::{
    Event,
    Input,
    Motion,
    RenderArgs,
    RenderEvent,
    UpdateArgs,
    UpdateEvent
};
use piston::window::{Window, WindowSettings};
use rebind::{Action, InputTranslator, RebindBuilder, Translated};
use std::cell::RefCell;
use std::rc::Rc;

type RcWindow = Rc<RefCell<GlutinWindow>>;
type RcGraphics = Rc<RefCell<GlGraphics>>;
type RcUi = Rc<RefCell<Ui<GlyphCache<'static>>>>;

struct App {
    window: RcWindow,
    graphics: RcGraphics,
    ui: RcUi,
    translator: InputTranslator<CharacterAction>,
    character: Character,
    cursor: VirtualCursor,
    bg_color: Color
}

impl App {
    fn input(&mut self, input: &Input) {
        if let Some(t) = self.translator.translate(input) {
            match t {
                Translated::Press(action) => {
                    const CHARACTER_WALK_SPEED_INCREMENT: f64 = 400.0;
                    match action {
                        CharacterAction::Jump => {
                            println!("You jumped! Yay!");
                        },
                        CharacterAction::MoveLeft => {
                            if self.character.current_velocity[0] > (self.character.max_velocity[0] * -1.0) {
                                self.character.current_velocity[0] -= CHARACTER_WALK_SPEED_INCREMENT;
                            }
                        },
                        CharacterAction::MoveRight => {
                            if self.character.current_velocity[0] < self.character.max_velocity[0] {
                                self.character.current_velocity[0] += CHARACTER_WALK_SPEED_INCREMENT;
                            }
                        }
                    }
                },
                Translated::Release(_) => {
                    self.character.current_velocity = [0.0, 0.0];
                },
                Translated::Move(Motion::MouseCursor(x, y)) => {
                    self.cursor.position = [x, y];
                },
                _ => { }
            }
        }
    }

    fn update(&mut self, args: &UpdateArgs) {
        // we need to pass the window to update (and set the size here) because using
        // the update event from the window events queue is currently broken.
        self.translator.set_size(self.window.borrow().size());

        // update the character's velocity
        let ctl = self.character.topleft;
        let v = self.character.current_velocity;

        self.character.topleft = [ctl[0] + (v[0] * args.dt), ctl[1] + (v[1] * args.dt)];
    }

    fn render(&mut self, args: &RenderArgs) {
        // This demo translates and rebinds in a single screen, forcing a clone of the translator. This
        // limitation is a result of the necessity of providing a different interface for rebinding. One
        // way you could avoid a clone every update/render is to structure your application to pass the
        // translator between separate options and game screens.
        let mut rebind = self.translator.clone().into_rebind();

        let mut gl_graphics = self.graphics.borrow_mut();
        let ui = &mut *self.ui.borrow_mut();

        // declare widget ids
        widget_ids! {
            X_INVERT_TOGGLE,
            Y_INVERT_TOGGLE
        }

        // draw the ui
        gl_graphics.draw(args.viewport(), |c, gl| {
            Background::new().color(self.bg_color).set(ui);

            Toggle::new(rebind.get_x_motion_inverted())
                .xy(-350.0, 260.0)
                .dimensions(80.0, 40.0)
                .color(grayscale(0.4))
                .frame(1.0)
                .label(&"Invert X")
                .label_color(self.bg_color.plain_contrast())
                .label_font_size(16)
                .react(|b| rebind.set_x_motion_inverted(b))
                .set(X_INVERT_TOGGLE, ui);

            Toggle::new(rebind.get_y_motion_inverted())
                .xy(-350.0, 200.0)
                .dimensions(80.0, 40.0)
                .color(grayscale(0.4))
                .frame(1.0)
                .label(&"Invert Y")
                .label_color(self.bg_color.plain_contrast())
                .label_font_size(16)
                .react(|b| rebind.set_y_motion_inverted(b))
                .set(Y_INVERT_TOGGLE, ui);

            ui.draw(c, gl);
        });

        // draw the character
        {
            let square = rectangle::square(self.character.topleft[0],
                                           self.character.topleft[1],
                                           self.character.size);

            gl_graphics.draw(args.viewport(), |c, gl| rectangle(self.character.color.to_fsa(),
                                                                square,
                                                                c.transform,
                                                                gl));
        }

        // draw the cursor dot
        {
            let dot = ellipse::circle(self.cursor.position[0],
                                      self.cursor.position[1],
                                      self.cursor.size);

            gl_graphics.draw(args.viewport(), |c, gl| ellipse(self.cursor.color.to_fsa(),
                                                              dot,
                                                              c.transform,
                                                              gl));
        }

        self.translator = rebind.into();
    }
}

struct Character {
    color: Color,
    topleft: [f64; 2],
    current_velocity: [f64; 2],
    max_velocity: [f64; 2],
    size: f64
}

impl Character {
    fn new(col: Color, tl: [f64; 2], sz: f64) -> Self {
        Character {
            color: col,
            topleft: tl,
            current_velocity: [0.0, 0.0],
            max_velocity: [800.0, 0.0],
            size: sz
        }
    }
}

struct VirtualCursor {
    position: [f64; 2],
    color: Color,
    size: f64
}

impl VirtualCursor {
    fn new() -> Self {
        VirtualCursor {
            position: [0.0, 0.0],
            color: green(),
            size: 5.0
        }
    }
}

#[derive(Copy, Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
enum CharacterAction {
    Jump,
    MoveLeft,
    MoveRight
}

impl Action for CharacterAction { }

fn main() {
    const OPENGL: OpenGL = OpenGL::V3_2;
    const WINDOW_SIZE: (u32, u32) = (800, 600);

    let window = WindowSettings::new("rebind-example", WINDOW_SIZE)
        .exit_on_esc(true)
        .fullscreen(false)
        .opengl(OPENGL)
        .vsync(true)
        .build()
        .ok()
        .expect("Could not create main window");

    let gl_graphics = GlGraphics::new(OPENGL);

    let translator = {
        use piston::input::keyboard::Key;
        use piston::input::Button::Keyboard;
        RebindBuilder::new(WINDOW_SIZE)
            .with_action_mapping(Keyboard(Key::Space), CharacterAction::Jump)
            .with_action_mapping(Keyboard(Key::Left),  CharacterAction::MoveLeft)
            .with_action_mapping(Keyboard(Key::A),     CharacterAction::MoveLeft)
            .with_action_mapping(Keyboard(Key::Right), CharacterAction::MoveRight)
            .with_action_mapping(Keyboard(Key::D),     CharacterAction::MoveRight)
            .build_translator()
    };

    let character = {
        const INITIAL_CHARACTER_POS: [f64; 2] = [WINDOW_SIZE.0 as f64 / 20.0,
                                                 WINDOW_SIZE.1 as f64 * 0.85];
        Character::new(red(), INITIAL_CHARACTER_POS, 50.0)
    };

    let ui = {
        let glyph_cache = {
            let font_path = find_folder::Search::ParentsThenKids(3, 3)
                .for_folder("assets")
                .ok()
                .expect("Could not find assets folder")
                .join("fonts/NotoSans/NotoSans-Regular.ttf");

            GlyphCache::new(&font_path)
                .ok()
                .expect("Could not find font file within assets folder")
        };

        Ui::new(glyph_cache, Theme::default())
    };

    let mut app = App {
        window: Rc::new(RefCell::new(window)),
        graphics: Rc::new(RefCell::new(gl_graphics)),
        ui: Rc::new(RefCell::new(ui)),
        translator: translator,
        character: character,
        cursor: VirtualCursor::new(),
        bg_color: black()
    };

    for e in app.window.clone().events() {
        app.ui.borrow_mut().handle_event(&e);
        match e {
            Event::Input(i) => { app.input(&i); },
            Event::Update(u) => { app.update(&u); },
            Event::Render(r) => { app.render(&r); },
            _ => { }
        }
    }
}<|endoftext|>


<commit_before>extern crate glium;
extern crate clap;
extern crate image;
extern crate cgmath;
extern crate time;

use glium::backend::glutin_backend::GlutinFacade;
use clap::ArgMatches;
use std::f32;

struct RenderParams {
    flat: bool,
    iter: i32,
}

struct RenderBuffers(glium::VertexBuffer<RayVertex>, glium::IndexBuffer<u8>);
pub struct Renderer {
    program: glium::Program,
    background: glium::texture::SrgbTexture2d,

    buffers: RenderBuffers,

    params: RenderParams,
}

impl Renderer {
    pub fn new(display: &GlutinFacade, args: &ArgMatches) -> Self {
        let bg = {
            use std::io::Cursor;
            //FIXME: insert alternate bg images here

            let im = image::load(
                        Cursor::new(&include_bytes!("../resources/bg.jpg")[..]),
                        image::JPEG).unwrap().to_rgba();

            let imdim = im.dimensions();
            let im = glium::texture::RawImage2d::from_raw_rgba_reversed(
                        im.into_raw(), imdim);
            glium::texture::SrgbTexture2d::new(display, im).unwrap()
        };

        let prog = {
            //FIXME: add option for different shaders
            let shaders = &FLAT_SHADER;

            glium::Program::from_source(display,
                shaders.vert_shader,
                shaders.frag_shader,
                None).unwrap()
        };

        let bufs = {
            use glium::index::PrimitiveType::TrianglesList;
            RenderBuffers(glium::VertexBuffer::new(display, &VERTICES).unwrap(),
             glium::IndexBuffer::new(display, TrianglesList, &INDICES).unwrap())
        };

        Renderer { program: prog, background: bg, buffers: bufs,
            params: RenderParams::new(args) }
    }

    pub fn render(&self, display: &GlutinFacade, t: f32) {
        use glium::Surface;
        use time::precise_time_ns;

        let start = precise_time_ns();

        let mut target = display.draw();

        target.clear_color(0., 0., 0., 0.0);

        let (width, height) = target.get_dimensions();

        let src = [-10.0 * t.sin(),0.0,-10.0 * t.cos()];
        let facing_mat = {
            use cgmath::*;
            let dir = vec3(t.sin(), 0.0f32, t.cos());
            let up = vec3(0.,1.,0.0f32);

            // cgmath returns a tranposed look_at matrix for some reason
            Into::<[[f32;3];3]>::into(cgmath::Matrix3::look_at(dir, up)
                .transpose())
        };

        let (num_iter, time_step) = {
            let distance = 30.0f32; /* 2 * 15R_s, deflection is minimal by then */
            let num_iter = self.params.iter; /* arbitrary */

            (num_iter, distance / (num_iter as f32))
        };

        let uniforms = uniform! {
            height_ratio: (height as f32) / (width as f32),
            fov_ratio: (f32::consts::PI * 2. / 3. / 2.).tan(), // pi/2, 90 deg
            src: src,
            facing: facing_mat,
            tex: self.background
                .sampled().wrap_function(glium::uniforms::SamplerWrapFunction::Repeat),
            NUM_ITER: num_iter,
            TIME_STEP: time_step,
            FLAT: self.params.flat,
        };

        let params = glium::DrawParameters {
            blend: glium::Blend {
                color: glium::BlendingFunction::AlwaysReplace,
                alpha: glium::BlendingFunction::AlwaysReplace,
                constant_value: (0.0, 0.0, 0.0, 0.0),
            },
            .. Default::default()
        };

        target.draw(&self.buffers.0, &self.buffers.1, &self.program,
                    &uniforms, &params).unwrap();

        target.finish().unwrap();

        let end = precise_time_ns();
        println!("dt: {}ms", (end - start) as f32 / (1000000.0f32));
    }
}

impl RenderParams {
    fn new(args: &ArgMatches) -> Self {
        RenderParams {
            flat: args.is_present("flat"),
            iter: args.value_of("iter").unwrap().parse::<i32>().unwrap(),
        }
    }
}

#[derive(Copy, Clone)]
struct RayVertex {
    pos: (f32, f32),
}

implement_vertex!(RayVertex, pos);

const VERTICES: [RayVertex; 4] = [
    RayVertex { pos: (-1., -1.) },
    RayVertex { pos: (-1.,  1.) },
    RayVertex { pos: ( 1.,  1.) },
    RayVertex { pos: ( 1., -1.) },
];

const INDICES: [u8; 6] = [
    0, 1, 2,
    0, 2, 3
];

struct ShaderPair {
    vert_shader: &'static str,
    frag_shader: &'static str,
}

const FLAT_SHADER: ShaderPair = ShaderPair {
    vert_shader: r#"

#version 140

in vec2 pos;
out vec3 dir;
out vec2 pos_v;

uniform float height_ratio; // height / width
uniform float fov_ratio; // tan(fov / 2)

uniform mat3 facing;

void main() {
    float x = pos.x * fov_ratio;
    float y = pos.y * fov_ratio * height_ratio;
    dir = facing * vec3(x, y, 1.0);
    pos_v = pos;

    gl_Position = vec4(pos, 0.0, 1.0);
}

    "#,
    frag_shader: r#"

#version 140

#define M_PI 3.1415926535897932384626433832795

in vec3 dir;

out vec4 color;

uniform sampler2D bgtex;
uniform int NUM_ITER;
uniform float TIME_STEP;

/* we set constants to convenient values for now */
const float C = 1.0;
const float R_s = 1.0;
const float M = 0.5; /* must be R_s / 2 */
const float G = 1.0;

uniform bool FLAT;

uniform vec3 src;

float atan2(float y, float x) {
    return x == 0.0 ? sign(y) * M_PI / 2 : atan(y, x);
}

float yaw(vec3 v) {
    return atan2(v.x, v.z);
}

float yaw_coord(vec3 v) {
    return (yaw(v) + M_PI) / (2. * M_PI);
}

float pitch(vec3 v) {
    return asin(v.y);
}

float pitch_coord(vec3 v) {
    return (pitch(v) + M_PI / 2.) / M_PI;
}

vec4 bg_tex(vec3 dir) {
    float x = yaw_coord(dir);
    float y = pitch_coord(dir);

    vec2 tex_coords = vec2(x, y);

    float invert_x = x - 0.5;
    invert_x = invert_x - sign(invert_x) * 0.5;

    vec2 dx1 = dFdx(tex_coords);
    vec2 dx2 = dFdx(vec2(invert_x, y));

    vec2 dx = dot(dx1, dx1) < dot(dx2, dx2) ? dx1 : dx2;

    /* force the LOD so that GLSL doesn't flip out on the discontinuity
       at the texture border */
    return textureGrad(bgtex, tex_coords, dx, dFdy(tex_coords));
}

const vec4 ZERO = vec4(0.0, 0.0, 0.0, 0.0);

void main() {
    vec3 ndir = normalize(dir);

    /* closest approach to BH */
    float dist = length(cross(ndir, src));

    float alpha_rem = 1.0;
    vec4 ccolor = vec4(0.0, 0.0, 0.0, 0.0);
    vec3 cdir = ndir; /* current direction */
    //if(dist < 3) {
        vec3 pos = src;
        vec3 h = cross(pos, cdir);
        float h2 = dot(h, h); /* angular momentum */

        for(int i = 0; i < NUM_ITER; i++) {
            vec3 npos = pos + cdir * TIME_STEP;
            if(!FLAT) {
                vec3 accel = -pos * 1.5 * h2 * pow(dot(pos, pos), -2.5);
                cdir = cdir + accel * TIME_STEP;
                if(0 == i % 100) {
                    cdir = normalize(cdir);
                    h = cross(pos, cdir);
                    h2 = dot(h, h);
                }
                //cdir = cdir + accel * TIME_STEP;
            }

            /* check if its within a black hole */
            if(length(npos) <= R_s) {
                ccolor += alpha_rem * vec4(0.0, 0.0, 1.0, 1.0);
                alpha_rem *= 0.0;
            }

            pos = npos;
        }
    //}
    cdir = normalize(cdir);

    ccolor += alpha_rem * bg_tex(cdir);

    color = ccolor;
}

    "#,
};

/*const GR_SHADER: ShaderPair = ShaderPair {
    vert_shader: r#"

#version 140

in vec2 pos;
out vec2 pos_v;

void main() {
    pos_v = pos;

    gl_Position = vec4(pos, 0.0, 1.0);
}

    "#,
    frag_shader: r#"

#version 140

in vec2 pos_v;

out vec4 color;

void main() {
    float red = (pos_v.x + 1) / 2.;
    float green = (pos_v.y + 1) / 2.;
    color = vec4(red, green, 0.0, 1.0);
}

    "#,
};*/

<commit_msg>experimenting with different iteration count schemes and viewpoints<commit_after>extern crate glium;
extern crate clap;
extern crate image;
extern crate cgmath;
extern crate time;

use glium::backend::glutin_backend::GlutinFacade;
use clap::ArgMatches;
use std::f32;

struct RenderParams {
    flat: bool,
    iter: i32,
}

struct RenderBuffers(glium::VertexBuffer<RayVertex>, glium::IndexBuffer<u8>);
pub struct Renderer {
    program: glium::Program,
    background: glium::texture::SrgbTexture2d,

    buffers: RenderBuffers,

    params: RenderParams,
}

impl Renderer {
    pub fn new(display: &GlutinFacade, args: &ArgMatches) -> Self {
        let bg = {
            use std::io::Cursor;
            //FIXME: insert alternate bg images here

            let im = image::load(
                        Cursor::new(&include_bytes!("../resources/bg.jpg")[..]),
                        image::JPEG).unwrap().to_rgba();

            let imdim = im.dimensions();
            let im = glium::texture::RawImage2d::from_raw_rgba_reversed(
                        im.into_raw(), imdim);
            glium::texture::SrgbTexture2d::new(display, im).unwrap()
        };

        let prog = {
            //FIXME: add option for different shaders
            let shaders = &FLAT_SHADER;

            glium::Program::from_source(display,
                shaders.vert_shader,
                shaders.frag_shader,
                None).unwrap()
        };

        let bufs = {
            use glium::index::PrimitiveType::TrianglesList;
            RenderBuffers(glium::VertexBuffer::new(display, &VERTICES).unwrap(),
             glium::IndexBuffer::new(display, TrianglesList, &INDICES).unwrap())
        };

        Renderer { program: prog, background: bg, buffers: bufs,
            params: RenderParams::new(args) }
    }

    pub fn render(&self, display: &GlutinFacade, t: f32) {
        use glium::Surface;
        use time::precise_time_ns;

        let start = precise_time_ns();

        let mut target = display.draw();

        target.clear_color(0., 0., 0., 0.0);

        let (width, height) = target.get_dimensions();

        let src = [-3.0 * t.sin(),0.0,-3.0 * t.cos()];
        let facing_mat = {
            use cgmath::*;
            let dir = vec3(t.sin(), 0.0f32, t.cos());
            let up = vec3(0.,1.,0.0f32);

            // cgmath returns a tranposed look_at matrix for some reason
            Into::<[[f32;3];3]>::into(cgmath::Matrix3::look_at(dir, up)
                .transpose())
        };

        let (num_iter, time_step) = {
            let distance = 15.0f32; /* 2 * 15R_s, deflection is minimal by then */
            let num_iter = self.params.iter; /* arbitrary */

            (num_iter, distance / (num_iter as f32))
        };

        let uniforms = uniform! {
            height_ratio: (height as f32) / (width as f32),
            fov_ratio: (f32::consts::PI * 2. / 3. / 2.).tan(), // pi/2, 90 deg
            src: src,
            facing: facing_mat,
            tex: self.background
                .sampled().wrap_function(glium::uniforms::SamplerWrapFunction::Repeat),
            NUM_ITER: num_iter,
            TIME_STEP: time_step,
            FLAT: self.params.flat,
        };

        let params = glium::DrawParameters {
            blend: glium::Blend {
                color: glium::BlendingFunction::AlwaysReplace,
                alpha: glium::BlendingFunction::AlwaysReplace,
                constant_value: (0.0, 0.0, 0.0, 0.0),
            },
            .. Default::default()
        };

        target.draw(&self.buffers.0, &self.buffers.1, &self.program,
                    &uniforms, &params).unwrap();

        target.finish().unwrap();

        let end = precise_time_ns();
        println!("dt: {}ms", (end - start) as f32 / (1000000.0f32));
    }
}

impl RenderParams {
    fn new(args: &ArgMatches) -> Self {
        RenderParams {
            flat: args.is_present("flat"),
            iter: args.value_of("iter").unwrap().parse::<i32>().unwrap(),
        }
    }
}

#[derive(Copy, Clone)]
struct RayVertex {
    pos: (f32, f32),
}

implement_vertex!(RayVertex, pos);

const VERTICES: [RayVertex; 4] = [
    RayVertex { pos: (-1., -1.) },
    RayVertex { pos: (-1.,  1.) },
    RayVertex { pos: ( 1.,  1.) },
    RayVertex { pos: ( 1., -1.) },
];

const INDICES: [u8; 6] = [
    0, 1, 2,
    0, 2, 3
];

struct ShaderPair {
    vert_shader: &'static str,
    frag_shader: &'static str,
}

const FLAT_SHADER: ShaderPair = ShaderPair {
    vert_shader: r#"

#version 330

in vec2 pos;
out vec3 dir;
out vec2 pos_v;

uniform float height_ratio; // height / width
uniform float fov_ratio; // tan(fov / 2)

uniform mat3 facing;

void main() {
    float x = pos.x * fov_ratio;
    float y = pos.y * fov_ratio * height_ratio;
    dir = facing * vec3(x, y, 1.0);
    pos_v = pos;

    gl_Position = vec4(pos, 0.0, 1.0);
}

    "#,
    frag_shader: r#"

#version 330

#define M_PI 3.1415926535897932384626433832795

in vec3 dir;

out vec4 color;

uniform sampler2D bgtex;
uniform int NUM_ITER;
uniform float TIME_STEP;

/* we set constants to convenient values for now */
const float C = 1.0;
const float R_s = 1.0;
const float M = 0.5; /* must be R_s / 2 */
const float G = 1.0;

uniform bool FLAT;

uniform vec3 src;

float atan2(float y, float x) {
    return x == 0.0 ? sign(y) * M_PI / 2 : atan(y, x);
}

float yaw(vec3 v) {
    return atan2(v.x, v.z);
}

float yaw_coord(vec3 v) {
    return (yaw(v) + M_PI) / (2. * M_PI);
}

float pitch(vec3 v) {
    return asin(v.y);
}

float pitch_coord(vec3 v) {
    return (pitch(v) + M_PI / 2.) / M_PI;
}

vec4 bg_tex(vec3 dir) {
    float x = yaw_coord(dir);
    float y = pitch_coord(dir);

    vec2 tex_coords = vec2(x, y);

    float invert_x = x - 0.5;
    invert_x = invert_x - sign(invert_x) * 0.5;

    vec2 dx1 = dFdx(tex_coords);
    vec2 dx2 = dFdx(vec2(invert_x, y));

    vec2 dx = dot(dx1, dx1) < dot(dx2, dx2) ? dx1 : dx2;

    /* force the LOD so that GLSL doesn't flip out on the discontinuity
       at the texture border */
    return textureGrad(bgtex, tex_coords, dx, dFdy(tex_coords));
}

const vec4 ZERO = vec4(0.0, 0.0, 0.0, 0.0);

void main() {
    vec3 ndir = normalize(dir);

    /* closest approach to BH */
    float dist = length(cross(ndir, src));

    /* test iteration function */
    int num_iter = int((1 / (1 + dist * 0.2)) * NUM_ITER);

    float alpha_rem = 1.0;
    vec4 ccolor = vec4(0.0, 0.0, 0.0, 0.0);
    vec3 cdir = ndir; /* current direction */

    vec3 pos = src;
    vec3 h = cross(pos, cdir);
    float h2 = dot(h, h); /* angular momentum */

    for(int i = 0; i < num_iter; i++) {
        vec3 npos = pos + cdir * TIME_STEP;
        if(!FLAT) {
            vec3 accel = -pos * 1.5 * h2 * pow(dot(pos, pos), -2.5);
            cdir = cdir + accel * TIME_STEP;
            if(0 == i % 100) {
                cdir = normalize(cdir);
                h = cross(pos, cdir);
                h2 = dot(h, h);
            }
            //cdir = cdir + accel * TIME_STEP;
        }

        /* check if its within a black hole */
        if(length(npos) <= R_s) {
            ccolor += alpha_rem * vec4(0.0, 0.0, 1.0, 1.0);
            alpha_rem *= 0.0;
        }

        pos = npos;
    }
    cdir = normalize(cdir);

    ccolor += alpha_rem * bg_tex(cdir);

    color = ccolor;
}

    "#,
};

/*const GR_SHADER: ShaderPair = ShaderPair {
    vert_shader: r#"

#version 140

in vec2 pos;
out vec2 pos_v;

void main() {
    pos_v = pos;

    gl_Position = vec4(pos, 0.0, 1.0);
}

    "#,
    frag_shader: r#"

#version 140

in vec2 pos_v;

out vec4 color;

void main() {
    float red = (pos_v.x + 1) / 2.;
    float green = (pos_v.y + 1) / 2.;
    color = vec4(red, green, 0.0, 1.0);
}

    "#,
};*/

<|endoftext|>