parent
ec1660d006
commit
f5aa5e86ff
@ -0,0 +1,153 @@
|
||||
use super::*;
|
||||
use palette::{FromColor, Srgb};
|
||||
|
||||
#[non_exhaustive]
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub struct TextChar {
|
||||
pub ch: char,
|
||||
pub fg: Srgb<u8>,
|
||||
pub bg: Option<Srgb<u8>>,
|
||||
}
|
||||
|
||||
impl TextChar {
|
||||
#[inline]
|
||||
pub fn new<C1, C2>(ch: char, fg: C1, bg: Option<C2>) -> Self
|
||||
where
|
||||
Srgb<u8>: FromColor<C1>,
|
||||
Srgb<u8>: FromColor<C2>,
|
||||
{
|
||||
Self {
|
||||
ch,
|
||||
fg: Srgb::from_color(fg),
|
||||
bg: bg.map(|x| Srgb::from_color(x)),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn from_char(ch: impl Into<char>) -> Self {
|
||||
Self {
|
||||
ch: ch.into(),
|
||||
fg: Srgb::new(255, 255, 255),
|
||||
bg: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn from_state(ch: impl Into<char>, state: State) -> Self {
|
||||
Self {
|
||||
ch: ch.into(),
|
||||
fg: match state {
|
||||
State::Idle => Srgb::new(128, 128, 128),
|
||||
State::Active => Srgb::new(220, 255, 255),
|
||||
State::Dormant => Srgb::new(100, 60, 60),
|
||||
},
|
||||
bg: None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TextChar {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
ch: ' ',
|
||||
fg: Srgb::new(255, 255, 255),
|
||||
bg: None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TextSurface {
|
||||
width: usize,
|
||||
height: usize,
|
||||
|
||||
chars: Vec<TextChar>,
|
||||
}
|
||||
|
||||
impl TextSurface {
|
||||
pub fn new(width: usize, height: usize) -> Self {
|
||||
Self {
|
||||
width,
|
||||
height,
|
||||
|
||||
chars: vec![TextChar::default(); width * height],
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the [`TextChar`] at `(x, y)`, if it exists.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```
|
||||
/// # use stackline::prelude::*;
|
||||
/// let surface = TextSurface::new(1, 1);
|
||||
///
|
||||
/// assert_eq!(surface.get(0, 0), Some(TextChar::default()));
|
||||
/// ```
|
||||
pub fn get(&self, x: usize, y: usize) -> Option<TextChar> {
|
||||
if self.in_bounds(x, y) {
|
||||
Some(self.chars[y * self.width + x])
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the [`TextChar`] at `(x, y)` to `c`, if `(x, y)` is within the bounds of this `TextSurface`.
|
||||
/// Returns `None` if `(x, y)` is outside of the bounds.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```
|
||||
/// # use stackline::prelude::*;
|
||||
/// let mut surface = TextSurface::new(2, 2);
|
||||
///
|
||||
/// surface.set(0, 0, TextChar::from_char('a')).unwrap();
|
||||
/// assert_eq!(surface.get(0, 0,), Some(TextChar::from_char('a')));
|
||||
/// ```
|
||||
pub fn set(&mut self, x: usize, y: usize, c: TextChar) -> Option<()> {
|
||||
if self.in_bounds(x, y) {
|
||||
self.chars[y * self.width + x] = c;
|
||||
Some(())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` iff `(x, y)` is within the bounds of this `TextSurface`.
|
||||
pub fn in_bounds(&self, x: usize, y: usize) -> bool {
|
||||
x < self.width && y < self.height
|
||||
}
|
||||
|
||||
pub fn width(&self) -> usize {
|
||||
self.width
|
||||
}
|
||||
|
||||
pub fn height(&self) -> usize {
|
||||
self.height
|
||||
}
|
||||
|
||||
// TODO: resize
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl std::fmt::Display for TextSurface {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
use colored::Colorize;
|
||||
|
||||
for y in 0..self.height {
|
||||
for x in 0..self.width {
|
||||
let ch = self.chars[y * self.width + x];
|
||||
let mut string = String::from(ch.ch)
|
||||
.truecolor(ch.fg.red, ch.fg.green, ch.fg.blue);
|
||||
|
||||
if let Some(bg) = ch.bg {
|
||||
string = string.on_truecolor(bg.red, bg.green, bg.blue);
|
||||
}
|
||||
|
||||
write!(f, "{}", string)?;
|
||||
}
|
||||
write!(f, "\n")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -0,0 +1,117 @@
|
||||
use super::*;
|
||||
|
||||
/** Represents a tile that may be empty and may have a signal. The tile may only have a signal if it isn't empty.
|
||||
Cloning a `FullTile` results in a `FullTile` that does not have any signal.
|
||||
|
||||
## Invariants
|
||||
|
||||
- `self.cell.is_none() -> self.signal.is_none()`
|
||||
- `self.accepts_signal() -> self.cell.is_some()`
|
||||
|
||||
**/
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct FullTile {
|
||||
cell: Option<AnyTile>,
|
||||
signal: Option<Signal>,
|
||||
state: State,
|
||||
pub(crate) updated: bool,
|
||||
}
|
||||
|
||||
// NOTE: should not implement Tile
|
||||
impl FullTile {
|
||||
pub fn new(cell: Option<AnyTile>) -> Self {
|
||||
Self {
|
||||
cell,
|
||||
signal: None,
|
||||
state: State::default(),
|
||||
updated: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn accepts_signal(&self, direction: Direction) -> bool {
|
||||
match self.cell {
|
||||
Some(ref tile) => self.state.accepts_signal() && tile.accepts_signal(direction),
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `Some` iff self.cell.is_some()
|
||||
pub(crate) fn set_signal(&mut self, signal: Option<Signal>) -> Option<()> {
|
||||
if self.cell.is_some() {
|
||||
self.signal = signal;
|
||||
Some(())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the internal state of this full tile
|
||||
#[inline]
|
||||
pub fn get<'b>(&'b self) -> Option<&'b AnyTile> {
|
||||
self.cell.as_ref()
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the internal state of this tile
|
||||
#[inline]
|
||||
pub fn get_mut<'b>(&'b mut self) -> Option<&'b mut AnyTile> {
|
||||
self.cell.as_mut()
|
||||
}
|
||||
|
||||
/// Returns the signal of this tile
|
||||
#[inline]
|
||||
pub fn signal<'b>(&'b self) -> Option<&'b Signal> {
|
||||
self.signal.as_ref()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn take_signal(&mut self) -> Option<Signal> {
|
||||
std::mem::take(&mut self.signal)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn state(&self) -> State {
|
||||
self.state
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_state(&mut self, state: State) {
|
||||
if self.cell.is_some() {
|
||||
self.state = state
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn next_state(&mut self) {
|
||||
self.state = self.state.next();
|
||||
}
|
||||
|
||||
/// Draws itself on a [`TextSurface`] at `(x, y)`.
|
||||
/// If the tile is empty, does nothing
|
||||
pub fn draw(&self, x: usize, y: usize, surface: &mut TextSurface) {
|
||||
match self.cell {
|
||||
Some(ref cell) => cell.draw(x, y, self.state, surface),
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for FullTile {
|
||||
#[inline]
|
||||
fn default() -> Self {
|
||||
Self::new(None)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Tile + 'static> From<T> for FullTile {
|
||||
#[inline]
|
||||
fn from(tile: T) -> Self {
|
||||
Self::new(Some(AnyTile::new(tile)))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<()> for FullTile {
|
||||
#[inline]
|
||||
fn from(_empty: ()) -> Self {
|
||||
Self::new(None)
|
||||
}
|
||||
}
|
Loading…
Reference in new issue