make transport focusable

This commit is contained in:
🪞👃🪞 2024-07-12 13:23:32 +03:00
parent a7a798b484
commit 449615eea8
14 changed files with 220 additions and 237 deletions

View file

@ -24,6 +24,8 @@ handle!{
fn handle_focused (state: &mut App, e: &AppEvent) -> Usually<bool> {
match state.section {
AppSection::Transport =>
Ok(false),
AppSection::Arranger =>
handle_keymap(state, e, crate::control::arranger::KEYMAP_ARRANGER),
AppSection::Sequencer =>

View file

@ -3,6 +3,7 @@
//#![feature(fn_traits)]
//#![feature(unboxed_closures)]
#![allow(macro_expanded_macro_exports_accessed_by_absolute_paths)]
#![allow(ambiguous_glob_reexports)]
extern crate clap;
extern crate jack as _jack;

View file

@ -217,21 +217,23 @@ impl App {
}
#[derive(PartialEq, Clone, Copy)]
pub enum AppSection { Arranger, Sequencer, Chain, }
pub enum AppSection { Transport, Arranger, Sequencer, Chain, }
impl Default for AppSection { fn default () -> Self { Self::Arranger } }
impl AppSection {
pub fn prev (&mut self) {
*self = match self {
Self::Arranger => Self::Chain,
Self::Transport => Self::Chain,
Self::Arranger => Self::Transport,
Self::Sequencer => Self::Arranger,
Self::Chain => Self::Sequencer,
Self::Chain => Self::Sequencer,
}
}
pub fn next (&mut self) {
*self = match self {
Self::Arranger => Self::Sequencer,
Self::Transport => Self::Arranger,
Self::Arranger => Self::Sequencer,
Self::Sequencer => Self::Chain,
Self::Chain => Self::Arranger,
Self::Chain => Self::Transport,
}
}
}

View file

@ -9,8 +9,36 @@ pub struct Sampler {
pub buffer: Vec<Vec<f32>>,
pub output_gain: f32
}
render!(Sampler |self, buf, area| {
let Rect { x, y, height, .. } = area;
let style = Style::default().gray();
let title = format!(" {} ({})", self.name, self.voices.len());
title.blit(buf, x+1, y, Some(style.white().bold().not_dim()))?;
let mut width = title.len() + 2;
for (i, (note, sample)) in self.samples.iter().enumerate() {
let style = if i == self.cursor.0 {
Style::default().green()
} else {
Style::default()
};
let i = i as u16;
let y1 = y+1+i;
if y1 >= y + height {
break
}
if i as usize == self.cursor.0 {
"".blit(buf, x+1, y1, Some(style.bold()))?;
}
let label1 = format!("{note:3} {:12}", sample.name);
let label2 = format!("{:>6} {:>6} +0.0", sample.start, sample.end);
label1.blit(buf, x+2, y1, Some(style.bold()))?;
label2.blit(buf, x+3+label1.len()as u16, y1, Some(style))?;
width = width.max(label1.len() + label2.len() + 4);
}
let height = ((2 + self.samples.len()) as u16).min(height);
Ok(Rect { x, y, width: (width as u16).min(area.width), height })
});
render!(Sampler = crate::view::sampler::render);
handle!(Sampler = crate::control::sampler::handle);
//jack!(Sampler {
//process = Sampler::process,
@ -184,3 +212,48 @@ impl Iterator for Voice {
None
}
}
//fn render_table (
//self: &mut Sampler,
//stdout: &mut Stdout,
//offset: (u16, u16),
//) -> Result<(), Box<dyn Error>> {
//let move_to = |col, row| crossterm::cursor::MoveTo(offset.0 + col, offset.1 + row);
//stdout.queue(move_to(0, 3))?.queue(
//Print(" Name Rate Trigger Route")
//)?;
//for (i, sample) in self.samples.lock().unwrap().iter().enumerate() {
//let row = 4 + i as u16;
//for (j, (column, field)) in [
//(0, format!(" {:7} ", sample.name)),
//(9, format!(" {:.1}Hz ", sample.rate)),
//(18, format!(" MIDI C{} {} ", sample.trigger.0, sample.trigger.1)),
//(33, format!(" {:.1}dB -> Output ", sample.gain)),
//(50, format!(" {} ", sample.playing.unwrap_or(0))),
//].into_iter().enumerate() {
//stdout.queue(move_to(column, row))?;
//if self.selected_sample == i && self.selected_column == j {
//stdout.queue(PrintStyledContent(field.to_string().bold().reverse()))?;
//} else {
//stdout.queue(PrintStyledContent(field.to_string().bold()))?;
//}
//}
//}
//Ok(())
//}
//fn render_meters (
//self: &mut Sampler,
//stdout: &mut Stdout,
//offset: (u16, u16),
//) -> Result<(), Box<dyn Error>> {
//let move_to = |col, row| crossterm::cursor::MoveTo(offset.0 + col, offset.1 + row);
//for (i, sample) in self.samples.lock().iter().enumerate() {
//let row = 4 + i as u16;
//stdout.queue(move_to(32, row))?.queue(
//PrintStyledContent("▁".green())
//)?;
//}
//Ok(())
//}

View file

@ -20,17 +20,6 @@ impl App {
Ok(&mut self.tracks[self.track_cursor - 1])
}
fn add_track_with_cb (
&mut self, name: Option<&str>, init: impl FnOnce(&Client, &mut Track)->Usually<()>,
) -> Usually<&mut Track> {
let name = name.ok_or_else(||self.new_track_name())?;
let mut track = Track::new(&name, None, None)?;
init(self.client(), &mut track)?;
self.tracks.push(track);
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;
@ -61,6 +50,7 @@ pub struct Track {
pub sequence: Option<usize>,
/// Output from current sequence.
pub midi_out: Option<Port<MidiOut>>,
/// MIDI output buffer
midi_out_buf: Vec<Vec<Vec<u8>>>,
/// Device chain
pub devices: Vec<JackDevice>,
@ -74,6 +64,7 @@ pub struct Track {
/// Highlight keys on piano roll.
pub notes_out: [bool;128],
}
impl Track {
fn new (
name: &str,

View file

@ -1,12 +1,13 @@
pub mod chain;
pub mod arranger;
pub mod sampler;
pub mod sequencer;
pub mod transport;
pub mod plugin;
pub mod border;
pub mod theme;
pub use self::border::*;
pub use self::theme::*;
pub use self::transport::TransportView;
pub use self::arranger::*;
pub use self::chain::ChainView;

View file

@ -24,37 +24,6 @@ impl<'a> ArrangerView<'a> {
vertical
}
}
fn track_names (&self) -> Vec<&'a str> {
let mut track_names = vec!["Mix"];
for track in self.tracks.iter() {
track_names.push(track.name.as_str());
}
track_names
}
fn track_widths (&self) -> Vec<u16> {
self.track_names()
.iter()
.map(|name|(name.len() as u16).max(10) + 3)
.collect()
}
fn bg_color_lo (&self) -> Color {
if self.focused && self.entered {
Color::Rgb(25, 60, 15)
} else if self.focused {
Color::Rgb(20, 45, 5)
} else {
Color::Reset
}
}
fn bg_color_hi (&self) -> Color {
if self.focused && self.entered {
Color::Rgb(30, 90, 25)
} else if self.focused {
Color::Rgb(25, 60, 15)
} else {
Color::Reset
}
}
}
impl<'a> Render for ArrangerView<'a> {
@ -64,7 +33,7 @@ impl<'a> Render for ArrangerView<'a> {
} else {
self.tracks.len() as u16 * 2
});
fill_bg(buf, area, self.bg_color_lo());
fill_bg(buf, area, Nord::bg_lo(self.focused, self.entered));
area = if self.vertical {
self.draw_vertical(buf, area)
} else {
@ -73,7 +42,7 @@ impl<'a> Render for ArrangerView<'a> {
if self.focused {
//HELP.blit(buf, area.x + 2, area.y + area.height - 1, Some(Style::default().dim()))?;
if self.entered {
QuarterV(Style::default().green().dim()).draw(buf, area)?;
Corners(Style::default().green().not_dim()).draw(buf, area)?;
}
}
Ok(area)
@ -82,45 +51,6 @@ impl<'a> Render for ArrangerView<'a> {
impl<'a> ArrangerView<'a> {
fn vertical_clips (&self, buf: &mut Buffer, x: u16, y: u16, height: u16, track: usize) -> Usually<u16> {
let mut index = 0;
let mut width = 10;
loop {
if index >= self.scenes.len() {
break
}
if y + index as u16 >= height {
break
}
width = 10.max(if let Some(scene) = self.scenes.get(index) {
let hi = (track + 1 == self.cursor.0) &&
(index + 1 == self.cursor.1);
let style = Some(highlight(self.focused, hi));
let clip = scene.clips.get(track);
let index = index as u16;
let label = if let Some(Some(clip)) = clip {
if let Some(phrase) = self.tracks[track].phrases.get(*clip) {
format!("{} {}", if self.tracks[track].sequence == Some(*clip) {
""
} else {
" "
}, phrase.name)
} else {
format!(" ??? ")
}
} else {
format!(" ·········")
};
label.blit(buf, x, y + index, style)?;
label.len() as u16
} else {
0u16
});
index = index + 1;
}
Ok(width)
}
fn draw_vertical (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
return Split::right([
@ -140,7 +70,7 @@ impl<'a> ArrangerView<'a> {
"".blit(buf, x+1, y, style)?;
scene.name.blit(buf, x + 2, y, style)?;
}
let width = 2+self.scenes.iter()
let width = 2 + self.scenes.iter()
.map(|x|&x.name).fold(0, |x,y|x.max(y.len() as u16+1));
Ok(Rect { width, ..area })
},
@ -180,20 +110,13 @@ impl<'a> ArrangerView<'a> {
//width = width.max(2label.len() as u16 + 3);
}
if track_index + 1 == self.cursor.0 {
let bg = Nord::bg_hi(self.focused, self.entered);
if self.cursor.1 == 0 {
let y = area.y;
fill_bg(buf, Rect { x, y, width, height: 1 }, if self.focused {
Color::Rgb(30, 90, 25)
} else {
Color::Rgb(25, 60, 15)
})
fill_bg(buf, Rect { x, y, width, height: 1 }, bg)
} else {
let y = 1 + area.y + 2 * (self.cursor.1 as u16 - 1);
fill_bg(buf, Rect { x, y, width, height: 2 }, if self.focused {
Color::Rgb(30, 90, 25)
} else {
Color::Rgb(25, 60, 15)
})
fill_bg(buf, Rect { x, y, width, height: 2 }, bg)
}
};
x = x + width as u16;
@ -202,9 +125,7 @@ impl<'a> ArrangerView<'a> {
}
]).render(buf, area);
}
}
impl<'a> ArrangerView<'a> {
fn draw_horizontal (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
let style1 = Some(Style::default().bold().not_dim());
let style2 = Some(Style::default().not_dim());

View file

@ -99,6 +99,7 @@ pub struct LozengeDotted(pub Style);
pub struct Quarter(pub Style);
pub struct QuarterV(pub Style);
pub struct Chamfer(pub Style);
pub struct Corners(pub Style);
border! {
Lozenge {
@ -148,5 +149,13 @@ border! {
fn style (&self) -> Option<Style> {
Some(self.0)
}
},
Corners {
"🬆" "" "🬊"
"" ""
"🬱" "" "🬵"
fn style (&self) -> Option<Style> {
Some(self.0)
}
}
}

View file

@ -6,6 +6,7 @@ pub struct ChainView<'a> {
pub track: Option<&'a Track>,
pub direction: Direction,
pub focused: bool,
pub entered: bool,
}
impl<'a> ChainView<'a> {
@ -18,6 +19,7 @@ impl<'a> ChainView<'a> {
pub fn new (app: &'a App, direction: Direction) -> Self {
Self {
direction,
entered: app.entered,
focused: app.section == AppSection::Chain,
track: match app.track_cursor {
0 => None,
@ -34,14 +36,18 @@ impl<'a> Render for ChainView<'a> {
Direction::Down => area.width = area.width.min(40),
Direction::Right => area.width = area.width.min(10),
}
fill_bg(buf, area, if self.focused { Color::Rgb(20, 45, 5) } else { Color::Reset });
self.direction
fill_bg(buf, area, Nord::bg_lo(self.focused, self.entered));
let area = self.direction
.split_focus(0, track.devices.as_slice(), if self.focused {
Style::default().green().dim()
} else {
Style::default().dim()
})
.render(buf, area)
.render(buf, area)?;
if self.focused && self.entered {
Corners(Style::default().green().not_dim()).draw(buf, area)?;
}
Ok(area)
} else {
let Rect { x, y, width, height } = area;
let label = "No track selected";
@ -86,7 +92,7 @@ impl<'a> Render for SplitFocus<'a> {
}
results
.get(self.1)
.map(|focused|Lozenge(self.3).draw(buf, *focused))
.map(|focused|Lozenge(self.3).draw(buf, Rect { width, ..*focused }))
.transpose()?;
Ok(area)
}

View file

@ -1,78 +0,0 @@
use crate::core::*;
use crate::model::*;
pub fn render (state: &Sampler, buf: &mut Buffer, Rect { x, y, height, .. }: Rect)
-> Usually<Rect>
{
let style = Style::default().gray();
let title = format!(" {} ({})", state.name, state.voices.len());
title.blit(buf, x+1, y, Some(style.white().bold().not_dim()))?;
let mut width = title.len() + 2;
for (i, (note, sample)) in state.samples.iter().enumerate() {
let style = if i == state.cursor.0 {
Style::default().green()
} else {
Style::default()
};
let i = i as u16;
let y1 = y+1+i;
if y1 >= y + height {
break
}
if i as usize == state.cursor.0 {
"".blit(buf, x+1, y1, Some(style.bold()))?;
}
let label1 = format!("{note:3} {:8}", sample.name);
let label2 = format!("{:>6} {:>6}", sample.start, sample.end);
label1.blit(buf, x+2, y1, Some(style.bold()))?;
label2.blit(buf, x+3+label1.len()as u16, y1, Some(style))?;
width = width.max(label1.len() + label2.len() + 4);
}
let height = ((2 + state.samples.len()) as u16).min(height);
Ok(Rect { x, y, width: width as u16, height })
}
//fn render_table (
//state: &mut Sampler,
//stdout: &mut Stdout,
//offset: (u16, u16),
//) -> Result<(), Box<dyn Error>> {
//let move_to = |col, row| crossterm::cursor::MoveTo(offset.0 + col, offset.1 + row);
//stdout.queue(move_to(0, 3))?.queue(
//Print(" Name Rate Trigger Route")
//)?;
//for (i, sample) in state.samples.lock().unwrap().iter().enumerate() {
//let row = 4 + i as u16;
//for (j, (column, field)) in [
//(0, format!(" {:7} ", sample.name)),
//(9, format!(" {:.1}Hz ", sample.rate)),
//(18, format!(" MIDI C{} {} ", sample.trigger.0, sample.trigger.1)),
//(33, format!(" {:.1}dB -> Output ", sample.gain)),
//(50, format!(" {} ", sample.playing.unwrap_or(0))),
//].into_iter().enumerate() {
//stdout.queue(move_to(column, row))?;
//if state.selected_sample == i && state.selected_column == j {
//stdout.queue(PrintStyledContent(field.to_string().bold().reverse()))?;
//} else {
//stdout.queue(PrintStyledContent(field.to_string().bold()))?;
//}
//}
//}
//Ok(())
//}
//fn render_meters (
//state: &mut Sampler,
//stdout: &mut Stdout,
//offset: (u16, u16),
//) -> Result<(), Box<dyn Error>> {
//let move_to = |col, row| crossterm::cursor::MoveTo(offset.0 + col, offset.1 + row);
//for (i, sample) in state.samples.lock().iter().enumerate() {
//let row = 4 + i as u16;
//stdout.queue(move_to(32, row))?.queue(
//PrintStyledContent("▁".green())
//)?;
//}
//Ok(())
//}

View file

@ -1,4 +1,4 @@
use crate::{core::*,model::*};
use crate::{core::*,model::*,view::*};
pub struct BufferedSequencerView {
pub ppq: usize,
@ -79,6 +79,7 @@ impl BufferedSequencerView {
pub struct SequencerView<'a> {
focused: bool,
entered: bool,
/// Displayed phrase
phrase: Option<&'a Phrase>,
/// Resolution of MIDI sequence
@ -111,6 +112,7 @@ impl<'a> SequencerView<'a> {
Self {
phrase: app.phrase(),
focused: app.section == AppSection::Sequencer,
entered: app.entered,
ppq: app.timebase.ppq() as usize,
now: app.timebase.frame_to_pulse(app.playhead as f64) as usize,
time_cursor: app.time_cursor,
@ -126,9 +128,11 @@ impl<'a> SequencerView<'a> {
impl<'a> Render for SequencerView<'a> {
fn render (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
let bg = if self.focused { Color::Rgb(20, 45, 5) } else { Color::Reset };
fill_bg(buf, area, bg);
fill_bg(buf, area, Nord::bg_lo(self.focused, self.entered));
self.horizontal_draw(buf, area)?;
if self.focused && self.entered {
Corners(Style::default().green().not_dim()).draw(buf, area)?;
}
Ok(area)
}
}

0
src/view/split.rs Normal file
View file

45
src/view/theme.rs Normal file
View file

@ -0,0 +1,45 @@
use crate::core::*;
pub trait Theme {
const BG0: Color;
const BG1: Color;
const BG2: Color;
const BG3: Color;
const BG4: Color;
const RED: Color;
const YELLOW: Color;
const GREEN: Color;
fn bg_hi (focused: bool, entered: bool) -> Color {
if focused && entered {
Self::BG2
} else if focused {
Self::BG1
} else {
Self::BG0
}
}
fn bg_lo (focused: bool, entered: bool) -> Color {
if focused && entered {
Self::BG1
} else if focused {
Self::BG0
} else {
Color::Reset
}
}
}
pub struct Nord;
impl Theme for Nord {
const BG0: Color = Color::Rgb(41, 46, 57);
const BG1: Color = Color::Rgb(46, 52, 64);
const BG2: Color = Color::Rgb(59, 66, 82);
const BG3: Color = Color::Rgb(67, 76, 94);
const BG4: Color = Color::Rgb(76, 86, 106);
const RED: Color = Color::Rgb(191, 97, 106);
const YELLOW: Color = Color::Rgb(235, 203, 139);
const GREEN: Color = Color::Rgb(163, 190, 140);
}

View file

@ -1,22 +1,20 @@
use crate::{core::*, view::Split, model::App};
use crate::{core::*,view::*,model::{App, AppSection}};
pub struct TransportView {
pub playing: TransportState,
pub record: bool,
pub overdub: bool,
pub monitor: bool,
pub quant: usize,
pub ppq: usize,
pub bpm: usize,
pub pulse: usize,
pub usecs: usize,
focused: bool,
entered: bool,
playing: TransportState,
quant: usize,
ppq: usize,
bpm: usize,
pulse: usize,
usecs: usize,
}
impl TransportView {
pub fn new (app: &App) -> Self {
Self {
focused: app.section == AppSection::Transport,
entered: app.entered,
playing: *app.playing.as_ref().unwrap_or(&TransportState::Stopped),
monitor: app.track().map(|t|t.1.monitoring).unwrap_or(false),
record: app.track().map(|t|t.1.recording).unwrap_or(false),
overdub: app.track().map(|t|t.1.overdub).unwrap_or(false),
quant: app.quant,
ppq: app.timebase.ppq() as usize,
bpm: app.timebase.bpm() as usize,
@ -27,66 +25,53 @@ impl TransportView {
}
render!(TransportView |self, buf, area| {
let mut area = area;
area.height = if area.width > 100 { 1 } else { 2 };
area.height = 2;
let Self { ppq, bpm, quant, pulse, usecs, .. } = self;
fill_bg(buf, area, Color::Rgb(20, 45, 5));
let dim = Style::default().dim();
let not_dim = Style::default().not_dim();
let red = not_dim.red();
let green = not_dim.green();
let yellow = not_dim.yellow();
Split::right([
fill_bg(buf, area, Nord::bg_lo(self.focused, self.entered));
let gray = Style::default().gray();
let not_dim = Style::default().not_dim();
let not_dim_bold = not_dim.bold();
let area = Split::right([
// Play/Pause button
&|buf: &mut Buffer, Rect { x, y, .. }: Rect|{
let style = Style::default().gray();
let style = Some(match &self.playing {
TransportState::Stopped => style.dim().bold(),
TransportState::Starting => style.not_dim().bold(),
TransportState::Rolling => style.not_dim().white().bold()
TransportState::Stopped => gray.dim().bold(),
TransportState::Starting => gray.not_dim().bold(),
TransportState::Rolling => gray.not_dim().white().bold()
});
let label = match &self.playing {
TransportState::Rolling => "▶ PLAYING",
TransportState::Starting => "READY ...",
TransportState::Stopped => "⏹ STOPPED",
};
label.blit(buf, x, y, style)
},
// Record button/indicator
&|buf: &mut Buffer, Rect { x, y, .. }: Rect|{
"⏺ REC".blit(buf, x, y, Some(if self.record { red } else { dim }))
},
// Overdub button/indicator
&|buf: &mut Buffer, Rect { x, y, .. }: Rect|{
"⏺ DUB".blit(buf, x, y, Some(if self.overdub { yellow } else { dim }))
},
// Monitor button/indicator
&|buf: &mut Buffer, Rect { x, y, .. }: Rect|{
"⏺ MON".blit(buf, x, y, Some(if self.monitor { green } else { dim }))
let mut result = label.blit(buf, x + 1, y, style)?;
result.width = result.width + 1;
Ok(result)
},
// Beats per minute
&|buf: &mut Buffer, Rect { x, y, .. }: Rect|{
"BPM".blit(buf, x, y, Some(not_dim))?;
format!("{}.{:03}", bpm, bpm % 1).blit(buf, x + 4, y, Some(not_dim.bold()))?;
Ok(Rect { x, y, width: 13, height: 1 })
let width = format!("{}.{:03}", bpm, bpm % 1).blit(buf, x, y + 1, Some(not_dim_bold))?.width;
Ok(Rect { x, y, width: (width + 2).max(10), height: 2 })
},
// Quantization
&|buf: &mut Buffer, Rect { x, y, .. }: Rect|{
"QUANT".blit(buf, x, y, Some(not_dim))?;
ppq_to_name(*quant).blit(buf, x + 6, y, Some(not_dim.bold()))?;
Ok(Rect { x, y, width: 12, height: 1 })
let width = ppq_to_name(*quant).blit(buf, x, y + 1, Some(not_dim_bold))?.width;
Ok(Rect { x, y, width: (width + 2).max(10), height: 2 })
},
// Clip launch sync
&|buf: &mut Buffer, Rect { x, y, .. }: Rect|{
"SYNC".blit(buf, x, y, Some(not_dim))?;
"4/4".blit(buf, x + 5, y, Some(not_dim.bold()))?;
Ok(Rect { x, y, width: 12, height: 1 })
let width = "4/4".blit(buf, x, y + 1, Some(not_dim_bold))?.width;
Ok(Rect { x, y, width: (width + 2).max(10), height: 2 })
},
// Clock
@ -96,8 +81,29 @@ render!(TransportView |self, buf, area| {
let (seconds, msecs) = (usecs / 1000000, usecs / 1000 % 1000);
let (minutes, seconds) = (seconds / 60, seconds % 60);
let timer = format!("{minutes}:{seconds:02}:{msecs:03} {bars}.{beats}.{pulses:02}");
timer.blit(buf, x + width - timer.len() as u16 - 1, y, Some(Style::default().not_dim()))
timer.blit(buf, x + width - timer.len() as u16 - 1, y, Some(not_dim))
}
]).render(buf, area)
]).render(buf, area)?;
Ok(if self.focused && self.entered {
Corners(Style::default().green().not_dim()).draw(buf, area)?
} else {
area
})
});
// Record button/indicator
//&|buf: &mut Buffer, Rect { x, y, .. }: Rect|{
//"⏺ REC".blit(buf, x, y, Some(if self.record { red } else { dim }))
//},
//// Overdub button/indicator
//&|buf: &mut Buffer, Rect { x, y, .. }: Rect|{
//"⏺ DUB".blit(buf, x, y, Some(if self.overdub { yellow } else { dim }))
//},
//// Monitor button/indicator
//&|buf: &mut Buffer, Rect { x, y, .. }: Rect|{
//"⏺ MON".blit(buf, x, y, Some(if self.monitor { green } else { dim }))
//},