Skip to content

Commit

Permalink
#332 Fix non-deactivating preset save button
Browse files Browse the repository at this point in the history
by applying project independence transformation onto current mappings, not
just the ones in the preset itself
  • Loading branch information
helgoboss committed May 18, 2021
1 parent 3ec5785 commit 5bd2663
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 145 deletions.
78 changes: 1 addition & 77 deletions main/src/application/preset.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
use crate::application::{
GroupModel, MappingModel, ParameterSetting, SharedGroup, SharedMapping, TargetCategory,
};
use crate::domain::{ExtendedProcessorContext, VirtualFx, VirtualTrack};
use crate::application::{GroupModel, MappingModel, ParameterSetting, SharedGroup, SharedMapping};
use std::collections::HashMap;
use std::fmt;
use std::fmt::Debug;
Expand Down Expand Up @@ -35,76 +32,3 @@ pub trait PresetManager: fmt::Debug {
groups: &[SharedGroup],
) -> bool;
}

pub fn mappings_have_project_references(mappings: &[MappingModel]) -> bool {
mappings.iter().any(mapping_has_project_references)
}

pub fn make_mappings_project_independent(
mappings: &mut [MappingModel],
context: ExtendedProcessorContext,
) {
mappings
.iter_mut()
.for_each(|m| make_mapping_project_independent(m, context));
}

/// Checks if the given mapping has references to a project, e.g. refers to track or FX by ID.
fn mapping_has_project_references(mapping: &MappingModel) -> bool {
let target = &mapping.target_model;
match target.category.get() {
TargetCategory::Reaper => {
if target.r#type.get().supports_track() && target.track_type.get().refers_to_project() {
return true;
}
target.supports_fx() && target.fx_type.get().refers_to_project()
}
TargetCategory::Virtual => false,
}
}

fn make_mapping_project_independent(mapping: &mut MappingModel, context: ExtendedProcessorContext) {
let compartment = mapping.compartment();
let target = &mut mapping.target_model;
match target.category.get() {
TargetCategory::Reaper => {
let changed_to_track_ignore_fx = if target.supports_fx() {
let refers_to_project = target.fx_type.get().refers_to_project();
if refers_to_project {
let target_with_context = target.with_context(context, compartment);
let virtual_fx = if target_with_context.fx().ok().as_ref()
== Some(context.context().containing_fx())
{
// This is ourselves!
VirtualFx::This
} else {
VirtualFx::Focused
};
target.set_virtual_fx(virtual_fx);
true
} else {
false
}
} else {
false
};
if target.r#type.get().supports_track() && target.track_type.get().refers_to_project() {
let new_virtual_track = if changed_to_track_ignore_fx {
// Track doesn't matter at all. We change it to <This>. Looks nice.
Some(VirtualTrack::This)
} else if let Ok(t) = target
.with_context(context, compartment)
.first_effective_track()
{
t.index().map(VirtualTrack::ByIndex)
} else {
None
};
if let Some(t) = new_virtual_track {
target.set_virtual_track(t);
}
}
}
TargetCategory::Virtual => {}
}
}
93 changes: 88 additions & 5 deletions main/src/application/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ use crate::domain::{
MappingId, MidiControlInput, MidiDestination, NormalMainTask, NormalRealTimeTask, OscDeviceId,
ParameterArray, ProcessorContext, ProjectionFeedbackValue, QualifiedMappingId, RealSource,
RealTimeSender, RealearnTarget, ReaperTarget, SharedInstanceState, TargetValueChangedEvent,
VirtualControlElementId, VirtualSource, COMPARTMENT_PARAMETER_COUNT, ZEROED_PLUGIN_PARAMETERS,
VirtualControlElementId, VirtualFx, VirtualSource, VirtualTrack, COMPARTMENT_PARAMETER_COUNT,
ZEROED_PLUGIN_PARAMETERS,
};
use derivative::Derivative;
use enum_map::{enum_map, EnumMap};
Expand Down Expand Up @@ -285,6 +286,17 @@ impl Session {
})
}

pub fn mappings_have_project_references(&self, compartment: MappingCompartment) -> bool {
mappings_have_project_references(self.mappings[compartment].iter())
}

pub fn make_mappings_project_independent(&self, compartment: MappingCompartment) {
make_mappings_project_independent(
self.mappings[compartment].iter(),
self.extended_context(),
);
}

pub fn get_parameter_settings(
&self,
compartment: MappingCompartment,
Expand Down Expand Up @@ -1907,10 +1919,6 @@ impl Session {
}
}

