From 380d990f3b1064e424c3620d1ec7435fe60c615e Mon Sep 17 00:00:00 2001 From: lobo Date: Wed, 11 Mar 2026 23:18:20 +0100 Subject: [PATCH] feat: merge AMP and LFO sections side-by-side in synth knobs panel Move LFO section to the right of AMP in the same row group, reducing synth knobs panel height from 30 to 25 rows (saves 10 rows total for dual synths). Updates both rendering and mouse hit-testing. Co-Authored-By: Claude Opus 4.6 --- src/mouse.rs | 74 ++++++++++++++++++++++++------------------- src/ui/layout.rs | 4 +-- src/ui/synth_knobs.rs | 48 +++++++++++++++------------- 3 files changed, 69 insertions(+), 57 deletions(-) diff --git a/src/mouse.rs b/src/mouse.rs index 6504dc3..67b44d4 100644 --- a/src/mouse.rs +++ b/src/mouse.rs @@ -415,8 +415,7 @@ fn hit_test_synth_knobs(col: u16, row: u16, knobs_area: Rect) -> Option Option 0 { - let rel_x = (col - row_groups[2].x) as usize; - let idx = (rel_x / col_width).min(3); - return match idx { - 0 => Some(SynthControlField::LfoWaveform), - 1 => Some(SynthControlField::LfoDivision), - 2 => Some(SynthControlField::LfoDepth), - _ => Some(SynthControlField::LfoDest), - }; - } - return Some(SynthControlField::LfoWaveform); - } + let cols = Layout::default() + .direction(Direction::Horizontal) + .constraints([ + Constraint::Percentage(50), // AMP + Constraint::Percentage(50), // LFO + ]) + .split(row_groups[2]); - // ── Row group 3: AMP ───────────────────────────────────────────────── - if hit_test_area(col, row, row_groups[3]) { - let amp_w = row_groups[3].width.min(40); - let amp_body = Rect::new( - row_groups[3].x, - row_groups[3].y + 1, - amp_w, - row_groups[3].height.saturating_sub(1), - ); - if hit_test_area(col, row, amp_body) { - let col_width = amp_w as usize / 4; + if hit_test_area(col, row, cols[0]) { + // AMP: skip header row + let amp_body = Rect::new( + cols[0].x, + cols[0].y + 1, + cols[0].width, + cols[0].height.saturating_sub(1), + ); + if hit_test_area(col, row, amp_body) { + let col_width = cols[0].width as usize / 4; + if col_width > 0 { + let rel_x = (col - amp_body.x) as usize; + let idx = (rel_x / col_width).min(3); + return match idx { + 0 => Some(SynthControlField::Volume), + 1 => Some(SynthControlField::SendReverb), + 2 => Some(SynthControlField::SendDelay), + _ => Some(SynthControlField::Volume), // Sat not a synth field + }; + } + } + return Some(SynthControlField::Volume); + } else if hit_test_area(col, row, cols[1]) { + // LFO + let col_width = cols[1].width as usize / 4; if col_width > 0 { - let rel_x = (col - amp_body.x) as usize; - let idx = (rel_x / col_width).min(2); + let rel_x = (col - cols[1].x) as usize; + let idx = (rel_x / col_width).min(3); return match idx { - 0 => Some(SynthControlField::Volume), - 1 => Some(SynthControlField::SendReverb), - _ => Some(SynthControlField::SendDelay), + 0 => Some(SynthControlField::LfoWaveform), + 1 => Some(SynthControlField::LfoDivision), + 2 => Some(SynthControlField::LfoDepth), + _ => Some(SynthControlField::LfoDest), }; } + return Some(SynthControlField::LfoWaveform); } - return Some(SynthControlField::Volume); } None diff --git a/src/ui/layout.rs b/src/ui/layout.rs index 2b7cd78..c601651 100644 --- a/src/ui/layout.rs +++ b/src/ui/layout.rs @@ -17,8 +17,8 @@ pub const KNOBS_HEIGHT: u16 = 9; /// Alias used by the new dual-synth layout. pub const DRUM_KNOBS_HEIGHT: u16 = KNOBS_HEIGHT; -/// Height of the synth knobs panel (OSC 8 + ENV/FILT 8 + LFO 3 + AMP 7 + 2 border = 30). -pub const SYNTH_KNOBS_HEIGHT: u16 = 30; +/// Height of the synth knobs panel (OSC 8 + ENV/FILT 8 + AMP+LFO 7 + 2 border = 25). +pub const SYNTH_KNOBS_HEIGHT: u16 = 25; /// Height of the synth step row (2 border + header + spacer + step row + spacer = 6). pub const SYNTH_GRID_HEIGHT: u16 = 6; diff --git a/src/ui/synth_knobs.rs b/src/ui/synth_knobs.rs index 97b2f65..0212621 100644 --- a/src/ui/synth_knobs.rs +++ b/src/ui/synth_knobs.rs @@ -98,14 +98,13 @@ pub fn render_synth_knobs(f: &mut Frame, area: Rect, app: &App, synth_id: SynthI let params = &pattern.params; let sel = ui.ctrl_field; - // Split inner into 4 row groups: OSC (8), ENV+FILT (8), LFO (3), AMP (remaining) + // Split inner into 3 row groups: OSC (8), ENV+FILT (8), AMP+LFO (remaining) let row_groups = Layout::default() .direction(Direction::Vertical) .constraints([ Constraint::Length(8), // Row group 1: OSC1 + OSC2 Constraint::Length(8), // Row group 2: ENV1 + ENV2 + FILT - Constraint::Length(3), // Row group 3: LFO - Constraint::Min(7), // Row group 4: AMP + Constraint::Min(7), // Row group 3: AMP (left) + LFO (right) ]) .split(inner); @@ -204,30 +203,35 @@ pub fn render_synth_knobs(f: &mut Frame, area: Rect, app: &App, synth_id: SynthI render_adsr_bars(f, filt_split[1], params, FILT_ENV_ADSR, sel, focused); } - // ── Row group 3: LFO ──────────────────────────────────────────────── + // ── Row group 3: AMP (left) + LFO (right) ───────────────────────── { - render_section_header(f, row_groups[2], "LFO"); - let lfo_body = Rect::new( - row_groups[2].x, - row_groups[2].y + 1, - row_groups[2].width, - row_groups[2].height.saturating_sub(1), - ); - render_lfo_row(f, lfo_body, params, sel, focused); - } - - // ── Row group 4: AMP ──────────────────────────────────────────────── - { - render_section_header(f, row_groups[3], "AMP"); + let cols = Layout::default() + .direction(Direction::Horizontal) + .constraints([ + Constraint::Percentage(50), // AMP + Constraint::Percentage(50), // LFO + ]) + .split(row_groups[2]); + // AMP section + render_section_header(f, cols[0], "AMP"); let amp_body = Rect::new( - row_groups[3].x, - row_groups[3].y + 1, - row_groups[3].width.min(40), - row_groups[3].height.saturating_sub(1), + cols[0].x, + cols[0].y + 1, + cols[0].width, + cols[0].height.saturating_sub(1), ); - render_amp_group(f, amp_body, params, app.effect_params.synth_saturator_drive, sel, focused); + + // LFO section + render_section_header(f, cols[1], "LFO"); + let lfo_body = Rect::new( + cols[1].x, + cols[1].y + 1, + cols[1].width, + cols[1].height.saturating_sub(1), + ); + render_lfo_row(f, lfo_body, params, sel, focused); } }