From 4ae62c5bc2db3a034b0120b4139ce356ac3724a2 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 14 Jun 2024 16:54:28 +0300 Subject: [PATCH] systematizing jack handlers --- src/device.rs | 4 + src/device/chain.rs | 7 ++ src/device/launcher.rs | 7 ++ src/device/looper.rs | 7 ++ src/device/mixer.rs | 34 +++-- src/device/sampler.rs | 102 ++++++++------- src/device/sequencer.rs | 272 ++++++++++++++++++++++++---------------- src/device/transport.rs | 110 +++++++++------- src/layout.rs | 5 +- src/prelude.rs | 2 +- 10 files changed, 342 insertions(+), 208 deletions(-) diff --git a/src/device.rs b/src/device.rs index d101029a..02074693 100644 --- a/src/device.rs +++ b/src/device.rs @@ -165,6 +165,10 @@ pub enum EngineEvent { Update, /// Update the whole form. Redraw, + /// Device gains focus + Focus, + /// Device loses focus + Blur, } pub fn activate_jack_client ( diff --git a/src/device/chain.rs b/src/device/chain.rs index c28d3ec6..b243217d 100644 --- a/src/device/chain.rs +++ b/src/device/chain.rs @@ -14,6 +14,13 @@ impl Chain { } } +pub fn process ( + client: &Client, + scope: &ProcessScope +) -> Control { + Control::Continue +} + pub fn render (state: &Chain, buf: &mut Buffer, area: Rect) -> Usually { diff --git a/src/device/launcher.rs b/src/device/launcher.rs index 652d9303..53a042c9 100644 --- a/src/device/launcher.rs +++ b/src/device/launcher.rs @@ -12,6 +12,13 @@ impl Launcher { } } +pub fn process ( + client: &Client, + scope: &ProcessScope +) -> Control { + Control::Continue +} + pub fn render (state: &Launcher, buf: &mut Buffer, area: Rect) -> Usually { Ok(Rect::default()) } diff --git a/src/device/looper.rs b/src/device/looper.rs index 28993bbb..c7700dc8 100644 --- a/src/device/looper.rs +++ b/src/device/looper.rs @@ -12,6 +12,13 @@ impl Looper { } } +pub fn process ( + client: &Client, + scope: &ProcessScope +) -> Control { + Control::Continue +} + pub fn render (state: &Looper, buf: &mut Buffer, area: Rect) -> Usually { diff --git a/src/device/mixer.rs b/src/device/mixer.rs index 5ad91dfb..33d1190a 100644 --- a/src/device/mixer.rs +++ b/src/device/mixer.rs @@ -2,7 +2,7 @@ use crate::prelude::*; pub struct Mixer { name: String, - jack: Jack, + jack: Jack, tracks: Vec, selected_track: usize, selected_column: usize, @@ -14,14 +14,9 @@ impl Mixer { name, ClientOptions::NO_START_SERVER )?; - let jack = client.activate_async( - Notifications, - ClosureProcessHandler::new(Box::new( - move |_: &Client, _: &ProcessScope| -> Control { - Control::Continue - }) as BoxControl + Send> - ) - )?; + let jack = MixerJack.activate(client, ClosureProcessHandler::new(Box::new( + move|client: &Client, scope: &ProcessScope|process(client, scope) + ) as BoxedProcessHandler))?; Ok(DynamicDevice::new(render, handle, |_|{}, Self { name: name.into(), selected_column: 0, @@ -41,6 +36,13 @@ impl Mixer { } } +pub fn process ( + client: &Client, + scope: &ProcessScope +) -> Control { + Control::Continue +} + pub fn render (state: &Mixer, buf: &mut Buffer, mut area: Rect) -> Usually { @@ -211,9 +213,17 @@ impl Track { } } -pub struct Notifications; +pub struct MixerJack; -impl NotificationHandler for Notifications { +impl MixerJack { + fn activate

(self, client: Client, handler: P) -> Usually> + where P: 'static + Send + ::jack::ProcessHandler + { + Ok(client.activate_async(self, handler)?) + } +} + +impl NotificationHandler for MixerJack { fn thread_init (&self, _: &Client) { } @@ -254,7 +264,7 @@ impl NotificationHandler for Notifications { //fn handle (&mut self, engine: &mut TUI) -> Result> { //Ok(None) //} -//} +// //impl Output, [u16;2]> for Mixer { //fn render (&self, envine: &mut TUI) -> Result> { diff --git a/src/device/sampler.rs b/src/device/sampler.rs index 0ac2b8a3..db4ac260 100644 --- a/src/device/sampler.rs +++ b/src/device/sampler.rs @@ -7,7 +7,7 @@ pub const ACTIONS: [(&'static str, &'static str);2] = [ pub struct Sampler { name: String, - jack: Jack, + jack: Jack, samples: Arc>>, selected_sample: usize, selected_column: usize, @@ -23,57 +23,65 @@ impl Sampler { ]; let samples = Arc::new(Mutex::new(samples)); let input = client.register_port("trigger", ::jack::MidiIn::default())?; - let handler: BoxedProcessHandler = Box::new({ - let exited = exited.clone(); + let jack = SamplerJack.activate(client, ClosureProcessHandler::new({ + let exited = exited.clone(); let samples = samples.clone(); - Box::new(move |_: &Client, scope: &ProcessScope| -> Control { - if exited.fetch_and(true, Ordering::Relaxed) { - return Control::Quit - } - let mut samples = samples.lock().unwrap(); - for event in input.iter(scope) { - let len = 3.min(event.bytes.len()); - let mut data = [0; 3]; - data[..len].copy_from_slice(&event.bytes[..len]); - if (data[0] >> 4) == 0b1001 { // note on - let channel = data[0] & 0b00001111; - let note = data[1]; - let velocity = data[2]; - for sample in samples.iter_mut() { - if /*sample.trigger.0 == channel &&*/ sample.trigger.1 == note { - sample.play(velocity); - } - } - } - for sample in samples.iter_mut() { - if let Some(playing) = sample.playing { - for (index, value) in sample.port.as_mut_slice(scope).iter_mut().enumerate() { - *value = *sample.data[0].get(playing + index).unwrap_or(&0f32); - } - if playing + scope.n_frames() as usize > sample.data[0].len() { - sample.playing = None - } else { - sample.playing = Some(playing + scope.n_frames() as usize) - } - } - } - } + Box::new(move |client: &Client, scope: &ProcessScope|{ + process(client, scope, &exited, &samples, &input); Control::Continue - }) - }); + }) as BoxedProcessHandler + }))?; Ok(DynamicDevice::new(render, handle, |_|{}, Self { name: name.into(), selected_sample: 0, selected_column: 0, samples, - jack: client.activate_async( - self::Notifications, - ClosureProcessHandler::new(handler) - )?, + jack, })) } } +pub fn process ( + client: &Client, + scope: &ProcessScope, + exited: &Arc, + samples: &Arc>>, + input: &Port +) -> Control { + if exited.fetch_and(true, Ordering::Relaxed) { + return Control::Quit + } + let mut samples = samples.lock().unwrap(); + for event in input.iter(scope) { + let len = 3.min(event.bytes.len()); + let mut data = [0; 3]; + data[..len].copy_from_slice(&event.bytes[..len]); + if (data[0] >> 4) == 0b1001 { // note on + let channel = data[0] & 0b00001111; + let note = data[1]; + let velocity = data[2]; + for sample in samples.iter_mut() { + if /*sample.trigger.0 == channel &&*/ sample.trigger.1 == note { + sample.play(velocity); + } + } + } + for sample in samples.iter_mut() { + if let Some(playing) = sample.playing { + for (index, value) in sample.port.as_mut_slice(scope).iter_mut().enumerate() { + *value = *sample.data[0].get(playing + index).unwrap_or(&0f32); + } + if playing + scope.n_frames() as usize > sample.data[0].len() { + sample.playing = None + } else { + sample.playing = Some(playing + scope.n_frames() as usize) + } + } + } + } + Control::Continue +} + pub struct Sample { port: Port, name: String, @@ -217,9 +225,17 @@ pub fn handle (state: &mut Sampler, event: &EngineEvent) -> Result<(), Box (self, client: Client, handler: P) -> Usually> + where P: 'static + Send + ::jack::ProcessHandler + { + Ok(client.activate_async(self, handler)?) + } +} + +impl NotificationHandler for SamplerJack { fn thread_init (&self, _: &Client) { } diff --git a/src/device/sequencer.rs b/src/device/sequencer.rs index 6e0781c8..b29a7594 100644 --- a/src/device/sequencer.rs +++ b/src/device/sequencer.rs @@ -3,6 +3,7 @@ use ratatui::style::Stylize; pub struct Sequencer { name: String, + jack: Jack, playing: Arc, recording: Arc, overdub: Arc, @@ -10,9 +11,16 @@ pub struct Sequencer { outputs_open: Arc, cursor: (u16, u16, u16), timesig: (f32, f32), - pub jack_client: Jack, sequence: Arc>>>>, sequences: Arc>>, + mode: SequencerMode, +} + +pub enum SequencerMode { + Tiny, + Compact, + Vertical, + Horizontal, } impl Sequencer { @@ -31,15 +39,6 @@ impl Sequencer { let playing = Arc::new(AtomicBool::new(true)); let recording = Arc::new(AtomicBool::new(true)); let overdub = Arc::new(AtomicBool::new(false)); - let (client, _status) = Client::new( - name, ClientOptions::NO_START_SERVER - )?; - let mut input = client.register_port( - "input", ::jack::MidiIn::default() - )?; - let mut output = client.register_port( - "output", ::jack::MidiOut::default() - )?; let sequence: Arc>>>> = Arc::new( Mutex::new(vec![vec![None;64];128]) ); @@ -53,7 +52,17 @@ impl Sequencer { for (index, frame) in step_frames.iter().enumerate() { frame_steps[*frame] = Some(index); } + let (client, _status) = Client::new( + name, ClientOptions::NO_START_SERVER + )?; + let mut input = client.register_port( + "input", ::jack::MidiIn::default() + )?; + let mut output = client.register_port( + "output", ::jack::MidiOut::default() + )?; Ok(DynamicDevice::new(render, handle, |_|{}, Self { + mode: SequencerMode::Vertical, name: name.into(), playing: playing.clone(), recording: recording.clone(), @@ -65,67 +74,70 @@ impl Sequencer { ])), cursor: (11, 0, 0), timesig: (4.0, 4.0), - jack_client: crate::device::activate_jack_client( - client, - Notifications, - Box::new(move |client: &Client, scope: &ProcessScope| -> Control { - if exited.fetch_and(true, Ordering::Relaxed) { - return Control::Quit - } - if client.transport().query_state().unwrap() == TransportState::Rolling { - let chunk_start = scope.last_frame_time(); - let chunk_size = scope.n_frames(); - let chunk_end = chunk_start + chunk_size; - let start_looped = chunk_start as usize % loop_frames; - let end_looped = chunk_end as usize % loop_frames; - - // Write MIDI notes from input to sequence - if recording.fetch_and(true, Ordering::Relaxed) { - //let overdub = overdub.fetch_and(true, Ordering::Relaxed); - for event in input.iter(scope) { - let ::jack::RawMidi { time, bytes } = event; - println!("\n{time} {bytes:?}"); - } - } - - // Write MIDI notes from sequence to output - if playing.fetch_and(true, Ordering::Relaxed) { - let mut writer = output.writer(scope); - let sequence = sequence.lock().unwrap(); - for frame in 0..chunk_size { - let value = frame_steps[(start_looped + frame as usize) % loop_frames]; - if let Some(step) = value { - for track in sequence.iter() { - for event in track[step].iter() { - writer.write(&::jack::RawMidi { - time: frame as u32, - bytes: &match event { - SequencerEvent::NoteOn(pitch, velocity) => [ - 0b10010000, - *pitch, - *velocity - ], - SequencerEvent::NoteOff(pitch) => [ - 0b10000000, - *pitch, - 0b00000000 - ], - } - }).unwrap() - } - } - } - } - } - } - Control::Continue - }) - )? + jack: SequencerJack.activate(client, ClosureProcessHandler::new( + Box::new(move |client: &Client, scope: &ProcessScope| process(client, scope)) as BoxedProcessHandler + ))? })) } } +pub fn process ( + client: &Client, + scope: &ProcessScope +) -> Control { + //if exited.fetch_and(true, Ordering::Relaxed) { + //return Control::Quit + //} + //if client.transport().query_state().unwrap() == TransportState::Rolling { + //let chunk_start = scope.last_frame_time(); + //let chunk_size = scope.n_frames(); + //let chunk_end = chunk_start + chunk_size; + //let start_looped = chunk_start as usize % loop_frames; + //let end_looped = chunk_end as usize % loop_frames; + + //// Write MIDI notes from input to sequence + //if recording.fetch_and(true, Ordering::Relaxed) { + ////let overdub = overdub.fetch_and(true, Ordering::Relaxed); + //for event in input.iter(scope) { + //let ::jack::RawMidi { time, bytes } = event; + //println!("\n{time} {bytes:?}"); + //} + //} + + //// Write MIDI notes from sequence to output + //if playing.fetch_and(true, Ordering::Relaxed) { + //let mut writer = output.writer(scope); + //let sequence = sequence.lock().unwrap(); + //for frame in 0..chunk_size { + //let value = frame_steps[(start_looped + frame as usize) % loop_frames]; + //if let Some(step) = value { + //for track in sequence.iter() { + //for event in track[step].iter() { + //writer.write(&::jack::RawMidi { + //time: frame as u32, + //bytes: &match event { + //SequencerEvent::NoteOn(pitch, velocity) => [ + //0b10010000, + //*pitch, + //*velocity + //], + //SequencerEvent::NoteOff(pitch) => [ + //0b10000000, + //*pitch, + //0b00000000 + //], + //} + //}).unwrap() + //} + //} + //} + //} + //} + //} + Control::Continue +} + pub const ACTIONS: [(&'static str, &'static str);4] = [ ("+/-", "Zoom"), ("A/D", "Add/delete note"), @@ -135,6 +147,9 @@ pub const ACTIONS: [(&'static str, &'static str);4] = [ pub fn handle (state: &mut Sequencer, event: &EngineEvent) -> Result<(), Box> { + if let EngineEvent::Focus = event { + } + if let EngineEvent::Input(Event::Key(event)) = event { match event.code { KeyCode::Down => { @@ -197,33 +212,38 @@ pub fn handle (state: &mut Sequencer, event: &EngineEvent) -> Result<(), Box Usually { - let style = Style::default().gray(); let header = draw_sequencer_header(sequencer, buf, area)?; - let piano = draw_sequencer_vertical(sequencer, buf, Rect { - x: area.x, - y: area.y + header.height, - width: area.width, - height: area.height - header.height - })?; - let area = Rect { + let piano = match sequencer.mode { + SequencerMode::Tiny => Rect { + x: area.x, + y: area.y, + width: area.width, + height: 0 + }, + SequencerMode::Compact => Rect { + x: area.x, + y: area.y, + width: area.width, + height: 0 + }, + SequencerMode::Vertical => draw_sequencer_vertical(sequencer, buf, Rect { + x: area.x, + y: area.y + header.height, + width: area.width, + height: area.height - header.height + })?, + _ => unimplemented!() + }; + //draw_box_styled(buf, area, Some(Style::default().red())); + Ok(Rect { x: area.x, y: area.y, width: header.width.max(piano.width), height: header.height + piano.height - }; - //draw_box_styled(buf, area, Some(Style::default().red())); - Ok(area) + }) } fn draw_sequencer_header (sequencer: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually { @@ -235,32 +255,52 @@ fn draw_sequencer_header (sequencer: &Sequencer, buf: &mut Buffer, area: Rect) - Ok(Rect { x, y, width, height: 4 }) } +const KEY_WHITE: Style = Style { + fg: Some(Color::Gray), + bg: None, + underline_color: None, + add_modifier: ::ratatui::style::Modifier::empty(), + sub_modifier: ::ratatui::style::Modifier::empty(), +}; + +const KEY_BLACK: Style = Style { + fg: Some(Color::Black), + bg: None, + underline_color: None, + add_modifier: ::ratatui::style::Modifier::empty(), + sub_modifier: ::ratatui::style::Modifier::empty(), +}; + +const KEY_HORIZONTAL_STYLE: [Style;12] = [ + KEY_WHITE, KEY_BLACK, KEY_WHITE, KEY_BLACK, KEY_WHITE, + KEY_WHITE, KEY_BLACK, KEY_WHITE, KEY_BLACK, KEY_WHITE, KEY_BLACK, KEY_WHITE, +]; + fn draw_sequencer_vertical (sequencer: &Sequencer, buf: &mut Buffer, area: Rect) -> Usually { let Rect { x, y, .. } = area; - let white = Style::default().gray(); - let black = Style::default().black(); - for i in 0..area.width-4 { - let color = match i % 12 { - 0 => white, - 1 => black, - 2 => white, - 3 => black, - 4 => white, - 5 => white, - 6 => black, - 7 => white, - 8 => black, - 9 => white, - 10 => black, - 11 => white, - _ => unreachable!() - }; - buf.set_string(x + 2 + i, y - 1, &format!("▄"), color); - buf.set_string(x + 2 + i, y, &format!("▀"), color); + for i in 0..area.width-7 { + let color = KEY_HORIZONTAL_STYLE[i as usize % 12]; + buf.set_string(x + 5 + i, y - 1, &format!("▄"), color); + buf.set_string(x + 5 + i, y, &format!("▀"), color); } let bg = Style::default().on_black(); for i in 0..area.height - 8 { - buf.set_string(x + 2, y + 1 + i, &" ".repeat((area.width-4)as usize), bg); + buf.set_string(x + 5, y + 1 + i, &" ".repeat((area.width-7)as usize), bg); + if i % 4 == 0 { + buf.set_string(x + 2, y + 1 + i, &format!("{:2}", i / 4 + 1), Style::default()); + } + } + for octave in -2i16..=8i16 { + let x = x + 5 + (24i16 + octave * 12) as u16; + if x >= area.x + area.width - 6 { + break + } + buf.set_string( + x, + y + 1, + &format!("C{octave}"), + Style::default() + ); } Ok(Rect { x, y, width: area.width, height: area.height - 6 }) } @@ -373,6 +413,14 @@ fn draw_rec_dub_button ( buf.set_string(x + 11, y + 2, "┴", Style::default().gray()); } +const NOTE_NAMES: [&'static str;12] = [ + "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B", +]; + +const KEYS_VERTICAL: [&'static str; 6] = [ + "▀", "▀", "▀", "█", "▄", "▄", +]; + fn draw_sequence_keys ( area: Rect, buf: &mut Buffer, @@ -470,9 +518,17 @@ fn draw_sequence_cursor ( buf.set_string(area.x + cursor.1 + 3, area.y + 1 + cursor_y, cursor_text, style); } -pub struct Notifications; +pub struct SequencerJack; -impl NotificationHandler for Notifications { +impl SequencerJack { + fn activate (self, client: Client, handler: ClosureProcessHandler) + -> Usually>> + { + Ok(client.activate_async(self, handler)?) + } +} + +impl NotificationHandler for SequencerJack { fn thread_init (&self, _: &Client) { } diff --git a/src/device/transport.rs b/src/device/transport.rs index cbbe68d5..f5245229 100644 --- a/src/device/transport.rs +++ b/src/device/transport.rs @@ -47,50 +47,74 @@ pub fn render (state: &Transport, buf: &mut Buffer, mut area: Rect) area.width = area.width.min(80); area.height = 5; //draw_box(buf, area); - draw_leaf(buf, area, 1, 0, "REC"); - draw_leaf(buf, area, 1, 5, "DUB"); - draw_leaf(buf, area, 1, 10, "STOP"); - draw_leaf(buf, area, 1, 16, "PLAY/PAUSE"); - draw_leaf(buf, area, 1, 28, "START"); - draw_leaf(buf, area, 1, 35, "Project: Witty Gerbil - Sha Na Na "); - draw_leaf(buf, area, 3, 0, &format!("BPM {:03}.{:03}", - state.bpm as u64, - ((state.bpm % 1.0) * 1000.0) as u64 - )); - let position = state.transport.as_ref().map(|t|t.query()); - if let Some(Ok(position)) = position { - let rate = position.pos.frame_rate().unwrap(); - let frame = position.pos.frame(); - let second = (frame as f64) / (rate as f64); - let minute = second / 60f64; - let bpm = 120f64; - let div = 4; - let beats = minute * bpm; - let bars = beats as u32 / div as u32; - let beat = beats as u32 % div as u32 + 1; - let beat_sub = beats % 1.0; - //buf.set_string( - //area.x - 18, area.y + area.height, - //format!("BBT {bars:04}:{beat:02}.{:02}", (beat_sub * 16.0) as u32), - //Style::default() - //); - draw_leaf(buf, area, 3, 13, &format!("BBT {bars:04}:{beat:02}.{:02}", - (beat_sub * 16.0) as u32 - )); - let time = frame as f64 / rate as f64; - let seconds = time % 60.0; - let msec = seconds % 1.0; - let minutes = (time / 60.0) % 60.0; - let hours = time / 3600.0; - draw_leaf(buf, area, 3, 29, &format!("Time {:02}:{:02}:{:02}.{:03}", - hours as u64, - minutes as u64, - seconds as u64, - (msec * 1000.0) as u64 - )); - draw_leaf(buf, area, 3, 48, &format!("Rate {:>6}Hz", rate)); - draw_leaf(buf, area, 3, 63, &format!("Frame {:>10}", frame)); + let label = Style::default().white().not_dim(); + let border = Style::default().gray().dim(); + let mut x = 2; + for button in [ + "PLAY", + "STOP", + "REC", + "DUB", + "0.0.00", + "0:00.000", + &format!("BPM {:03}.{:03}", + state.bpm as u64, + ((state.bpm % 1.0) * 1000.0) as u64 + ) + ].iter() { + buf.set_string(area.x + x, area.y + 2, button, label); + x = x + button.len() as u16 + 1; + buf.set_string(area.x + x, area.y + 1, "│", border); + buf.set_string(area.x + x, area.y + 2, "│", border); + buf.set_string(area.x + x, area.y + 3, "│", border); + x = x + 2; } + //buf.set_string(area.x, area.y + 5, "Witty Gerbil - Sha Na Na", label.bold()); + //&format!(" │ 00:00.00 / 00:00.00"), style); + //draw_leaf(buf, area, 1, 0, "REC"); + //draw_leaf(buf, area, 1, 5, "DUB"); + //draw_leaf(buf, area, 1, 10, "STOP"); + //draw_leaf(buf, area, 1, 16, "PLAY/PAUSE"); + //draw_leaf(buf, area, 1, 28, "START"); + //draw_leaf(buf, area, 1, 35, "Project: Witty Gerbil - Sha Na Na "); + //draw_leaf(buf, area, 3, 0, &format!("BPM {:03}.{:03}", + //state.bpm as u64, + //((state.bpm % 1.0) * 1000.0) as u64 + //)); + //let position = state.transport.as_ref().map(|t|t.query()); + //if let Some(Ok(position)) = position { + //let rate = position.pos.frame_rate().unwrap(); + //let frame = position.pos.frame(); + //let second = (frame as f64) / (rate as f64); + //let minute = second / 60f64; + //let bpm = 120f64; + //let div = 4; + //let beats = minute * bpm; + //let bars = beats as u32 / div as u32; + //let beat = beats as u32 % div as u32 + 1; + //let beat_sub = beats % 1.0; + ////buf.set_string( + ////area.x - 18, area.y + area.height, + ////format!("BBT {bars:04}:{beat:02}.{:02}", (beat_sub * 16.0) as u32), + ////Style::default() + ////); + //draw_leaf(buf, area, 3, 13, &format!("BBT {bars:04}:{beat:02}.{:02}", + //(beat_sub * 16.0) as u32 + //)); + //let time = frame as f64 / rate as f64; + //let seconds = time % 60.0; + //let msec = seconds % 1.0; + //let minutes = (time / 60.0) % 60.0; + //let hours = time / 3600.0; + //draw_leaf(buf, area, 3, 29, &format!("Time {:02}:{:02}:{:02}.{:03}", + //hours as u64, + //minutes as u64, + //seconds as u64, + //(msec * 1000.0) as u64 + //)); + //draw_leaf(buf, area, 3, 48, &format!("Rate {:>6}Hz", rate)); + //draw_leaf(buf, area, 3, 63, &format!("Frame {:>10}", frame)); + //} //let bbt = position.pos.bbt().map(|mut bbt|*bbt //.with_bpm(state.bpm) //.with_timesig(state.timesig.0, state.timesig.1)); diff --git a/src/layout.rs b/src/layout.rs index 4776568c..997e6a33 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -18,6 +18,7 @@ impl Device for Rows { match event { EngineEvent::Input(Event::Key(KeyEvent { code: KeyCode::Esc, .. })) => { self.focused = true; + self.items[self.focus].handle(&EngineEvent::Blur)?; Ok(()) }, _ => self.items[self.focus].handle(event) @@ -39,9 +40,11 @@ impl Device for Rows { }, Event::Key(KeyEvent { code: KeyCode::Enter, .. }) => { self.focused = false; + self.items[self.focus].handle(&EngineEvent::Focus)?; }, Event::Key(KeyEvent { code: KeyCode::Esc, .. }) => { self.focused = true; + self.items[self.focus].handle(&EngineEvent::Blur)?; }, _ => { println!("{event:?}"); @@ -63,7 +66,7 @@ impl Device for Rows { height: area.height - h })?; if self.focused && i == self.focus { - draw_box_styled(buf, result, Some(Style::default().gray().bold().not_dim())) + draw_box_styled(buf, result, Some(Style::default().green().dim().bold())) } w = w.max(result.width); h = h + result.height; diff --git a/src/prelude.rs b/src/prelude.rs index a3e25122..7dec2c96 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -57,6 +57,6 @@ pub use crate::render::*; pub use crate::device::{ Device, DynamicDevice, - EngineEvent + EngineEvent, }; pub type Usually = Result>;