pub fn parameters(&self) -> &ParameterArray {
&self.parameters
}

/// Just syncs whether control globally enabled or not.
fn sync_control_is_globally_enabled(&self) {
let enabled = self.control_is_globally_enabled();
Expand Down Expand Up @@ -2129,3 +2137,78 @@ pub enum InputDescriptor {
pub fn empty_parameter_settings() -> Vec<ParameterSetting> {
vec![Default::default(); COMPARTMENT_PARAMETER_COUNT as usize]
}

fn mappings_have_project_references<'a>(
mut mappings: impl Iterator<Item = &'a SharedMapping>,
) -> bool {
mappings.any(mapping_has_project_references)
}

fn make_mappings_project_independent<'a>(
mappings: impl Iterator<Item = &'a SharedMapping>,
context: ExtendedProcessorContext,
) {
mappings.for_each(|m| make_mapping_project_independent(m, context));
}

/// Checks if the given mapping has references to a project, e.g. refers to track or FX by ID.
fn mapping_has_project_references(mapping: &SharedMapping) -> bool {
let mapping = mapping.borrow();
let target = &mapping.target_model;
match target.category.get() {
TargetCategory::Reaper => {
if target.r#type.get().supports_track() && target.track_type.get().refers_to_project() {
return true;
}
target.supports_fx() && target.fx_type.get().refers_to_project()
}
TargetCategory::Virtual => false,
}
}

fn make_mapping_project_independent(mapping: &SharedMapping, context: ExtendedProcessorContext) {
let mut mapping = mapping.borrow_mut();
let compartment = mapping.compartment();
let target = &mut mapping.target_model;
match target.category.get() {
TargetCategory::Reaper => {
let changed_to_track_ignore_fx = if target.supports_fx() {
let refers_to_project = target.fx_type.get().refers_to_project();
if refers_to_project {
let target_with_context = target.with_context(context, compartment);
let virtual_fx = if target_with_context.fx().ok().as_ref()
== Some(context.context().containing_fx())
{
// This is ourselves!
VirtualFx::This
} else {
VirtualFx::Focused
};
target.set_virtual_fx(virtual_fx);
true
} else {
false
}
} else {
false
};
if target.r#type.get().supports_track() && target.track_type.get().refers_to_project() {
let new_virtual_track = if changed_to_track_ignore_fx {
// Track doesn't matter at all. We change it to <This>. Looks nice.
Some(VirtualTrack::This)
} else if let Ok(t) = target
.with_context(context, compartment)
.first_effective_track()
{
t.index().map(VirtualTrack::ByIndex)
} else {
None
};
if let Some(t) = new_virtual_track {
target.set_virtual_track(t);
}
}
}
TargetCategory::Virtual => {}
}
}
3 changes: 2 additions & 1 deletion main/src/infrastructure/ui/dialog_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ pub fn prompt_for(caption: &str, initial_value: &str) -> Option<String> {
Reaper::get()
.medium_reaper()
.get_user_inputs("ReaLearn", 1, caption, initial_value, 256)
.map(|r| r.into_string())
.map(|r| r.to_str().trim().to_owned())
.filter(|r| !r.is_empty())
}
102 changes: 40 additions & 62 deletions main/src/infrastructure/ui/header_panel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,12 @@ use slog::debug;
use swell_ui::{MenuBar, Pixels, Point, SharedView, View, ViewContext, Window};

