don't crash on add track/scene

This commit is contained in:
🪞👃🪞 2024-07-12 14:46:20 +03:00
parent 5a9ec0a63d
commit 6a738375e2
9 changed files with 205 additions and 207 deletions

View file

@ -148,17 +148,12 @@ pub fn input_thread (
// Listen for events and send them to the main thread
if ::crossterm::event::poll(poll).is_ok() {
let event = ::crossterm::event::read().unwrap();
match event {
Event::Key(KeyEvent {
code: KeyCode::Char('c'),
modifiers: KeyModifiers::CONTROL,
..
}) => {
exited.store(true, Ordering::Relaxed);
},
_ => if device.write().unwrap().handle(&AppEvent::Input(event)).is_err() {
break
}
if let Event::Key(KeyEvent {
code: KeyCode::Char('c'), modifiers: KeyModifiers::CONTROL, ..
}) = event {
exited.store(true, Ordering::Relaxed);
} else if let Err(e) = device.write().unwrap().handle(&AppEvent::Input(event)) {
panic!("{e}")
}
}
})

View file

@ -1,6 +1,8 @@
use crate::core::*;
pub(crate) use ratatui::prelude::*;
pub(crate) use ratatui::buffer::Cell;
pub(crate) use ratatui::prelude::CrosstermBackend;
pub(crate) use ratatui::style::{Stylize, Style, Color};
pub(crate) use ratatui::layout::Rect;
pub(crate) use ratatui::buffer::{Buffer, Cell};
use ratatui::widgets::WidgetRef;
pub fn fill_bg (buf: &mut Buffer, area: Rect, color: Color) {
@ -105,72 +107,6 @@ impl WidgetRef for dyn Render {
}
}
pub struct LineBuffer {
width: usize,
cells: Vec<Cell>,
style: Option<Style>,
bg: Cell,
}
impl LineBuffer {
pub fn new (bg: Cell, width: usize, height: usize) -> Self {
Self {
style: None,
width: width,
cells: vec![bg.clone();width*height],
bg,
}
}
pub fn height (&self) -> usize {
self.cells.len() / self.width
}
pub fn style (&mut self, style: Style) -> &mut Self {
self.style = Some(style);
self
}
pub fn no_style (&mut self) -> &mut Self {
self.style = None;
self
}
pub fn put (&mut self, data: &str, x: usize, y: usize) -> &mut Self {
if x < self.width {
for (i, c) in data.chars().enumerate() {
if x + i >= self.width {
break;
}
let index = y * self.width + x + i;
while index >= self.cells.len() {
self.cells.extend_from_slice(&vec![self.bg.clone();self.width]);
}
self.cells[index].set_char(c);
if let Some(s) = self.style {
self.cells[index].set_style(s);
}
}
}
self
}
pub fn show (&self, buf: &mut Buffer, area: Rect, offset: isize) -> Usually<Rect> {
let Rect { x, mut y, width, height } = area;
for row in offset..self.height() as isize {
let length = self.cells.len();
let start = ((row.max(0) as usize)*self.width).min(length);
let end = (((row + 1).max(0) as usize)*self.width).min(length);
for (column, cell) in self.cells[start..end].iter().enumerate() {
if column >= width as usize {
break
}
*buf.get_mut(x + column as u16, y + row as u16) = cell.clone();
}
y = y + 1;
if y > height {
break
}
}
Ok(area)
}
}
#[cfg(test)]
mod test {
use super::*;
@ -190,6 +126,5 @@ mod test {
assert_eq!(buffer.cells.len(), 12);
buffer.put("FOO", 0, 1);
assert_eq!(buffer.cells.len(), 24);
}
}

View file

