mirror of
https://codeberg.org/unspeaker/tek.git
synced 2026-09-18 04:46:43 +02:00
600 lines
22 KiB
Rust
600 lines
22 KiB
Rust
use crate::{*, arrange::*};
|
|
|
|
/// A track consists of a sequencer and zero or more devices chained after it.
|
|
///
|
|
/// ```
|
|
/// let track: tek::Track = Default::default();
|
|
/// ```
|
|
#[derive(Debug, Default)]
|
|
pub struct Track {
|
|
/// Name of track
|
|
pub name: Arc<str>,
|
|
/// Identifying color of track
|
|
pub color: ItemTheme,
|
|
/// Preferred width of track column
|
|
pub width: usize,
|
|
/// MIDI sequencer state
|
|
pub sequencer: Sequencer,
|
|
/// Device chain
|
|
pub devices: Vec<Device>,
|
|
}
|
|
|
|
impl Track {
|
|
/// Create a new track with only the default [Sequencer].
|
|
pub fn new (
|
|
name: &impl AsRef<str>,
|
|
color: Option<ItemTheme>,
|
|
jack: &Jack<'static>,
|
|
clock: Option<&Clock>,
|
|
clip: Option<&Arc<RwLock<MidiClip>>>,
|
|
midi_from: &[Connect],
|
|
midi_to: &[Connect],
|
|
) -> Usually<Self> {
|
|
Ok(Self {
|
|
name: name.as_ref().into(),
|
|
color: color.unwrap_or_default(),
|
|
sequencer: Sequencer::new(
|
|
format!("{}/sequencer", name.as_ref()), jack, clock, clip, midi_from, midi_to
|
|
)?,
|
|
..Default::default()
|
|
})
|
|
}
|
|
pub fn audio_ins (&self) -> &[AudioInput] {
|
|
self.devices.first().map(|x|x.audio_ins()).unwrap_or_default()
|
|
}
|
|
pub fn audio_outs (&self) -> &[AudioOutput] {
|
|
self.devices.last().map(|x|x.audio_outs()).unwrap_or_default()
|
|
}
|
|
pub fn per <'a, T: Draw<Tui> + 'a, U: TracksSizes<'a>> (
|
|
tracks: impl Fn() -> U + Send + Sync + 'a,
|
|
callback: &'a (impl Fn(usize, &'a Track)->T + Send + Sync + 'a)
|
|
) -> impl Draw<Tui> + 'a {
|
|
iter_east(move||tracks().map(|(index, track, x1, x2): (usize, &Track, usize, usize)|{
|
|
fg_bg(
|
|
track.color.lightest.term,
|
|
track.color.base.term,
|
|
callback(index, track)
|
|
).exact_w((x2 - x1) as u16)
|
|
}))
|
|
}
|
|
pub fn stop (&mut self) -> Perhaps<TrackCommand> {
|
|
self.sequencer.enqueue_next(None);
|
|
Ok(None)
|
|
}
|
|
}
|
|
|
|
impl HasWidth for Track {
|
|
const MIN_WIDTH: usize = 9;
|
|
fn width_inc (&mut self) {
|
|
self.width += 1;
|
|
}
|
|
fn width_dec (&mut self) {
|
|
if self.width > Track::MIN_WIDTH { self.width -= 1; }
|
|
}
|
|
}
|
|
|
|
impl<T: AsRefOpt<Track> + AsMutOpt<Track>> HasTrack for T {}
|
|
|
|
pub trait HasTrack: AsRefOpt<Track> + AsMutOpt<Track> {
|
|
fn track (&self) -> Option<&Track> {
|
|
self.as_ref_opt()
|
|
}
|
|
fn track_mut (&mut self) -> Option<&mut Track> {
|
|
self.as_mut_opt()
|
|
}
|
|
}
|
|
|
|
impl<T: HasTrack
|
|
+ Namespace<usize>
|
|
+ Namespace<Arc<str>>
|
|
+ Namespace<ItemTheme>
|
|
+ Namespace<Option<bool>>
|
|
> TrackController for T {}
|
|
|
|
#[tek_proc::commands(TrackCommand)]
|
|
pub trait TrackController: HasTrack
|
|
+ Namespace<usize>
|
|
+ Namespace<Arc<str>>
|
|
+ Namespace<ItemTheme>
|
|
+ Namespace<Option<bool>>
|
|
{
|
|
#[command(Stop = "track/stop")]
|
|
fn track_stop (&mut self) -> Perhaps<TrackCommand> {
|
|
self.track_mut().map(|track|track.sequencer.enqueue_next(None));
|
|
Ok(None)
|
|
}
|
|
#[command(SetMute = "track/mute")]
|
|
fn track_set_mute (&mut self, _mute: Option<bool>) -> Perhaps<TrackCommand> {
|
|
todo!()
|
|
}
|
|
#[command(SetSolo = "track/solo")]
|
|
fn track_set_solo (&mut self, _solo: Option<bool>) -> Perhaps<TrackCommand> {
|
|
todo!()
|
|
}
|
|
#[command(SetSize = "track/size")]
|
|
fn track_set_size (&mut self, _size: usize) -> Perhaps<TrackCommand> {
|
|
todo!()
|
|
}
|
|
#[command(SetZoom = "track/zoom")]
|
|
fn track_set_zoom (&mut self, _zoom: usize) -> Perhaps<TrackCommand> {
|
|
todo!()
|
|
}
|
|
#[command(SetName = "track/name")]
|
|
fn track_set_name (&mut self, name: Arc<str>) -> Perhaps<TrackCommand> {
|
|
self.track_mut()
|
|
.map(|track|swap_value(&mut track.name, &name, |name|TrackCommand::SetName { name }))
|
|
.transpose()
|
|
.map(Option::flatten)
|
|
}
|
|
#[command(SetColor = "track/color")]
|
|
fn track_set_color (&mut self, color: ItemTheme) -> Perhaps<TrackCommand> {
|
|
self.track_mut()
|
|
.map(|track|swap_value(&mut track.color, &color, |color|TrackCommand::SetColor { color }))
|
|
.transpose()
|
|
.map(Option::flatten)
|
|
}
|
|
#[command(SetRec = "track/rec")]
|
|
fn track_set_rec (&mut self, rec: Option<bool>) -> Perhaps<TrackCommand> {
|
|
self.track_mut()
|
|
.map(|track|toggle_bool(&mut track.sequencer.recording, &rec, |rec|TrackCommand::SetRec { rec }))
|
|
.transpose()
|
|
.map(Option::flatten)
|
|
}
|
|
#[command(SetMon = "track/mon")]
|
|
fn track_set_mon (&mut self, mon: Option<bool>) -> Perhaps<TrackCommand> {
|
|
self.track_mut()
|
|
.map(|track|toggle_bool(&mut track.sequencer.monitoring, &mon, |mon|TrackCommand::SetMon { mon }))
|
|
.transpose()
|
|
.map(Option::flatten)
|
|
}
|
|
}
|
|
|
|
pub(crate) fn track_width (_index: usize, track: &Track) -> u16 {
|
|
track.width as u16
|
|
}
|
|
|
|
def_sizes_iter!(TracksSizes => Track);
|
|
impl_has!(Vec<Track>: |self: Arrangement| self.tracks);
|
|
impl_as_ref!(Vec<Track>: |self: App| self.project.as_ref());
|
|
impl_as_mut!(Vec<Track>: |self: App| self.project.as_mut());
|
|
|
|
impl<T: AsRef<Vec<Track>> + AsMut<Vec<Track>> + HasClock + HasTrackScroll> HasTracks for T {}
|
|
|
|
pub trait HasTracks: AsRef<Vec<Track>> + AsMut<Vec<Track>> + HasClock + HasTrackScroll {
|
|
/// Spacing between tracks.
|
|
const TRACK_SPACING: usize = 0;
|
|
|
|
/// Read-only reference to collection of [Track]s.
|
|
fn tracks (&self) -> &Vec<Track> {
|
|
self.as_ref()
|
|
}
|
|
|
|
/// Mutable reference to collection of [Track]s.
|
|
fn tracks_mut (&mut self) -> &mut Vec<Track> {
|
|
self.as_mut()
|
|
}
|
|
|
|
/// Run audio callbacks for every track and every device
|
|
fn tracks_jack_process (&mut self, client: &Client, scope: &ProcessScope) -> Control
|
|
where Self: HasJack<'static>
|
|
{
|
|
for track in self.tracks_mut().iter_mut() {
|
|
if Control::Quit == Audio::process(&mut track.sequencer, client, scope) {
|
|
return Control::Quit
|
|
}
|
|
for device in track.devices.iter_mut() {
|
|
if Control::Quit == DeviceAudio(device).process(client, scope) {
|
|
return Control::Quit
|
|
}
|
|
}
|
|
}
|
|
Control::Continue
|
|
}
|
|
|
|
/// Add multiple tracks
|
|
fn tracks_add_many (
|
|
&mut self,
|
|
count: usize,
|
|
width: Option<usize>,
|
|
mins: Arc<[Connect]>,
|
|
mouts: Arc<[Connect]>,
|
|
) -> Usually<()>
|
|
where Self: HasJack<'static> + HasScenes
|
|
{
|
|
let track_color_1 = ItemColor::random();
|
|
let track_color_2 = ItemColor::random();
|
|
for i in 0..count {
|
|
let color = track_color_1.mix(track_color_2, i as f32 / count as f32).into();
|
|
let track = self.tracks_add_one(None, Some(color), mins.clone(), mouts.clone())?.1;
|
|
if let Some(width) = width {
|
|
track.width = width;
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Add a track
|
|
fn tracks_add_one (
|
|
&mut self,
|
|
name: Option<Arc<str>>,
|
|
color: Option<ItemTheme>,
|
|
mins: Arc<[Connect]>,
|
|
mouts: Arc<[Connect]>,
|
|
) -> Usually<(usize, &mut Track)>
|
|
where Self: HasJack<'static> + HasScenes
|
|
{
|
|
let name: Arc<str> = name.map_or_else(
|
|
||format!("trk{:02}", self.tracks_last()).into(),
|
|
|x|x.to_string().into()
|
|
);
|
|
*self.tracks_last_mut() += 1;
|
|
let track = Track {
|
|
width: (name.len() + 2).max(12),
|
|
color: color.unwrap_or_else(ItemTheme::random),
|
|
sequencer: Sequencer::new(
|
|
&format!("{name}"),
|
|
self.jack(),
|
|
Some(self.clock()),
|
|
None,
|
|
mins.as_ref(),
|
|
mouts.as_ref()
|
|
)?,
|
|
name,
|
|
..Default::default()
|
|
};
|
|
self.tracks_mut().push(track);
|
|
let len = self.tracks().len();
|
|
let index = len - 1;
|
|
for scene in self.scenes_mut().iter_mut() {
|
|
while scene.clips.len() < len {
|
|
scene.clips.push(None);
|
|
}
|
|
}
|
|
Ok((index, &mut self.tracks_mut()[index]))
|
|
}
|
|
|
|
fn track_longest_name (&self) -> usize {
|
|
self.tracks().iter().map(|s|s.name.len()).fold(0, usize::max)
|
|
}
|
|
}
|
|
|
|
impl<T: HasTracks
|
|
+ HasScenes
|
|
+ HasJack<'static>
|
|
+ Namespace<usize>
|
|
+ Namespace<Arc<[Connect]>>
|
|
+ Namespace<Option<usize>>
|
|
+ Namespace<Option<Arc<str>>>
|
|
+ Namespace<Option<ItemTheme>>
|
|
+ Namespace<Option<Arc<[Connect]>>>
|
|
+ Namespace<Option<Vec<Option<Arc<RwLock<MidiClip>>>>>>
|
|
> TracksController for T {}
|
|
|
|
#[tek_proc::commands(TracksCommand)]
|
|
pub trait TracksController: HasTracks
|
|
+ HasScenes
|
|
+ HasJack<'static>
|
|
+ Namespace<usize>
|
|
+ Namespace<Arc<[Connect]>>
|
|
+ Namespace<Option<usize>>
|
|
+ Namespace<Option<Arc<str>>>
|
|
+ Namespace<Option<ItemTheme>>
|
|
+ Namespace<Option<Arc<[Connect]>>>
|
|
+ Namespace<Option<Vec<Option<Arc<RwLock<MidiClip>>>>>>
|
|
{
|
|
|
|
#[command(Stop = "stop")]
|
|
/// Stop all playing clips
|
|
fn tracks_stop_all (&mut self) -> Perhaps<TracksCommand> {
|
|
for track in self.tracks_mut().iter_mut() {
|
|
track.sequencer.enqueue_next(None);
|
|
}
|
|
Ok(None)
|
|
}
|
|
|
|
#[command(Launch = "launch")]
|
|
/// Launch multiple clips
|
|
fn tracks_launch (
|
|
&mut self, clips: Option<Vec<Option<Arc<RwLock<MidiClip>>>>>
|
|
) -> Perhaps<TracksCommand> {
|
|
if let Some(clips) = clips {
|
|
for (clip, track) in clips.iter().zip(self.tracks_mut()) {
|
|
track.sequencer.enqueue_next(clip.as_ref());
|
|
}
|
|
} else {
|
|
for track in self.tracks_mut().iter_mut() {
|
|
track.sequencer.enqueue_next(None);
|
|
}
|
|
}
|
|
Ok(None)
|
|
}
|
|
|
|
#[command(Add = "add")]
|
|
fn tracks_add (&mut self) -> Perhaps<TracksCommand>
|
|
where Self: HasScenes + HasJack<'static>
|
|
{
|
|
let (_index, _) = self.tracks_add_one(None, None, [].into(), [].into())?;
|
|
Ok(None)
|
|
}
|
|
}
|
|
|
|
impl<
|
|
T: HasTracks + ScenesView + HasMidiIns + HasMidiOuts + HasTrack
|
|
> TracksView for T {}
|
|
|
|
pub trait TracksView: HasTracks + ScenesView + HasMidiIns + HasMidiOuts + HasTrack {
|
|
|
|
/// Iterate over tracks with their corresponding sizes.
|
|
fn tracks_with_sizes (&self) -> impl TracksSizes<'_> {
|
|
let _editor_width = self.editor().map(|e|e.size.w());
|
|
let _active_track = self.selection().track();
|
|
let mut x = 0;
|
|
let w = self.clips_size().w() as usize;
|
|
self.tracks().iter().enumerate().map_while(move |(index, track)|{
|
|
let width = track.width.max(8);
|
|
if x + width < w {
|
|
let data = (index, track, x, x + width);
|
|
x += width + Self::TRACK_SPACING;
|
|
Some(data)
|
|
} else {
|
|
None
|
|
}
|
|
})
|
|
}
|
|
|
|
/// Draw name of each track
|
|
fn view_track_names (&self, theme: ItemTheme) -> impl Draw<Tui> {
|
|
let track_count = self.tracks().len();
|
|
let scene_count = self.scenes().len();
|
|
let selected = self.selection();
|
|
let button = south(
|
|
button_3("t", "rack ", format!("{}{track_count}", selected.track()
|
|
.map(|track|format!("{track}/")).unwrap_or_default()), false),
|
|
button_3("s", "cene ", format!("{}{scene_count}", selected.scene()
|
|
.map(|scene|format!("{scene}/")).unwrap_or_default()), false));
|
|
let button_2 = south(
|
|
button_2("T", "+", false),
|
|
button_2("S", "+", false));
|
|
view_track_row_section(theme, button, button_2, bg(theme.darker.term,
|
|
draw(|to: &mut Tui|{
|
|
for (index, track, x1, _x2) in self.tracks_with_sizes() {
|
|
let b = if selected.track() == Some(index) {
|
|
track.color.light.term
|
|
} else {
|
|
track.color.base.term
|
|
};
|
|
bg(b, south(east(
|
|
format!("·t{index:02} "),
|
|
fg(Rgb(255, 255, 255), bold(true, &track.name))
|
|
).align_nw().full_w(), ""))
|
|
.exact_w(track_width(index, track))
|
|
.push_x(x1 as u16)
|
|
.draw(to)?;
|
|
}
|
|
Ok(Some(XYWH(0, 0, 0, 0)))
|
|
}).exact_h(2)))
|
|
}
|
|
|
|
/// Draw outputs per track
|
|
fn view_track_outputs <'a> (&'a self, theme: ItemTheme, _h: u16) -> impl Draw<Tui> {
|
|
view_track_row_section(theme,
|
|
south(button_2("o", "utput", false).align_w().full_w(),
|
|
draw(|to: &mut Tui|{
|
|
for port in self.midi_outs().iter() {
|
|
let _ = port.port_name().align_w().full_w().draw(to)?;
|
|
}
|
|
Ok(Some(XYWH(0, 0, 0, 0)))
|
|
})),
|
|
button_2("O", "+", false),
|
|
bg(theme.darker.term, draw(|to: &mut Tui|{
|
|
for (index, track, _x1, _x2) in self.tracks_with_sizes() {
|
|
let f = Rgb(255, 255, 255);
|
|
let b = track.color.dark.term;
|
|
iter_south(||track.sequencer.midi_outs.iter().map(|port: &MidiOutput|{
|
|
fg(f, bg(b, format!("·o{index:02} {}", port.port_name()).full_w().align_w()).exact_h(1))
|
|
}))
|
|
.full_h().align_nw().exact_w(track_width(index, track)).draw(to)?;
|
|
}
|
|
Ok(Some(XYWH(0, 0, 0, 0)))
|
|
}).align_w()))
|
|
}
|
|
|
|
/// Draw inputs per track
|
|
fn view_track_inputs <'a> (&'a self, theme: ItemTheme) -> impl Draw<Tui> {
|
|
let mut height = 0u16;
|
|
for track in self.tracks().iter() {
|
|
height = height.max(track.sequencer.midi_ins.len() as u16);
|
|
}
|
|
view_track_row_section(theme, button_2("i", "nput", false), button_2("I", "+", false),
|
|
bg(theme.darker.term, draw(move|to: &mut Tui|{
|
|
for (index, track, _x1, _x2) in self.tracks_with_sizes() {
|
|
south(
|
|
bg(track.color.base.term,
|
|
east!(
|
|
either(track.sequencer.monitoring, fg(Green, "●mon "), "·mon "),
|
|
either(track.sequencer.recording, fg(Red, "●rec "), "·rec "),
|
|
either(track.sequencer.overdub, fg(Yellow, "●dub "), "·dub "),
|
|
).align_w().full_w()),
|
|
iter_south(||track.sequencer.midi_ins.iter().map(|port|fg_bg(Rgb(255, 255, 255), track.color.dark.term,
|
|
format!("·i{index:02} {}", port.port_name()).align_w().full_w())))
|
|
).align_nw().exact_wh(track_width(index, track), height + 1).draw(to)?;
|
|
}
|
|
Ok(Some(XYWH(0, 0, 0, 0)))
|
|
}).align_w()))
|
|
}
|
|
|
|
fn view_track_devices (&self, theme: ItemTheme) -> impl Draw<Tui> {
|
|
let height = self.tracks_devices_height();
|
|
let btn1 = button_3("d", "evice", format!("{}", self.track().map(|t|t.devices.len()).unwrap_or(0)), false);
|
|
let btn2 = button_2("D", "+", false);
|
|
view_track_row_section(theme, btn1, btn2, iter_east(move||self.tracks_with_sizes()
|
|
.enumerate()
|
|
.map(move|(index, (_, track, _x1, _x2))|bg(
|
|
track.color.dark.term,
|
|
iter_south(move||(0..height).map(|_|fg_bg(
|
|
ItemTheme::G[32].lightest.term,
|
|
ItemTheme::G[32].dark.term,
|
|
format!(" · {}", "--").align_nw()
|
|
).exact_wh(track.width as u16, 2)))
|
|
.align_nw()
|
|
)
|
|
.exact_wh(
|
|
Some(track_width(index, track)),
|
|
Some(height + 1),
|
|
))))
|
|
}
|
|
|
|
fn tracks_devices_height (&self) -> u16 {
|
|
let mut h = 2;
|
|
for track in self.tracks().iter() {
|
|
h = h.max(track.devices.len() * 2);
|
|
}
|
|
h as u16
|
|
}
|
|
|
|
fn view_inputs (&self, _theme: ItemTheme) -> impl Draw<Tui> + '_ {
|
|
let title_1 = button_3("i", "nput ", format!("{}", self.midi_ins().len()), false).align_w().exact_wh(20, 1);
|
|
let title_2 = button_2("I", "+", false).exact_wh(4, 1);
|
|
east(title_1, west(title_2, draw(move|to: &mut Tui|{
|
|
for (_index, track, x1, _x2) in self.tracks_with_sizes() {
|
|
let _ = south(
|
|
bg(track.color.dark.term, east!(
|
|
either(track.sequencer.monitoring, fg(Green, "mon "), "mon "),
|
|
either(track.sequencer.recording, fg(Red, "rec "), "rec "),
|
|
either(track.sequencer.overdub, fg(Yellow, "dub "), "dub "),
|
|
).exact_w(track.width as u16)).align_w().push_x(x1 as u16),
|
|
draw(move |to: &mut Tui|{
|
|
for (index, port) in self.midi_ins().as_slice().iter().enumerate() {
|
|
let _ = east(
|
|
east(
|
|
" ● ",
|
|
bold(true, fg(Rgb(255,255,255), port.port_name()))
|
|
).align_w().exact_w(20),
|
|
west(
|
|
().exact_w(4),
|
|
bg(track.color.darker.term, east!(
|
|
either(track.sequencer.monitoring, fg(Green, " ● "), " · "),
|
|
either(track.sequencer.recording, fg(Red, " ● "), " · "),
|
|
either(track.sequencer.overdub, fg(Yellow, " ● "), " · "),
|
|
).exact_w(track.width as u16).align_w())
|
|
)
|
|
).push_x(index as u16 * 10).exact_h(1).draw(to)?;
|
|
}
|
|
todo!()
|
|
})
|
|
).draw(to)?;
|
|
}
|
|
Ok(Some(to.area()))
|
|
})))
|
|
}
|
|
|
|
fn outputs_height (&self) -> u16 {
|
|
let mut h = 1;
|
|
for output in self.midi_outs().iter() {
|
|
h += 1 + output.connections.len();
|
|
}
|
|
h as u16
|
|
}
|
|
|
|
fn view_outputs (&self, theme: ItemTheme) -> impl Draw<Tui> {
|
|
let height = self.outputs_height();
|
|
let list = south(
|
|
button_3(
|
|
"o", "utput", format!("{}", self.midi_outs().len()), false
|
|
).align_w().full_w().exact_h(1),
|
|
draw(|to: &mut Tui|{
|
|
for (_index, port) in self.midi_outs().iter().enumerate() {
|
|
east(
|
|
east(" ● ", fg(Rgb(255,255,255), bold(true, port.port_name()))).align_w(),
|
|
format!("{}/{} ",
|
|
port.port().get_connections().len(),
|
|
port.connections.len()).align_e().full_w().exact_h(1)).full_w().draw(to)?;
|
|
for (index, conn) in port.connections.iter().enumerate() {
|
|
format!(" c{index:02}{}", conn.info()).align_w().full_w().exact_h(1).draw(to)?;
|
|
}
|
|
}
|
|
todo!();
|
|
}).align_nw().full_wh().exact_h(height - 1)
|
|
);
|
|
view_track_row_section(theme, list, button_2("O", "+", false),
|
|
bg(theme.darker.term, draw(|to: &mut Tui|{
|
|
for (index, track, _x1, _x2) in self.tracks_with_sizes() {
|
|
let _ = draw(|to: &mut Tui|{
|
|
east(
|
|
either(true, fg(Green, "play "), "play "),
|
|
either(false, fg(Yellow, "solo "), "solo "),
|
|
).align_w().exact_h(1).draw(to)?;
|
|
for (_index, port) in self.midi_outs().iter().enumerate() {
|
|
east(
|
|
either(true, fg(Green, " ● "), " · "),
|
|
either(false, fg(Yellow, " ● "), " · "),
|
|
).align_w().exact_h(1).draw(to)?;
|
|
for (_index, _conn) in port.connections.iter().enumerate() {
|
|
"".full_w().exact_h(1).draw(to)?;
|
|
}
|
|
}
|
|
todo!()
|
|
}).exact_w(track_width(index, track)).draw(to)?;
|
|
}
|
|
todo!()
|
|
}).align_w().full_w())).exact_h(height)
|
|
}
|
|
|
|
}
|
|
|
|
pub trait HasTrackScroll {
|
|
fn track_scroll (&self) -> usize;
|
|
fn tracks_last (&self) -> usize;
|
|
fn tracks_last_mut (&mut self) -> &mut usize;
|
|
}
|
|
|
|
impl HasTrackScroll for &mut Arrangement {
|
|
fn track_scroll (&self) -> usize {
|
|
self.track_scroll
|
|
}
|
|
fn tracks_last (&self) -> usize {
|
|
self.track_last
|
|
}
|
|
fn tracks_last_mut (&mut self) -> &mut usize {
|
|
&mut self.track_last
|
|
}
|
|
}
|
|
|
|
impl HasTrackScroll for Arrangement {
|
|
fn track_scroll (&self) -> usize {
|
|
self.track_scroll
|
|
}
|
|
fn tracks_last (&self) -> usize {
|
|
self.track_last
|
|
}
|
|
fn tracks_last_mut (&mut self) -> &mut usize {
|
|
&mut self.track_last
|
|
}
|
|
}
|
|
|
|
impl HasTrackScroll for App {
|
|
fn track_scroll (&self) -> usize {
|
|
self.project.track_scroll()
|
|
}
|
|
fn tracks_last (&self) -> usize {
|
|
self.project.track_last
|
|
}
|
|
fn tracks_last_mut (&mut self) -> &mut usize {
|
|
&mut self.project.track_last
|
|
}
|
|
}
|
|
|
|
fn view_track_row_section (
|
|
_theme: ItemTheme,
|
|
button: impl Draw<Tui>,
|
|
button_add: impl Draw<Tui>,
|
|
content: impl Draw<Tui>,
|
|
) -> impl Draw<Tui> {
|
|
west(
|
|
button_add.align_nw().exact_w(4).full_h(),
|
|
east(
|
|
button.align_nw().full_h().exact_w(20),
|
|
content.align_c().full_wh()
|
|
)
|
|
)
|
|
}
|