wip6 (60e):

This commit is contained in:
🪞👃🪞 2024-12-09 18:31:53 +01:00
parent 7d78811f68
commit 3b73da86e0
25 changed files with 212 additions and 227 deletions

181
crates/tek/src/tui.rs Normal file
View file

@ -0,0 +1,181 @@
use crate::*;
submod! {
engine_focus
engine_input
engine_output
engine_style
app_arranger
app_sequencer
app_transport
jack_transport
jack_sequencer
jack_arranger
ctrl_arranger
ctrl_file_browser
ctrl_phrase_editor
ctrl_phrase_length
ctrl_phrase_list
ctrl_phrase_rename
ctrl_sequencer
ctrl_transport
model_arranger
model_clock
model_file_browser
model_phrase_editor
model_phrase_length
model_phrase_list
model_phrase_player
view_arranger
view_file_browser
view_phrase_editor
view_phrase_length
view_phrase_list
view_phrase_selector
view_sequencer
view_status_bar
view_transport
}
#[macro_export] macro_rules! render {
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
impl $(<$($L),*$($T $(: $U)?),*>)? Render<Tui> for $Struct $(<$($L),*$($T),*>)? {
fn min_size (&$self, to: [u16;2]) -> Perhaps<[u16;2]> {
$cb.min_size(to)
}
fn render (&$self, to: &mut TuiOutput) -> Usually<()> {
$cb.render(to)
}
}
}
}
pub struct Tui {
pub exited: Arc<AtomicBool>,
pub buffer: Buffer,
pub backend: CrosstermBackend<Stdout>,
pub area: [u16;4], // FIXME auto resize
}
impl crate::core::Engine for Tui {
type Unit = u16;
type Size = [Self::Unit;2];
type Area = [Self::Unit;4];
type Input = TuiInput;
type Handled = bool;
type Output = TuiOutput;
fn exited (&self) -> bool {
self.exited.fetch_and(true, Ordering::Relaxed)
}
fn setup (&mut self) -> Usually<()> {
let better_panic_handler = Settings::auto().verbosity(Verbosity::Full).create_panic_handler();
std::panic::set_hook(Box::new(move |info: &std::panic::PanicHookInfo|{
stdout().execute(LeaveAlternateScreen).unwrap();
CrosstermBackend::new(stdout()).show_cursor().unwrap();
disable_raw_mode().unwrap();
better_panic_handler(info);
}));
stdout().execute(EnterAlternateScreen)?;
self.backend.hide_cursor()?;
enable_raw_mode().map_err(Into::into)
}
fn teardown (&mut self) -> Usually<()> {
stdout().execute(LeaveAlternateScreen)?;
self.backend.show_cursor()?;
disable_raw_mode().map_err(Into::into)
}
}
impl Tui {
/// Run the main loop.
pub fn run <R: Component<Tui> + Sized + 'static> (
state: Arc<RwLock<R>>
) -> Usually<Arc<RwLock<R>>> {
let backend = CrosstermBackend::new(stdout());
let area = backend.size()?;
let engine = Self {
exited: Arc::new(AtomicBool::new(false)),
buffer: Buffer::empty(area),
area: area.xywh(),
backend,
};
let engine = Arc::new(RwLock::new(engine));
let _input_thread = Self::spawn_input_thread(&engine, &state, Duration::from_millis(100));
engine.write().unwrap().setup()?;
let render_thread = Self::spawn_render_thread(&engine, &state, Duration::from_millis(10));
render_thread.join().expect("main thread failed");
engine.write().unwrap().teardown()?;
Ok(state)
}
fn spawn_input_thread <R: Component<Tui> + Sized + 'static> (
engine: &Arc<RwLock<Self>>, state: &Arc<RwLock<R>>, poll: Duration
) -> JoinHandle<()> {
let exited = engine.read().unwrap().exited.clone();
let state = state.clone();
spawn(move || loop {
if exited.fetch_and(true, Ordering::Relaxed) {
break
}
if ::crossterm::event::poll(poll).is_ok() {
let event = TuiEvent::Input(::crossterm::event::read().unwrap());
match event {
key!(Ctrl-KeyCode::Char('c')) => {
exited.store(true, Ordering::Relaxed);
},
_ => {
let exited = exited.clone();
if let Err(e) = state.write().unwrap().handle(&TuiInput { event, exited }) {
panic!("{e}")
}
}
}
}
})
}
fn spawn_render_thread <R: Component<Tui> + Sized + 'static> (
engine: &Arc<RwLock<Self>>, state: &Arc<RwLock<R>>, sleep: Duration
) -> JoinHandle<()> {
let exited = engine.read().unwrap().exited.clone();
let engine = engine.clone();
let state = state.clone();
let size = engine.read().unwrap().backend.size().expect("get size failed");
let mut buffer = Buffer::empty(size);
spawn(move || loop {
if exited.fetch_and(true, Ordering::Relaxed) {
break
}
let size = engine.read().unwrap().backend.size()
.expect("get size failed");
if let Ok(state) = state.try_read() {
if buffer.area != size {
engine.write().unwrap().backend.clear_region(ClearType::All)
.expect("clear failed");
buffer.resize(size);
buffer.reset();
}
let mut output = TuiOutput { buffer, area: size.xywh() };
state.render(&mut output).expect("render failed");
buffer = engine.write().unwrap().flip(output.buffer, size);
}
std::thread::sleep(sleep);
})
}
fn flip (&mut self, mut buffer: Buffer, size: ratatui::prelude::Rect) -> Buffer {
if self.buffer.area != size {
self.backend.clear_region(ClearType::All).unwrap();
self.buffer.resize(size);
self.buffer.reset();
}
let updates = self.buffer.diff(&buffer);
self.backend.draw(updates.into_iter()).expect("failed to render");
self.backend.flush().expect("failed to flush output buffer");
std::mem::swap(&mut self.buffer, &mut buffer);
buffer.reset();
buffer
}
}