sparkles
This commit is contained in:
@@ -23,6 +23,7 @@ rand = "0.8"
|
|||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
tui-textarea = "0.7"
|
tui-textarea = "0.7"
|
||||||
|
tui-big-text = "0.7"
|
||||||
arboard = "3"
|
arboard = "3"
|
||||||
minimad = "0.13"
|
minimad = "0.13"
|
||||||
crossbeam-channel = "0.5"
|
crossbeam-channel = "0.5"
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ use settings::Settings;
|
|||||||
use state::audio::RefreshRate;
|
use state::audio::RefreshRate;
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
#[command(name = "seq", about = "A step sequencer with Ableton Link support")]
|
#[command(name = "cagire", about = "A step sequencer with Ableton Link support")]
|
||||||
struct Args {
|
struct Args {
|
||||||
/// Directory containing audio samples to load (can be specified multiple times)
|
/// Directory containing audio samples to load (can be specified multiple times)
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
|
|||||||
@@ -2,7 +2,15 @@ use std::time::{Duration, Instant};
|
|||||||
|
|
||||||
use crate::state::Modal;
|
use crate::state::Modal;
|
||||||
|
|
||||||
|
pub struct Sparkle {
|
||||||
|
pub x: u16,
|
||||||
|
pub y: u16,
|
||||||
|
pub char_idx: usize,
|
||||||
|
pub life: u8,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct UiState {
|
pub struct UiState {
|
||||||
|
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 modal: Modal,
|
pub modal: Modal,
|
||||||
@@ -16,6 +24,7 @@ pub struct UiState {
|
|||||||
impl Default for UiState {
|
impl Default for UiState {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
sparkles: Vec::new(),
|
||||||
status_message: None,
|
status_message: None,
|
||||||
flash_until: None,
|
flash_until: None,
|
||||||
modal: Modal::None,
|
modal: Modal::None,
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ pub fn render(frame: &mut Frame, app: &mut App, link: &LinkState, snapshot: &Seq
|
|||||||
let term = frame.area();
|
let term = frame.area();
|
||||||
|
|
||||||
if app.ui.show_title {
|
if app.ui.show_title {
|
||||||
title_view::render(frame, term);
|
title_view::render(frame, term, &mut app.ui);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,51 +1,107 @@
|
|||||||
|
use rand::Rng;
|
||||||
use ratatui::layout::{Alignment, Constraint, Layout, Rect};
|
use ratatui::layout::{Alignment, Constraint, Layout, Rect};
|
||||||
use ratatui::style::{Color, Modifier, Style};
|
use ratatui::style::{Color, Style, Stylize};
|
||||||
use ratatui::text::{Line, Span};
|
use ratatui::text::{Line, Span};
|
||||||
use ratatui::widgets::Paragraph;
|
use ratatui::widgets::Paragraph;
|
||||||
use ratatui::Frame;
|
use ratatui::Frame;
|
||||||
|
use tui_big_text::{BigText, PixelSize};
|
||||||
|
|
||||||
pub fn render(frame: &mut Frame, area: Rect) {
|
use crate::state::ui::{Sparkle, UiState};
|
||||||
let title_style = Style::new().fg(Color::Cyan).add_modifier(Modifier::BOLD);
|
|
||||||
let subtitle_style = Style::new().fg(Color::White);
|
|
||||||
let dim_style = Style::new()
|
|
||||||
.fg(Color::Rgb(120, 125, 135))
|
|
||||||
.add_modifier(Modifier::DIM);
|
|
||||||
let link_style = Style::new().fg(Color::Rgb(100, 160, 180));
|
|
||||||
|
|
||||||
let lines = vec![
|
const SPARKLE_CHARS: &[char] = &['·', '✦', '✧', '°', '•', '+', '⋆', '*'];
|
||||||
|
const SPARKLE_COLORS: &[(u8, u8, u8)] = &[
|
||||||
|
(200, 220, 255),
|
||||||
|
(255, 200, 150),
|
||||||
|
(150, 255, 200),
|
||||||
|
(255, 150, 200),
|
||||||
|
(200, 150, 255),
|
||||||
|
];
|
||||||
|
|
||||||
|
pub fn render(frame: &mut Frame, area: Rect, ui: &mut UiState) {
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
|
||||||
|
// Spawn new sparkles
|
||||||
|
for _ in 0..3 {
|
||||||
|
if rng.gen_bool(0.6) {
|
||||||
|
let x = rng.gen_range(0..area.width);
|
||||||
|
let y = rng.gen_range(0..area.height);
|
||||||
|
ui.sparkles.push(Sparkle {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
char_idx: rng.gen_range(0..SPARKLE_CHARS.len()),
|
||||||
|
life: rng.gen_range(15..40),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Age and remove dead sparkles
|
||||||
|
ui.sparkles.iter_mut().for_each(|s| s.life = s.life.saturating_sub(1));
|
||||||
|
ui.sparkles.retain(|s| s.life > 0);
|
||||||
|
|
||||||
|
// Render sparkles
|
||||||
|
for sparkle in &ui.sparkles {
|
||||||
|
let color = SPARKLE_COLORS[sparkle.char_idx % SPARKLE_COLORS.len()];
|
||||||
|
let intensity = (sparkle.life as f32 / 30.0).min(1.0);
|
||||||
|
let r = (color.0 as f32 * intensity) as u8;
|
||||||
|
let g = (color.1 as f32 * intensity) as u8;
|
||||||
|
let b = (color.2 as f32 * intensity) as u8;
|
||||||
|
|
||||||
|
let ch = SPARKLE_CHARS[sparkle.char_idx];
|
||||||
|
let span = Span::styled(ch.to_string(), Style::new().fg(Color::Rgb(r, g, b)));
|
||||||
|
let para = Paragraph::new(Line::from(span));
|
||||||
|
let sparkle_area = Rect::new(sparkle.x, sparkle.y, 1, 1);
|
||||||
|
if sparkle_area.x < area.width && sparkle_area.y < area.height {
|
||||||
|
frame.render_widget(para, sparkle_area);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main content
|
||||||
|
let author_style = Style::new().fg(Color::Rgb(180, 140, 200));
|
||||||
|
let link_style = Style::new().fg(Color::Rgb(120, 200, 180));
|
||||||
|
let license_style = Style::new().fg(Color::Rgb(200, 160, 100));
|
||||||
|
|
||||||
|
let big_title = BigText::builder()
|
||||||
|
.pixel_size(PixelSize::Quadrant)
|
||||||
|
.style(Style::new().cyan().bold())
|
||||||
|
.lines(vec!["CAGIRE".into()])
|
||||||
|
.centered()
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let subtitle_lines = vec![
|
||||||
Line::from(""),
|
Line::from(""),
|
||||||
|
Line::from(Span::styled(
|
||||||
|
"A Forth Music Sequencer",
|
||||||
|
Style::new().fg(Color::White),
|
||||||
|
)),
|
||||||
Line::from(""),
|
Line::from(""),
|
||||||
Line::from(Span::styled("seq", title_style)),
|
Line::from(Span::styled("by BuboBubo", author_style)),
|
||||||
Line::from(""),
|
|
||||||
Line::from(Span::styled("A Forth Music Sequencer", subtitle_style)),
|
|
||||||
Line::from(""),
|
|
||||||
Line::from(""),
|
|
||||||
Line::from(Span::styled("by BuboBubo", dim_style)),
|
|
||||||
Line::from(Span::styled("Raphael Maurice Forment", dim_style)),
|
|
||||||
Line::from(""),
|
Line::from(""),
|
||||||
Line::from(Span::styled("https://raphaelforment.fr", link_style)),
|
Line::from(Span::styled("https://raphaelforment.fr", link_style)),
|
||||||
Line::from(""),
|
Line::from(""),
|
||||||
Line::from(""),
|
Line::from(Span::styled("AGPL-3.0", license_style)),
|
||||||
Line::from(Span::styled("AGPL-3.0", dim_style)),
|
|
||||||
Line::from(""),
|
|
||||||
Line::from(""),
|
Line::from(""),
|
||||||
Line::from(""),
|
Line::from(""),
|
||||||
Line::from(Span::styled(
|
Line::from(Span::styled(
|
||||||
"Press any key to continue",
|
"Press any key to continue",
|
||||||
Style::new().fg(Color::DarkGray),
|
Style::new().fg(Color::Rgb(140, 160, 170)),
|
||||||
)),
|
)),
|
||||||
];
|
];
|
||||||
|
|
||||||
let text_height = lines.len() as u16;
|
let big_text_height = 4;
|
||||||
let vertical_padding = area.height.saturating_sub(text_height) / 2;
|
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 [_, center_area, _] = Layout::vertical([
|
let [_, title_area, subtitle_area, _] = Layout::vertical([
|
||||||
Constraint::Length(vertical_padding),
|
Constraint::Length(vertical_padding),
|
||||||
Constraint::Length(text_height),
|
Constraint::Length(big_text_height),
|
||||||
|
Constraint::Length(subtitle_height),
|
||||||
Constraint::Fill(1),
|
Constraint::Fill(1),
|
||||||
])
|
])
|
||||||
.areas(area);
|
.areas(area);
|
||||||
|
|
||||||
let paragraph = Paragraph::new(lines).alignment(Alignment::Center);
|
frame.render_widget(big_title, title_area);
|
||||||
frame.render_widget(paragraph, center_area);
|
|
||||||
|
let subtitle = Paragraph::new(subtitle_lines).alignment(Alignment::Center);
|
||||||
|
frame.render_widget(subtitle, subtitle_area);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user