Euclidean + hue rotation
This commit is contained in:
@@ -16,7 +16,9 @@ use crate::app::App;
|
||||
use crate::engine::{LinkState, SequencerSnapshot};
|
||||
use crate::model::{SourceSpan, StepContext, Value};
|
||||
use crate::page::Page;
|
||||
use crate::state::{FlashKind, Modal, PanelFocus, PatternField, SidePanel, StackCache};
|
||||
use crate::state::{
|
||||
EuclideanField, FlashKind, Modal, PanelFocus, PatternField, SidePanel, StackCache,
|
||||
};
|
||||
use crate::theme;
|
||||
use crate::views::highlight::{self, highlight_line, highlight_line_with_runtime};
|
||||
use crate::widgets::{
|
||||
@@ -1038,5 +1040,131 @@ fn render_modal(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, term
|
||||
hint_area,
|
||||
);
|
||||
}
|
||||
Modal::EuclideanDistribution {
|
||||
source_step,
|
||||
field,
|
||||
pulses,
|
||||
steps,
|
||||
rotation,
|
||||
..
|
||||
} => {
|
||||
let width = 50u16;
|
||||
let height = 11u16;
|
||||
let x = (term.width.saturating_sub(width)) / 2;
|
||||
let y = (term.height.saturating_sub(height)) / 2;
|
||||
let area = Rect::new(x, y, width, height);
|
||||
|
||||
let block = Block::bordered()
|
||||
.title(format!(" Euclidean Distribution (Step {:02}) ", source_step + 1))
|
||||
.border_style(Style::default().fg(theme.modal.input));
|
||||
|
||||
let inner = block.inner(area);
|
||||
frame.render_widget(Clear, area);
|
||||
|
||||
// Fill background with theme color
|
||||
let bg_fill = " ".repeat(area.width as usize);
|
||||
for row in 0..area.height {
|
||||
let line_area = Rect::new(area.x, area.y + row, area.width, 1);
|
||||
frame.render_widget(
|
||||
Paragraph::new(bg_fill.clone()).style(Style::new().bg(theme.ui.bg)),
|
||||
line_area,
|
||||
);
|
||||
}
|
||||
|
||||
frame.render_widget(block, area);
|
||||
|
||||
let fields = [
|
||||
(
|
||||
"Pulses",
|
||||
pulses.as_str(),
|
||||
*field == EuclideanField::Pulses,
|
||||
),
|
||||
("Steps", steps.as_str(), *field == EuclideanField::Steps),
|
||||
(
|
||||
"Rotation",
|
||||
rotation.as_str(),
|
||||
*field == EuclideanField::Rotation,
|
||||
),
|
||||
];
|
||||
|
||||
for (i, (label, value, selected)) in fields.iter().enumerate() {
|
||||
let row_y = inner.y + i as u16;
|
||||
if row_y >= inner.y + inner.height {
|
||||
break;
|
||||
}
|
||||
|
||||
let (label_style, value_style) = if *selected {
|
||||
(
|
||||
Style::default()
|
||||
.fg(theme.hint.key)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
Style::default()
|
||||
.fg(theme.ui.text_primary)
|
||||
.bg(theme.ui.surface),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
Style::default().fg(theme.ui.text_muted),
|
||||
Style::default().fg(theme.ui.text_primary),
|
||||
)
|
||||
};
|
||||
|
||||
let label_area = Rect::new(inner.x + 1, row_y, 14, 1);
|
||||
let value_area = Rect::new(inner.x + 16, row_y, inner.width.saturating_sub(18), 1);
|
||||
|
||||
frame.render_widget(
|
||||
Paragraph::new(format!("{label}:")).style(label_style),
|
||||
label_area,
|
||||
);
|
||||
frame.render_widget(Paragraph::new(*value).style(value_style), value_area);
|
||||
}
|
||||
|
||||
let preview_y = inner.y + 4;
|
||||
if preview_y < inner.y + inner.height {
|
||||
let pulses_val: usize = pulses.parse().unwrap_or(0);
|
||||
let steps_val: usize = steps.parse().unwrap_or(0);
|
||||
let rotation_val: usize = rotation.parse().unwrap_or(0);
|
||||
let preview = format_euclidean_preview(pulses_val, steps_val, rotation_val);
|
||||
let preview_line = Line::from(vec![
|
||||
Span::styled("Preview: ", Style::default().fg(theme.ui.text_muted)),
|
||||
Span::styled(preview, Style::default().fg(theme.modal.input)),
|
||||
]);
|
||||
let preview_area =
|
||||
Rect::new(inner.x + 1, preview_y, inner.width.saturating_sub(2), 1);
|
||||
frame.render_widget(Paragraph::new(preview_line), preview_area);
|
||||
}
|
||||
|
||||
let hint_area = Rect::new(inner.x, inner.y + inner.height - 1, inner.width, 1);
|
||||
let hint_line = Line::from(vec![
|
||||
Span::styled("↑↓", Style::default().fg(theme.hint.key)),
|
||||
Span::styled(" nav ", Style::default().fg(theme.hint.text)),
|
||||
Span::styled("←→", Style::default().fg(theme.hint.key)),
|
||||
Span::styled(" adjust ", Style::default().fg(theme.hint.text)),
|
||||
Span::styled("Enter", Style::default().fg(theme.hint.key)),
|
||||
Span::styled(" apply ", Style::default().fg(theme.hint.text)),
|
||||
Span::styled("Esc", Style::default().fg(theme.hint.key)),
|
||||
Span::styled(" cancel", Style::default().fg(theme.hint.text)),
|
||||
]);
|
||||
frame.render_widget(Paragraph::new(hint_line), hint_area);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn format_euclidean_preview(pulses: usize, steps: usize, rotation: usize) -> String {
|
||||
if pulses == 0 || steps == 0 || pulses > steps {
|
||||
return "[invalid]".to_string();
|
||||
}
|
||||
|
||||
let mut pattern = vec![false; steps];
|
||||
for i in 0..pulses {
|
||||
let pos = (i * steps) / pulses;
|
||||
pattern[pos] = true;
|
||||
}
|
||||
|
||||
if rotation > 0 {
|
||||
pattern.rotate_left(rotation % steps);
|
||||
}
|
||||
|
||||
let chars: Vec<&str> = pattern.iter().map(|&h| if h { "x" } else { "." }).collect();
|
||||
format!("[{}]", chars.join(" "))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user