Feat: documentation
This commit is contained in:
@@ -291,12 +291,14 @@ impl App {
|
||||
let palette = scheme.to_palette();
|
||||
let rotated = cagire_ratatui::theme::transform::rotate_palette(&palette, self.ui.hue_rotation);
|
||||
crate::theme::set(rotated);
|
||||
self.ui.invalidate_help_cache();
|
||||
}
|
||||
AppCommand::SetHueRotation(degrees) => {
|
||||
self.ui.hue_rotation = degrees;
|
||||
let palette = self.ui.color_scheme.to_palette();
|
||||
let rotated = cagire_ratatui::theme::transform::rotate_palette(&palette, degrees);
|
||||
crate::theme::set(rotated);
|
||||
self.ui.invalidate_help_cache();
|
||||
}
|
||||
AppCommand::ToggleRuntimeHighlight => {
|
||||
self.ui.runtime_highlight = !self.ui.runtime_highlight;
|
||||
|
||||
@@ -17,10 +17,8 @@ pub(super) fn handle_main_page(ctx: &mut InputContext, key: KeyEvent, ctrl: bool
|
||||
ctx.app.panel.visible = false;
|
||||
ctx.app.panel.focus = PanelFocus::Main;
|
||||
} else {
|
||||
if ctx.app.panel.side.is_none() {
|
||||
let state = SampleBrowserState::new(&ctx.app.audio.config.sample_paths);
|
||||
ctx.app.panel.side = Some(SidePanel::SampleBrowser(state));
|
||||
}
|
||||
let state = SampleBrowserState::new(&ctx.app.audio.config.sample_paths);
|
||||
ctx.app.panel.side = Some(SidePanel::SampleBrowser(state));
|
||||
ctx.app.panel.visible = true;
|
||||
ctx.app.panel.focus = PanelFocus::Side;
|
||||
}
|
||||
@@ -127,13 +125,21 @@ pub(super) fn handle_main_page(ctx: &mut InputContext, key: KeyEvent, ctrl: bool
|
||||
if let Some(range) = ctx.app.editor_ctx.selection_range() {
|
||||
let steps: Vec<usize> = range.collect();
|
||||
ctx.dispatch(AppCommand::OpenModal(Modal::Confirm {
|
||||
action: ConfirmAction::DeleteSteps { bank, pattern, steps },
|
||||
action: ConfirmAction::DeleteSteps {
|
||||
bank,
|
||||
pattern,
|
||||
steps,
|
||||
},
|
||||
selected: false,
|
||||
}));
|
||||
} else {
|
||||
let step = ctx.app.editor_ctx.step;
|
||||
ctx.dispatch(AppCommand::OpenModal(Modal::Confirm {
|
||||
action: ConfirmAction::DeleteStep { bank, pattern, step },
|
||||
action: ConfirmAction::DeleteStep {
|
||||
bank,
|
||||
pattern,
|
||||
step,
|
||||
},
|
||||
selected: false,
|
||||
}));
|
||||
}
|
||||
@@ -172,7 +178,11 @@ pub(super) fn handle_main_page(ctx: &mut InputContext, key: KeyEvent, ctrl: bool
|
||||
.and_then(|s| s.name.clone())
|
||||
.unwrap_or_default();
|
||||
ctx.dispatch(AppCommand::OpenModal(Modal::Rename {
|
||||
target: RenameTarget::Step { bank, pattern, step },
|
||||
target: RenameTarget::Step {
|
||||
bank,
|
||||
pattern,
|
||||
step,
|
||||
},
|
||||
name: current_name,
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -10,12 +10,12 @@ pub const DOCS: &[DocEntry] = &[
|
||||
Section("Getting Started"),
|
||||
Topic("Welcome", include_str!("../../docs/welcome.md")),
|
||||
Topic(
|
||||
"Moving Around",
|
||||
"Navigation",
|
||||
include_str!("../../docs/getting-started/navigation.md"),
|
||||
),
|
||||
Topic(
|
||||
"How Does It Work?",
|
||||
include_str!("../../docs/getting-started/how_it_works.md"),
|
||||
"The Big Picture",
|
||||
include_str!("../../docs/getting-started/big_picture.md"),
|
||||
),
|
||||
Topic(
|
||||
"Banks & Patterns",
|
||||
@@ -33,6 +33,22 @@ pub const DOCS: &[DocEntry] = &[
|
||||
"Editing a Step",
|
||||
include_str!("../../docs/getting-started/editing.md"),
|
||||
),
|
||||
Topic(
|
||||
"The Audio Engine",
|
||||
include_str!("../../docs/getting-started/engine.md"),
|
||||
),
|
||||
Topic(
|
||||
"Options",
|
||||
include_str!("../../docs/getting-started/options.md"),
|
||||
),
|
||||
Topic(
|
||||
"Saving & Loading",
|
||||
include_str!("../../docs/getting-started/saving.md"),
|
||||
),
|
||||
Topic(
|
||||
"The Sample Browser",
|
||||
include_str!("../../docs/getting-started/samples.md"),
|
||||
),
|
||||
// Forth fundamentals
|
||||
Section("Forth"),
|
||||
Topic(
|
||||
@@ -60,10 +76,7 @@ pub const DOCS: &[DocEntry] = &[
|
||||
Topic("Settings", include_str!("../../docs/engine/settings.md")),
|
||||
Topic("Sources", include_str!("../../docs/engine/sources.md")),
|
||||
Topic("Samples", include_str!("../../docs/engine/samples.md")),
|
||||
Topic(
|
||||
"Wavetables",
|
||||
include_str!("../../docs/engine/wavetable.md"),
|
||||
),
|
||||
Topic("Wavetables", include_str!("../../docs/engine/wavetable.md")),
|
||||
Topic("Filters", include_str!("../../docs/engine/filters.md")),
|
||||
Topic(
|
||||
"Modulation",
|
||||
@@ -78,10 +91,7 @@ pub const DOCS: &[DocEntry] = &[
|
||||
"Audio-Rate Mod",
|
||||
include_str!("../../docs/engine/audio_modulation.md"),
|
||||
),
|
||||
Topic(
|
||||
"Words & Sounds",
|
||||
include_str!("../../docs/engine/words.md"),
|
||||
),
|
||||
Topic("Words & Sounds", include_str!("../../docs/engine/words.md")),
|
||||
// MIDI
|
||||
Section("MIDI"),
|
||||
Topic("Introduction", include_str!("../../docs/midi/intro.md")),
|
||||
@@ -101,10 +111,7 @@ pub const DOCS: &[DocEntry] = &[
|
||||
"Generators",
|
||||
include_str!("../../docs/tutorials/generators.md"),
|
||||
),
|
||||
Topic(
|
||||
"Timing with at",
|
||||
include_str!("../../docs/tutorials/at.md"),
|
||||
),
|
||||
Topic("Timing with at", include_str!("../../docs/tutorials/at.md")),
|
||||
Topic(
|
||||
"Using Variables",
|
||||
include_str!("../../docs/tutorials/variables.md"),
|
||||
|
||||
@@ -165,4 +165,8 @@ impl UiState {
|
||||
pub fn dismiss_minimap(&mut self) {
|
||||
self.minimap = MinimapMode::Hidden;
|
||||
}
|
||||
|
||||
pub fn invalidate_help_cache(&self) {
|
||||
self.help_parsed.borrow_mut().iter_mut().for_each(|slot| *slot = None);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -432,7 +432,7 @@ fn render_samples(frame: &mut Frame, app: &App, area: Rect) {
|
||||
dim,
|
||||
)));
|
||||
lines.push(Line::from(Span::styled(
|
||||
" Add folders containing .wav files",
|
||||
" Add folders containing audio files",
|
||||
dim,
|
||||
)));
|
||||
} else {
|
||||
|
||||
@@ -56,11 +56,19 @@ pub fn adjust_resolved_for_line(
|
||||
) -> Vec<(SourceSpan, String)> {
|
||||
resolved
|
||||
.iter()
|
||||
.filter_map(|(s, display)| clip_span(*s, line_start, line_len).map(|cs| (cs, display.clone())))
|
||||
.filter_map(|(s, display)| {
|
||||
clip_span(*s, line_start, line_len).map(|cs| (cs, display.clone()))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn render(frame: &mut Frame, app: &App, link: &LinkState, snapshot: &SequencerSnapshot, elapsed: Duration) {
|
||||
pub fn render(
|
||||
frame: &mut Frame,
|
||||
app: &App,
|
||||
link: &LinkState,
|
||||
snapshot: &SequencerSnapshot,
|
||||
elapsed: Duration,
|
||||
) {
|
||||
let term = frame.area();
|
||||
|
||||
let theme = theme::get();
|
||||
@@ -212,7 +220,11 @@ fn render_side_panel(frame: &mut Frame, app: &App, area: Rect) {
|
||||
});
|
||||
|
||||
let duration = sample.total_frames as f32 / app.audio.config.sample_rate;
|
||||
let ch_label = if sample.channels == 1 { "mono" } else { "stereo" };
|
||||
let ch_label = if sample.channels == 1 {
|
||||
"mono"
|
||||
} else {
|
||||
"stereo"
|
||||
};
|
||||
let info = Paragraph::new(format!(" {duration:.1}s · {ch_label}"))
|
||||
.style(Style::new().fg(theme::get().ui.text_dim));
|
||||
frame.render_widget(info, info_area);
|
||||
@@ -349,7 +361,9 @@ fn render_header(
|
||||
} else {
|
||||
theme.header.stats_fg
|
||||
};
|
||||
let dim = Style::new().bg(theme.header.stats_bg).fg(theme.header.stats_fg);
|
||||
let dim = Style::new()
|
||||
.bg(theme.header.stats_bg)
|
||||
.fg(theme.header.stats_fg);
|
||||
let stats_line = Line::from(vec![
|
||||
Span::styled(format!(" CPU {cpu_pct:.0}%"), dim.fg(cpu_color)),
|
||||
Span::styled(format!(" V:{voices} L:{peers} "), dim),
|
||||
@@ -407,9 +421,9 @@ fn render_footer(frame: &mut Frame, app: &App, area: Rect) {
|
||||
Page::Engine => vec![
|
||||
("Tab", "Section"),
|
||||
("←→", "Switch/Adjust"),
|
||||
("↑↓", "Navigate"),
|
||||
("Enter", "Select"),
|
||||
("A", "Add path"),
|
||||
("R", "Restart"),
|
||||
("h", "Hush"),
|
||||
("?", "Keys"),
|
||||
],
|
||||
Page::Options => vec![
|
||||
@@ -484,14 +498,18 @@ fn render_footer(frame: &mut Frame, app: &App, area: Rect) {
|
||||
frame.render_widget(footer, area);
|
||||
}
|
||||
|
||||
fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term: Rect) -> Option<Rect> {
|
||||
fn render_modal(
|
||||
frame: &mut Frame,
|
||||
app: &App,
|
||||
snapshot: &SequencerSnapshot,
|
||||
term: Rect,
|
||||
) -> Option<Rect> {
|
||||
let theme = theme::get();
|
||||
let user_words: HashSet<String> = app.dict.lock().keys().cloned().collect();
|
||||
let inner = match &app.ui.modal {
|
||||
Modal::None => return None,
|
||||
Modal::Confirm { action, selected } => {
|
||||
ConfirmModal::new("Confirm", &action.message(), *selected)
|
||||
.render_centered(frame, term)
|
||||
ConfirmModal::new("Confirm", &action.message(), *selected).render_centered(frame, term)
|
||||
}
|
||||
Modal::FileBrowser(state) => {
|
||||
use crate::state::file_browser::FileBrowserMode;
|
||||
@@ -577,26 +595,42 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term
|
||||
} => {
|
||||
use crate::state::PatternPropsField;
|
||||
|
||||
let inner =
|
||||
ModalFrame::new(&format!(" Pattern B{:02}:P{:02} ", bank + 1, pattern + 1))
|
||||
.width(50)
|
||||
.height(12)
|
||||
.border_color(theme.modal.input)
|
||||
.render_centered(frame, term);
|
||||
let inner = ModalFrame::new(&format!(" Pattern B{:02}:P{:02} ", bank + 1, pattern + 1))
|
||||
.width(50)
|
||||
.height(12)
|
||||
.border_color(theme.modal.input)
|
||||
.render_centered(frame, term);
|
||||
|
||||
let speed_label = speed.label();
|
||||
let fields: Vec<(&str, &str, bool)> = vec![
|
||||
("Name", name.as_str(), *field == PatternPropsField::Name),
|
||||
("Length", length.as_str(), *field == PatternPropsField::Length),
|
||||
(
|
||||
"Length",
|
||||
length.as_str(),
|
||||
*field == PatternPropsField::Length,
|
||||
),
|
||||
("Speed", &speed_label, *field == PatternPropsField::Speed),
|
||||
("Quantization", quantization.label(), *field == PatternPropsField::Quantization),
|
||||
("Sync Mode", sync_mode.label(), *field == PatternPropsField::SyncMode),
|
||||
(
|
||||
"Quantization",
|
||||
quantization.label(),
|
||||
*field == PatternPropsField::Quantization,
|
||||
),
|
||||
(
|
||||
"Sync Mode",
|
||||
sync_mode.label(),
|
||||
*field == PatternPropsField::SyncMode,
|
||||
),
|
||||
];
|
||||
|
||||
render_props_form(frame, inner, &fields);
|
||||
|
||||
let hint_area = Rect::new(inner.x, inner.y + inner.height - 1, inner.width, 1);
|
||||
let hints = hint_line(&[("↑↓", "nav"), ("←→", "change"), ("Enter", "save"), ("Esc", "cancel")]);
|
||||
let hints = hint_line(&[
|
||||
("↑↓", "nav"),
|
||||
("←→", "change"),
|
||||
("Enter", "save"),
|
||||
("Esc", "cancel"),
|
||||
]);
|
||||
frame.render_widget(Paragraph::new(hints), hint_area);
|
||||
|
||||
inner
|
||||
@@ -625,7 +659,8 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term
|
||||
lines
|
||||
};
|
||||
let key_lines = keys.len() as u16;
|
||||
let modal_height = (3 + desc_lines + 1 + key_lines + 2).min(term.height.saturating_sub(4));
|
||||
let modal_height =
|
||||
(3 + desc_lines + 1 + key_lines + 2).min(term.height.saturating_sub(4));
|
||||
|
||||
let title = if page_count > 1 {
|
||||
format!(" {} ({}/{}) ", app.page.name(), page_idx + 1, page_count)
|
||||
@@ -654,16 +689,13 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term
|
||||
}
|
||||
let line = Line::from(vec![
|
||||
Span::raw(" "),
|
||||
Span::styled(
|
||||
format!("{:>8}", key),
|
||||
Style::new().fg(theme.hint.key),
|
||||
),
|
||||
Span::styled(
|
||||
format!(" {action}"),
|
||||
Style::new().fg(theme.hint.text),
|
||||
),
|
||||
Span::styled(format!("{:>8}", key), Style::new().fg(theme.hint.key)),
|
||||
Span::styled(format!(" {action}"), Style::new().fg(theme.hint.text)),
|
||||
]);
|
||||
frame.render_widget(Paragraph::new(line), Rect::new(inner.x + 1, y, inner.width.saturating_sub(2), 1));
|
||||
frame.render_widget(
|
||||
Paragraph::new(line),
|
||||
Rect::new(inner.x + 1, y, inner.width.saturating_sub(2), 1),
|
||||
);
|
||||
y += 1;
|
||||
}
|
||||
|
||||
@@ -677,7 +709,10 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term
|
||||
}
|
||||
hints_vec.push(("Enter", "don't show again"));
|
||||
let hints = hint_line(&hints_vec);
|
||||
frame.render_widget(Paragraph::new(hints).alignment(Alignment::Center), hint_area);
|
||||
frame.render_widget(
|
||||
Paragraph::new(hints).alignment(Alignment::Center),
|
||||
hint_area,
|
||||
);
|
||||
|
||||
inner
|
||||
}
|
||||
@@ -702,7 +737,11 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term
|
||||
let fields: Vec<(&str, &str, bool)> = vec![
|
||||
("Pulses", pulses.as_str(), *field == EuclideanField::Pulses),
|
||||
("Steps", steps.as_str(), *field == EuclideanField::Steps),
|
||||
("Rotation", rotation.as_str(), *field == EuclideanField::Rotation),
|
||||
(
|
||||
"Rotation",
|
||||
rotation.as_str(),
|
||||
*field == EuclideanField::Rotation,
|
||||
),
|
||||
];
|
||||
|
||||
render_props_form(frame, inner, &fields);
|
||||
@@ -723,7 +762,12 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term
|
||||
}
|
||||
|
||||
let hint_area = Rect::new(inner.x, inner.y + inner.height - 1, inner.width, 1);
|
||||
let hints = hint_line(&[("↑↓", "nav"), ("←→", "adjust"), ("Enter", "apply"), ("Esc", "cancel")]);
|
||||
let hints = hint_line(&[
|
||||
("↑↓", "nav"),
|
||||
("←→", "adjust"),
|
||||
("Enter", "apply"),
|
||||
("Esc", "cancel"),
|
||||
]);
|
||||
frame.render_widget(Paragraph::new(hints), hint_area);
|
||||
|
||||
inner
|
||||
@@ -791,12 +835,7 @@ fn render_modal_preview(
|
||||
};
|
||||
|
||||
let resolved_display: Vec<(SourceSpan, String)> = trace
|
||||
.map(|t| {
|
||||
t.resolved
|
||||
.iter()
|
||||
.map(|(s, v)| (*s, v.display()))
|
||||
.collect()
|
||||
})
|
||||
.map(|t| t.resolved.iter().map(|(s, v)| (*s, v.display())).collect())
|
||||
.unwrap_or_default();
|
||||
|
||||
let mut line_start = 0usize;
|
||||
@@ -804,21 +843,10 @@ fn render_modal_preview(
|
||||
.lines()
|
||||
.map(|line_str| {
|
||||
let tokens = if let Some(t) = trace {
|
||||
let exec = adjust_spans_for_line(
|
||||
&t.executed_spans,
|
||||
line_start,
|
||||
line_str.len(),
|
||||
);
|
||||
let sel = adjust_spans_for_line(
|
||||
&t.selected_spans,
|
||||
line_start,
|
||||
line_str.len(),
|
||||
);
|
||||
let res = adjust_resolved_for_line(
|
||||
&resolved_display,
|
||||
line_start,
|
||||
line_str.len(),
|
||||
);
|
||||
let exec = adjust_spans_for_line(&t.executed_spans, line_start, line_str.len());
|
||||
let sel = adjust_spans_for_line(&t.selected_spans, line_start, line_str.len());
|
||||
let res =
|
||||
adjust_resolved_for_line(&resolved_display, line_start, line_str.len());
|
||||
highlight_line_with_runtime(line_str, &exec, &sel, &res, user_words)
|
||||
} else {
|
||||
highlight_line_with_runtime(line_str, &[], &[], &[], user_words)
|
||||
@@ -898,12 +926,7 @@ fn render_modal_editor(
|
||||
}
|
||||
|
||||
let resolved_display: Vec<(SourceSpan, String)> = trace
|
||||
.map(|t| {
|
||||
t.resolved
|
||||
.iter()
|
||||
.map(|(s, v)| (*s, v.display()))
|
||||
.collect()
|
||||
})
|
||||
.map(|t| t.resolved.iter().map(|(s, v)| (*s, v.display())).collect())
|
||||
.unwrap_or_default();
|
||||
|
||||
let highlighter = |row: usize, line: &str| -> Vec<(Style, String, bool)> {
|
||||
@@ -919,8 +942,8 @@ fn render_modal_editor(
|
||||
highlight::highlight_line_with_runtime(line, &exec, &sel, &res, user_words)
|
||||
};
|
||||
|
||||
let show_search = app.editor_ctx.editor.search_active()
|
||||
|| !app.editor_ctx.editor.search_query().is_empty();
|
||||
let show_search =
|
||||
app.editor_ctx.editor.search_active() || !app.editor_ctx.editor.search_query().is_empty();
|
||||
|
||||
let reserved_lines = 1 + if show_search { 1 } else { 0 };
|
||||
let editor_height = inner.height.saturating_sub(reserved_lines);
|
||||
@@ -1061,10 +1084,7 @@ fn render_modal_keybindings(frame: &mut Frame, app: &App, scroll: usize, term: R
|
||||
height: 1,
|
||||
};
|
||||
let hints = hint_line(&[("↑↓", "scroll"), ("PgUp/Dn", "page"), ("Esc/?", "close")]);
|
||||
frame.render_widget(
|
||||
Paragraph::new(hints).alignment(Alignment::Right),
|
||||
hint_area,
|
||||
);
|
||||
frame.render_widget(Paragraph::new(hints).alignment(Alignment::Right), hint_area);
|
||||
|
||||
inner
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user