Feat: begin slight refactoring
This commit is contained in:
402
src/app.rs
402
src/app.rs
@@ -120,14 +120,24 @@ impl App {
|
||||
midi: crate::settings::MidiSettings {
|
||||
output_devices: {
|
||||
let outputs = crate::midi::list_midi_outputs();
|
||||
self.midi.selected_outputs.iter()
|
||||
.map(|opt| opt.and_then(|idx| outputs.get(idx).map(|d| d.name.clone())).unwrap_or_default())
|
||||
self.midi
|
||||
.selected_outputs
|
||||
.iter()
|
||||
.map(|opt| {
|
||||
opt.and_then(|idx| outputs.get(idx).map(|d| d.name.clone()))
|
||||
.unwrap_or_default()
|
||||
})
|
||||
.collect()
|
||||
},
|
||||
input_devices: {
|
||||
let inputs = crate::midi::list_midi_inputs();
|
||||
self.midi.selected_inputs.iter()
|
||||
.map(|opt| opt.and_then(|idx| inputs.get(idx).map(|d| d.name.clone())).unwrap_or_default())
|
||||
self.midi
|
||||
.selected_inputs
|
||||
.iter()
|
||||
.map(|opt| {
|
||||
opt.and_then(|idx| inputs.get(idx).map(|d| d.name.clone()))
|
||||
.unwrap_or_default()
|
||||
})
|
||||
.collect()
|
||||
},
|
||||
},
|
||||
@@ -139,6 +149,21 @@ impl App {
|
||||
(self.editor_ctx.bank, self.editor_ctx.pattern)
|
||||
}
|
||||
|
||||
fn selected_steps(&self) -> Vec<usize> {
|
||||
match self.editor_ctx.selection_range() {
|
||||
Some(range) => range.collect(),
|
||||
None => vec![self.editor_ctx.step],
|
||||
}
|
||||
}
|
||||
|
||||
fn annotate_copy_name(name: &Option<String>) -> Option<String> {
|
||||
match name {
|
||||
Some(n) if !n.ends_with(" (copy)") => Some(format!("{n} (copy)")),
|
||||
Some(n) => Some(n.clone()),
|
||||
None => Some("(copy)".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mark_all_patterns_dirty(&mut self) {
|
||||
self.project_state.mark_all_dirty();
|
||||
}
|
||||
@@ -216,17 +241,8 @@ impl App {
|
||||
|
||||
pub fn toggle_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],
|
||||
};
|
||||
for idx in indices {
|
||||
pattern_editor::toggle_step(
|
||||
&mut self.project_state.project,
|
||||
bank,
|
||||
pattern,
|
||||
idx,
|
||||
);
|
||||
for idx in self.selected_steps() {
|
||||
pattern_editor::toggle_step(&mut self.project_state.project, bank, pattern, idx);
|
||||
}
|
||||
self.project_state.mark_dirty(bank, pattern);
|
||||
}
|
||||
@@ -261,6 +277,37 @@ impl App {
|
||||
self.project_state.mark_dirty(change.bank, change.pattern);
|
||||
}
|
||||
|
||||
fn create_step_context(&self, step_idx: usize, link: &LinkState) -> StepContext {
|
||||
let (bank, pattern) = self.current_bank_pattern();
|
||||
let speed = self
|
||||
.project_state
|
||||
.project
|
||||
.pattern_at(bank, pattern)
|
||||
.speed
|
||||
.multiplier();
|
||||
StepContext {
|
||||
step: step_idx,
|
||||
beat: link.beat(),
|
||||
bank,
|
||||
pattern,
|
||||
tempo: link.tempo(),
|
||||
phase: link.phase(),
|
||||
slot: 0,
|
||||
runs: 0,
|
||||
iter: 0,
|
||||
speed,
|
||||
fill: false,
|
||||
nudge_secs: 0.0,
|
||||
cc_access: None,
|
||||
#[cfg(feature = "desktop")]
|
||||
mouse_x: 0.5,
|
||||
#[cfg(feature = "desktop")]
|
||||
mouse_y: 0.5,
|
||||
#[cfg(feature = "desktop")]
|
||||
mouse_down: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
fn load_step_to_editor(&mut self) {
|
||||
let (bank, pattern) = self.current_bank_pattern();
|
||||
if let Some(script) = pattern_editor::get_step_script(
|
||||
@@ -310,37 +357,7 @@ impl App {
|
||||
link: &LinkState,
|
||||
audio_tx: &arc_swap::ArcSwap<Sender<crate::engine::AudioCommand>>,
|
||||
) -> Result<(), String> {
|
||||
let (bank, pattern) = self.current_bank_pattern();
|
||||
let step_idx = self.editor_ctx.step;
|
||||
let speed = self
|
||||
.project_state
|
||||
.project
|
||||
.pattern_at(bank, pattern)
|
||||
.speed
|
||||
.multiplier();
|
||||
|
||||
let ctx = StepContext {
|
||||
step: step_idx,
|
||||
beat: link.beat(),
|
||||
bank,
|
||||
pattern,
|
||||
tempo: link.tempo(),
|
||||
phase: link.phase(),
|
||||
slot: 0,
|
||||
runs: 0,
|
||||
iter: 0,
|
||||
speed,
|
||||
fill: false,
|
||||
nudge_secs: 0.0,
|
||||
cc_memory: None,
|
||||
#[cfg(feature = "desktop")]
|
||||
mouse_x: 0.5,
|
||||
#[cfg(feature = "desktop")]
|
||||
mouse_y: 0.5,
|
||||
#[cfg(feature = "desktop")]
|
||||
mouse_down: 0.0,
|
||||
};
|
||||
|
||||
let ctx = self.create_step_context(self.editor_ctx.step, link);
|
||||
let cmds = self.script_engine.evaluate(script, &ctx)?;
|
||||
for cmd in cmds {
|
||||
let _ = audio_tx
|
||||
@@ -370,33 +387,7 @@ impl App {
|
||||
return;
|
||||
}
|
||||
|
||||
let speed = self
|
||||
.project_state
|
||||
.project
|
||||
.pattern_at(bank, pattern)
|
||||
.speed
|
||||
.multiplier();
|
||||
let ctx = StepContext {
|
||||
step: step_idx,
|
||||
beat: link.beat(),
|
||||
bank,
|
||||
pattern,
|
||||
tempo: link.tempo(),
|
||||
phase: link.phase(),
|
||||
slot: 0,
|
||||
runs: 0,
|
||||
iter: 0,
|
||||
speed,
|
||||
fill: false,
|
||||
nudge_secs: 0.0,
|
||||
cc_memory: None,
|
||||
#[cfg(feature = "desktop")]
|
||||
mouse_x: 0.5,
|
||||
#[cfg(feature = "desktop")]
|
||||
mouse_y: 0.5,
|
||||
#[cfg(feature = "desktop")]
|
||||
mouse_down: 0.0,
|
||||
};
|
||||
let ctx = self.create_step_context(step_idx, link);
|
||||
|
||||
match self.script_engine.evaluate(&script, &ctx) {
|
||||
Ok(cmds) => {
|
||||
@@ -454,33 +445,7 @@ impl App {
|
||||
continue;
|
||||
}
|
||||
|
||||
let speed = self
|
||||
.project_state
|
||||
.project
|
||||
.pattern_at(bank, pattern)
|
||||
.speed
|
||||
.multiplier();
|
||||
let ctx = StepContext {
|
||||
step: step_idx,
|
||||
beat: 0.0,
|
||||
bank,
|
||||
pattern,
|
||||
tempo: link.tempo(),
|
||||
phase: 0.0,
|
||||
slot: 0,
|
||||
runs: 0,
|
||||
iter: 0,
|
||||
speed,
|
||||
fill: false,
|
||||
nudge_secs: 0.0,
|
||||
cc_memory: None,
|
||||
#[cfg(feature = "desktop")]
|
||||
mouse_x: 0.5,
|
||||
#[cfg(feature = "desktop")]
|
||||
mouse_y: 0.5,
|
||||
#[cfg(feature = "desktop")]
|
||||
mouse_down: 0.0,
|
||||
};
|
||||
let ctx = self.create_step_context(step_idx, link);
|
||||
|
||||
if let Ok(cmds) = self.script_engine.evaluate(&script, &ctx) {
|
||||
if let Some(step) = self
|
||||
@@ -582,7 +547,8 @@ impl App {
|
||||
self.project_state.project.tempo = link.tempo();
|
||||
match model::save(&self.project_state.project, &path) {
|
||||
Ok(final_path) => {
|
||||
self.ui.set_status(format!("Saved: {}", final_path.display()));
|
||||
self.ui
|
||||
.set_status(format!("Saved: {}", final_path.display()));
|
||||
self.project_state.file_path = Some(final_path);
|
||||
}
|
||||
Err(e) => {
|
||||
@@ -715,11 +681,7 @@ impl App {
|
||||
pub fn paste_pattern(&mut self, bank: usize, pattern: usize) {
|
||||
if let Some(src) = &self.copied_pattern {
|
||||
let mut pat = src.clone();
|
||||
pat.name = match &src.name {
|
||||
Some(name) if !name.ends_with(" (copy)") => Some(format!("{name} (copy)")),
|
||||
Some(name) => Some(name.clone()),
|
||||
None => Some("(copy)".to_string()),
|
||||
};
|
||||
pat.name = Self::annotate_copy_name(&src.name);
|
||||
self.project_state.project.banks[bank].patterns[pattern] = pat;
|
||||
self.project_state.mark_dirty(bank, pattern);
|
||||
if self.editor_ctx.bank == bank && self.editor_ctx.pattern == pattern {
|
||||
@@ -738,11 +700,7 @@ impl App {
|
||||
pub fn paste_bank(&mut self, bank: usize) {
|
||||
if let Some(src) = &self.copied_bank {
|
||||
let mut b = src.clone();
|
||||
b.name = match &src.name {
|
||||
Some(name) if !name.ends_with(" (copy)") => Some(format!("{name} (copy)")),
|
||||
Some(name) => Some(name.clone()),
|
||||
None => Some("(copy)".to_string()),
|
||||
};
|
||||
b.name = Self::annotate_copy_name(&src.name);
|
||||
self.project_state.project.banks[bank] = b;
|
||||
for pattern in 0..self.project_state.project.banks[bank].patterns.len() {
|
||||
self.project_state.mark_dirty(bank, pattern);
|
||||
@@ -756,10 +714,7 @@ impl App {
|
||||
|
||||
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],
|
||||
};
|
||||
let indices = self.selected_steps();
|
||||
|
||||
let pat = self.project_state.project.pattern_at(bank, pattern);
|
||||
let resolutions: Vec<(usize, String)> = indices
|
||||
@@ -796,18 +751,15 @@ impl App {
|
||||
if count == 1 {
|
||||
self.ui.flash("Step hardened", 150, FlashKind::Success);
|
||||
} else {
|
||||
self.ui.flash(&format!("{count} steps hardened"), 150, FlashKind::Success);
|
||||
self.ui
|
||||
.flash(&format!("{count} steps hardened"), 150, FlashKind::Success);
|
||||
}
|
||||
}
|
||||
|
||||
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 indices = self.selected_steps();
|
||||
|
||||
let mut steps = Vec::new();
|
||||
let mut scripts = Vec::new();
|
||||
@@ -836,7 +788,8 @@ impl App {
|
||||
let _ = clip.set_text(scripts.join("\n"));
|
||||
}
|
||||
|
||||
self.ui.flash(&format!("Copied {count} steps"), 150, FlashKind::Info);
|
||||
self.ui
|
||||
.flash(&format!("Copied {count} steps"), 150, FlashKind::Info);
|
||||
}
|
||||
|
||||
pub fn paste_steps(&mut self, link: &LinkState) {
|
||||
@@ -855,7 +808,12 @@ impl App {
|
||||
if target >= pat_len {
|
||||
break;
|
||||
}
|
||||
if let Some(step) = self.project_state.project.pattern_at_mut(bank, pattern).step_mut(target) {
|
||||
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;
|
||||
@@ -885,7 +843,11 @@ impl App {
|
||||
}
|
||||
|
||||
self.editor_ctx.clear_selection();
|
||||
self.ui.flash(&format!("Pasted {} steps", copied.steps.len()), 150, FlashKind::Success);
|
||||
self.ui.flash(
|
||||
&format!("Pasted {} steps", copied.steps.len()),
|
||||
150,
|
||||
FlashKind::Success,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn link_paste_steps(&mut self) {
|
||||
@@ -897,7 +859,8 @@ impl App {
|
||||
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());
|
||||
self.ui
|
||||
.set_status("Can only link within same pattern".to_string());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -918,7 +881,12 @@ impl App {
|
||||
if source_idx == Some(target) {
|
||||
continue;
|
||||
}
|
||||
if let Some(step) = self.project_state.project.pattern_at_mut(bank, pattern).step_mut(target) {
|
||||
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;
|
||||
@@ -928,17 +896,18 @@ impl App {
|
||||
self.project_state.mark_dirty(bank, pattern);
|
||||
self.load_step_to_editor();
|
||||
self.editor_ctx.clear_selection();
|
||||
self.ui.flash(&format!("Linked {} steps", copied.steps.len()), 150, FlashKind::Success);
|
||||
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 indices = self.selected_steps();
|
||||
let count = indices.len();
|
||||
let paste_at = *indices.last().unwrap() + 1;
|
||||
|
||||
@@ -958,7 +927,12 @@ impl App {
|
||||
if target >= pat_len {
|
||||
break;
|
||||
}
|
||||
if let Some(step) = self.project_state.project.pattern_at_mut(bank, pattern).step_mut(target) {
|
||||
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() {
|
||||
@@ -984,7 +958,11 @@ impl App {
|
||||
}
|
||||
|
||||
self.editor_ctx.clear_selection();
|
||||
self.ui.flash(&format!("Duplicated {count} steps"), 150, FlashKind::Success);
|
||||
self.ui.flash(
|
||||
&format!("Duplicated {count} steps"),
|
||||
150,
|
||||
FlashKind::Success,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn open_pattern_modal(&mut self, field: PatternField) {
|
||||
@@ -1139,7 +1117,9 @@ impl App {
|
||||
step,
|
||||
name,
|
||||
} => {
|
||||
if let Some(s) = self.project_state.project.banks[bank].patterns[pattern].step_mut(step) {
|
||||
if let Some(s) =
|
||||
self.project_state.project.banks[bank].patterns[pattern].step_mut(step)
|
||||
{
|
||||
s.name = name;
|
||||
}
|
||||
self.project_state.mark_dirty(bank, pattern);
|
||||
@@ -1323,6 +1303,160 @@ impl App {
|
||||
let pattern = self.patterns_nav.selected_pattern();
|
||||
self.stage_pattern_toggle(bank, pattern, snapshot);
|
||||
}
|
||||
|
||||
// UI state
|
||||
AppCommand::ClearMinimap => {
|
||||
self.ui.minimap_until = None;
|
||||
}
|
||||
AppCommand::HideTitle => {
|
||||
self.ui.show_title = false;
|
||||
}
|
||||
AppCommand::ToggleEditorStack => {
|
||||
self.editor_ctx.show_stack = !self.editor_ctx.show_stack;
|
||||
}
|
||||
AppCommand::SetColorScheme(scheme) => {
|
||||
self.ui.color_scheme = scheme;
|
||||
crate::theme::set(scheme.to_theme());
|
||||
}
|
||||
AppCommand::ToggleRuntimeHighlight => {
|
||||
self.ui.runtime_highlight = !self.ui.runtime_highlight;
|
||||
}
|
||||
AppCommand::ToggleCompletion => {
|
||||
self.ui.show_completion = !self.ui.show_completion;
|
||||
self.editor_ctx
|
||||
.editor
|
||||
.set_completion_enabled(self.ui.show_completion);
|
||||
}
|
||||
AppCommand::AdjustFlashBrightness(delta) => {
|
||||
self.ui.flash_brightness = (self.ui.flash_brightness + delta).clamp(0.0, 1.0);
|
||||
}
|
||||
|
||||
// Live keys
|
||||
AppCommand::ToggleLiveKeysFill => {
|
||||
self.live_keys.flip_fill();
|
||||
}
|
||||
|
||||
// Panel
|
||||
AppCommand::ClosePanel => {
|
||||
self.panel.visible = false;
|
||||
self.panel.focus = crate::state::PanelFocus::Main;
|
||||
}
|
||||
|
||||
// Selection
|
||||
AppCommand::SetSelectionAnchor(step) => {
|
||||
self.editor_ctx.selection_anchor = Some(step);
|
||||
}
|
||||
AppCommand::ClearSelectionAnchor => {
|
||||
self.editor_ctx.selection_anchor = None;
|
||||
}
|
||||
|
||||
// Audio settings (engine page)
|
||||
AppCommand::AudioNextSection => {
|
||||
self.audio.next_section();
|
||||
}
|
||||
AppCommand::AudioPrevSection => {
|
||||
self.audio.prev_section();
|
||||
}
|
||||
AppCommand::AudioOutputListUp => {
|
||||
self.audio.output_list.move_up();
|
||||
}
|
||||
AppCommand::AudioOutputListDown(count) => {
|
||||
self.audio.output_list.move_down(count);
|
||||
}
|
||||
AppCommand::AudioOutputPageUp => {
|
||||
self.audio.output_list.page_up();
|
||||
}
|
||||
AppCommand::AudioOutputPageDown(count) => {
|
||||
self.audio.output_list.page_down(count);
|
||||
}
|
||||
AppCommand::AudioInputListUp => {
|
||||
self.audio.input_list.move_up();
|
||||
}
|
||||
AppCommand::AudioInputListDown(count) => {
|
||||
self.audio.input_list.move_down(count);
|
||||
}
|
||||
AppCommand::AudioInputPageDown(count) => {
|
||||
self.audio.input_list.page_down(count);
|
||||
}
|
||||
AppCommand::AudioSettingNext => {
|
||||
self.audio.setting_kind = self.audio.setting_kind.next();
|
||||
}
|
||||
AppCommand::AudioSettingPrev => {
|
||||
self.audio.setting_kind = self.audio.setting_kind.prev();
|
||||
}
|
||||
AppCommand::SetOutputDevice(name) => {
|
||||
self.audio.config.output_device = Some(name);
|
||||
}
|
||||
AppCommand::SetInputDevice(name) => {
|
||||
self.audio.config.input_device = Some(name);
|
||||
}
|
||||
AppCommand::SetDeviceKind(kind) => {
|
||||
self.audio.device_kind = kind;
|
||||
}
|
||||
AppCommand::AdjustAudioSetting { setting, delta } => {
|
||||
use crate::state::SettingKind;
|
||||
match setting {
|
||||
SettingKind::Channels => self.audio.adjust_channels(delta as i16),
|
||||
SettingKind::BufferSize => self.audio.adjust_buffer_size(delta),
|
||||
SettingKind::Polyphony => self.audio.adjust_max_voices(delta),
|
||||
SettingKind::Nudge => {
|
||||
self.metrics.nudge_ms =
|
||||
(self.metrics.nudge_ms + delta as f64).clamp(-50.0, 50.0);
|
||||
}
|
||||
SettingKind::Lookahead => self.audio.adjust_lookahead(delta),
|
||||
}
|
||||
}
|
||||
AppCommand::AudioTriggerRestart => {
|
||||
self.audio.trigger_restart();
|
||||
}
|
||||
AppCommand::RemoveLastSamplePath => {
|
||||
self.audio.remove_last_sample_path();
|
||||
}
|
||||
AppCommand::AudioRefreshDevices => {
|
||||
self.audio.refresh_devices();
|
||||
}
|
||||
|
||||
// Options page
|
||||
AppCommand::OptionsNextFocus => {
|
||||
self.options.next_focus();
|
||||
}
|
||||
AppCommand::OptionsPrevFocus => {
|
||||
self.options.prev_focus();
|
||||
}
|
||||
AppCommand::ToggleRefreshRate => {
|
||||
self.audio.toggle_refresh_rate();
|
||||
}
|
||||
AppCommand::ToggleScope => {
|
||||
self.audio.config.show_scope = !self.audio.config.show_scope;
|
||||
}
|
||||
AppCommand::ToggleSpectrum => {
|
||||
self.audio.config.show_spectrum = !self.audio.config.show_spectrum;
|
||||
}
|
||||
|
||||
// Metrics
|
||||
AppCommand::ResetPeakVoices => {
|
||||
self.metrics.peak_voices = 0;
|
||||
}
|
||||
|
||||
// MIDI connections
|
||||
AppCommand::ConnectMidiOutput { slot, port } => {
|
||||
if let Err(e) = self.midi.connect_output(slot, port) {
|
||||
self.ui
|
||||
.flash(&format!("MIDI error: {e}"), 300, FlashKind::Error);
|
||||
}
|
||||
}
|
||||
AppCommand::DisconnectMidiOutput(slot) => {
|
||||
self.midi.disconnect_output(slot);
|
||||
}
|
||||
AppCommand::ConnectMidiInput { slot, port } => {
|
||||
if let Err(e) = self.midi.connect_input(slot, port) {
|
||||
self.ui
|
||||
.flash(&format!("MIDI error: {e}"), 300, FlashKind::Error);
|
||||
}
|
||||
}
|
||||
AppCommand::DisconnectMidiInput(slot) => {
|
||||
self.midi.disconnect_input(slot);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user