@ -1,4 +1,4 @@
use crate::{core::*, model::App};
use crate::{core::*, model::*};
impl App {
pub fn next_scene (&mut self) {
@ -11,8 +11,11 @@ impl App {
format!("Scene {}", self.scenes.len() + 1)
}
pub fn add_scene (&mut self, name: Option<&str>) -> Usually<&mut Scene> {
let name = name.ok_or_else(||self.new_scene_name())?;
self.scenes.push(Scene::new(&name, vec![]));
let clips = vec![None;self.tracks.len()];
self.scenes.push(match name {
Some(name) => Scene::new(name, clips),
None => Scene::new(&self.new_scene_name(), clips)
});
self.scene_cursor = self.scenes.len();
Ok(&mut self.scenes[self.scene_cursor - 1])
}

View file

@ -1,5 +1,4 @@
use crate::core::*;
use crate::model::*;
use crate::{core::*, model::*};
impl App {
pub fn next_track (&mut self) {
@ -8,32 +7,29 @@ impl App {
pub fn prev_track (&mut self) {
self.track_cursor = self.track_cursor.saturating_sub(1);
}
pub fn new_track_name (&self) -> String {
format!("Track {}", self.tracks.len() + 1)
}
pub fn add_track (&mut self, name: Option<&str>) -> Usually<&mut Track> {
let name = name.ok_or_else(||self.new_track_name())?;
self.tracks.push(Track::new(&name, None, None)?);
self.tracks.push(match name {
Some(name) => Track::new(name, None, None)?,
None => Track::new(&self.new_track_name(), None, None)?
});
self.track_cursor = self.tracks.len();
Ok(&mut self.tracks[self.track_cursor - 1])
}
pub fn track (&self) -> Option<(usize, &Track)> {
match self.track_cursor { 0 => None, _ => {
let id = self.track_cursor as usize - 1;
self.tracks.get(id).map(|t|(id, t))
} }
}
pub fn track_mut (&mut self) -> Option<(usize, &mut Track)> {
match self.track_cursor { 0 => None, _ => {
let id = self.track_cursor as usize - 1;
self.tracks.get_mut(id).map(|t|(id, t))
} }
}
}
pub struct Track {

View file

@ -5,7 +5,9 @@ pub mod transport;
pub mod plugin;
pub mod border;
pub mod theme;
pub mod split;
pub use self::split::*;
pub use self::border::*;
pub use self::theme::*;
pub use self::arranger::*;
@ -28,70 +30,3 @@ render!(App |self, buf, area| {
}
Ok(area)
});
pub struct If<'a>(pub bool, pub &'a (dyn Render + Sync));
impl<'a> Render for If<'a> {
fn render (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
if self.0 {
self.1.render(buf, area)
} else {
().render(buf, area)
}
}
}
pub trait Modal<T>: Device {
fn handle_with_state (&self, state: &mut T, event: &AppEvent) -> Usually<bool>;
}
#[derive(Copy, Clone)]
pub enum Direction { Down, Right }
impl Direction {
pub fn split <'a, const N: usize> (&self, items: [&'a (dyn Render + Sync);N]) -> Split<'a, N> {
Split(*self, items)
}
pub fn is_down (&self) -> bool {
match self { Self::Down => true, _ => false }
}
pub fn is_right (&self) -> bool {
match self { Self::Right => true, _ => false }
}
}
pub struct Split<'a, const N: usize>(
pub Direction, pub [&'a (dyn Render + Sync);N]
);
impl<'a, const N: usize> Split<'a, N> {
pub fn down (items: [&'a (dyn Render + Sync);N]) -> Self {
Self(Direction::Down, items)
}
pub fn right (items: [&'a (dyn Render + Sync);N]) -> Self {
Self(Direction::Right, items)
}
}
impl<'a, const N: usize> Render for Split<'a, N> {
fn render (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
let Rect { mut x, mut y, mut width, mut height } = area;
for item in self.1 {
if width == 0 || height == 0 {
break
}
let result = item.render(buf, Rect { x, y, width, height })?;
match self.0 {
Direction::Down => {
y = y + result.height;
height = height.saturating_sub(result.height);
},
Direction::Right => {
x = x + result.width;
width = width.saturating_sub(result.width);
},
}
}
Ok(area)
}
}

View file

@ -83,15 +83,14 @@ impl<'a> ArrangerView<'a> {
if y + 2 * scene_index as u16 >= height {
break
}
let label = if let Some(Some(clip)) = scene.clips.get(track_index) {
if let Some(phrase) = track.phrases.get(*clip) {
let icon = match track.sequence { Some(_) => "", None => "" };
let label = match scene.clips.get(track_index) {
Some(Some(clip)) => if let Some(phrase) = track.phrases.get(*clip) {
let icon = if track.sequence == Some(*clip) { "" } else { "" };
format!("{icon} {}", phrase.name)
} else {
format!(" ??? ")
}
} else {
format!("┊ ········")
},
_ => format!("┊ ········")
};
let hi = (track_index + 1 == self.cursor.0) && (scene_index + 1 == self.cursor.1);
let style = Some(Nord::style_hi(self.focused, hi));

View file

@ -1,6 +1,4 @@
use crate::core::*;
use crate::model::*;
use crate::view::{*, Direction};
use crate::{core::*, model::*, view::*};
pub struct ChainView<'a> {
pub track: Option<&'a Track>,
@ -58,42 +56,3 @@ impl<'a> Render for ChainView<'a> {
}
}
}
type Renderables<'a> = &'a [JackDevice];
impl<'a> Direction {
fn split_focus (&self, index: usize, items: Renderables<'a>, style: Style) -> SplitFocus<'a> {
SplitFocus(*self, index, items, style)
}
}
pub struct SplitFocus<'a>(pub Direction, pub usize, pub Renderables<'a>, pub Style);
impl<'a> Render for SplitFocus<'a> {
fn render (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
let Rect { mut x, mut y, mut width, mut height } = area;
let mut results = vec![];
for item in self.2.iter() {
if width == 0 || height == 0 {
break
}
let result = item.render(buf, Rect { x, y, width, height })?;
results.push(result);
match self.0 {
Direction::Down => {
y = y + result.height;
height = height.saturating_sub(result.height);
},
Direction::Right => {
x = x + result.width;
width = width.saturating_sub(result.width);
},
}
}
results
.get(self.1)
.map(|focused|Lozenge(self.3).draw(buf, Rect { width, ..*focused }))
.transpose()?;
Ok(area)
}
}

66
src/view/linebuf.rs Normal file
View file

@ -0,0 +1,66 @@
pub struct LineBuffer {
width: usize,
cells: Vec<Cell>,
style: Option<Style>,
bg: Cell,
}
impl LineBuffer {
pub fn new (bg: Cell, width: usize, height: usize) -> Self {
Self {
style: None,
width: width,
cells: vec![bg.clone();width*height],
bg,
}
}
pub fn height (&self) -> usize {
self.cells.len() / self.width
}
pub fn style (&mut self, style: Style) -> &mut Self {
self.style = Some(style);
self
}
pub fn no_style (&mut self) -> &mut Self {
self.style = None;
self
}
pub fn put (&mut self, data: &str, x: usize, y: usize) -> &mut Self {
if x < self.width {
for (i, c) in data.chars().enumerate() {
if x + i >= self.width {
break;
}
let index = y * self.width + x + i;
while index >= self.cells.len() {
self.cells.extend_from_slice(&vec![self.bg.clone();self.width]);
}
self.cells[index].set_char(c);
if let Some(s) = self.style {
self.cells[index].set_style(s);
}
}
}
self
}
pub fn show (&self, buf: &mut Buffer, area: Rect, offset: isize) -> Usually<Rect> {
let Rect { x, mut y, width, height } = area;
for row in offset..self.height() as isize {
let length = self.cells.len();
let start = ((row.max(0) as usize)*self.width).min(length);
let end = (((row + 1).max(0) as usize)*self.width).min(length);
for (column, cell) in self.cells[start..end].iter().enumerate() {
if column >= width as usize {
break
}
*buf.get_mut(x + column as u16, y + row as u16) = cell.clone();
}
y = y + 1;
if y > height {
break
}
}
Ok(area)
}
}

View file

@ -0,0 +1,110 @@
use crate::{core::*, view::*};
pub struct If<'a>(pub bool, pub &'a (dyn Render + Sync));
impl<'a> Render for If<'a> {
fn render (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
match self.0 {
true => self.1 as &dyn Render,
false => &() as &dyn Render
}.render(buf, area)
}
}
pub struct IfElse<'a>(pub bool, pub &'a (dyn Render + Sync), pub &'a (dyn Render + Sync));
impl<'a> Render for IfElse<'a> {
fn render (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
match self.0 {
true => self.1 as &dyn Render,
false => &() as &dyn Render
}.render(buf, area)
}
}
#[derive(Copy, Clone)]
pub enum Direction { Down, Right }
impl Direction {
pub fn split <'a, const N: usize> (&self, items: [&'a (dyn Render + Sync);N]) -> Split<'a, N> {
Split(*self, items)
}
pub fn split_focus <'a> (&self, index: usize, items: Renderables<'a>, style: Style) -> SplitFocus<'a> {
SplitFocus(*self, index, items, style)
}
pub fn is_down (&self) -> bool {
match self { Self::Down => true, _ => false }
}
pub fn is_right (&self) -> bool {
match self { Self::Right => true, _ => false }
}
}
pub struct Split<'a, const N: usize>(
pub Direction, pub [&'a (dyn Render + Sync);N]
);
impl<'a, const N: usize> Split<'a, N> {
pub fn down (items: [&'a (dyn Render + Sync);N]) -> Self {
Self(Direction::Down, items)
}
pub fn right (items: [&'a (dyn Render + Sync);N]) -> Self {
Self(Direction::Right, items)
}
}
impl<'a, const N: usize> Render for Split<'a, N> {
fn render (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
let Rect { mut x, mut y, mut width, mut height } = area;
for item in self.1 {
if width == 0 || height == 0 {
break
}
let result = item.render(buf, Rect { x, y, width, height })?;
match self.0 {
Direction::Down => {
y = y + result.height;
height = height.saturating_sub(result.height);
},
Direction::Right => {
x = x + result.width;
width = width.saturating_sub(result.width);
},
}
}
Ok(area)
}
}
type Renderables<'a> = &'a [JackDevice];
pub struct SplitFocus<'a>(pub Direction, pub usize, pub Renderables<'a>, pub Style);
impl<'a> Render for SplitFocus<'a> {
fn render (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
let Rect { mut x, mut y, mut width, mut height } = area;
let mut results = vec![];
for item in self.2.iter() {
if width == 0 || height == 0 {
break
}
let result = item.render(buf, Rect { x, y, width, height })?;
results.push(result);
match self.0 {
Direction::Down => {
y = y + result.height;
height = height.saturating_sub(result.height);
},
Direction::Right => {
x = x + result.width;
width = width.saturating_sub(result.width);
},
}
}
results
.get(self.1)
.map(|focused|Lozenge(self.3).draw(buf, Rect { width, ..*focused }))
.transpose()?;
Ok(area)
}
}