WIP: half broken

This commit is contained in:
2026-01-24 01:59:51 +01:00
parent f75ea4bb97
commit 04f5e19ab2
21 changed files with 1310 additions and 119 deletions

View File

@@ -8,9 +8,9 @@ use crate::app::App;
use crate::engine::{LinkState, SequencerSnapshot};
use crate::model::SourceSpan;
use crate::page::Page;
use crate::state::{Modal, PatternField};
use crate::state::{Modal, PanelFocus, PatternField, SidePanel};
use crate::views::highlight::{self, highlight_line, highlight_line_with_runtime};
use crate::widgets::{ConfirmModal, ModalFrame, TextInputModal};
use crate::widgets::{ConfirmModal, ModalFrame, SampleBrowser, TextInputModal};
use super::{audio_view, doc_view, main_view, patterns_view, title_view};
@@ -55,17 +55,58 @@ pub fn render(frame: &mut Frame, app: &mut App, link: &LinkState, snapshot: &Seq
render_header(frame, app, link, snapshot, header_area);
let (page_area, panel_area) = if app.panel.visible && app.panel.side.is_some() {
if body_area.width >= 120 {
let panel_width = body_area.width * 35 / 100;
let [main, side] = Layout::horizontal([
Constraint::Fill(1),
Constraint::Length(panel_width),
])
.areas(body_area);
(main, Some(side))
} else {
let panel_height = body_area.height * 40 / 100;
let [main, side] = Layout::vertical([
Constraint::Fill(1),
Constraint::Length(panel_height),
])
.areas(body_area);
(main, Some(side))
}
} else {
(body_area, None)
};
match app.page {
Page::Main => main_view::render(frame, app, snapshot, body_area),
Page::Patterns => patterns_view::render(frame, app, snapshot, body_area),
Page::Audio => audio_view::render(frame, app, link, body_area),
Page::Doc => doc_view::render(frame, app, body_area),
Page::Main => main_view::render(frame, app, snapshot, page_area),
Page::Patterns => patterns_view::render(frame, app, snapshot, page_area),
Page::Audio => audio_view::render(frame, app, link, page_area),
Page::Doc => doc_view::render(frame, app, page_area),
}
if let Some(side_area) = panel_area {
render_side_panel(frame, app, side_area);
}
render_footer(frame, app, footer_area);
render_modal(frame, app, snapshot, term);
}
fn render_side_panel(frame: &mut Frame, app: &App, area: Rect) {
let focused = app.panel.focus == PanelFocus::Side;
match &app.panel.side {
Some(SidePanel::SampleBrowser(state)) => {
let entries = state.entries();
SampleBrowser::new(&entries, state.cursor)
.scroll_offset(state.scroll_offset)
.search(&state.search_query, state.search_active)
.focused(focused)
.render(frame, area);
}
None => {}
}
}
fn render_header(
frame: &mut Frame,
app: &App,
@@ -373,11 +414,19 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term
.border_color(Color::Magenta)
.render_centered(frame, term);
}
Modal::AddSamplePath(path) => {
TextInputModal::new("Add Sample Path", path)
.hint("Enter directory path containing samples")
.width(60)
Modal::AddSamplePath(state) => {
use crate::widgets::FileBrowserModal;
let entries: Vec<(String, bool)> = state
.entries
.iter()
.map(|e| (e.name.clone(), e.is_dir))
.collect();
FileBrowserModal::new("Add Sample Path", &state.input, &entries)
.selected(state.selected)
.scroll_offset(state.scroll_offset)
.border_color(Color::Magenta)
.width(60)
.height(18)
.render_centered(frame, term);
}
Modal::Preview => {
@@ -461,8 +510,6 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term
.border_color(border_color)
.render_centered(frame, term);
let (cursor_row, cursor_col) = app.editor_ctx.text.cursor();
let trace = if app.ui.runtime_highlight && app.playback.playing {
let source = app.current_edit_pattern().resolve_source(app.editor_ctx.step);
snapshot.get_trace(app.editor_ctx.bank, app.editor_ctx.pattern, source)
@@ -470,7 +517,7 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term
None
};
let text_lines = app.editor_ctx.text.lines();
let text_lines = app.editor_ctx.editor.lines();
let mut line_offsets: Vec<usize> = Vec::with_capacity(text_lines.len());
let mut offset = 0;
for line in text_lines.iter() {
@@ -478,68 +525,19 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term
offset += line.len() + 1;
}
let lines: Vec<Line> = text_lines
.iter()
.enumerate()
.map(|(row, line)| {
let mut spans: Vec<Span> = Vec::new();
let highlighter = |row: usize, line: &str| -> Vec<(Style, String)> {
let line_start = line_offsets[row];
let (exec, sel) = match trace {
Some(t) => (
adjust_spans_for_line(&t.executed_spans, line_start, line.len()),
adjust_spans_for_line(&t.selected_spans, line_start, line.len()),
),
None => (Vec::new(), Vec::new()),
};
highlight::highlight_line_with_runtime(line, &exec, &sel)
};
let line_start = line_offsets[row];
let (exec_spans, sel_spans) = if let Some(t) = trace {
(
adjust_spans_for_line(&t.executed_spans, line_start, line.len()),
adjust_spans_for_line(&t.selected_spans, line_start, line.len()),
)
} else {
(Vec::new(), Vec::new())
};
let tokens = highlight::highlight_line_with_runtime(line, &exec_spans, &sel_spans);
if row == cursor_row {
let mut col = 0;
for (style, text) in tokens {
let text_len = text.chars().count();
if cursor_col >= col && cursor_col < col + text_len {
let before =
text.chars().take(cursor_col - col).collect::<String>();
let cursor_char = text.chars().nth(cursor_col - col).unwrap_or(' ');
let after =
text.chars().skip(cursor_col - col + 1).collect::<String>();
if !before.is_empty() {
spans.push(Span::styled(before, style));
}
spans.push(Span::styled(
cursor_char.to_string(),
Style::default().bg(Color::White).fg(Color::Black),
));
if !after.is_empty() {
spans.push(Span::styled(after, style));
}
} else {
spans.push(Span::styled(text, style));
}
col += text_len;
}
if cursor_col >= col {
spans.push(Span::styled(
" ",
Style::default().bg(Color::White).fg(Color::Black),
));
}
} else {
for (style, text) in tokens {
spans.push(Span::styled(text, style));
}
}
Line::from(spans)
})
.collect();
let paragraph = Paragraph::new(lines);
frame.render_widget(paragraph, inner);
app.editor_ctx.editor.render(frame, inner, &highlighter);
}
}
}