use crate::application::{
make_mappings_project_independent, mappings_have_project_references, ControllerPreset, FxId,
MainPreset, MainPresetAutoLoadMode, MappingModel, ParameterSetting, Preset, PresetManager,
SharedMapping, SharedSession, VirtualControlElementType, WeakSession,
ControllerPreset, FxId, MainPreset, MainPresetAutoLoadMode, ParameterSetting, Preset,
PresetManager, SharedMapping, SharedSession, VirtualControlElementType, WeakSession,
};
use crate::base::when;
use crate::domain::{
ControlInput, ExtendedProcessorContext, GroupId, MappingCompartment, OscDeviceId, ReaperTarget,
ControlInput, GroupId, MappingCompartment, OscDeviceId, ReaperTarget,
COMPARTMENT_PARAMETER_COUNT,
};
use crate::domain::{MidiControlInput, MidiDestination};
Expand Down Expand Up @@ -159,9 +158,6 @@ impl HeaderPanel {

fn add_group(&self) {
if let Some(name) = dialog_util::prompt_for("Group name", "") {
if name.is_empty() {
return;
}
let id = self
.session()
.borrow_mut()
Expand Down Expand Up @@ -1677,34 +1673,34 @@ impl HeaderPanel {
let _ = App::get().main_preset_manager().borrow_mut().load_presets();
}

fn make_mappings_project_independent_if_desired(&self) {
let session = self.session();
let compartment = self.active_compartment();
if session
.borrow()
.mappings_have_project_references(compartment)
&& self.ask_user_if_project_independence_desired()
{
session
.borrow()
.make_mappings_project_independent(compartment);
}
}

fn save_active_preset(&self) -> Result<(), &'static str> {
self.make_mappings_project_independent_if_desired();
let session = self.session();
let (context, params, mut mappings, preset_id, compartment) = {
let session = session.borrow();
let compartment = self.active_compartment();
let preset_id = match compartment {
MappingCompartment::ControllerMappings => session.active_controller_preset_id(),
MappingCompartment::MainMappings => session.active_main_preset_id(),
};
let preset_id = match preset_id {
None => return Err("no active preset"),
Some(id) => id,
};
let mappings: Vec<_> = session
.mappings(compartment)
.map(|ptr| ptr.borrow().clone())
.collect();
(
session.context().clone(),
*session.parameters(),
mappings,
preset_id.to_owned(),
compartment,
)
};
let extended_context = ExtendedProcessorContext::new(&context, &params);
self.make_mappings_project_independent_if_desired(extended_context, &mut mappings);
let session = session.borrow();
let compartment = self.active_compartment();
let preset_id = match compartment {
MappingCompartment::ControllerMappings => session.active_controller_preset_id(),
MappingCompartment::MainMappings => session.active_main_preset_id(),
}
.ok_or("no active preset")?;
let mappings: Vec<_> = session
.mappings(compartment)
.map(|ptr| ptr.borrow().clone())
.collect();
let default_group = session.default_group(compartment).borrow().clone();
let parameter_settings = session.non_default_parameter_settings_by_compartment(compartment);
let groups = session
Expand Down Expand Up @@ -1743,7 +1739,7 @@ impl HeaderPanel {
let current_session_id = { self.session().borrow().id.get_ref().clone() };
let new_session_id = match dialog_util::prompt_for("Session ID", &current_session_id) {
None => return,
Some(n) => n.trim().to_string(),
Some(n) => n,
};
if new_session_id == current_session_id {
return;
Expand All @@ -1765,44 +1761,26 @@ impl HeaderPanel {
}

/// Don't borrow the session while calling this!
fn make_mappings_project_independent_if_desired(
&self,
context: ExtendedProcessorContext,
mut mappings: &mut [MappingModel],
) {
fn ask_user_if_project_independence_desired(&self) -> bool {
let msg = "Some of the mappings have references to this particular project. This usually doesn't make too much sense for a preset that's supposed to be reusable among different projects. Do you want ReaLearn to automatically adjust the mappings so that track targets refer to tracks by their position and FX targets relate to whatever FX is currently focused?";
if mappings_have_project_references(&mappings)
&& self.view.require_window().confirm("ReaLearn", msg)
{
make_mappings_project_independent(&mut mappings, context);
}
self.view.require_window().confirm("ReaLearn", msg)
}

fn save_as_preset(&self) -> Result<(), &'static str> {
let session = self.session();
let (context, params, mut mappings, compartment, param_settings) = {
let session = session.borrow_mut();
let compartment = self.active_compartment();
let mappings: Vec<_> = session
.mappings(compartment)
.map(|ptr| ptr.borrow().clone())
.collect();
(
session.context().clone(),
*session.parameters(),
mappings,
compartment,
session.non_default_parameter_settings_by_compartment(compartment),
)
};
let extended_context = ExtendedProcessorContext::new(&context, &params);
self.make_mappings_project_independent_if_desired(extended_context, &mut mappings);
let preset_name = match dialog_util::prompt_for("Preset name", "") {
None => return Ok(()),
Some(n) => n,
};
let preset_id = slug::slugify(&preset_name);
self.make_mappings_project_independent_if_desired();
let session = self.session();
let mut session = session.borrow_mut();
let compartment = self.active_compartment();
let mappings: Vec<_> = session
.mappings(compartment)
.map(|ptr| ptr.borrow().clone())
.collect();
let param_settings = session.non_default_parameter_settings_by_compartment(compartment);
let preset_id = slug::slugify(&preset_name);
let default_group = session.default_group(compartment).borrow().clone();
let groups = session
.groups(compartment)
Expand Down

0 comments on commit 5bd2663

Please sign in to comment.