Fix layout

This commit is contained in:
2026-02-02 12:18:22 +01:00
parent 2af0b67714
commit 7348bd38b1
12 changed files with 294 additions and 65 deletions

View File

@@ -1,65 +1,117 @@
use ratatui::layout::{Alignment, Constraint, Layout, Rect};
use ratatui::style::{Color, Modifier, Style};
use ratatui::widgets::Paragraph;
use ratatui::widgets::{Block, Borders, Paragraph};
use ratatui::Frame;
use crate::app::App;
use crate::engine::SequencerSnapshot;
use crate::state::MainLayout;
use crate::theme;
use crate::widgets::{Orientation, Scope, Spectrum, VuMeter};
use crate::widgets::{ActivePatterns, Orientation, Scope, Spectrum, VuMeter};
pub fn render(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, area: Rect) {
let [left_area, _spacer, vu_area] = Layout::horizontal([
let [patterns_area, _, main_area, _, vu_area] = Layout::horizontal([
Constraint::Length(13),
Constraint::Length(2),
Constraint::Fill(1),
Constraint::Length(2),
Constraint::Length(8),
Constraint::Length(10),
])
.areas(area);
let show_scope = app.audio.config.show_scope;
let show_spectrum = app.audio.config.show_spectrum;
let viz_height = if show_scope || show_spectrum { 14 } else { 0 };
let has_viz = show_scope || show_spectrum;
let layout = app.audio.config.layout;
let [viz_area, sequencer_area] = Layout::vertical([
Constraint::Length(viz_height),
Constraint::Fill(1),
])
.areas(left_area);
let (viz_area, sequencer_area) = match layout {
MainLayout::Top => {
let viz_height = if has_viz { 16 } else { 0 };
let [viz, seq] = Layout::vertical([
Constraint::Length(viz_height),
Constraint::Fill(1),
])
.areas(main_area);
(viz, seq)
}
MainLayout::Bottom => {
let viz_height = if has_viz { 16 } else { 0 };
let [seq, viz] = Layout::vertical([
Constraint::Fill(1),
Constraint::Length(viz_height),
])
.areas(main_area);
(viz, seq)
}
MainLayout::Left => {
let viz_width = if has_viz { 33 } else { 0 };
let [viz, _spacer, seq] = Layout::horizontal([
Constraint::Percentage(viz_width),
Constraint::Length(2),
Constraint::Fill(1),
])
.areas(main_area);
(viz, seq)
}
MainLayout::Right => {
let viz_width = if has_viz { 33 } else { 0 };
let [seq, _spacer, viz] = Layout::horizontal([
Constraint::Fill(1),
Constraint::Length(2),
Constraint::Percentage(viz_width),
])
.areas(main_area);
(viz, seq)
}
};
if show_scope && show_spectrum {
let [scope_area, _, spectrum_area] = Layout::horizontal([
Constraint::Percentage(50),
Constraint::Length(2),
Constraint::Percentage(50),
])
.areas(viz_area);
render_scope(frame, app, scope_area);
render_spectrum(frame, app, spectrum_area);
} else if show_scope {
render_scope(frame, app, viz_area);
} else if show_spectrum {
render_spectrum(frame, app, viz_area);
if has_viz {
render_viz_area(frame, app, viz_area, layout, show_scope, show_spectrum);
}
render_sequencer(frame, app, snapshot, sequencer_area);
render_vu_meter(frame, app, vu_area);
render_active_patterns(frame, app, snapshot, patterns_area);
}
// Calculate actual grid height to align VU meter
let pattern = app.current_edit_pattern();
let page = app.editor_ctx.step / STEPS_PER_PAGE;
let page_start = page * STEPS_PER_PAGE;
let steps_on_page = (page_start + STEPS_PER_PAGE).min(pattern.length) - page_start;
let num_rows = steps_on_page.div_ceil(8);
let spacing = num_rows.saturating_sub(1) as u16;
let row_height = sequencer_area.height.saturating_sub(spacing) / num_rows as u16;
let actual_grid_height = row_height * num_rows as u16 + spacing;
fn render_viz_area(
frame: &mut Frame,
app: &App,
area: Rect,
layout: MainLayout,
show_scope: bool,
show_spectrum: bool,
) {
let is_vertical_layout = matches!(layout, MainLayout::Left | MainLayout::Right);
let aligned_vu_area = Rect {
y: sequencer_area.y,
height: actual_grid_height,
..vu_area
};
render_vu_meter(frame, app, aligned_vu_area);
if show_scope && show_spectrum {
if is_vertical_layout {
let [scope_area, spectrum_area] = Layout::vertical([
Constraint::Fill(1),
Constraint::Fill(1),
])
.areas(area);
render_scope(frame, app, scope_area, Orientation::Vertical);
render_spectrum(frame, app, spectrum_area);
} else {
let [scope_area, spectrum_area] = Layout::horizontal([
Constraint::Fill(1),
Constraint::Fill(1),
])
.areas(area);
render_scope(frame, app, scope_area, Orientation::Horizontal);
render_spectrum(frame, app, spectrum_area);
}
} else if show_scope {
let orientation = if is_vertical_layout {
Orientation::Vertical
} else {
Orientation::Horizontal
};
render_scope(frame, app, area, orientation);
} else if show_spectrum {
render_spectrum(frame, app, area);
}
}
const STEPS_PER_PAGE: usize = 32;
@@ -233,21 +285,65 @@ fn render_tile(
}
}
fn render_scope(frame: &mut Frame, app: &App, area: Rect) {
fn render_scope(frame: &mut Frame, app: &App, area: Rect, orientation: Orientation) {
let theme = theme::get();
let block = Block::default()
.borders(Borders::ALL)
.border_style(Style::new().fg(theme.ui.border));
let inner = block.inner(area);
frame.render_widget(block, area);
let scope = Scope::new(&app.metrics.scope)
.orientation(Orientation::Horizontal)
.orientation(orientation)
.color(theme.meter.low);
frame.render_widget(scope, area);
frame.render_widget(scope, inner);
}
fn render_spectrum(frame: &mut Frame, app: &App, area: Rect) {
let area = Rect { height: area.height.saturating_sub(1), ..area };
let theme = theme::get();
let block = Block::default()
.borders(Borders::ALL)
.border_style(Style::new().fg(theme.ui.border));
let inner = block.inner(area);
frame.render_widget(block, area);
let spectrum = Spectrum::new(&app.metrics.spectrum);
frame.render_widget(spectrum, area);
frame.render_widget(spectrum, inner);
}
fn render_vu_meter(frame: &mut Frame, app: &App, area: Rect) {
let theme = theme::get();
let block = Block::default()
.borders(Borders::ALL)
.border_style(Style::new().fg(theme.ui.border));
let inner = block.inner(area);
frame.render_widget(block, area);
let vu = VuMeter::new(app.metrics.peak_left, app.metrics.peak_right);
frame.render_widget(vu, area);
frame.render_widget(vu, inner);
}
fn render_active_patterns(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, area: Rect) {
let theme = theme::get();
let block = Block::default()
.borders(Borders::ALL)
.border_style(Style::new().fg(theme.ui.border));
let inner = block.inner(area);
frame.render_widget(block, area);
let patterns: Vec<(usize, usize, usize)> = snapshot
.active_patterns
.iter()
.map(|p| (p.bank, p.pattern, p.iter))
.collect();
let step_info = snapshot
.get_step(app.editor_ctx.bank, app.editor_ctx.pattern)
.map(|step| (step, app.current_edit_pattern().length));
let mut widget = ActivePatterns::new(&patterns);
if let Some((step, total)) = step_info {
widget = widget.with_step(step, total);
}
frame.render_widget(widget, inner);
}

View File

@@ -3,6 +3,7 @@ use ratatui::style::Style;
use ratatui::text::{Line, Span};
use ratatui::widgets::Paragraph;
use ratatui::Frame;
#[cfg(not(feature = "desktop"))]
use tui_big_text::{BigText, PixelSize};
use crate::state::ui::UiState;
@@ -16,13 +17,21 @@ pub fn render(frame: &mut Frame, area: Rect, ui: &UiState) {
let link_style = Style::new().fg(theme.title.link);
let license_style = Style::new().fg(theme.title.license);
#[cfg(not(feature = "desktop"))]
let big_title = BigText::builder()
.pixel_size(PixelSize::Quadrant)
.pixel_size(PixelSize::Full)
.style(Style::new().fg(theme.title.big_title).bold())
.lines(vec!["CAGIRE".into()])
.centered()
.build();
#[cfg(feature = "desktop")]
let big_title = Paragraph::new(Line::from(Span::styled(
"CAGIRE",
Style::new().fg(theme.title.big_title).bold(),
)))
.alignment(Alignment::Center);
let version_style = Style::new().fg(theme.title.subtitle);
let subtitle_lines = vec![
@@ -49,21 +58,43 @@ pub fn render(frame: &mut Frame, area: Rect, ui: &UiState) {
)),
];
let big_text_height = 4;
#[cfg(not(feature = "desktop"))]
let big_text_height = 8;
#[cfg(feature = "desktop")]
let big_text_height = 1;
let min_title_width = 30;
let subtitle_height = subtitle_lines.len() as u16;
let total_height = big_text_height + subtitle_height;
let vertical_padding = area.height.saturating_sub(total_height) / 2;
let [_, title_area, subtitle_area, _] = Layout::vertical([
Constraint::Length(vertical_padding),
Constraint::Length(big_text_height),
Constraint::Length(subtitle_height),
Constraint::Fill(1),
])
.areas(area);
let show_big_title =
area.height >= (big_text_height + subtitle_height) && area.width >= min_title_width;
frame.render_widget(big_title, title_area);
if show_big_title {
let total_height = big_text_height + subtitle_height;
let vertical_padding = area.height.saturating_sub(total_height) / 2;
let subtitle = Paragraph::new(subtitle_lines).alignment(Alignment::Center);
frame.render_widget(subtitle, subtitle_area);
let [_, title_area, subtitle_area, _] = Layout::vertical([
Constraint::Length(vertical_padding),
Constraint::Length(big_text_height),
Constraint::Length(subtitle_height),
Constraint::Fill(1),
])
.areas(area);
frame.render_widget(big_title, title_area);
let subtitle = Paragraph::new(subtitle_lines).alignment(Alignment::Center);
frame.render_widget(subtitle, subtitle_area);
} else {
let vertical_padding = area.height.saturating_sub(subtitle_height) / 2;
let [_, subtitle_area, _] = Layout::vertical([
Constraint::Length(vertical_padding),
Constraint::Length(subtitle_height),
Constraint::Fill(1),
])
.areas(area);
let subtitle = Paragraph::new(subtitle_lines).alignment(Alignment::Center);
frame.render_widget(subtitle, subtitle_area);
}
}