1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
//! Contains utilities for where and how to draw things

extern crate sdl2;

use sdl2::event::Event;
use sdl2::rect::{Point, Rect};
use sdl2::render::Canvas;
use sdl2::video::Window;

/// Where to draw a specific object.
#[allow(unused)]
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum Position {
    /// Place the top left corner at a specific position, ignoring size. Only used in the most
    /// primitive situations
    TopLeftCorner(Point),
    /// Center the object at this position
    Center(Point),
    /// Draw the object in a specific rectangle
    Rect(Rect),
}

/// Settings to specify how an object is supposed to be drawed
#[derive(Clone, Copy, PartialEq, Debug)]
pub struct DrawSettings {
    /// Is the object being drawed in the notes window, or the main window? In the notes window,
    /// more debug information is drawed.
    pub notes_view: bool,
    /// Color of the background in RGB order
    pub background_color: (u8, u8, u8),
}

/// The default draw settings for the main window
pub const DSETTINGS_MAIN: DrawSettings = DrawSettings {
    notes_view: false,
    background_color: (255, 248, 234),
};

/// The default draw settings for the notes window
pub const DSETTINGS_NOTES: DrawSettings = DrawSettings {
    notes_view: true,
    ..DSETTINGS_MAIN
};

/// The state for a specific object on screen, used in the [`Drawable::state()`]
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub enum State {
    /// The object is still in the process of being shown, such as fading in.
    Working,
    /// The object is complete and the next call to step will start to hide it
    Final,
    /// The object is being hidden
    Hidden,
}

impl Position {
    /// Convert a `Position` into a Rect with the specified size. The size is not gauranteed to be
    /// (width, height) such as when the `Position` is a `Position::Rect` with a smaller size
    ///
    /// For `Position::Rect` the center is preserved, but the size is changed.
    ///
    /// ```
    /// use ytesrev::{Point, Rect};
    /// use ytesrev::drawable::Position;
    ///
    /// let tlc = Position::TopLeftCorner(Point::new(10, 20));
    /// assert_eq!(tlc.into_rect_with_size(5, 8), Rect::new(10, 20, 5, 8));
    ///
    /// let center = Position::Center(Point::new(20, 30));
    /// assert_eq!(center.into_rect_with_size(4, 10), Rect::new(18, 25, 4, 10));
    ///
    /// let rect = Position::Rect(Rect::new(10, 40, 20, 20));
    /// assert_eq!(rect.into_rect_with_size(6, 4), Rect::new(17, 48, 6, 4));
    ///
    /// let rect_too_short = Position::Rect(Rect::new(10, 20, 30, 8));
    /// assert_eq!(rect_too_short.into_rect_with_size(20, 69), Rect::new(15, 20, 20, 8));
    ///
    /// ```
    pub fn into_rect_with_size(self, width: u32, height: u32) -> Rect {
        match self {
            Position::TopLeftCorner(point) => Rect::new(point.x, point.y, width, height),
            Position::Center(point) => Rect::new(
                point.x - width as i32 / 2,
                point.y - height as i32 / 2,
                width,
                height,
            ),
            Position::Rect(rect) => {
                let center_x = rect.x() + rect.width() as i32 / 2;
                let center_y = rect.y() + rect.height() as i32 / 2;

                let x = (center_x - width as i32 / 2).max(rect.x);
                let y = (center_y - height as i32 / 2).max(rect.y);

                let width = if x + width as i32 > rect.x + rect.width() as i32 {
                    rect.width()
                } else {
                    width
                };

                let height = if y + height as i32 > rect.y + rect.height() as i32 {
                    rect.height()
                } else {
                    height
                };

                Rect::new(x, y, width, height)
            }
        }
    }

    /// Convert a Position into a Rect, like [`Position::into_rect_with_size`], but this doesn't
    /// check if the [`Position::Rect`] is too small.
    /// ```
    /// use ytesrev::{Point, Rect};
    /// use ytesrev::drawable::Position;
    ///
    /// let tlc = Position::TopLeftCorner(Point::new(20, 50));
    /// assert_eq!(tlc.into_rect_with_size(2, 7), Rect::new(20, 50, 2, 7));
    ///
    /// let center = Position::Center(Point::new(50, 10));
    /// assert_eq!(center.into_rect_with_size(2, 8), Rect::new(49, 6, 2, 8));
    ///
    /// let rect = Position::Rect(Rect::new(10, 40, 20, 20));
    /// assert_eq!(rect.into_rect_with_size(6, 4), Rect::new(17, 48, 6, 4));
    ///
    /// ```
    pub fn into_rect_with_size_unbounded(self, width: u32, height: u32) -> Rect {
        match self {
            Position::TopLeftCorner(point) => Rect::new(point.x, point.y, width, height),
            Position::Center(point) => Rect::new(
                point.x - width as i32 / 2,
                point.y - height as i32 / 2,
                width,
                height,
            ),
            Position::Rect(rect) => {
                let center_x = rect.x() + rect.width() as i32 / 2;
                let center_y = rect.y() + rect.height() as i32 / 2;

                let x = (center_x - width as i32 / 2).max(rect.x);
                let y = (center_y - height as i32 / 2).max(rect.y);

                Rect::new(x, y, width, height)
            }
        }
    }
}

/// An object that can be drawn
pub trait Drawable: Send {
    /// What this object contains
    fn content(&self) -> Vec<&dyn Drawable>;
    /// What this object contains, mutably
    fn content_mut(&mut self) -> Vec<&mut dyn Drawable>;

    /// Register all content. This is mostly just used by [`LatexObj`]s, that need to be
    /// registered before loaded.
    ///
    /// [`LatexObj`]: ../latex/latex_obj/struct.LatexObj.rs
    fn register(&mut self) {
        for content in &mut self.content_mut() {
            content.register();
        }
    }

    /// Load all content
    fn load(&mut self) {
        for content in &mut self.content_mut() {
            content.load();
        }
    }

    /// When the user presses space, the state of the presentation is advanced. This
    /// method is what is called.
    fn step(&mut self);

    /// When any event occurs
    fn event(&mut self, e: Event) {
        for x in self.content_mut() {
            x.event(e.clone());
        }
    }

    /// What state the object is in
    fn state(&self) -> State;

    /// Tick the object
    fn update(&mut self, dt: f64) {
        for content in &mut self.content_mut() {
            content.update(dt);
        }
    }

    /// Draw everything
    fn draw(&self, _canvas: &mut Canvas<Window>, _position: &Position, _settings: DrawSettings);
}

/// An object that has a determined size, like an image, but not a solid that can fit any
/// shape it's called with
pub trait KnownSize: Drawable {
    /// The width of the object

    fn width(&self) -> usize;
    /// The height of the object
    fn height(&self) -> usize;
}