vastly improved selection system
This commit is contained in:
387
src/app.rs
387
src/app.rs
@@ -187,15 +187,21 @@ impl App {
|
||||
self.load_step_to_editor();
|
||||
}
|
||||
|
||||
pub fn toggle_step(&mut self) {
|
||||
pub fn toggle_steps(&mut self) {
|
||||
let (bank, pattern) = self.current_bank_pattern();
|
||||
let change = pattern_editor::toggle_step(
|
||||
&mut self.project_state.project,
|
||||
bank,
|
||||
pattern,
|
||||
self.editor_ctx.step,
|
||||
);
|
||||
self.project_state.mark_dirty(change.bank, change.pattern);
|
||||
let indices: Vec<usize> = match self.editor_ctx.selection_range() {
|
||||
Some(range) => range.collect(),
|
||||
None => vec![self.editor_ctx.step],
|
||||
};
|
||||
for idx in indices {
|
||||
pattern_editor::toggle_step(
|
||||
&mut self.project_state.project,
|
||||
bank,
|
||||
pattern,
|
||||
idx,
|
||||
);
|
||||
}
|
||||
self.project_state.mark_dirty(bank, pattern);
|
||||
}
|
||||
|
||||
pub fn length_increase(&mut self) {
|
||||
@@ -517,26 +523,6 @@ impl App {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn copy_step(&mut self) {
|
||||
let (bank, pattern) = self.current_bank_pattern();
|
||||
let step = self.editor_ctx.step;
|
||||
let script =
|
||||
pattern_editor::get_step_script(&self.project_state.project, bank, pattern, step);
|
||||
|
||||
if let Some(script) = script {
|
||||
if let Some(clip) = &mut self.clipboard {
|
||||
if clip.set_text(&script).is_ok() {
|
||||
self.editor_ctx.copied_step = Some(crate::state::CopiedStep {
|
||||
bank,
|
||||
pattern,
|
||||
step,
|
||||
});
|
||||
self.ui.set_status("Copied".to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn delete_step(&mut self, bank: usize, pattern: usize, step: usize) {
|
||||
let pat = self.project_state.project.pattern_at_mut(bank, pattern);
|
||||
for s in &mut pat.steps {
|
||||
@@ -573,6 +559,46 @@ impl App {
|
||||
self.ui.flash("Step deleted", 150, FlashKind::Success);
|
||||
}
|
||||
|
||||
pub fn delete_steps(&mut self, bank: usize, pattern: usize, steps: &[usize]) {
|
||||
for &step in steps {
|
||||
let pat = self.project_state.project.pattern_at_mut(bank, pattern);
|
||||
for s in &mut pat.steps {
|
||||
if s.source == Some(step) {
|
||||
s.source = None;
|
||||
s.script.clear();
|
||||
s.command = None;
|
||||
}
|
||||
}
|
||||
|
||||
let change = pattern_editor::set_step_script(
|
||||
&mut self.project_state.project,
|
||||
bank,
|
||||
pattern,
|
||||
step,
|
||||
String::new(),
|
||||
);
|
||||
if let Some(s) = self
|
||||
.project_state
|
||||
.project
|
||||
.pattern_at_mut(bank, pattern)
|
||||
.step_mut(step)
|
||||
{
|
||||
s.command = None;
|
||||
s.source = None;
|
||||
}
|
||||
self.project_state.mark_dirty(change.bank, change.pattern);
|
||||
}
|
||||
if self.editor_ctx.bank == bank && self.editor_ctx.pattern == pattern {
|
||||
self.load_step_to_editor();
|
||||
}
|
||||
self.editor_ctx.clear_selection();
|
||||
self.ui.flash(
|
||||
&format!("{} steps deleted", steps.len()),
|
||||
150,
|
||||
FlashKind::Success,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn reset_pattern(&mut self, bank: usize, pattern: usize) {
|
||||
self.project_state.project.banks[bank].patterns[pattern] = Pattern::default();
|
||||
self.project_state.mark_dirty(bank, pattern);
|
||||
@@ -641,108 +667,235 @@ impl App {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn paste_step(&mut self, link: &LinkState) {
|
||||
let text = self
|
||||
.clipboard
|
||||
.as_mut()
|
||||
.and_then(|clip| clip.get_text().ok());
|
||||
pub fn harden_steps(&mut self) {
|
||||
let (bank, pattern) = self.current_bank_pattern();
|
||||
let indices: Vec<usize> = match self.editor_ctx.selection_range() {
|
||||
Some(range) => range.collect(),
|
||||
None => vec![self.editor_ctx.step],
|
||||
};
|
||||
|
||||
if let Some(text) = text {
|
||||
let (bank, pattern) = self.current_bank_pattern();
|
||||
let change = pattern_editor::set_step_script(
|
||||
&mut self.project_state.project,
|
||||
bank,
|
||||
pattern,
|
||||
self.editor_ctx.step,
|
||||
text,
|
||||
);
|
||||
self.project_state.mark_dirty(change.bank, change.pattern);
|
||||
self.load_step_to_editor();
|
||||
self.compile_current_step(link);
|
||||
let pat = self.project_state.project.pattern_at(bank, pattern);
|
||||
let resolutions: Vec<(usize, String)> = indices
|
||||
.iter()
|
||||
.filter_map(|&idx| {
|
||||
let step = pat.step(idx)?;
|
||||
step.source?;
|
||||
let script = pat.resolve_script(idx)?.to_string();
|
||||
Some((idx, script))
|
||||
})
|
||||
.collect();
|
||||
|
||||
if resolutions.is_empty() {
|
||||
self.ui.set_status("No linked steps to harden".to_string());
|
||||
return;
|
||||
}
|
||||
|
||||
let count = resolutions.len();
|
||||
for (idx, script) in resolutions {
|
||||
if let Some(s) = self
|
||||
.project_state
|
||||
.project
|
||||
.pattern_at_mut(bank, pattern)
|
||||
.step_mut(idx)
|
||||
{
|
||||
s.source = None;
|
||||
s.script = script;
|
||||
}
|
||||
}
|
||||
|
||||
self.project_state.mark_dirty(bank, pattern);
|
||||
self.load_step_to_editor();
|
||||
self.editor_ctx.clear_selection();
|
||||
if count == 1 {
|
||||
self.ui.flash("Step hardened", 150, FlashKind::Success);
|
||||
} else {
|
||||
self.ui.flash(&format!("{count} steps hardened"), 150, FlashKind::Success);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn link_paste_step(&mut self) {
|
||||
let Some(copied) = self.editor_ctx.copied_step else {
|
||||
pub fn copy_steps(&mut self) {
|
||||
let (bank, pattern) = self.current_bank_pattern();
|
||||
let pat = self.project_state.project.pattern_at(bank, pattern);
|
||||
|
||||
let indices: Vec<usize> = match self.editor_ctx.selection_range() {
|
||||
Some(range) => range.collect(),
|
||||
None => vec![self.editor_ctx.step],
|
||||
};
|
||||
|
||||
let mut steps = Vec::new();
|
||||
let mut scripts = Vec::new();
|
||||
for &idx in &indices {
|
||||
if let Some(step) = pat.step(idx) {
|
||||
let resolved = pat.resolve_script(idx).unwrap_or("").to_string();
|
||||
scripts.push(resolved.clone());
|
||||
steps.push(crate::state::CopiedStepData {
|
||||
script: resolved,
|
||||
active: step.active,
|
||||
source: step.source,
|
||||
original_index: idx,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let count = steps.len();
|
||||
self.editor_ctx.copied_steps = Some(crate::state::CopiedSteps {
|
||||
bank,
|
||||
pattern,
|
||||
steps,
|
||||
});
|
||||
|
||||
if let Some(clip) = &mut self.clipboard {
|
||||
let _ = clip.set_text(scripts.join("\n"));
|
||||
}
|
||||
|
||||
self.ui.flash(&format!("Copied {count} steps"), 150, FlashKind::Info);
|
||||
}
|
||||
|
||||
pub fn paste_steps(&mut self, link: &LinkState) {
|
||||
let Some(copied) = self.editor_ctx.copied_steps.clone() else {
|
||||
self.ui.set_status("Nothing copied".to_string());
|
||||
return;
|
||||
};
|
||||
|
||||
let (bank, pattern) = self.current_bank_pattern();
|
||||
let step = self.editor_ctx.step;
|
||||
let pat_len = self.project_state.project.pattern_at(bank, pattern).length;
|
||||
let cursor = self.editor_ctx.step;
|
||||
|
||||
if copied.bank != bank || copied.pattern != pattern {
|
||||
self.ui
|
||||
.set_status("Can only link within same pattern".to_string());
|
||||
return;
|
||||
let same_pattern = copied.bank == bank && copied.pattern == pattern;
|
||||
for (i, data) in copied.steps.iter().enumerate() {
|
||||
let target = cursor + i;
|
||||
if target >= pat_len {
|
||||
break;
|
||||
}
|
||||
if let Some(step) = self.project_state.project.pattern_at_mut(bank, pattern).step_mut(target) {
|
||||
let source = if same_pattern { data.source } else { None };
|
||||
step.active = data.active;
|
||||
step.source = source;
|
||||
if source.is_some() {
|
||||
step.script.clear();
|
||||
step.command = None;
|
||||
} else {
|
||||
step.script = data.script.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if copied.step == step {
|
||||
self.ui.set_status("Cannot link step to itself".to_string());
|
||||
return;
|
||||
}
|
||||
|
||||
let source_step = self
|
||||
.project_state
|
||||
.project
|
||||
.pattern_at(bank, pattern)
|
||||
.step(copied.step);
|
||||
if source_step.map(|s| s.source.is_some()).unwrap_or(false) {
|
||||
self.ui
|
||||
.set_status("Cannot link to a linked step".to_string());
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(s) = self
|
||||
.project_state
|
||||
.project
|
||||
.pattern_at_mut(bank, pattern)
|
||||
.step_mut(step)
|
||||
{
|
||||
s.source = Some(copied.step);
|
||||
s.script.clear();
|
||||
s.command = None;
|
||||
}
|
||||
self.project_state.mark_dirty(bank, pattern);
|
||||
self.load_step_to_editor();
|
||||
self.ui.flash(
|
||||
&format!("Linked to step {:02}", copied.step + 1),
|
||||
150,
|
||||
FlashKind::Success,
|
||||
);
|
||||
|
||||
// Compile affected steps
|
||||
for i in 0..copied.steps.len() {
|
||||
let target = cursor + i;
|
||||
if target >= pat_len {
|
||||
break;
|
||||
}
|
||||
let saved_step = self.editor_ctx.step;
|
||||
self.editor_ctx.step = target;
|
||||
self.compile_current_step(link);
|
||||
self.editor_ctx.step = saved_step;
|
||||
}
|
||||
|
||||
self.editor_ctx.clear_selection();
|
||||
self.ui.flash(&format!("Pasted {} steps", copied.steps.len()), 150, FlashKind::Success);
|
||||
}
|
||||
|
||||
pub fn harden_step(&mut self) {
|
||||
let (bank, pattern) = self.current_bank_pattern();
|
||||
let step = self.editor_ctx.step;
|
||||
|
||||
let resolved_script = self
|
||||
.project_state
|
||||
.project
|
||||
.pattern_at(bank, pattern)
|
||||
.resolve_script(step)
|
||||
.map(|s| s.to_string());
|
||||
|
||||
let Some(script) = resolved_script else {
|
||||
pub fn link_paste_steps(&mut self) {
|
||||
let Some(copied) = self.editor_ctx.copied_steps.clone() else {
|
||||
self.ui.set_status("Nothing copied".to_string());
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some(s) = self
|
||||
.project_state
|
||||
.project
|
||||
.pattern_at_mut(bank, pattern)
|
||||
.step_mut(step)
|
||||
{
|
||||
if s.source.is_none() {
|
||||
self.ui.set_status("Step is not linked".to_string());
|
||||
return;
|
||||
}
|
||||
s.source = None;
|
||||
s.script = script;
|
||||
let (bank, pattern) = self.current_bank_pattern();
|
||||
|
||||
if copied.bank != bank || copied.pattern != pattern {
|
||||
self.ui.set_status("Can only link within same pattern".to_string());
|
||||
return;
|
||||
}
|
||||
|
||||
let pat_len = self.project_state.project.pattern_at(bank, pattern).length;
|
||||
let cursor = self.editor_ctx.step;
|
||||
|
||||
for (i, data) in copied.steps.iter().enumerate() {
|
||||
let target = cursor + i;
|
||||
if target >= pat_len {
|
||||
break;
|
||||
}
|
||||
let source_idx = if data.source.is_some() {
|
||||
// Original was linked, link to same source
|
||||
data.source
|
||||
} else {
|
||||
Some(data.original_index)
|
||||
};
|
||||
if source_idx == Some(target) {
|
||||
continue;
|
||||
}
|
||||
if let Some(step) = self.project_state.project.pattern_at_mut(bank, pattern).step_mut(target) {
|
||||
step.source = source_idx;
|
||||
step.script.clear();
|
||||
step.command = None;
|
||||
}
|
||||
}
|
||||
|
||||
self.project_state.mark_dirty(bank, pattern);
|
||||
self.load_step_to_editor();
|
||||
self.ui.flash("Step hardened", 150, FlashKind::Success);
|
||||
self.editor_ctx.clear_selection();
|
||||
self.ui.flash(&format!("Linked {} steps", copied.steps.len()), 150, FlashKind::Success);
|
||||
}
|
||||
|
||||
pub fn duplicate_steps(&mut self, link: &LinkState) {
|
||||
let (bank, pattern) = self.current_bank_pattern();
|
||||
let pat = self.project_state.project.pattern_at(bank, pattern);
|
||||
let pat_len = pat.length;
|
||||
let indices: Vec<usize> = match self.editor_ctx.selection_range() {
|
||||
Some(range) => range.collect(),
|
||||
None => vec![self.editor_ctx.step],
|
||||
};
|
||||
let count = indices.len();
|
||||
let paste_at = *indices.last().unwrap() + 1;
|
||||
|
||||
let dupe_data: Vec<(bool, String, Option<usize>)> = indices
|
||||
.iter()
|
||||
.filter_map(|&idx| {
|
||||
let step = pat.step(idx)?;
|
||||
let script = pat.resolve_script(idx).unwrap_or("").to_string();
|
||||
let source = step.source;
|
||||
Some((step.active, script, source))
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut pasted = 0;
|
||||
for (i, (active, script, source)) in dupe_data.into_iter().enumerate() {
|
||||
let target = paste_at + i;
|
||||
if target >= pat_len {
|
||||
break;
|
||||
}
|
||||
if let Some(step) = self.project_state.project.pattern_at_mut(bank, pattern).step_mut(target) {
|
||||
step.active = active;
|
||||
step.source = source;
|
||||
if source.is_some() {
|
||||
step.script.clear();
|
||||
step.command = None;
|
||||
} else {
|
||||
step.script = script;
|
||||
step.command = None;
|
||||
}
|
||||
}
|
||||
pasted += 1;
|
||||
}
|
||||
|
||||
self.project_state.mark_dirty(bank, pattern);
|
||||
self.load_step_to_editor();
|
||||
|
||||
for i in 0..pasted {
|
||||
let target = paste_at + i;
|
||||
let saved = self.editor_ctx.step;
|
||||
self.editor_ctx.step = target;
|
||||
self.compile_current_step(link);
|
||||
self.editor_ctx.step = saved;
|
||||
}
|
||||
|
||||
self.editor_ctx.clear_selection();
|
||||
self.ui.flash(&format!("Duplicated {count} steps"), 150, FlashKind::Success);
|
||||
}
|
||||
|
||||
pub fn open_pattern_modal(&mut self, field: PatternField) {
|
||||
@@ -787,7 +940,7 @@ impl App {
|
||||
AppCommand::SelectEditPattern(pattern) => self.select_edit_pattern(pattern),
|
||||
|
||||
// Pattern editing
|
||||
AppCommand::ToggleStep => self.toggle_step(),
|
||||
AppCommand::ToggleSteps => self.toggle_steps(),
|
||||
AppCommand::LengthIncrease => self.length_increase(),
|
||||
AppCommand::LengthDecrease => self.length_decrease(),
|
||||
AppCommand::SpeedIncrease => self.speed_increase(),
|
||||
@@ -836,6 +989,13 @@ impl App {
|
||||
} => {
|
||||
self.delete_step(bank, pattern, step);
|
||||
}
|
||||
AppCommand::DeleteSteps {
|
||||
bank,
|
||||
pattern,
|
||||
steps,
|
||||
} => {
|
||||
self.delete_steps(bank, pattern, &steps);
|
||||
}
|
||||
AppCommand::ResetPattern { bank, pattern } => {
|
||||
self.reset_pattern(bank, pattern);
|
||||
}
|
||||
@@ -856,10 +1016,11 @@ impl App {
|
||||
}
|
||||
|
||||
// Clipboard
|
||||
AppCommand::CopyStep => self.copy_step(),
|
||||
AppCommand::PasteStep => self.paste_step(link),
|
||||
AppCommand::LinkPasteStep => self.link_paste_step(),
|
||||
AppCommand::HardenStep => self.harden_step(),
|
||||
AppCommand::HardenSteps => self.harden_steps(),
|
||||
AppCommand::CopySteps => self.copy_steps(),
|
||||
AppCommand::PasteSteps => self.paste_steps(link),
|
||||
AppCommand::LinkPasteSteps => self.link_paste_steps(),
|
||||
AppCommand::DuplicateSteps => self.duplicate_steps(link),
|
||||
|
||||
// Pattern playback (staging)
|
||||
AppCommand::StagePatternToggle { bank, pattern } => {
|
||||
|
||||
Reference in New Issue
Block a user