Make all drawing happen to a viewport
We now have an inner and outer viewport, and entity positions are relative to the inner one while drawing happens to the outer one.
This commit is contained in:
		
							parent
							
								
									de081d7b1d
								
							
						
					
					
						commit
						78a52142d1
					
				
					 8 changed files with 267 additions and 59 deletions
				
			
		
							
								
								
									
										7
									
								
								proptest-regressions/display/viewport.txt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								proptest-regressions/display/viewport.txt
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,7 @@
 | 
			
		|||
# Seeds for failure cases proptest has generated in the past. It is
 | 
			
		||||
# automatically read and these particular cases re-run before any
 | 
			
		||||
# novel cases are generated.
 | 
			
		||||
#
 | 
			
		||||
# It is recommended to check this file in to source control so that
 | 
			
		||||
# everyone who runs the test benefits from these saved cases.
 | 
			
		||||
cc b84a5a6dbba5cfc69329a119d9e20328c0372e0db2b72e5d71d971e3f13f8749 # shrinks to pos = Position { x: 0, y: 0 }, outer = BoundingBox { dimensions: Dimensions { w: 0, h: 0 }, position: Position { x: 0, y: 0 } }
 | 
			
		||||
| 
						 | 
				
			
			@ -1,9 +1,18 @@
 | 
			
		|||
pub mod draw_box;
 | 
			
		||||
pub mod utils;
 | 
			
		||||
pub mod viewport;
 | 
			
		||||
use crate::types::Positioned;
 | 
			
		||||
pub use draw_box::{make_box, BoxStyle};
 | 
			
		||||
use std::io::{self, Write};
 | 
			
		||||
use termion::{clear, cursor, style};
 | 
			
		||||
pub use viewport::Viewport;
 | 
			
		||||
 | 
			
		||||
