Flash
This commit is contained in:
@@ -68,6 +68,10 @@ impl Editor {
|
|||||||
self.completion.candidates = candidates;
|
self.completion.candidates = candidates;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn insert_str(&mut self, s: &str) {
|
||||||
|
self.text.insert_str(s);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn content(&self) -> String {
|
pub fn content(&self) -> String {
|
||||||
self.text.lines().join("\n")
|
self.text.lines().join("\n")
|
||||||
}
|
}
|
||||||
|
|||||||
26
src/app.rs
26
src/app.rs
@@ -5,6 +5,7 @@ use std::path::PathBuf;
|
|||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use crossbeam_channel::Sender;
|
use crossbeam_channel::Sender;
|
||||||
|
use ratatui::style::Color;
|
||||||
|
|
||||||
use crate::commands::AppCommand;
|
use crate::commands::AppCommand;
|
||||||
use crate::engine::{
|
use crate::engine::{
|
||||||
@@ -319,7 +320,7 @@ impl App {
|
|||||||
Some(cmds.join("\n"))
|
Some(cmds.join("\n"))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
self.ui.flash("Script compiled", 150);
|
self.ui.flash("Script compiled", 150, Color::White);
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
if let Some(step) = self
|
if let Some(step) = self
|
||||||
@@ -330,7 +331,7 @@ impl App {
|
|||||||
{
|
{
|
||||||
step.command = None;
|
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.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) {
|
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 {
|
if self.editor_ctx.bank == bank && self.editor_ctx.pattern == pattern {
|
||||||
self.load_step_to_editor();
|
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) {
|
pub fn reset_bank(&mut self, bank: usize) {
|
||||||
@@ -559,13 +560,13 @@ impl App {
|
|||||||
if self.editor_ctx.bank == bank {
|
if self.editor_ctx.bank == bank {
|
||||||
self.load_step_to_editor();
|
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) {
|
pub fn copy_pattern(&mut self, bank: usize, pattern: usize) {
|
||||||
let pat = self.project_state.project.banks[bank].patterns[pattern].clone();
|
let pat = self.project_state.project.banks[bank].patterns[pattern].clone();
|
||||||
self.copied_pattern = Some(pat);
|
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) {
|
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 {
|
if self.editor_ctx.bank == bank && self.editor_ctx.pattern == pattern {
|
||||||
self.load_step_to_editor();
|
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) {
|
pub fn copy_bank(&mut self, bank: usize) {
|
||||||
let b = self.project_state.project.banks[bank].clone();
|
let b = self.project_state.project.banks[bank].clone();
|
||||||
self.copied_bank = Some(b);
|
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) {
|
pub fn paste_bank(&mut self, bank: usize) {
|
||||||
@@ -606,7 +607,7 @@ impl App {
|
|||||||
if self.editor_ctx.bank == bank {
|
if self.editor_ctx.bank == bank {
|
||||||
self.load_step_to_editor();
|
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.project_state.mark_dirty(bank, pattern);
|
||||||
self.load_step_to_editor();
|
self.load_step_to_editor();
|
||||||
self.ui
|
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) {
|
pub fn harden_step(&mut self) {
|
||||||
@@ -708,7 +709,7 @@ impl App {
|
|||||||
}
|
}
|
||||||
self.project_state.mark_dirty(bank, pattern);
|
self.project_state.mark_dirty(bank, pattern);
|
||||||
self.load_step_to_editor();
|
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) {
|
pub fn open_pattern_modal(&mut self, field: PatternField) {
|
||||||
@@ -841,7 +842,8 @@ impl App {
|
|||||||
AppCommand::Flash {
|
AppCommand::Flash {
|
||||||
message,
|
message,
|
||||||
duration_ms,
|
duration_ms,
|
||||||
} => self.ui.flash(&message, duration_ms),
|
color,
|
||||||
|
} => self.ui.flash(&message, duration_ms, color),
|
||||||
AppCommand::OpenModal(modal) => {
|
AppCommand::OpenModal(modal) => {
|
||||||
if matches!(modal, Modal::Editor) {
|
if matches!(modal, Modal::Editor) {
|
||||||
// If current step is a shallow copy, navigate to source step
|
// If current step is a shallow copy, navigate to source step
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use ratatui::style::Color;
|
||||||
|
|
||||||
use crate::engine::PatternChange;
|
use crate::engine::PatternChange;
|
||||||
use crate::model::PatternSpeed;
|
use crate::model::PatternSpeed;
|
||||||
use crate::state::{Modal, PatternField};
|
use crate::state::{Modal, PatternField};
|
||||||
@@ -100,6 +102,7 @@ pub enum AppCommand {
|
|||||||
Flash {
|
Flash {
|
||||||
message: String,
|
message: String,
|
||||||
duration_ms: u64,
|
duration_ms: u64,
|
||||||
|
color: Color,
|
||||||
},
|
},
|
||||||
OpenModal(Modal),
|
OpenModal(Modal),
|
||||||
CloseModal,
|
CloseModal,
|
||||||
|
|||||||
14
src/main.rs
14
src/main.rs
@@ -17,7 +17,7 @@ use std::sync::Arc;
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use crossterm::event::{self, Event};
|
use crossterm::event::{self, Event, EnableBracketedPaste, DisableBracketedPaste};
|
||||||
use crossterm::terminal::{
|
use crossterm::terminal::{
|
||||||
disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
|
disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
|
||||||
};
|
};
|
||||||
@@ -137,6 +137,7 @@ fn main() -> io::Result<()> {
|
|||||||
app.mark_all_patterns_dirty();
|
app.mark_all_patterns_dirty();
|
||||||
|
|
||||||
enable_raw_mode()?;
|
enable_raw_mode()?;
|
||||||
|
io::stdout().execute(EnableBracketedPaste)?;
|
||||||
io::stdout().execute(EnterAlternateScreen)?;
|
io::stdout().execute(EnterAlternateScreen)?;
|
||||||
let backend = CrosstermBackend::new(io::stdout());
|
let backend = CrosstermBackend::new(io::stdout());
|
||||||
let mut terminal = Terminal::new(backend)?;
|
let mut terminal = Terminal::new(backend)?;
|
||||||
@@ -203,7 +204,8 @@ fn main() -> io::Result<()> {
|
|||||||
terminal.draw(|frame| views::render(frame, &mut app, &link, &seq_snapshot))?;
|
terminal.draw(|frame| views::render(frame, &mut app, &link, &seq_snapshot))?;
|
||||||
|
|
||||||
if event::poll(Duration::from_millis(app.audio.config.refresh_rate.millis()))? {
|
if event::poll(Duration::from_millis(app.audio.config.refresh_rate.millis()))? {
|
||||||
if let Event::Key(key) = event::read()? {
|
match event::read()? {
|
||||||
|
Event::Key(key) => {
|
||||||
let mut ctx = InputContext {
|
let mut ctx = InputContext {
|
||||||
app: &mut app,
|
app: &mut app,
|
||||||
link: &link,
|
link: &link,
|
||||||
@@ -216,11 +218,19 @@ fn main() -> io::Result<()> {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Event::Paste(text) => {
|
||||||
|
if matches!(app.ui.modal, state::Modal::Editor) {
|
||||||
|
app.editor_ctx.editor.insert_str(&text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
disable_raw_mode()?;
|
disable_raw_mode()?;
|
||||||
|
io::stdout().execute(DisableBracketedPaste)?;
|
||||||
io::stdout().execute(LeaveAlternateScreen)?;
|
io::stdout().execute(LeaveAlternateScreen)?;
|
||||||
|
|
||||||
sequencer.shutdown();
|
sequencer.shutdown();
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
use ratatui::style::Color;
|
||||||
|
|
||||||
use crate::state::Modal;
|
use crate::state::Modal;
|
||||||
|
|
||||||
pub struct Sparkle {
|
pub struct Sparkle {
|
||||||
@@ -13,6 +15,7 @@ pub struct UiState {
|
|||||||
pub sparkles: Vec<Sparkle>,
|
pub sparkles: Vec<Sparkle>,
|
||||||
pub status_message: Option<String>,
|
pub status_message: Option<String>,
|
||||||
pub flash_until: Option<Instant>,
|
pub flash_until: Option<Instant>,
|
||||||
|
pub flash_color: Color,
|
||||||
pub modal: Modal,
|
pub modal: Modal,
|
||||||
pub doc_topic: usize,
|
pub doc_topic: usize,
|
||||||
pub doc_scroll: usize,
|
pub doc_scroll: usize,
|
||||||
@@ -28,6 +31,7 @@ impl Default for UiState {
|
|||||||
sparkles: Vec::new(),
|
sparkles: Vec::new(),
|
||||||
status_message: None,
|
status_message: None,
|
||||||
flash_until: None,
|
flash_until: None,
|
||||||
|
flash_color: Color::Green,
|
||||||
modal: Modal::None,
|
modal: Modal::None,
|
||||||
doc_topic: 0,
|
doc_topic: 0,
|
||||||
doc_scroll: 0,
|
doc_scroll: 0,
|
||||||
@@ -40,9 +44,14 @@ impl Default for UiState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl 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.status_message = Some(msg.to_string());
|
||||||
self.flash_until = Some(Instant::now() + Duration::from_millis(duration_ms));
|
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) {
|
pub fn set_status(&mut self, msg: String) {
|
||||||
|
|||||||
@@ -498,10 +498,10 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term
|
|||||||
let height = (term.height * 60 / 100).max(10);
|
let height = (term.height * 60 / 100).max(10);
|
||||||
let step_num = app.editor_ctx.step + 1;
|
let step_num = app.editor_ctx.step + 1;
|
||||||
|
|
||||||
let border_color = if app.ui.is_flashing() {
|
let flash_color = app.ui.flash_color();
|
||||||
Color::Green
|
let border_color = match flash_color {
|
||||||
} else {
|
Some(c) => c,
|
||||||
Color::Rgb(100, 160, 180)
|
None => Color::Rgb(100, 160, 180),
|
||||||
};
|
};
|
||||||
|
|
||||||
let inner = ModalFrame::new(&format!("Step {step_num:02} Script"))
|
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)
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user