diff --git a/crates/tek_tui/src/tui_view_phrase_editor.rs b/crates/tek_tui/src/tui_view_phrase_editor.rs index f2787561..187ffff7 100644 --- a/crates/tek_tui/src/tui_view_phrase_editor.rs +++ b/crates/tek_tui/src/tui_view_phrase_editor.rs @@ -84,108 +84,11 @@ impl<'a> Content for PhraseView<'a> { now, .. } = self; - let keys = move|to: &mut TuiOutput|Ok(if to.area().h() >= 2 { - for y in to.area.y()..to.area.y2() { - let n = y - to.area.y(); - let m = note_hi.saturating_sub(n as usize*2); - //let c = format!("{m:>03} {n}"); - //to.blit(&c, to.area.x(), y, None); - let x = to.area.x(); - let s = Some(Style::default().fg(Color::Rgb(255,255,255)).bg(Color::Rgb(0,0,0))); - match m % 2 { - 1 => match (m / 2) % 6 { - 5 => to.blit(&"▀", x, y, s), - 4 => to.blit(&"▀", x, y, s), - 3 => to.blit(&"▀", x, y, s), - 2 => to.blit(&"█", x, y, s), - 1 => to.blit(&"▄", x, y, s), - 0 => to.blit(&"▄", x, y, s), - _ => unreachable!(), - }, - 0 => match (m / 2) % 6 { - 5 => to.blit(&"▄", x, y, s), - 4 => to.blit(&"▄", x, y, s), - 3 => to.blit(&"▄", x, y, s), - 2 => to.blit(&"▀", x, y, s), - 1 => to.blit(&"▀", x, y, s), - 0 => to.blit(&"█", x, y, s), - _ => unreachable!(), - }, - _ => unreachable!() - } - - } - }); - let notes_bg_null = Color::Rgb(28, 35, 25); - let notes = move|to: &mut TuiOutput|{ - let area = to.area(); - let h = area.h() as usize; - size.set_wh(area.w(), h - 1); - Ok(if to.area().h() >= 2 { - let area = to.area(); - view_mode.blit(buffer, &mut to.buffer, area, *time_start, *note_hi); - //to.buffer_update(area, &move |cell, x, y|{ - //cell.set_bg(notes_bg_null); - //let src_x = (x as usize + time_start) * time_scale; - //let src_y = y as usize + note_lo / 2; - //if src_x < buffer.width && src_y < buffer.height - 1 { - //buffer.get(src_x, (note_hi - y as usize) / 2).map(|src|{ - //cell.set_symbol(src.symbol()); - //cell.set_fg(src.fg); - //cell.set_bg(src.bg); - //}); - //} - //}); - }) - }; - let cursor = move|to: &mut TuiOutput|Ok(if *focused && *entered { - let area = to.area(); - let x1 = area.x() + (time_point / time_scale) as u16; - let x2 = x1 + (note_len / time_scale) as u16; - let y = area.y() + (note_hi - note_point) as u16 / 2; - let c = if note_lo % 2 == 0 { - if note_point % 2 == 0 { "▄" } else { "▀" } - } else { - if note_point % 2 == 0 { "▀" } else { "▄" } - }; - let style = Some(Style::default().fg(Color::Rgb(0,255,0))); - for x in x1..x2 { - to.blit(&c, x, y, style); - } - }); + let border_color = if *focused{Color::Rgb(100, 110, 40)}else{Color::Rgb(70, 80, 50)}; + let title_color = if *focused{Color::Rgb(150, 160, 90)}else{Color::Rgb(120, 130, 100)}; + let border = Lozenge(Style::default().bg(Color::Rgb(40, 50, 30)).fg(border_color)); let playhead_inactive = Style::default().fg(Color::Rgb(255,255,255)).bg(Color::Rgb(40,50,30)); let playhead_active = playhead_inactive.clone().yellow().bold().not_dim(); - let playhead = move|to: &mut TuiOutput|{ - if let Some(_) = phrase { - let now = now.get() as usize; // TODO FIXME: self.now % phrase.read().unwrap().length; - let time_clamp = time_clamp; - for x in 0..(time_clamp/time_scale).saturating_sub(*time_start) { - let this_step = time_start + (x + 0) * time_scale; - let next_step = time_start + (x + 1) * time_scale; - let x = to.area().x() + x as u16; - let active = this_step <= now && now < next_step; - let character = if active { "|" } else { "·" }; - let style = if active { playhead_active } else { playhead_inactive }; - to.blit(&character, x, to.area.y(), Some(style)); - } - } - Ok(()) - }; - let border_color = if *focused{Color::Rgb(100, 110, 40)}else{Color::Rgb(70, 80, 50)}; - let title_color = if *focused{Color::Rgb(150, 160, 90)}else{Color::Rgb(120, 130, 100)}; - let border = Lozenge(Style::default().bg(Color::Rgb(40, 50, 30)).fg(border_color)); - let note_area = lay!( - CustomWidget::new(|to|Ok(Some(to)), notes).fill_x(), - CustomWidget::new(|to|Ok(Some(to)), cursor) - ).fill_x(); - let piano_roll = row!( - CustomWidget::new(|to:[u16;2]|Ok(Some(to.clip_w(2))), keys).fill_y(), - note_area - ).fill_x().bg(Color::Rgb(40, 50, 30)).border(border); - let content = lay!( - piano_roll, - CustomWidget::new(|to:[u16;2]|Ok(Some(to.clip_h(1))), playhead).push_x(6).align_sw() - ); let mut name = "".to_string(); if let Some(phrase) = phrase { name = phrase.read().unwrap().name.clone(); @@ -195,21 +98,59 @@ impl<'a> Content for PhraseView<'a> { let mut lower_right = format!("┤{}├", size.format()); if *focused && *entered { lower_right = format!("┤Note: {} ({}) {}├─{lower_right}", - note_point, - to_note_name(*note_point), - pulses_to_name(*note_len) + note_point, to_note_name(*note_point), pulses_to_name(*note_len) ); } let mut upper_right = format!("[{}]", if *entered {"■"} else {" "}); if let Some(phrase) = phrase { upper_right = format!("┤Time: {}/{} {}├{upper_right}", - time_point, - phrase.read().unwrap().length, - pulses_to_name(*time_scale), + time_point, phrase.read().unwrap().length, pulses_to_name(*time_scale), ) }; lay!( - content, + row!( + CustomWidget::new(|to:[u16;2]|Ok(Some(to.clip_w(2))), move|to: &mut TuiOutput|{ + Ok(if to.area().h() >= 2 { + view_mode.blit_keys(to, *note_hi) + }) + }).fill_y(), + lay!( + CustomWidget::new(|to|Ok(Some(to)), |to: &mut TuiOutput|{ + size.set_wh(to.area.w(), to.area.h() as usize - 1); + let draw = to.area().h() >= 2; + Ok(if draw { + view_mode.blit_notes( + to, buffer, *time_start, *note_hi + ) + }) + }).fill_x(), + CustomWidget::new(|to|Ok(Some(to)), move|to: &mut TuiOutput|{ + Ok(if *focused && *entered { + view_mode.blit_cursor( + to, + *time_point, *time_scale, + *note_len, *note_hi, *note_lo, *note_point + ) + }) + }) + ).fill_x() + ).fill_x().bg(Color::Rgb(40, 50, 30)).border(border), + CustomWidget::new(|to:[u16;2]|Ok(Some(to.clip_h(1))), move|to: &mut TuiOutput|{ + if let Some(_) = phrase { + let now = now.get() as usize; // TODO FIXME: self.now % phrase.read().unwrap().length; + let time_clamp = time_clamp; + for x in 0..(time_clamp/time_scale).saturating_sub(*time_start) { + let this_step = time_start + (x + 0) * time_scale; + let next_step = time_start + (x + 1) * time_scale; + let x = to.area().x() + x as u16; + let active = this_step <= now && now < next_step; + let character = if active { "|" } else { "·" }; + let style = if active { playhead_active } else { playhead_inactive }; + to.blit(&character, x, to.area.y(), Some(style)); + } + } + Ok(()) + }).push_x(6).align_sw(), TuiStyle::fg(upper_left.to_string(), title_color).push_x(1).align_nw(), TuiStyle::fg(lower_left.to_string(), title_color).push_x(1).align_sw(), TuiStyle::fg(upper_right.to_string(), title_color).pull_x(1).align_ne().fill_xy(), @@ -253,14 +194,15 @@ impl PhraseViewMode { buffer } /// Draw a subsection of the [BigBuffer] onto a regular ratatui [Buffer]. - fn blit ( + fn blit_notes ( &self, + target: &mut TuiOutput, source: &BigBuffer, - target: &mut Buffer, - area: impl Area, time_start: usize, note_hi: usize, ) { + let area = target.area(); + let target = &mut target.buffer; match self { Self::PianoHorizontal { .. } => { let [x0, y0, w, h] = area.xywh(); @@ -284,6 +226,61 @@ impl PhraseViewMode { _ => unimplemented!() } } + fn blit_keys (&self, to: &mut TuiOutput, note_hi: usize) { + for y in to.area.y()..to.area.y2() { + let n = y - to.area.y(); + let m = note_hi.saturating_sub(n as usize*2); + //let c = format!("{m:>03} {n}"); + //to.blit(&c, to.area.x(), y, None); + let x = to.area.x(); + let s = Some(Style::default().fg(Color::Rgb(255,255,255)).bg(Color::Rgb(0,0,0))); + match m % 2 { + 1 => match (m / 2) % 6 { + 5 => to.blit(&"▀", x, y, s), + 4 => to.blit(&"▀", x, y, s), + 3 => to.blit(&"▀", x, y, s), + 2 => to.blit(&"█", x, y, s), + 1 => to.blit(&"▄", x, y, s), + 0 => to.blit(&"▄", x, y, s), + _ => unreachable!(), + }, + 0 => match (m / 2) % 6 { + 5 => to.blit(&"▄", x, y, s), + 4 => to.blit(&"▄", x, y, s), + 3 => to.blit(&"▄", x, y, s), + 2 => to.blit(&"▀", x, y, s), + 1 => to.blit(&"▀", x, y, s), + 0 => to.blit(&"█", x, y, s), + _ => unreachable!(), + }, + _ => unreachable!() + } + } + } + fn blit_cursor ( + &self, + to: &mut TuiOutput, + time_point: usize, + time_scale: usize, + note_len: usize, + note_hi: usize, + note_lo: usize, + note_point: usize, + ) { + let area = to.area(); + let x1 = area.x() + (time_point / time_scale) as u16; + let x2 = x1 + (note_len / time_scale) as u16; + let y = area.y() + (note_hi - note_point) as u16 / 2; + let c = if note_lo % 2 == 0 { + if note_point % 2 == 0 { "▄" } else { "▀" } + } else { + if note_point % 2 == 0 { "▀" } else { "▄" } + }; + let style = Some(Style::default().fg(Color::Rgb(0,255,0))); + for x in x1..x2 { + to.blit(&c, x, y, style); + } + } /// Determine the required width to render the phrase. fn buffer_width (&self, phrase: &Phrase) -> usize { match self {