pub fn clear<T: Write>(out: &mut T) -> io::Result<()> {
 | 
			
		||||
    write!(out, "{}{}{}", clear::All, style::Reset, cursor::Goto(1, 1))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub trait Draw: Positioned {
 | 
			
		||||
    /// Draw this entity, assuming the character is already at the correct
 | 
			
		||||
    /// position
 | 
			
		||||
    fn do_draw<W: Write>(&self, out: &mut W) -> io::Result<()>;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										138
									
								
								src/display/viewport.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										138
									
								
								src/display/viewport.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,138 @@
 | 
			
		|||
use super::Draw;
 | 
			
		||||
use super::{make_box, BoxStyle};
 | 
			
		||||
use crate::types::{BoundingBox, Position, Positioned};
 | 
			
		||||
use std::fmt::{self, Debug};
 | 
			
		||||
use std::io::{self, Write};
 | 
			
		||||
 | 
			
		||||
pub struct Viewport<W> {
 | 
			
		||||
    /// The box describing the visible part of the viewport.
 | 
			
		||||
    ///
 | 
			
		||||
    /// Generally the size of the terminal, and positioned at 0, 0
 | 
			
		||||
    pub outer: BoundingBox,
 | 
			
		||||
 | 
			
		||||
    /// The box describing the inner part of the viewport
 | 
			
		||||
    ///
 | 
			
		||||
    /// Its position is relative to `outer.inner()`, and its size should generally not
 | 
			
		||||
    /// be smaller than outer
 | 
			
		||||
    pub inner: BoundingBox,
 | 
			
		||||
 | 
			
		||||
    /// The actual screen that the viewport writes to
 | 
			
		||||
    pub out: W,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<W> Debug for Viewport<W> {
 | 
			
		||||
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
 | 
			
		||||
        write!(
 | 
			
		||||
            f,
 | 
			
		||||
            "Viewport {{ outer: {:?}, inner: {:?}, out: <OUT> }}",
 | 
			
		||||
            self.outer, self.inner
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<W> Viewport<W> {
 | 
			
		||||
    /// Returns true if the (inner-relative) position of the given entity is
 | 
			
		||||
    /// visible within this viewport
 | 
			
		||||
    fn visible<E: Positioned>(&self, ent: &E) -> bool {
 | 
			
		||||
        self.on_screen(ent.position()).within(self.outer.inner())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Convert the given inner-relative position to one on the actual screen
 | 
			
		||||
    fn on_screen(&self, pos: Position) -> Position {
 | 
			
		||||
        pos + self.inner.position + self.outer.inner().position
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<W: Write> Viewport<W> {
 | 
			
		||||
    /// Draw the given entity to the viewport at its position, if visible
 | 
			
		||||
    pub fn draw<T: Draw>(&mut self, entity: &T) -> io::Result<()> {
 | 
			
		||||
        if !self.visible(entity) {
 | 
			
		||||
            return Ok(());
 | 
			
		||||
        }
 | 
			
		||||
        write!(
 | 
			
		||||
            self,
 | 
			
		||||
            "{}",
 | 
			
		||||
            (entity.position()
 | 
			
		||||
                + self.inner.position
 | 
			
		||||
                + self.outer.inner().position)
 | 
			
		||||
                .cursor_goto()
 | 
			
		||||
        )?;
 | 
			
		||||
        entity.do_draw(self)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Clear whatever is drawn at the given inner-relative position, if visible
 | 
			
		||||
    pub fn clear(&mut self, pos: Position) -> io::Result<()> {
 | 
			
		||||
        write!(self, "{} ", self.on_screen(pos).cursor_goto(),)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Initialize this viewport by drawing its outer box to the screen
 | 
			
		||||
    pub fn init(&mut self) -> io::Result<()> {
 | 
			
		||||
        write!(self, "{}", make_box(BoxStyle::Thin, self.outer.dimensions))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<W> Positioned for Viewport<W> {
 | 
			
		||||
    fn position(&self) -> Position {
 | 
			
		||||
        self.outer.position
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<W: Write> Write for Viewport<W> {
 | 
			
		||||
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
 | 
			
		||||
        self.out.write(buf)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn flush(&mut self) -> io::Result<()> {
 | 
			
		||||
        self.out.flush()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
 | 
			
		||||
        self.out.write_all(buf)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use super::*;
 | 
			
		||||
    use crate::types::Dimensions;
 | 
			
		||||
    // use proptest::prelude::*;
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_visible() {
 | 
			
		||||
        assert!(Viewport {
 | 
			
		||||
            outer: BoundingBox::at_origin(Dimensions { w: 10, h: 10 }),
 | 
			
		||||
            inner: BoundingBox {
 | 
			
		||||
                position: Position { x: -10, y: -10 },
 | 
			
		||||
                dimensions: Dimensions { w: 15, h: 15 },
 | 
			
		||||
            },
 | 
			
		||||
            out: (),
 | 
			
		||||
        }
 | 
			
		||||
        .visible(&Position { x: 13, y: 13 }));
 | 
			
		||||
 | 
			
		||||
        assert!(!Viewport {
 | 
			
		||||
            outer: BoundingBox::at_origin(Dimensions { w: 10, h: 10 }),
 | 
			
		||||
            inner: BoundingBox {
 | 
			
		||||
                position: Position { x: -10, y: -10 },
 | 
			
		||||
                dimensions: Dimensions { w: 15, h: 15 },
 | 
			
		||||
            },
 | 
			
		||||
            out: (),
 | 
			
		||||
        }
 | 
			
		||||
        .visible(&Position { x: 1, y: 1 }));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // proptest! {
 | 
			
		||||
    //     #[test]
 | 
			
		||||
    //     fn nothing_is_visible_in_viewport_off_screen(pos: Position, outer: BoundingBox) {
 | 
			
		||||
    //         let invisible_viewport = Viewport {
 | 
			
		||||
    //             outer,
 | 
			
		||||
    //             inner: BoundingBox {
 | 
			
		||||
    //                 position: Position {x: -(outer.dimensions.w as i16), y: -(outer.dimensions.h as i16)},
 | 
			
		||||
    //                 dimensions: outer.dimensions,
 | 
			
		||||
    //             },
 | 
			
		||||
    //             out: ()
 | 
			
		||||
    //         };
 | 
			
		||||
 | 
			
		||||
    //         assert!(!invisible_viewport.visible(&pos));
 | 
			
		||||
    //     }
 | 
			
		||||
    // }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,15 +1,38 @@
 | 
			
		|||
use proptest_derive::Arbitrary;
 | 
			
		||||
use std::io::{self, Write};
 | 
			
		||||
use termion::cursor;
 | 
			
		||||
 | 
			
		||||
use crate::display;
 | 
			
		||||
use crate::types::{Position, Speed};
 | 
			
		||||
 | 
			
		||||
const DEFAULT_SPEED: Speed = Speed(100);
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, PartialEq, Eq, Arbitrary)]
 | 
			
		||||
pub struct Character {
 | 
			
		||||
    position: Position,
 | 
			
		||||
    /// The position of the character, relative to the game
 | 
			
		||||
    pub position: Position,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Character {
 | 
			
		||||
    pub fn new() -> Character {
 | 
			
		||||
        Character {
 | 
			
		||||
            position: Position { x: 0, y: 0 },
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn speed(&self) -> Speed {
 | 
			
		||||
        Speed(100)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
positioned!(Character);
 | 
			
		||||
 | 
			
		||||
impl display::Draw for Character {
 | 
			
		||||
    fn do_draw<W: Write>(&self, out: &mut W) -> io::Result<()> {
 | 
			
		||||
        write!(
 | 
			
		||||
            out,
 | 
			
		||||
            "@{}",
 | 
			
		||||
            cursor::Left(1),
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1 +1,2 @@
 | 
			
		|||
pub mod character;
 | 
			
		||||
pub use character::Character;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										94
									
								
								src/game.rs
									
										
									
									
									
								
							
							
						
						
									
										94
									
								
								src/game.rs
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -1,30 +1,28 @@
 | 
			
		|||
use std::thread;
 | 
			
		||||
use crate::settings::Settings;
 | 
			
		||||
use crate::types::Positioned;
 | 
			
		||||
use crate::types::{BoundingBox, Dimensions, Position};
 | 
			
		||||
use std::io::{self, StdinLock, StdoutLock, Write};
 | 
			
		||||
use termion::cursor;
 | 
			
		||||
use termion::input::Keys;
 | 
			
		||||
use termion::input::TermRead;
 | 
			
		||||
use termion::raw::RawTerminal;
 | 
			
		||||
 | 
			
		||||
use crate::display;
 | 
			
		||||
use crate::display::{self, Viewport};
 | 
			
		||||
use crate::entities::Character;
 | 
			
		||||
use crate::types::command::Command;
 | 
			
		||||
 | 
			
		||||
type Stdout<'a> = RawTerminal<StdoutLock<'a>>;
 | 
			
		||||
 | 
			
		||||
/// The full state of a running Game
 | 
			
		||||
pub struct Game<'a> {
 | 
			
		||||
    settings: Settings,
 | 
			
		||||
 | 
			
		||||
    /// The box describing the viewport. Generally the size of the terminal, and
 | 
			
		||||
    /// positioned at 0, 0
 | 
			
		||||
    viewport: BoundingBox,
 | 
			
		||||
    viewport: Viewport<Stdout<'a>>,
 | 
			
		||||
 | 
			
		||||
    /// An iterator on keypresses from the user
 | 
			
		||||
    keys: Keys<StdinLock<'a>>,
 | 
			
		||||
 | 
			
		||||
    stdout: RawTerminal<StdoutLock<'a>>,
 | 
			
		||||
 | 
			
		||||
    /// The position of the character
 | 
			
		||||
    character: Position,
 | 
			
		||||
    /// The player character
 | 
			
		||||
    character: Character,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<'a> Game<'a> {
 | 
			
		||||
| 
						 | 
				
			
			@ -37,35 +35,36 @@ impl<'a> Game<'a> {
 | 
			
		|||
    ) -> Game<'a> {
 | 
			
		||||
        Game {
 | 
			
		||||
            settings: settings,
 | 
			
		||||
            viewport: BoundingBox::at_origin(Dimensions { w, h }),
 | 
			
		||||
            viewport: Viewport {
 | 
			
		||||
                outer: BoundingBox::at_origin(Dimensions { w, h }),
 | 
			
		||||
                inner: BoundingBox::at_origin(Dimensions {
 | 
			
		||||
                    w: w - 2,
 | 
			
		||||
                    h: h - 2,
 | 
			
		||||
                }),
 | 
			
		||||
                out: stdout,
 | 
			
		||||
            },
 | 
			
		||||
            keys: stdin.keys(),
 | 
			
		||||
            stdout: stdout,
 | 
			
		||||
            character: Position { x: 1, y: 1 },
 | 
			
		||||
            character: Character::new(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Returns true if there's a collision in the game at the given Position
 | 
			
		||||
    fn collision_at(&self, pos: Position) -> bool {
 | 
			
		||||
        !pos.within(self.viewport.inner())
 | 
			
		||||
        !pos.within(self.viewport.inner)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn draw_entities(&mut self) -> io::Result<()> {
 | 
			
		||||
        self.viewport.draw(&self.character)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Run the game
 | 
			
		||||
    pub fn run(mut self) {
 | 
			
		||||
    pub fn run(mut self) -> io::Result<()> {
 | 
			
		||||
        info!("Running game");
 | 
			
		||||
        write!(
 | 
			
		||||
            self,
 | 
			
		||||
            "{}{}@{}",
 | 
			
		||||
            display::make_box(
 | 
			
		||||
                display::BoxStyle::Thin,
 | 
			
		||||
                self.viewport.dimensions
 | 
			
		||||
            ),
 | 
			
		||||
            cursor::Goto(2, 2),
 | 
			
		||||
            cursor::Left(1),
 | 
			
		||||
        )
 | 
			
		||||
        .unwrap();
 | 
			
		||||
        self.flush().unwrap();
 | 
			
		||||
        self.viewport.init()?;
 | 
			
		||||
        self.draw_entities()?;
 | 
			
		||||
        self.flush()?;
 | 
			
		||||
        loop {
 | 
			
		||||
            let mut character_moved = false;
 | 
			
		||||
            let mut old_position = None;
 | 
			
		||||
            match Command::from_key(self.keys.next().unwrap().unwrap()) {
 | 
			
		||||
                Some(Command::Quit) => {
 | 
			
		||||
                    info!("Quitting game due to user request");
 | 
			
		||||
| 
						 | 
				
			
			@ -73,46 +72,51 @@ impl<'a> Game<'a> {
 | 
			
		|||
                }
 | 
			
		||||
 | 
			
		||||
                Some(Command::Move(direction)) => {
 | 
			
		||||
                    let new_pos = self.character + direction;
 | 
			
		||||
                    let new_pos = self.character.position + direction;
 | 
			
		||||
                    if !self.collision_at(new_pos) {
 | 
			
		||||
                        self.character = new_pos;
 | 
			
		||||
                        character_moved = true;
 | 
			
		||||
                        old_position = Some(self.character.position);
 | 
			
		||||
                        self.character.position = new_pos;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                _ => (),
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if character_moved {
 | 
			
		||||
                debug!("char: {:?}", self.character);
 | 
			
		||||
                write!(
 | 
			
		||||
                    self,
 | 
			
		||||
                    " {}@{}",
 | 
			
		||||
                    cursor::Goto(self.character.x + 1, self.character.y + 1,),
 | 
			
		||||
                    cursor::Left(1)
 | 
			
		||||
                )
 | 
			
		||||
                .unwrap();
 | 
			
		||||
            match old_position {
 | 
			
		||||
                Some(old_pos) => {
 | 
			
		||||
                    self.viewport.clear(old_pos)?;
 | 
			
		||||
                    self.viewport.draw(&self.character)?;
 | 
			
		||||
                }
 | 
			
		||||
            self.flush().unwrap();
 | 
			
		||||
                None => ()
 | 
			
		||||
            }
 | 
			
		||||
            self.flush()?;
 | 
			
		||||
            debug!("{:?}", self.character);
 | 
			
		||||
        }
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<'a> Drop for Game<'a> {
 | 
			
		||||
    fn drop(&mut self) {
 | 
			
		||||
        display::clear(self).unwrap();
 | 
			
		||||
        display::clear(self).unwrap_or(());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<'a> Write for Game<'a> {
 | 
			
		||||
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
 | 
			
		||||
        self.stdout.write(buf)
 | 
			
		||||
        self.viewport.write(buf)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn flush(&mut self) -> io::Result<()> {
 | 
			
		||||
        self.stdout.flush()
 | 
			
		||||
        self.viewport.flush()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
 | 
			
		||||
        self.stdout.write_all(buf)
 | 
			
		||||
        self.viewport.write_all(buf)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<'a> Positioned for Game<'a> {
 | 
			
		||||
    fn position(&self) -> Position {
 | 
			
		||||
        Position { x: 0, y: 0 }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -23,6 +23,7 @@ use prettytable::format::consts::FORMAT_BOX_CHARS;
 | 
			
		|||
use settings::Settings;
 | 
			
		||||
 | 
			
		||||
use std::io::{self, StdinLock, StdoutLock};
 | 
			
		||||
use std::panic;
 | 
			
		||||
 | 
			
		||||
use termion::raw::IntoRawMode;
 | 
			
		||||
use termion::raw::RawTerminal;
 | 
			
		||||
| 
						 | 
				
			
			@ -34,8 +35,9 @@ fn init(
 | 
			
		|||
    w: u16,
 | 
			
		||||
    h: u16,
 | 
			
		||||
) {
 | 
			
		||||
    panic::set_hook(Box::new(|info| error!("{}", info)));
 | 
			
		||||
    let game = Game::new(settings, stdout, stdin, w, h);
 | 
			
		||||
    game.run()
 | 
			
		||||
    game.run().unwrap()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn main() {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,6 +5,7 @@ pub mod direction;
 | 
			
		|||
pub use direction::Direction;
 | 
			
		||||
pub use direction::Direction::{Down, Left, Right, Up};
 | 
			
		||||
use proptest_derive::Arbitrary;
 | 
			
		||||
use termion::cursor;
 | 
			
		||||
 | 
			
		||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Arbitrary)]
 | 
			
		||||
pub struct Dimensions {
 | 
			
		||||
| 
						 | 
				
			
			@ -42,11 +43,21 @@ impl BoundingBox {
 | 
			
		|||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn from_corners(top_left: Position, lower_right: Position) -> BoundingBox {
 | 
			
		||||
        BoundingBox {
 | 
			
		||||
            position: top_left,
 | 
			
		||||
            dimensions: Dimensions {
 | 
			
		||||
                w: (lower_right.x - top_left.x) as u16,
 | 
			
		||||
                h: (lower_right.y - top_left.y) as u16,
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn lr_corner(self) -> Position {
 | 
			
		||||
        self.position
 | 
			
		||||
            + (Position {
 | 
			
		||||
                x: self.dimensions.w,
 | 
			
		||||
                y: self.dimensions.h,
 | 
			
		||||
                x: self.dimensions.w as i16,
 | 
			
		||||
                y: self.dimensions.h as i16,
 | 
			
		||||
            })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -80,12 +91,12 @@ impl ops::Sub<Dimensions> for BoundingBox {
 | 
			
		|||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Arbitrary)]
 | 
			
		||||
pub struct Position {
 | 
			
		||||
    /// x (horizontal) position
 | 
			
		||||
    #[proptest(strategy = "std::ops::Range::<u16>::from(0..100)")]
 | 
			
		||||
    pub x: u16,
 | 
			
		||||
    #[proptest(strategy = "std::ops::Range::<i16>::from(0..100)")]
 | 
			
		||||
    pub x: i16,
 | 
			
		||||
 | 
			
		||||
    #[proptest(strategy = "std::ops::Range::<u16>::from(0..100)")]
 | 
			
		||||
    #[proptest(strategy = "std::ops::Range::<i16>::from(0..100)")]
 | 
			
		||||
    /// y (vertical) position
 | 
			
		||||
    pub y: u16,
 | 
			
		||||
    pub y: i16,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub const ORIGIN: Position = Position { x: 0, y: 0 };
 | 
			
		||||
| 
						 | 
				
			
			@ -97,6 +108,13 @@ impl Position {
 | 
			
		|||
    pub fn within(self, b: BoundingBox) -> bool {
 | 
			
		||||
        (self > b.position - UNIT_POSITION) && self < (b.lr_corner())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Returns a sequence of ASCII escape characters for moving the cursor to
 | 
			
		||||
    /// this Position
 | 
			
		||||
    pub fn cursor_goto(&self) -> cursor::Goto {
 | 
			
		||||
        // + 1 because Goto is 1-based, but position is 0-based
 | 
			
		||||
        cursor::Goto(self.x as u16 + 1, self.y as u16 + 1)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl PartialOrd for Position {
 | 
			
		||||
| 
						 | 
				
			
			@ -131,7 +149,7 @@ impl ops::Add<Direction> for Position {
 | 
			
		|||
    fn add(self, dir: Direction) -> Position {
 | 
			
		||||
        match dir {
 | 
			
		||||
            Left => {
 | 
			
		||||
                if self.x > 0 {
 | 
			
		||||
                if self.x > std::i16::MIN {
 | 
			
		||||
                    Position {
 | 
			
		||||
                        x: self.x - 1,
 | 
			
		||||
                        ..self
 | 
			
		||||
| 
						 | 
				
			
			@ -141,7 +159,7 @@ impl ops::Add<Direction> for Position {
 | 
			
		|||
                }
 | 
			
		||||
            }
 | 
			
		||||
            Right => {
 | 
			
		||||
                if self.x < std::u16::MAX {
 | 
			
		||||
                if self.x < std::i16::MAX {
 | 
			
		||||
                    Position {
 | 
			
		||||
                        x: self.x + 1,
 | 
			
		||||
                        ..self
 | 
			
		||||
| 
						 | 
				
			
			@ -151,7 +169,7 @@ impl ops::Add<Direction> for Position {
 | 
			
		|||
                }
 | 
			
		||||
            }
 | 
			
		||||
            Up => {
 | 
			
		||||
                if self.y > 0 {
 | 
			
		||||
                if self.y > std::i16::MIN {
 | 
			
		||||
                    Position {
 | 
			
		||||
                        y: self.y - 1,
 | 
			
		||||
                        ..self
 | 
			
		||||
| 
						 | 
				
			
			@ -161,7 +179,7 @@ impl ops::Add<Direction> for Position {
 | 
			
		|||
                }
 | 
			
		||||
            }
 | 
			
		||||
            Down => {
 | 
			
		||||
                if self.y < std::u16::MAX {
 | 
			
		||||
                if self.y < std::i16::MAX {
 | 
			
		||||
                    Position {
 | 
			
		||||
                        y: self.y + 1,
 | 
			
		||||
                        ..self
 | 
			
		||||
| 
						 | 
				
			
			@ -194,12 +212,18 @@ impl ops::Sub<Position> for Position {
 | 
			
		|||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Positioned for Position {
 | 
			
		||||
    fn position(&self) -> Position {
 | 
			
		||||
        *self
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub trait Positioned {
 | 
			
		||||
    fn x(&self) -> u16 {
 | 
			
		||||
    fn x(&self) -> i16 {
 | 
			
		||||
        self.position().x
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn y(&self) -> u16 {
 | 
			
		||||
    fn y(&self) -> i16 {
 | 
			
		||||
        self.position().y
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue