mod error;
pub(crate) use error::{CreateTerminalError, DisplayCmdFailure, UserActionFailure};
use {
core::{
cell::{RefCell, RefMut},
convert::{TryFrom, TryInto},
ops::Deref,
time::Duration,
},
crossterm::{
cursor::{Hide, MoveTo},
event::{self, Event, KeyCode, KeyEvent, KeyModifiers},
execute,
style::Print,
terminal::{EnterAlternateScreen, LeaveAlternateScreen},
},
error::{DestroyError, InitError, PollFailure, ReachedEnd, ReadFailure, WriteFailure},
fehler::{throw, throws},
log::{trace, warn},
market::{Consumer, Producer},
parse_display::Display as ParseDisplay,
std::io::{self, Stdout, Write},
};
static NO_DURATION: Duration = Duration::from_secs(0);
#[throws(PollFailure)]
fn is_action_available() -> bool {
event::poll(NO_DURATION)?
}
#[throws(ReadFailure)]
fn read_action() -> UserAction {
event::read().map(UserAction::from)?
}
#[derive(Debug, Default)]
pub(crate) struct Terminal {
presenter: Presenter,
}
impl Terminal {
#[throws(CreateTerminalError)]
pub(crate) fn new() -> Self {
let terminal = Self::default();
terminal.presenter.init()?;
terminal
}
}
impl Drop for Terminal {
fn drop(&mut self) {
if let Err(error) = self.presenter.destroy() {
warn!("Error while destroying user interface: {}", error);
}
}
}
impl Producer for Terminal {
type Good = DisplayCmd;
type Failure = market::ProduceFailure<DisplayCmdFailure>;
#[throws(Self::Failure)]
fn produce(&self, good: Self::Good) {
match good {
DisplayCmd::Rows { rows } => {
let mut row = RowId(0);
for text in rows {
self.presenter
.single_line(
row.try_into().map_err(|error: ReachedEnd| {
market::ProduceFailure::Fault(error.into())
})?,
text.to_string(),
)
.map_err(|failure| market::ProduceFailure::Fault(failure.into()))?;
row.step_forward()
.map_err(|failure| market::ProduceFailure::Fault(failure.into()))?;
}
}
DisplayCmd::Header { header } => {
self.presenter
.single_line(Unit(0), header)
.map_err(|failure| market::ProduceFailure::Fault(failure.into()))?;
}
}
}
}
pub(crate) struct UserActionConsumer;
impl Consumer for UserActionConsumer {
type Good = UserAction;
type Failure = market::ConsumeFailure<UserActionFailure>;
#[throws(Self::Failure)]
fn consume(&self) -> Self::Good {
if is_action_available().map_err(|error| market::ConsumeFailure::Fault(error.into()))? {
read_action().map_err(|error| market::ConsumeFailure::Fault(error.into()))?
} else {
throw!(market::ConsumeFailure::EmptyStock);
}
}
}
#[derive(Debug)]
struct Presenter {
out: RefCell<Stdout>,
}
impl Presenter {
fn out_mut(&self) -> RefMut<'_, Stdout> {
self.out.borrow_mut()
}
#[throws(InitError)]
fn init(&self) {
execute!(self.out_mut(), EnterAlternateScreen, Hide)?;
}
#[throws(DestroyError)]
fn destroy(&self) {
execute!(self.out_mut(), LeaveAlternateScreen)?;
}
#[throws(WriteFailure)]
fn single_line(&self, row: Unit, text: String) {
trace!("Writing to {}: `{}`", row, text);
execute!(
self.out_mut(),
MoveTo(0, *row),
Print(text),
crossterm::terminal::Clear(crossterm::terminal::ClearType::UntilNewLine)
)?;
}
}
impl Default for Presenter {
fn default() -> Self {
Self {
out: RefCell::new(io::stdout()),
}
}
}
#[derive(Clone, Copy, Debug)]
pub(crate) enum UserAction {
Resize {
dimensions: Dimensions,
},
Mouse,
Key {
code: KeyCode,
modifiers: KeyModifiers,
},
}
impl From<Event> for UserAction {
#[inline]
fn from(value: Event) -> Self {
match value {
Event::Resize(columns, rows) => Self::Resize {
dimensions: Dimensions {
height: rows.saturating_sub(1).into(),
width: columns.into(),
},
},
Event::Mouse(..) => Self::Mouse,
Event::Key(key) => key.into(),
}
}
}
impl From<KeyEvent> for UserAction {
#[inline]
fn from(value: KeyEvent) -> Self {
Self::Key {
code: value.code,
modifiers: value.modifiers,
}
}
}
#[derive(Debug, ParseDisplay)]
#[display("DisplayCmd")]
pub(crate) enum DisplayCmd {
Rows {
rows: Vec<String>,
},
Header {
header: String,
},
}
#[derive(Clone, Copy, Debug, Default, Eq, ParseDisplay, PartialEq)]
#[display("{height}h x {width}w")]
pub(crate) struct Dimensions {
pub(crate) height: Unit,
pub(crate) width: Unit,
}
#[derive(Clone, Copy, Debug, Default, Eq, ParseDisplay, PartialEq)]
#[display("{0}")]
pub(crate) struct Unit(u16);
impl Deref for Unit {
type Target = u16;
#[inline]
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl From<u16> for Unit {
#[inline]
fn from(value: u16) -> Self {
Self(value)
}
}
impl TryFrom<RowId> for Unit {
type Error = ReachedEnd;
#[throws(Self::Error)]
#[inline]
fn try_from(value: RowId) -> Self {
value.0.checked_add(1).ok_or(ReachedEnd)?.into()
}
}
impl From<Unit> for usize {
#[inline]
fn from(unit: Unit) -> Self {
unit.0.into()
}
}
#[derive(Clone, Copy, Debug, ParseDisplay)]
#[display("{0}")]
pub(crate) struct RowId(u16);
impl RowId {
#[throws(ReachedEnd)]
fn step_forward(&mut self) {
self.0 = self.0.checked_add(1).ok_or(ReachedEnd)?;
}
}
impl Deref for RowId {
type Target = u16;
fn deref(&self) -> &Self::Target {
&self.0
}
}