Files
Cagire/src/app/clipboard.rs
Raphaël Forment 11cc925faf
All checks were successful
Deploy Website / deploy (push) Has been skipped
more fixes
2026-03-01 03:33:22 +01:00

407 lines
14 KiB
Rust

//! Clipboard operations on steps, patterns, and banks.
use crate::model;
use crate::services::clipboard;
use crate::state::FlashKind;
use super::App;
impl App {
pub fn copy_pattern(&mut self, bank: usize, pattern: usize) {
self.copied_patterns = Some(vec![clipboard::copy_pattern(
&self.project_state.project,
bank,
pattern,
)]);
self.ui.flash("Pattern copied", 150, FlashKind::Success);
}
pub fn paste_pattern(&mut self, bank: usize, pattern: usize) {
if let Some(patterns) = self.copied_patterns.take() {
if let Some(src) = patterns.first() {
clipboard::paste_pattern(&mut self.project_state.project, bank, pattern, src);
self.project_state.mark_dirty(bank, pattern);
if self.editor_ctx.bank == bank && self.editor_ctx.pattern == pattern {
self.load_step_to_editor();
}
self.ui.flash("Pattern pasted", 150, FlashKind::Success);
}
self.copied_patterns = Some(patterns);
}
}
pub fn copy_patterns(&mut self, bank: usize, patterns: &[usize]) {
self.copied_patterns = Some(clipboard::copy_patterns(
&self.project_state.project,
bank,
patterns,
));
let n = patterns.len();
self.ui.flash(
&format!("{n} pattern{} copied", if n == 1 { "" } else { "s" }),
150,
FlashKind::Success,
);
}
pub fn paste_patterns(&mut self, bank: usize, start: usize) {
if let Some(sources) = self.copied_patterns.take() {
let count = clipboard::paste_patterns(
&mut self.project_state.project,
bank,
start,
&sources,
);
self.copied_patterns = Some(sources);
for i in 0..count {
self.project_state.mark_dirty(bank, start + i);
}
if self.editor_ctx.bank == bank {
self.load_step_to_editor();
}
self.ui.flash(
&format!("{count} pattern{} pasted", if count == 1 { "" } else { "s" }),
150,
FlashKind::Success,
);
}
}
pub fn shift_patterns_up(&mut self) {
let bank = self.patterns_nav.bank_cursor;
let patterns = self.patterns_nav.selected_patterns();
let start = *patterns.first().expect("selected_patterns non-empty");
let end = *patterns.last().expect("selected_patterns non-empty");
if let Some(dirty) = clipboard::shift_patterns_up(
&mut self.project_state.project,
bank,
start..=end,
) {
for (b, p) in &dirty {
self.project_state.mark_dirty(*b, *p);
}
self.patterns_nav.pattern_cursor -= 1;
if let Some(ref mut anchor) = self.patterns_nav.pattern_anchor {
*anchor -= 1;
}
}
}
pub fn shift_patterns_down(&mut self) {
let bank = self.patterns_nav.bank_cursor;
let patterns = self.patterns_nav.selected_patterns();
let start = *patterns.first().expect("selected_patterns non-empty");
let end = *patterns.last().expect("selected_patterns non-empty");
if let Some(dirty) = clipboard::shift_patterns_down(
&mut self.project_state.project,
bank,
start..=end,
) {
for (b, p) in &dirty {
self.project_state.mark_dirty(*b, *p);
}
self.patterns_nav.pattern_cursor += 1;
if let Some(ref mut anchor) = self.patterns_nav.pattern_anchor {
*anchor += 1;
}
}
}
pub fn copy_bank(&mut self, bank: usize) {
self.copied_banks = Some(vec![clipboard::copy_bank(
&self.project_state.project,
bank,
)]);
self.ui.flash("Bank copied", 150, FlashKind::Success);
}
pub fn paste_bank(&mut self, bank: usize) {
if let Some(banks) = self.copied_banks.take() {
if let Some(src) = banks.first() {
let pat_count =
clipboard::paste_bank(&mut self.project_state.project, bank, src);
for pattern in 0..pat_count {
self.project_state.mark_dirty(bank, pattern);
}
if self.editor_ctx.bank == bank {
self.load_step_to_editor();
}
self.ui.flash("Bank pasted", 150, FlashKind::Success);
}
self.copied_banks = Some(banks);
}
}
pub fn copy_banks(&mut self, banks: &[usize]) {
self.copied_banks = Some(clipboard::copy_banks(
&self.project_state.project,
banks,
));
let n = banks.len();
self.ui.flash(
&format!("{n} bank{} copied", if n == 1 { "" } else { "s" }),
150,
FlashKind::Success,
);
}
pub fn paste_banks(&mut self, start: usize) {
if let Some(sources) = self.copied_banks.take() {
let count = clipboard::paste_banks(
&mut self.project_state.project,
start,
&sources,
);
self.copied_banks = Some(sources);
for i in 0..count {
let bank = start + i;
for pattern in 0..model::MAX_PATTERNS {
self.project_state.mark_dirty(bank, pattern);
}
}
if (start..start + count).contains(&self.editor_ctx.bank) {
self.load_step_to_editor();
}
self.ui.flash(
&format!("{count} bank{} pasted", if count == 1 { "" } else { "s" }),
150,
FlashKind::Success,
);
}
}
pub fn share_pattern(&mut self, bank: usize, pattern: usize) {
let pattern_data = &self.project_state.project.banks[bank].patterns[pattern];
match model::share::export(pattern_data) {
Ok(encoded) => {
let len = encoded.len();
if let Ok(mut clip) = arboard::Clipboard::new() {
let _ = clip.set_text(encoded);
}
if len > 2000 {
self.ui.flash(
&format!("Shared ({len} chars — too long for Discord)"),
200,
FlashKind::Error,
);
} else {
self.ui
.flash(&format!("Shared ({len} chars)"), 150, FlashKind::Success);
}
}
Err(e) => {
self.ui
.flash(&format!("Share failed: {e}"), 200, FlashKind::Error);
}
}
}
pub fn share_bank(&mut self, bank: usize) {
let bank_data = &self.project_state.project.banks[bank];
match model::share::export_bank(bank_data) {
Ok(encoded) => {
let len = encoded.len();
if let Ok(mut clip) = arboard::Clipboard::new() {
let _ = clip.set_text(encoded);
}
if len > 2000 {
self.ui.flash(
&format!("Bank shared ({len} chars — too long for Discord)"),
200,
FlashKind::Error,
);
} else {
self.ui
.flash(&format!("Bank shared ({len} chars)"), 150, FlashKind::Success);
}
}
Err(e) => {
self.ui
.flash(&format!("Share failed: {e}"), 200, FlashKind::Error);
}
}
}
pub fn import_bank(&mut self, bank: usize) {
let text = match arboard::Clipboard::new().ok().and_then(|mut c| c.get_text().ok()) {
Some(t) => t,
None => {
self.ui.flash("Clipboard empty", 150, FlashKind::Error);
return;
}
};
match model::share::import_bank(&text) {
Ok(imported) => {
self.project_state.project.banks[bank] = imported;
for pattern in 0..model::MAX_PATTERNS {
self.project_state.mark_dirty(bank, pattern);
}
if self.editor_ctx.bank == bank {
self.load_step_to_editor();
}
self.ui
.flash("Bank imported", 150, FlashKind::Success);
}
Err(e) => {
self.ui
.flash(&format!("Import failed: {e}"), 200, FlashKind::Error);
}
}
}
pub fn import_pattern(&mut self, bank: usize, pattern: usize) {
let text = match arboard::Clipboard::new().ok().and_then(|mut c| c.get_text().ok()) {
Some(t) => t,
None => {
self.ui
.flash("Clipboard empty", 150, FlashKind::Error);
return;
}
};
match model::share::import(&text) {
Ok(imported) => {
self.project_state.project.banks[bank].patterns[pattern] = imported;
self.project_state.mark_dirty(bank, pattern);
if self.editor_ctx.bank == bank && self.editor_ctx.pattern == pattern {
self.load_step_to_editor();
}
self.ui
.flash("Pattern imported", 150, FlashKind::Success);
}
Err(e) => {
self.ui
.flash(&format!("Import failed: {e}"), 200, FlashKind::Error);
}
}
}
/// Convert linked steps into independent copies.
pub fn harden_steps(&mut self) {
let (bank, pattern) = self.current_bank_pattern();
let indices = self.selected_steps();
let count = clipboard::harden_steps(&mut self.project_state.project, bank, pattern, &indices);
if count == 0 {
self.ui.set_status("No linked steps to harden".to_string());
return;
}
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 copy_steps(&mut self) {
let (bank, pattern) = self.current_bank_pattern();
let indices = self.selected_steps();
let copied = clipboard::copy_steps(
&self.project_state.project,
bank,
pattern,
&indices,
);
let count = copied.steps.len();
if let Ok(mut clip) = arboard::Clipboard::new() {
let text: String = copied.steps.iter().map(|s| s.script.as_str()).collect::<Vec<_>>().join("\n");
let _ = clip.set_text(text);
}
self.editor_ctx.copied_steps = Some(copied);
self.ui
.flash(&format!("Copied {count} steps"), 150, FlashKind::Info);
}
pub fn paste_steps(&mut self, link: &crate::engine::LinkState) {
let Some(copied) = self.editor_ctx.copied_steps.take() else {
self.ui.set_status("Nothing copied".to_string());
return;
};
let (bank, pattern) = self.current_bank_pattern();
let cursor = self.editor_ctx.step;
let result = clipboard::paste_steps(
&mut self.project_state.project,
bank,
pattern,
cursor,
&copied,
);
self.editor_ctx.copied_steps = Some(copied);
self.project_state.mark_dirty(bank, pattern);
self.load_step_to_editor();
for &target in &result.compile_targets {
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!("Pasted {} steps", result.count),
150,
FlashKind::Success,
);
}
/// Paste steps as linked references to the originals.
pub fn link_paste_steps(&mut self) {
let Some(copied) = self.editor_ctx.copied_steps.take() else {
self.ui.set_status("Nothing copied".to_string());
return;
};
let (bank, pattern) = self.current_bank_pattern();
let cursor = self.editor_ctx.step;
let result = clipboard::link_paste_steps(
&mut self.project_state.project,
bank,
pattern,
cursor,
&copied,
);
self.editor_ctx.copied_steps = Some(copied);
match result {
None => {
self.ui
.set_status("Can only link within same pattern".to_string());
}
Some(count) => {
self.project_state.mark_dirty(bank, pattern);
self.load_step_to_editor();
self.editor_ctx.clear_selection();
self.ui.flash(
&format!("Linked {count} steps"),
150,
FlashKind::Success,
);
}
}
}
pub fn duplicate_steps(&mut self, link: &crate::engine::LinkState) {
let (bank, pattern) = self.current_bank_pattern();
let indices = self.selected_steps();
let result = clipboard::duplicate_steps(
&mut self.project_state.project,
bank,
pattern,
&indices,
);
self.project_state.mark_dirty(bank, pattern);
self.load_step_to_editor();
for &target in &result.compile_targets {
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 {} steps", result.count),
150,
FlashKind::Success,
);
}
}