This commit is contained in:
2026-01-24 02:16:18 +01:00
parent 04f5e19ab2
commit 6f5fa762a4
6 changed files with 84 additions and 29 deletions

View File

@@ -68,6 +68,10 @@ impl Editor {
self.completion.candidates = candidates;
}
pub fn insert_str(&mut self, s: &str) {
self.text.insert_str(s);
}
pub fn content(&self) -> String {
self.text.lines().join("\n")
}

View File

@@ -5,6 +5,7 @@ use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use crossbeam_channel::Sender;
use ratatui::style::Color;
use crate::commands::AppCommand;
use crate::engine::{
@@ -319,7 +320,7 @@ impl App {
Some(cmds.join("\n"))
};
}
self.ui.flash("Script compiled", 150);
self.ui.flash("Script compiled", 150, Color::White);
}
Err(e) => {
if let Some(step) = self
@@ -330,7 +331,7 @@ impl App {
{
step.command = None;
}
self.ui.set_status(format!("Script error: {e}"));
self.ui.flash(&format!("Script error: {e}"), 300, Color::Red);
}
}
}
@@ -539,7 +540,7 @@ impl App {
{
self.load_step_to_editor();
}
self.ui.flash("Step deleted", 150);
self.ui.flash("Step deleted", 150, Color::Green);
}
pub fn reset_pattern(&mut self, bank: usize, pattern: usize) {
@@ -548,7 +549,7 @@ impl App {
if self.editor_ctx.bank == bank && self.editor_ctx.pattern == pattern {
self.load_step_to_editor();
}
self.ui.flash("Pattern reset", 150);
self.ui.flash("Pattern reset", 150, Color::Green);
}
pub fn reset_bank(&mut self, bank: usize) {
@@ -559,13 +560,13 @@ impl App {
if self.editor_ctx.bank == bank {
self.load_step_to_editor();
}
self.ui.flash("Bank reset", 150);
self.ui.flash("Bank reset", 150, Color::Green);
}
pub fn copy_pattern(&mut self, bank: usize, pattern: usize) {
let pat = self.project_state.project.banks[bank].patterns[pattern].clone();
self.copied_pattern = Some(pat);
self.ui.flash("Pattern copied", 150);
self.ui.flash("Pattern copied", 150, Color::Green);
}
pub fn paste_pattern(&mut self, bank: usize, pattern: usize) {
@@ -581,14 +582,14 @@ impl App {
if self.editor_ctx.bank == bank && self.editor_ctx.pattern == pattern {
self.load_step_to_editor();
}
self.ui.flash("Pattern pasted", 150);
self.ui.flash("Pattern pasted", 150, Color::Green);
}
}
pub fn copy_bank(&mut self, bank: usize) {
let b = self.project_state.project.banks[bank].clone();
self.copied_bank = Some(b);
self.ui.flash("Bank copied", 150);
self.ui.flash("Bank copied", 150, Color::Green);
}
pub fn paste_bank(&mut self, bank: usize) {
@@ -606,7 +607,7 @@ impl App {
if self.editor_ctx.bank == bank {
self.load_step_to_editor();
}
self.ui.flash("Bank pasted", 150);
self.ui.flash("Bank pasted", 150, Color::Green);
}
}
@@ -675,7 +676,7 @@ impl App {
self.project_state.mark_dirty(bank, pattern);
self.load_step_to_editor();
self.ui
.flash(&format!("Linked to step {:02}", copied.step + 1), 150);
.flash(&format!("Linked to step {:02}", copied.step + 1), 150, Color::Green);
}
pub fn harden_step(&mut self) {
@@ -708,7 +709,7 @@ impl App {
}
self.project_state.mark_dirty(bank, pattern);
self.load_step_to_editor();
self.ui.flash("Step hardened", 150);
self.ui.flash("Step hardened", 150, Color::Green);
}
pub fn open_pattern_modal(&mut self, field: PatternField) {
@@ -841,7 +842,8 @@ impl App {
AppCommand::Flash {
message,
duration_ms,
} => self.ui.flash(&message, duration_ms),
color,
} => self.ui.flash(&message, duration_ms, color),
AppCommand::OpenModal(modal) => {
if matches!(modal, Modal::Editor) {
// If current step is a shallow copy, navigate to source step

View File

@@ -1,5 +1,7 @@
use std::path::PathBuf;
use ratatui::style::Color;
use crate::engine::PatternChange;
use crate::model::PatternSpeed;
use crate::state::{Modal, PatternField};
@@ -100,6 +102,7 @@ pub enum AppCommand {
Flash {
message: String,
duration_ms: u64,
color: Color,
},
OpenModal(Modal),
CloseModal,

View File

@@ -17,7 +17,7 @@ use std::sync::Arc;
use std::time::Duration;
use clap::Parser;
use crossterm::event::{self, Event};
use crossterm::event::{self, Event, EnableBracketedPaste, DisableBracketedPaste};
use crossterm::terminal::{
disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
};
@@ -137,6 +137,7 @@ fn main() -> io::Result<()> {
app.mark_all_patterns_dirty();
enable_raw_mode()?;
io::stdout().execute(EnableBracketedPaste)?;
io::stdout().execute(EnterAlternateScreen)?;
let backend = CrosstermBackend::new(io::stdout());
let mut terminal = Terminal::new(backend)?;
@@ -203,24 +204,33 @@ fn main() -> io::Result<()> {
terminal.draw(|frame| views::render(frame, &mut app, &link, &seq_snapshot))?;
if event::poll(Duration::from_millis(app.audio.config.refresh_rate.millis()))? {
if let Event::Key(key) = event::read()? {
let mut ctx = InputContext {
app: &mut app,
link: &link,
snapshot: &seq_snapshot,
playing: &playing,
audio_tx: &sequencer.audio_tx,
};
match event::read()? {
Event::Key(key) => {
let mut ctx = InputContext {
app: &mut app,
link: &link,
snapshot: &seq_snapshot,
playing: &playing,
audio_tx: &sequencer.audio_tx,
};
if let InputResult::Quit = handle_key(&mut ctx, key) {
break;
if let InputResult::Quit = handle_key(&mut ctx, key) {
break;
}
}
Event::Paste(text) => {
if matches!(app.ui.modal, state::Modal::Editor) {
app.editor_ctx.editor.insert_str(&text);
}
}
_ => {}
}
}
}
disable_raw_mode()?;
io::stdout().execute(DisableBracketedPaste)?;
io::stdout().execute(LeaveAlternateScreen)?;
sequencer.shutdown();

View File

@@ -1,5 +1,7 @@
use std::time::{Duration, Instant};
use ratatui::style::Color;
use crate::state::Modal;
pub struct Sparkle {
@@ -13,6 +15,7 @@ pub struct UiState {
pub sparkles: Vec<Sparkle>,
pub status_message: Option<String>,
pub flash_until: Option<Instant>,
pub flash_color: Color,
pub modal: Modal,
pub doc_topic: usize,
pub doc_scroll: usize,
@@ -28,6 +31,7 @@ impl Default for UiState {
sparkles: Vec::new(),
status_message: None,
flash_until: None,
flash_color: Color::Green,
modal: Modal::None,
doc_topic: 0,
doc_scroll: 0,
@@ -40,9 +44,14 @@ impl Default for UiState {
}
impl UiState {
pub fn flash(&mut self, msg: &str, duration_ms: u64) {
pub fn flash(&mut self, msg: &str, duration_ms: u64, color: Color) {
self.status_message = Some(msg.to_string());
self.flash_until = Some(Instant::now() + Duration::from_millis(duration_ms));
self.flash_color = color;
}
pub fn flash_color(&self) -> Option<Color> {
if self.is_flashing() { Some(self.flash_color) } else { None }
}
pub fn set_status(&mut self, msg: String) {

View File

@@ -498,10 +498,10 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term
let height = (term.height * 60 / 100).max(10);
let step_num = app.editor_ctx.step + 1;
let border_color = if app.ui.is_flashing() {
Color::Green
} else {
Color::Rgb(100, 160, 180)
let flash_color = app.ui.flash_color();
let border_color = match flash_color {
Some(c) => c,
None => Color::Rgb(100, 160, 180),
};
let inner = ModalFrame::new(&format!("Step {step_num:02} Script"))
@@ -537,7 +537,34 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term
highlight::highlight_line_with_runtime(line, &exec, &sel)
};
app.editor_ctx.editor.render(frame, inner, &highlighter);
let editor_area = Rect::new(inner.x, inner.y, inner.width, inner.height.saturating_sub(1));
let hint_area = Rect::new(inner.x, inner.y + editor_area.height, inner.width, 1);
if let Some(c) = flash_color {
let bg = match c {
Color::Red => Color::Rgb(60, 10, 10),
Color::White => Color::Rgb(30, 30, 40),
_ => Color::Rgb(10, 30, 10),
};
let flash_block = Block::default().style(Style::default().bg(bg));
frame.render_widget(flash_block, editor_area);
}
app.editor_ctx.editor.render(frame, editor_area, &highlighter);
let dim = Style::default().fg(Color::DarkGray);
let key = Style::default().fg(Color::Yellow);
let hint = Line::from(vec![
Span::styled("Esc", key), Span::styled(" save ", dim),
Span::styled("C-e", key), Span::styled(" eval ", dim),
Span::styled("C-u", key), Span::styled("/", dim),
Span::styled("C-r", key), Span::styled(" undo/redo ", dim),
Span::styled("C-j", key), Span::styled("/", dim),
Span::styled("C-k", key), Span::styled(" del-bol/eol ", dim),
Span::styled("C-x", key), Span::styled("/", dim),
Span::styled("C-c", key), Span::styled("/", dim),
Span::styled("C-y", key), Span::styled(" cut/copy/paste ", dim),
]);
frame.render_widget(Paragraph::new(hint).alignment(Alignment::Right), hint_area);
}
}
}