use crate::*; fn track_widths (tracks: &[ArrangementTrack]) -> Vec<(usize, usize)> { let mut widths = vec![]; let mut total = 0; for track in tracks.iter() { let width = track.width; widths.push((width, total)); total += width; } widths.push((0, total)); widths } pub fn arranger_content_vertical ( view: &ArrangerView, factor: usize ) -> impl Widget + use<'_> { let tracks = view.model.tracks.as_slice(); let scenes = view.model.scenes.as_slice(); let cols = track_widths(tracks); let rows = ArrangementScene::ppqs(scenes, factor); let bg = view.color; let clip_bg = TuiTheme::border_bg(); let sep_fg = TuiTheme::separator_fg(false); let header_h = 3u16;//5u16; let scenes_w = 3 + ArrangementScene::longest_name(scenes) as u16; // x of 1st track let clock = &view.sequencer.transport.model.clock; let arrangement = Layers::new(move |add|{ let rows: &[(usize, usize)] = rows.as_ref(); let cols: &[(usize, usize)] = cols.as_ref(); let any_size = |_|Ok(Some([0,0])); // column separators add(&CustomWidget::new(any_size, move|to: &mut TuiOutput|{ let style = Some(Style::default().fg(sep_fg)); Ok(for x in cols.iter().map(|col|col.1) { let x = scenes_w + to.area().x() + x as u16; for y in to.area().y()..to.area().y2() { to.blit(&"▎", x, y, style); } }) }))?; // row separators add(&CustomWidget::new(any_size, move|to: &mut TuiOutput|{ Ok(for y in rows.iter().map(|row|row.1) { let y = to.area().y() + (y / PPQ) as u16 + 1; if y >= to.buffer.area.height { break } for x in to.area().x()..to.area().x2().saturating_sub(2) { if x < to.buffer.area.x && y < to.buffer.area.y { let cell = to.buffer.get_mut(x, y); cell.modifier = Modifier::UNDERLINED; cell.underline_color = sep_fg; } } }) }))?; // track titles let header = row!((track, w) in tracks.iter().zip(cols.iter().map(|col|col.0))=>{ // name and width of track let name = track.name.read().unwrap(); let player = &track.player; let max_w = w.saturating_sub(1).min(name.len()).max(2); let name = format!("▎{}", &name[0..max_w]); let name = TuiStyle::bold(name, true); // beats elapsed let elapsed = if let Some((_, Some(phrase))) = player.phrase.as_ref() { let length = phrase.read().unwrap().length; let elapsed = player.pulses_since_start().unwrap(); let elapsed = clock.timebase().format_beats_1_short( (elapsed as usize % length) as f64 ); format!("▎+{elapsed:>}") } else { String::from("▎") }; // beats until switchover let until_next = player.next_phrase.as_ref().map(|(t, _)|{ let target = t.pulse.get(); let current = clock.current.pulse.get(); if target > current { let remaining = target - current; format!("▎-{:>}", clock.timebase().format_beats_0_short(remaining)) } else { String::new() } }).unwrap_or(String::from("▎")); // name of active MIDI input let input = format!("▎>{}", track.player.midi_inputs.get(0) .map(|port|port.short_name()) .transpose()? .unwrap_or("(none)".into())); // name of active MIDI output let output = format!("▎<{}", track.player.midi_outputs.get(0) .map(|port|port.short_name()) .transpose()? .unwrap_or("(none)".into())); col!(name, /*input, output,*/ until_next, elapsed) .min_xy(w as u16, header_h) .bg(track.color.rgb) .push_x(scenes_w) }); // scene titles let scene_name = |scene: &ArrangementScene, playing: bool, height|row!( if playing { "▶ " } else { " " }, TuiStyle::bold(scene.name.read().unwrap().as_str(), true), ).fixed_xy(scenes_w, height); // scene clips let scene_clip = |scene: &ArrangementScene, track: usize, w: u16, h: u16|Layers::new(move |add|{ let mut bg = clip_bg; match (tracks.get(track), scene.clips.get(track)) { (Some(track), Some(Some(phrase))) => { let name = &(phrase as &Arc>).read().unwrap().name; let name = format!("{}", name); let max_w = name.len().min((w as usize).saturating_sub(2)); let color = phrase.read().unwrap().color; add(&name.as_str()[0..max_w].push_x(1).fixed_x(w))?; bg = color.dark.rgb; if let Some((_, Some(ref playing))) = track.player.phrase { if *playing.read().unwrap() == *phrase.read().unwrap() { bg = color.light.rgb } }; }, _ => {} }; add(&Background(bg)) }).fixed_xy(w, h); // tracks and scenes let content = col!( // scenes: (scene, pulses) in scenes.iter().zip(rows.iter().map(|row|row.0)) => { let height = 1.max((pulses / PPQ) as u16); let playing = scene.is_playing(tracks); Stack::right(move |add| { // scene title: add(&scene_name(scene, playing, height).bg(scene.color.rgb))?; // clip per track: Ok(for (track, w) in cols.iter().map(|col|col.0).enumerate() { add(&scene_clip(scene, track, w as u16, height))?; }) }).fixed_y(height) } ).fixed_y((view.size.h() as u16).saturating_sub(header_h)); // full grid with header and footer add(&col!(header, content))?; // cursor add(&CustomWidget::new(any_size, move|to: &mut TuiOutput|{ let area = to.area(); let focused = view.focused; let selected = view.selected; let get_track_area = |t: usize| [ scenes_w + area.x() + cols[t].1 as u16, area.y(), cols[t].0 as u16, area.h(), ]; let get_scene_area = |s: usize| [ area.x(), header_h + area.y() + (rows[s].1 / PPQ) as u16, area.w(), (rows[s].0 / PPQ) as u16 ]; let get_clip_area = |t: usize, s: usize| [ scenes_w + area.x() + cols[t].1 as u16, header_h + area.y() + (rows[s].1/PPQ) as u16, cols[t].0 as u16, (rows[s].0 / PPQ) as u16 ]; let mut track_area: Option<[u16;4]> = None; let mut scene_area: Option<[u16;4]> = None; let mut clip_area: Option<[u16;4]> = None; let area = match selected { ArrangementEditorFocus::Mix => area, ArrangementEditorFocus::Track(t) => { track_area = Some(get_track_area(t)); area }, ArrangementEditorFocus::Scene(s) => { scene_area = Some(get_scene_area(s)); area }, ArrangementEditorFocus::Clip(t, s) => { track_area = Some(get_track_area(t)); scene_area = Some(get_scene_area(s)); clip_area = Some(get_clip_area(t, s)); area }, }; let bg = TuiTheme::border_bg(); if let Some([x, y, width, height]) = track_area { to.fill_fg([x, y, 1, height], bg); to.fill_fg([x + width, y, 1, height], bg); } if let Some([_, y, _, height]) = scene_area { to.fill_ul([area.x(), y - 1, area.w(), 1], bg); to.fill_ul([area.x(), y + height - 1, area.w(), 1], bg); } Ok(if focused { to.render_in(if let Some(clip_area) = clip_area { clip_area } else if let Some(track_area) = track_area { track_area.clip_h(header_h) } else if let Some(scene_area) = scene_area { scene_area.clip_w(scenes_w) } else { area.clip_w(scenes_w).clip_h(header_h) }, &CORNERS)? }) })) }).bg(bg.rgb); let color = TuiTheme::title_fg(view.focused); let size = format!("{}x{}", view.size.w(), view.size.h()); let lower_right = TuiStyle::fg(size, color).pull_x(1).align_se().fill_xy(); lay!(arrangement, lower_right) }