mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-07 20:26:42 +01:00
wip6 (60e):
This commit is contained in:
parent
7d78811f68
commit
3b73da86e0
25 changed files with 212 additions and 227 deletions
181
crates/tek/src/tui.rs
Normal file
181
crates/tek/src/tui.rs
Normal 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
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue