use crate::input::events::{AppEvent, Event};
use crate::input::{squash_input, Merge, Refresh};
use crate::output::command::{
CaptureFocus, CaptureMouse, EnterRawMode, Flush, ResetStyles, ShowCursor, TerminalCommander,
TerminalRequest, UseAlternateScreen, WrapLines,
};
use crate::output::io::CountingWrites;
use crate::sys::Control;
use crate::widget::Widget;
use crate::{geometry::*, Terminal};
use crate::{Painter, Screen};
use async_channel::Sender;
use futures_lite::prelude::*;
use log::*;
use std::io;
use std::io::BufWriter;
use std::pin::Pin;
use std::time::Duration;
pub struct Termit<'a, A: AppEvent> {
control: Box<dyn Control + 'a>,
input_closed: bool,
events: EventStream<'a, A>,
events_included: EventStream<'a, A>,
refresh_time_sender: Sender<Duration>,
out: Box<dyn io::Write + 'a>,
screen: Screen,
painter: Painter,
run_without_input: bool,
}
type EventStream<'a, A> = Pin<Box<dyn Stream<Item = io::Result<Event<A>>> + Unpin + 'a>>;
impl<'a, A: AppEvent> Termit<'a, A> {
pub fn new(
control: impl Control + 'a,
inp: impl AsyncRead + 'a,
out: impl io::Write + 'a,
) -> Self {
let raw_mode = control.is_raw();
let input_control = control.clone();
let events = Box::pin(crate::input::terminal_input_stream(
input_control,
inp,
raw_mode,
));
let refresh = Refresh::new(Duration::from_secs(1));
let refresh_time_sender = refresh.time_sender();
let events_included = Box::pin(refresh.map(io::Result::Ok));
let run_without_input = false;
let out_control = control.clone();
let out = Box::new(BufWriter::with_capacity(
1024 * 8,
TerminalResetOnDrop(out_control, out),
));
let control = Box::new(control);
let size = control.get_size().unwrap_or_default();
let painter = Painter::default().with_scope(size.window());
let mut screen = Screen::new(size);
screen.wipe = true;
Self {
control,
events,
events_included,
out,
screen,
run_without_input,
painter,
refresh_time_sender,
input_closed: false,
}
}
pub fn draw_over(mut self, draw_over: bool) -> Self {
self.screen.wipe = !draw_over;
self
}
pub fn true_color(mut self, true_color: bool) -> Self {
self.screen.true_color = true_color;
self
}
pub fn run_without_input(mut self, run: bool) -> Self {
self.run_without_input = run;
self
}
pub async fn step<M>(
&mut self,
model: &mut M,
ui: &mut impl Widget<M, A>,
) -> io::Result<usize> {
let mut count = 0;
let bunch = squash_input(Merge::new(&mut self.events, &mut self.events_included))
.next()
.await;
match bunch {
Some(input) => {
for input in input {
self.input_closed |= matches!(input, Ok(Event::InputClosed));
self.update(model, input?, ui);
count += 1;
}
}
None => {
return Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
"input event stream is closed",
));
}
}
if let Err(e) = self.print(None) {
return Err(io::Error::new(e.kind(), format!("output error {e}")));
}
if !self.run_without_input && self.input_closed {
return Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
"input is closed",
));
}
Ok(count)
}
pub fn refresh_time_sender(&self) -> Sender<Duration> {
self.refresh_time_sender.clone()
}
pub fn screen_mut(&mut self) -> &mut Screen {
&mut self.screen
}
pub fn input(&'a mut self) -> impl Stream<Item = io::Result<Event<A>>> + 'a {
Merge::new(&mut self.events, &mut self.events_included)
}
pub fn replace_input<A2: AppEvent, F>(self, replacement: F) -> Termit<'a, A2>
where
F: Fn(EventStream<'a, A>) -> EventStream<'a, A2>,
{
let Self {
control,
events,
events_included,
out,
screen,
painter,
run_without_input,
refresh_time_sender,
input_closed,
} = self;
let events = replacement(events);
let events_included = replacement(events_included);
Termit {
control,
events,
events_included,
out,
screen,
painter,
run_without_input,
refresh_time_sender,
input_closed,
}
}
pub fn include_input(
mut self,
additional: impl Stream<Item = io::Result<Event<A>>> + 'a,
) -> Self {
self.events = Box::pin(Merge::new(self.events, additional));
self
}
pub fn include_app_input(mut self, additional: impl Stream<Item = A> + 'a) -> Self {
self.events = Box::pin(Merge::new(
self.events,
additional.map(|ev| Ok(Event::App(ev))),
));
self
}
pub fn print(&mut self, scope: Option<Window>) -> io::Result<()> {
let mut out = CountingWrites::new(self.out.as_mut());
let commander = (self.control.as_ref(), &mut out);
self.screen.print(scope, commander)?;
if out.count != 0 {
trace!("IO: {}", out.count);
}
if out.busy {
self.out.flush()?;
}
Ok(())
}
pub fn update<M>(&mut self, model: &mut M, input: Event<A>, root: &mut impl Widget<M, A>) {
if let Event::Resize(new_size) = input {
if self.screen.size() != new_size {
self.screen.resize(new_size)
}
self.painter = self.painter.with_scope(new_size.window());
}
root.update_asserted(model, &input, &mut self.screen, &self.painter);
}
pub fn enter_raw_mode(mut self) -> io::Result<Self> {
self.send(EnterRawMode)?;
Ok(self)
}
pub fn use_alternate_screen(mut self, alt_screen: bool) -> io::Result<Self> {
self.send(UseAlternateScreen(alt_screen))?;
Ok(self)
}
pub fn capture_mouse(mut self, capture: bool) -> io::Result<Self> {
self.send(CaptureMouse(capture))?;
Ok(self)
}
pub fn enable_line_wrap(mut self, enable: bool) -> io::Result<Self> {
self.send(WrapLines(enable))?;
Ok(self)
}
pub fn show_cursor(mut self, show: bool) -> io::Result<Self> {
self.send(ShowCursor(show))?;
Ok(self)
}
pub fn handle_focus_events(mut self, handle: bool) -> io::Result<Self> {
self.send(CaptureFocus(handle))?;
Ok(self)
}
}
impl<'a> Termit<'a, NoAppEvent> {
pub fn with_app_event_type<A: AppEvent>(self) -> Termit<'a, A> {
self.replace_input(|stream| {
Box::pin(stream.map(|e| {
Ok(match e? {
Event::App(_) => panic!("No conversion between old and new app event"),
Event::Key(x) => Event::Key(x),
Event::Focus(f) => Event::Focus(f),
Event::CursorPosition(x) => Event::CursorPosition(x),
Event::Resize(x) => Event::Resize(x),
Event::Mouse(x) => Event::Mouse(x),
Event::Paste(x) => Event::Paste(x),
Event::Unknown(x) => Event::Unknown(x),
Event::Refresh(x) => Event::Refresh(x),
Event::InputClosed => Event::InputClosed,
})
}))
})
}
}
impl<'a, C, I, O, A: AppEvent> From<Terminal<C, I, O>> for Termit<'a, A>
where
C: Control + 'a,
I: AsyncRead + 'a,
O: io::Write + 'a,
{
fn from(terminal: Terminal<C, I, O>) -> Self {
Termit::new(terminal.control, terminal.input, terminal.output)
}
}
impl<A: AppEvent> TerminalCommander for Termit<'_, A> {
fn send<T: crate::output::command::TerminalRequest>(&mut self, request: T) -> io::Result<()> {
let mut commander = (self.control.as_ref(), self.out.as_mut());
commander.send(request)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum NoAppEvent {}
impl Default for NoAppEvent {
fn default() -> Self {
unreachable!("cannot construct empty enum")
}
}
struct TerminalResetOnDrop<C: Control, W: io::Write>(C, W);
impl<C: Control, W: io::Write> io::Write for TerminalResetOnDrop<C, W> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
W::write(&mut self.1, buf)
}
fn flush(&mut self) -> io::Result<()> {
W::flush(&mut self.1)
}
}
impl<C: Control, W: io::Write> TerminalCommander for TerminalResetOnDrop<C, W> {
fn send<T: TerminalRequest>(&mut self, request: T) -> io::Result<()> {
(&self.0, &mut self.1).send(request)
}
}
impl<C: Control, W: io::Write> Drop for TerminalResetOnDrop<C, W> {
fn drop(&mut self) {
trace!("Resetting output");
self.send(UseAlternateScreen(false))
.expect("Reseting alt screen");
self.send(ResetStyles).expect("Reseting style");
self.send(CaptureMouse(false))
.expect("Reseting mouse capture");
self.send(ShowCursor(true)).expect("Reseting cursor");
self.send(WrapLines(true)).expect("Reseting line wrap");
self.send(CaptureFocus(false))
.expect("Reseting focus changes");
self.send(Flush).expect("Flushing reset");
}
}