diff --git a/Cargo.lock b/Cargo.lock index c250a42d6..53096ed1c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2597,7 +2597,7 @@ dependencies = [ [[package]] name = "realearn" -version = "2.8.0-pre4" +version = "2.8.0-pre5" dependencies = [ "ascii", "askama", diff --git a/main/Cargo.toml b/main/Cargo.toml index ee647ce86..56e4c11cd 100644 --- a/main/Cargo.toml +++ b/main/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "realearn" -version = "2.8.0-pre4" +version = "2.8.0-pre5" authors = ["Benjamin Klum "] edition = "2018" build = "build.rs" diff --git a/main/src/application/controller_preset.rs b/main/src/application/controller_preset.rs index 289f43448..8b9b0d426 100644 --- a/main/src/application/controller_preset.rs +++ b/main/src/application/controller_preset.rs @@ -6,6 +6,8 @@ use std::fmt; pub struct ControllerPreset { id: String, name: String, + default_group: GroupModel, + groups: Vec, mappings: Vec, custom_data: HashMap, } @@ -14,12 +16,16 @@ impl ControllerPreset { pub fn new( id: String, name: String, + default_group: GroupModel, + groups: Vec, mappings: Vec, custom_data: HashMap, ) -> ControllerPreset { ControllerPreset { id, name, + default_group, + groups, mappings, custom_data, } @@ -36,7 +42,14 @@ impl ControllerPreset { self.custom_data.insert(key, value); } - pub fn update_realearn_data(&mut self, mappings: Vec) { + pub fn update_realearn_data( + &mut self, + default_group: GroupModel, + groups: Vec, + mappings: Vec, + ) { + self.default_group = default_group; + self.groups = groups; self.mappings = mappings; } } @@ -47,11 +60,11 @@ impl Preset for ControllerPreset { } fn default_group(&self) -> &GroupModel { - unimplemented!("controller presets don't support groups") + &self.default_group } fn groups(&self) -> &Vec { - unimplemented!("controller presets don't support groups") + &self.groups } fn mappings(&self) -> &Vec { diff --git a/main/src/application/mapping_model.rs b/main/src/application/mapping_model.rs index 7031d93b2..887e526a8 100644 --- a/main/src/application/mapping_model.rs +++ b/main/src/application/mapping_model.rs @@ -209,13 +209,9 @@ impl MappingModel { let source = self.source_model.create_source(); let mode = self.mode_model.create_mode(); let unresolved_target = self.target_model.create_target().ok(); - let activation_condition = if self.compartment == MappingCompartment::ControllerMappings { - // Controller mappings are always active, no matter what weird stuff is in the model. - ActivationCondition::Always - } else { - self.activation_condition_model - .create_activation_condition() - }; + let activation_condition = self + .activation_condition_model + .create_activation_condition(); let options = ProcessorMappingOptions { // TODO-medium Encapsulate, don't set here target_is_active: false, diff --git a/main/src/application/session.rs b/main/src/application/session.rs index dd36172dd..6622166a9 100644 --- a/main/src/application/session.rs +++ b/main/src/application/session.rs @@ -63,14 +63,15 @@ pub struct Session { active_main_preset_id: Option, context: ProcessorContext, mappings: EnumMap>, - default_group: SharedGroup, - groups: Vec, + default_main_group: SharedGroup, + default_controller_group: SharedGroup, + groups: EnumMap>, everything_changed_subject: LocalSubject<'static, (), ()>, mapping_list_changed_subject: LocalSubject<'static, (MappingCompartment, Option), ()>, - group_list_changed_subject: LocalSubject<'static, (), ()>, + group_list_changed_subject: LocalSubject<'static, MappingCompartment, ()>, mapping_changed_subject: LocalSubject<'static, MappingCompartment, ()>, - group_changed_subject: LocalSubject<'static, (), ()>, + group_changed_subject: LocalSubject<'static, MappingCompartment, ()>, source_touched_subject: LocalSubject<'static, CompoundMappingSource, ()>, mapping_subscriptions: EnumMap>>, group_subscriptions: Vec>, @@ -175,7 +176,8 @@ impl Session { active_main_preset_id: None, context, mappings: Default::default(), - default_group: Default::default(), + default_main_group: Default::default(), + default_controller_group: Default::default(), groups: Default::default(), everything_changed_subject: Default::default(), mapping_list_changed_subject: Default::default(), @@ -270,7 +272,9 @@ impl Session { } fn initial_sync(&mut self, weak_session: WeakSession) { - self.resubscribe_to_groups(weak_session.clone()); + for compartment in MappingCompartment::enum_iter() { + self.resubscribe_to_groups(weak_session.clone(), compartment); + } // It's important to sync feedback device first, otherwise the initial feedback messages // won't arrive! self.sync_settings(); @@ -311,10 +315,10 @@ impl Session { // (because a mapping could have changed its group). when(self.group_list_changed()) .with(weak_session.clone()) - .do_async(|shared_session, _| { + .do_async(|shared_session, compartment| { let mut session = shared_session.borrow_mut(); - session.resubscribe_to_groups(Rc::downgrade(&shared_session)); - session.sync_all_mappings_full(MappingCompartment::MainMappings); + session.resubscribe_to_groups(Rc::downgrade(&shared_session), compartment); + session.sync_all_mappings_full(compartment); }); // Whenever anything in a mapping list changes and other things which affect all // processors (including the real-time processor which takes care of sources only), resync @@ -590,9 +594,13 @@ impl Session { .collect(); } - fn resubscribe_to_groups(&mut self, weak_session: WeakSession) { + fn resubscribe_to_groups( + &mut self, + weak_session: WeakSession, + compartment: MappingCompartment, + ) { self.group_subscriptions = self - .groups_including_default_group() + .groups_including_default_group(compartment) .map(|shared_group| { // We don't need to take until "party is over" because if the session disappears, // we know the groups disappear as well. @@ -605,9 +613,9 @@ impl Session { .do_sync(move |session, _| { let mut session = session.borrow_mut(); // Change of a single group can affect many mappings - session.sync_all_mappings_full(MappingCompartment::MainMappings); + session.sync_all_mappings_full(compartment); session.mark_project_as_dirty(); - session.notify_group_changed(); + session.notify_group_changed(compartment); }); all_subscriptions.add(subscription); } @@ -618,7 +626,7 @@ impl Session { .do_sync(move |session, _| { let mut session = session.borrow_mut(); session.mark_project_as_dirty(); - session.notify_group_changed(); + session.notify_group_changed(compartment); }); all_subscriptions.add(subscription); } @@ -653,40 +661,58 @@ impl Session { ExtendedProcessorContext::new(&self.context, &self.parameters) } - pub fn add_default_group(&mut self, name: String) -> GroupId { + pub fn add_default_group(&mut self, compartment: MappingCompartment, name: String) -> GroupId { let group = GroupModel::new_from_ui(name); - self.add_group(group) + self.add_group(compartment, group) } - fn add_group(&mut self, group: GroupModel) -> GroupId { + fn add_group(&mut self, compartment: MappingCompartment, group: GroupModel) -> GroupId { let id = group.id(); let shared_group = Rc::new(RefCell::new(group)); - self.groups.push(shared_group); - self.notify_group_list_changed(); + self.groups[compartment].push(shared_group); + self.notify_group_list_changed(compartment); id } - pub fn find_group_index_by_id_sorted(&self, id: GroupId) -> Option { - self.groups_sorted().position(|g| g.borrow().id() == id) + pub fn find_group_index_by_id_sorted( + &self, + compartment: MappingCompartment, + id: GroupId, + ) -> Option { + self.groups_sorted(compartment) + .position(|g| g.borrow().id() == id) } - pub fn group_contains_mappings(&self, id: GroupId) -> bool { - self.mappings(MappingCompartment::MainMappings) + pub fn group_contains_mappings(&self, compartment: MappingCompartment, id: GroupId) -> bool { + self.mappings(compartment) .filter(|m| m.borrow().group_id.get() == id) .count() > 0 } - pub fn find_group_by_id(&self, id: GroupId) -> Option<&SharedGroup> { - self.groups.iter().find(|g| g.borrow().id() == id) + pub fn find_group_by_id( + &self, + compartment: MappingCompartment, + id: GroupId, + ) -> Option<&SharedGroup> { + self.groups[compartment] + .iter() + .find(|g| g.borrow().id() == id) } - pub fn find_group_by_index_sorted(&self, index: usize) -> Option<&SharedGroup> { - self.groups_sorted().nth(index) + pub fn find_group_by_index_sorted( + &self, + compartment: MappingCompartment, + index: usize, + ) -> Option<&SharedGroup> { + self.groups_sorted(compartment).nth(index) } - pub fn groups_sorted(&self) -> impl Iterator { - self.groups + pub fn groups_sorted( + &self, + compartment: MappingCompartment, + ) -> impl Iterator { + self.groups[compartment] .iter() .sorted_by_key(|g| g.borrow().name.get_ref().clone()) } @@ -701,24 +727,28 @@ impl Session { .find_mapping_and_index_by_id(compartment, mapping_id) .ok_or("no such mapping")?; mapping.borrow_mut().group_id.set(group_id); - self.notify_group_list_changed(); + self.notify_group_list_changed(compartment); Ok(()) } - pub fn remove_group(&mut self, id: GroupId, delete_mappings: bool) { - self.groups.retain(|g| g.borrow().id() != id); + pub fn remove_group( + &mut self, + compartment: MappingCompartment, + id: GroupId, + delete_mappings: bool, + ) { + self.groups[compartment].retain(|g| g.borrow().id() != id); if delete_mappings { - self.mappings[MappingCompartment::MainMappings] - .retain(|m| m.borrow().group_id.get() != id); + self.mappings[compartment].retain(|m| m.borrow().group_id.get() != id); } else { - for m in self.mappings(MappingCompartment::MainMappings) { + for m in self.mappings(compartment) { let mut m = m.borrow_mut(); if m.group_id.get() == id { m.group_id.set_without_notification(GroupId::default()); } } } - self.notify_group_list_changed(); + self.notify_group_list_changed(compartment); } pub fn add_default_mapping( @@ -948,16 +978,22 @@ impl Session { self.mappings[compartment].iter() } - pub fn default_group(&self) -> &SharedGroup { - &self.default_group + pub fn default_group(&self, compartment: MappingCompartment) -> &SharedGroup { + match compartment { + MappingCompartment::ControllerMappings => &self.default_controller_group, + MappingCompartment::MainMappings => &self.default_main_group, + } } - pub fn groups(&self) -> impl Iterator { - self.groups.iter() + pub fn groups(&self, compartment: MappingCompartment) -> impl Iterator { + self.groups[compartment].iter() } - fn groups_including_default_group(&self) -> impl Iterator { - std::iter::once(&self.default_group).chain(self.groups.iter()) + fn groups_including_default_group( + &self, + compartment: MappingCompartment, + ) -> impl Iterator { + std::iter::once(self.default_group(compartment)).chain(self.groups[compartment].iter()) } fn all_mappings(&self) -> impl Iterator { @@ -1242,7 +1278,7 @@ impl Session { self.active_main_preset_id = active_main_preset_id; } - pub fn active_controller_id(&self) -> Option<&str> { + pub fn active_controller_preset_id(&self) -> Option<&str> { self.active_controller_preset_id.as_deref() } @@ -1251,7 +1287,7 @@ impl Session { } pub fn active_controller(&self) -> Option { - let id = self.active_controller_id()?; + let id = self.active_controller_preset_id()?; self.controller_preset_manager.find_by_id(id) } @@ -1267,6 +1303,11 @@ impl Session { }; self.controller_preset_manager .mappings_are_dirty(id, &self.mappings[MappingCompartment::ControllerMappings]) + || self.controller_preset_manager.groups_are_dirty( + id, + &self.default_group(MappingCompartment::ControllerMappings), + &self.groups[MappingCompartment::ControllerMappings], + ) } pub fn main_preset_is_out_of_date(&self) -> bool { @@ -1279,12 +1320,14 @@ impl Session { }; self.main_preset_manager .mappings_are_dirty(id, &self.mappings[MappingCompartment::MainMappings]) - || self - .main_preset_manager - .groups_are_dirty(id, &self.default_group, &self.groups) + || self.main_preset_manager.groups_are_dirty( + id, + &self.default_main_group, + &self.groups[MappingCompartment::MainMappings], + ) } - pub fn activate_controller( + pub fn activate_controller_preset( &mut self, id: Option, weak_session: WeakSession, @@ -1320,17 +1363,17 @@ impl Session { .main_preset_manager .find_by_id(id) .ok_or("main preset not found")?; - self.default_group + self.default_main_group .replace(main_preset.default_group().clone()); - self.set_groups_without_notification(main_preset.groups().iter().cloned()); + self.set_groups_without_notification(compartment, main_preset.groups().iter().cloned()); self.set_mappings_without_notification( compartment, main_preset.mappings().iter().cloned(), ); } else { // preset - self.default_group.replace(Default::default()); - self.set_groups_without_notification(std::iter::empty()); + self.default_main_group.replace(Default::default()); + self.set_groups_without_notification(compartment, std::iter::empty()); self.set_mappings_without_notification(compartment, std::iter::empty()); } self.notify_everything_has_changed(weak_session); @@ -1376,15 +1419,22 @@ impl Session { /// Fires when a group has been added or removed. /// /// Doesn't fire if a group in the list or if the complete list has changed. - pub fn group_list_changed(&self) -> impl UnitEvent { + pub fn group_list_changed(&self) -> impl SharedItemEvent { self.group_list_changed_subject.clone() } /// Fires if a group itself has been changed. - pub fn group_changed(&self) -> impl UnitEvent { - self.default_group + pub fn group_changed(&self) -> impl SharedItemEvent { + self.default_main_group .borrow() .changed_processing_relevant() + .map(|_| MappingCompartment::MainMappings) + .merge( + self.default_controller_group + .borrow() + .changed_processing_relevant() + .map(|_| MappingCompartment::ControllerMappings), + ) .merge(self.group_changed_subject.clone()) } @@ -1414,8 +1464,12 @@ impl Session { self.mappings[compartment] = fixed_mappings.into_iter().map(share_mapping).collect(); } - pub fn set_groups_without_notification(&mut self, groups: impl Iterator) { - self.groups = groups.into_iter().map(share_group).collect(); + pub fn set_groups_without_notification( + &mut self, + compartment: MappingCompartment, + groups: impl Iterator, + ) { + self.groups[compartment] = groups.into_iter().map(share_group).collect(); } fn add_mapping( @@ -1547,13 +1601,13 @@ impl Session { /// Notifies listeners async that something in a group list has changed. /// /// Shouldn't be used if the complete list has changed. - fn notify_group_list_changed(&mut self) { - AsyncNotifier::notify(&mut self.group_list_changed_subject, &()); + fn notify_group_list_changed(&mut self, compartment: MappingCompartment) { + AsyncNotifier::notify(&mut self.group_list_changed_subject, &compartment); } /// Notifies listeners async a group in the group list has changed. - fn notify_group_changed(&mut self) { - AsyncNotifier::notify(&mut self.group_changed_subject, &()); + fn notify_group_changed(&mut self, compartment: MappingCompartment) { + AsyncNotifier::notify(&mut self.group_changed_subject, &compartment); } /// Notifies listeners async a mapping in a mapping list has changed. @@ -1591,14 +1645,15 @@ impl Session { } fn find_group_of_mapping(&self, mapping: &MappingModel) -> Option<&SharedGroup> { - if mapping.compartment() == MappingCompartment::ControllerMappings { - return None; - } let group_id = mapping.group_id.get(); if group_id.is_default() { - Some(&self.default_group) + let group = match mapping.compartment() { + MappingCompartment::ControllerMappings => &self.default_controller_group, + MappingCompartment::MainMappings => &self.default_main_group, + }; + Some(group) } else { - self.find_group_by_id(group_id) + self.find_group_by_id(mapping.compartment(), group_id) } } @@ -1661,18 +1716,13 @@ impl Session { /// Creates mappings from mapping models so they can be distributed to different processors. fn create_main_mappings(&self, compartment: MappingCompartment) -> Vec { - let group_map: HashMap> = - if compartment == MappingCompartment::ControllerMappings { - // We don't want controller mappings to use any groups! - Default::default() - } else { - self.groups_including_default_group() - .map(|group| { - let group = group.borrow(); - (group.id(), group) - }) - .collect() - }; + let group_map: HashMap> = self + .groups_including_default_group(compartment) + .map(|group| { + let group = group.borrow(); + (group.id(), group) + }) + .collect(); // TODO-medium This is non-optimal if we have a group that uses an EEL activation condition // and has many mappings. Because of our strategy of groups being an application-layer // concept only, we equip *all* n mappings in that group with the group activation diff --git a/main/src/infrastructure/data/controller_preset.rs b/main/src/infrastructure/data/controller_preset.rs index f333e281f..5fdf9f315 100644 --- a/main/src/infrastructure/data/controller_preset.rs +++ b/main/src/infrastructure/data/controller_preset.rs @@ -2,8 +2,8 @@ use crate::application::{ControllerPreset, Preset, PresetManager, SharedGroup, S use crate::core::default_util::is_default; use crate::domain::MappingCompartment; use crate::infrastructure::data::{ - ExtendedPresetManager, FileBasedPresetManager, MappingModelData, MigrationDescriptor, - PresetData, + ExtendedPresetManager, FileBasedPresetManager, GroupModelData, MappingModelData, + MigrationDescriptor, PresetData, }; use crate::infrastructure::plugin::App; @@ -63,6 +63,10 @@ pub struct ControllerPresetData { id: Option, name: String, #[serde(default, skip_serializing_if = "is_default")] + default_group: Option, + #[serde(default, skip_serializing_if = "is_default")] + groups: Vec, + #[serde(default, skip_serializing_if = "is_default")] mappings: Vec, #[serde(default, skip_serializing_if = "is_default")] custom_data: HashMap, @@ -71,25 +75,38 @@ pub struct ControllerPresetData { impl PresetData for ControllerPresetData { type P = ControllerPreset; - fn from_model(controller: &ControllerPreset) -> ControllerPresetData { + fn from_model(preset: &ControllerPreset) -> ControllerPresetData { ControllerPresetData { version: Some(App::version().clone()), - id: Some(controller.id().to_string()), - mappings: controller + id: Some(preset.id().to_string()), + default_group: Some(GroupModelData::from_model(preset.default_group())), + groups: preset + .groups() + .iter() + .map(|g| GroupModelData::from_model(g)) + .collect(), + mappings: preset .mappings() .iter() .map(|m| MappingModelData::from_model(&m)) .collect(), - name: controller.name().to_string(), - custom_data: controller.custom_data().clone(), + name: preset.name().to_string(), + custom_data: preset.custom_data().clone(), } } fn to_model(&self, id: String) -> ControllerPreset { let migration_descriptor = MigrationDescriptor::new(self.version.as_ref()); + let final_default_group = self + .default_group + .as_ref() + .map(|g| g.to_model()) + .unwrap_or_default(); ControllerPreset::new( id, self.name.clone(), + final_default_group, + self.groups.iter().map(|g| g.to_model()).collect(), self.mappings .iter() .map(|m| { diff --git a/main/src/infrastructure/data/session_data.rs b/main/src/infrastructure/data/session_data.rs index 29cea723d..38aeba130 100644 --- a/main/src/infrastructure/data/session_data.rs +++ b/main/src/infrastructure/data/session_data.rs @@ -1,4 +1,4 @@ -use crate::application::{MainPresetAutoLoadMode, ParameterSetting, Session}; +use crate::application::{GroupModel, MainPresetAutoLoadMode, ParameterSetting, Session}; use crate::core::default_util::{bool_true, is_bool_true, is_default}; use crate::domain::{ ExtendedProcessorContext, MappingCompartment, MidiControlInput, MidiFeedbackOutput, @@ -56,6 +56,10 @@ pub struct SessionData { #[serde(default, skip_serializing_if = "is_default")] groups: Vec, #[serde(default, skip_serializing_if = "is_default")] + default_controller_group: Option, + #[serde(default, skip_serializing_if = "is_default")] + controller_groups: Vec, + #[serde(default, skip_serializing_if = "is_default")] mappings: Vec, #[serde(default, skip_serializing_if = "is_default")] controller_mappings: Vec, @@ -96,7 +100,9 @@ impl Default for SessionData { control_device_id: None, feedback_device_id: None, default_group: None, + default_controller_group: None, groups: vec![], + controller_groups: vec![], mappings: vec![], controller_mappings: vec![], active_controller_id: None, @@ -119,6 +125,17 @@ impl SessionData { .map(|m| MappingModelData::from_model(m.borrow().deref())) .collect() }; + let from_groups = |compartment| { + session + .groups(compartment) + .map(|m| GroupModelData::from_model(m.borrow().deref())) + .collect() + }; + let from_group = |compartment| { + Some(GroupModelData::from_model( + session.default_group(compartment).borrow().deref(), + )) + }; SessionData { version: Some(App::version().clone()), id: Some(session.id().to_string()), @@ -144,16 +161,15 @@ impl SessionData { FxOutput => FeedbackDeviceId::MidiOrFxOutput("fx-output".to_owned()), }) }, - default_group: Some(GroupModelData::from_model( - session.default_group().borrow().deref(), - )), - groups: session - .groups() - .map(|m| GroupModelData::from_model(m.borrow().deref())) - .collect(), + default_group: from_group(MappingCompartment::MainMappings), + default_controller_group: from_group(MappingCompartment::ControllerMappings), + groups: from_groups(MappingCompartment::MainMappings), + controller_groups: from_groups(MappingCompartment::ControllerMappings), mappings: from_mappings(MappingCompartment::MainMappings), controller_mappings: from_mappings(MappingCompartment::ControllerMappings), - active_controller_id: session.active_controller_id().map(|id| id.to_string()), + active_controller_id: session + .active_controller_preset_id() + .map(|id| id.to_string()), active_main_preset_id: session.active_main_preset_id().map(|id| id.to_string()), main_preset_auto_load_mode: session.main_preset_auto_load_mode.get(), parameters: (0..PLUGIN_PARAMETER_COUNT) @@ -261,13 +277,25 @@ impl SessionData { .osc_output_device_id .set_without_notification(osc_feedback_output); // Groups - let final_default_group = self - .default_group - .as_ref() - .map(|g| g.to_model()) - .unwrap_or_default(); - session.default_group().replace(final_default_group); - session.set_groups_without_notification(self.groups.iter().map(|g| g.to_model())); + let get_final_default_group = |def_group: Option<&GroupModelData>| { + def_group.map(|g| g.to_model()).unwrap_or_default() + }; + session + .default_group(MappingCompartment::MainMappings) + .replace(get_final_default_group(self.default_group.as_ref())); + session.set_groups_without_notification( + MappingCompartment::MainMappings, + self.groups.iter().map(|g| g.to_model()), + ); + session + .default_group(MappingCompartment::ControllerMappings) + .replace(get_final_default_group( + self.default_controller_group.as_ref(), + )); + session.set_groups_without_notification( + MappingCompartment::ControllerMappings, + self.controller_groups.iter().map(|g| g.to_model()), + ); // Mappings let context = session.context().clone(); let extended_context = ExtendedProcessorContext::new(&context, ¶ms); diff --git a/main/src/infrastructure/server/mod.rs b/main/src/infrastructure/server/mod.rs index b88aaf5bd..2acd647e2 100644 --- a/main/src/infrastructure/server/mod.rs +++ b/main/src/infrastructure/server/mod.rs @@ -372,7 +372,7 @@ fn handle_controller_route(session_id: String) -> Result Option { - let group_filter = self.main_state.borrow().group_filter.get()?; + let group_filter = self + .main_state + .borrow() + .group_filter_for_active_compartment()?; Some(group_filter.group_id()) } @@ -154,11 +157,13 @@ impl HeaderPanel { if name.is_empty() { return; } - let id = self.session().borrow_mut().add_default_group(name); + let id = self + .session() + .borrow_mut() + .add_default_group(self.active_compartment(), name); self.main_state .borrow_mut() - .group_filter - .set(Some(GroupFilter(id))); + .set_group_filter_for_active_compartment(Some(GroupFilter(id))); } } @@ -580,16 +585,13 @@ impl HeaderPanel { fn invalidate_group_controls(&self) { self.invalidate_group_control_appearance(); - if self.active_compartment() != MappingCompartment::MainMappings { - return; - } self.invalidate_group_combo_box(); self.invalidate_group_buttons(); } fn invalidate_group_control_appearance(&self) { self.show_if( - self.active_compartment() == MappingCompartment::MainMappings, + true, &[ root::ID_GROUP_LABEL_TEXT, root::ID_GROUP_COMBO_BOX, @@ -617,11 +619,12 @@ impl HeaderPanel { (-2isize, "".to_string()), (-1isize, "".to_string()), ]; + let compartment = self.active_compartment(); combo.fill_combo_box_with_data_small( vec.into_iter().chain( self.session() .borrow() - .groups_sorted() + .groups_sorted(compartment) .enumerate() .map(|(i, g)| (i as isize, g.borrow().to_string())), ), @@ -630,13 +633,22 @@ impl HeaderPanel { fn invalidate_group_combo_box_value(&self) { let combo = self.view.require_control(root::ID_GROUP_COMBO_BOX); - let data = match self.main_state.borrow().group_filter.get() { + let compartment = self.active_compartment(); + let data = match self + .main_state + .borrow() + .group_filter_for_active_compartment() + { None => -2isize, Some(GroupFilter(id)) => { if id.is_default() { -1isize } else { - match self.session().borrow().find_group_index_by_id_sorted(id) { + match self + .session() + .borrow() + .find_group_index_by_id_sorted(compartment, id) + { None => { combo.select_new_combo_box_item(format!(" ({})", id)); return; @@ -652,7 +664,11 @@ impl HeaderPanel { fn invalidate_group_buttons(&self) { let remove_button = self.view.require_control(root::ID_GROUP_DELETE_BUTTON); let edit_button = self.view.require_control(root::ID_GROUP_EDIT_BUTTON); - let (remove_enabled, edit_enabled) = match self.main_state.borrow().group_filter.get() { + let (remove_enabled, edit_enabled) = match self + .main_state + .borrow() + .group_filter_for_active_compartment() + { None => (false, false), Some(GroupFilter(id)) if id.is_default() => (false, true), _ => (true, true), @@ -679,7 +695,7 @@ impl HeaderPanel { let session = session.borrow(); let (preset_is_active, is_dirty) = match self.active_compartment() { MappingCompartment::ControllerMappings => ( - session.active_controller_id().is_some(), + session.active_controller_preset_id().is_some(), session.controller_preset_is_out_of_date(), ), MappingCompartment::MainMappings => ( @@ -727,7 +743,7 @@ impl HeaderPanel { match self.active_compartment() { MappingCompartment::ControllerMappings => ( Box::new(App::get().controller_preset_manager()), - session.active_controller_id(), + session.active_controller_preset_id(), ), MappingCompartment::MainMappings => ( Box::new(App::get().main_preset_manager()), @@ -1024,11 +1040,20 @@ impl HeaderPanel { } fn remove_group(&self) { - let id = match self.main_state.borrow().group_filter.get() { + let id = match self + .main_state + .borrow() + .group_filter_for_active_compartment() + { Some(GroupFilter(id)) if !id.is_default() => id, _ => return, }; - let delete_mappings_result = if self.session().borrow().group_contains_mappings(id) { + let compartment = self.active_compartment(); + let delete_mappings_result = if self + .session() + .borrow() + .group_contains_mappings(compartment, id) + { let msg = "Do you also want to delete all mappings in that group? If you choose no, they will be automatically moved to the default group."; self.view .require_window() @@ -1039,23 +1064,29 @@ impl HeaderPanel { if let Some(delete_mappings) = delete_mappings_result { self.main_state .borrow_mut() - .group_filter - .set(Some(GroupFilter(GroupId::default()))); + .set_group_filter_for_active_compartment(Some(GroupFilter(GroupId::default()))); self.session() .borrow_mut() - .remove_group(id, delete_mappings); + .remove_group(compartment, id, delete_mappings); } } fn edit_group(&self) { - let weak_group = match self.main_state.borrow().group_filter.get() { + let compartment = self.active_compartment(); + let weak_group = match self + .main_state + .borrow() + .group_filter_for_active_compartment() + { Some(GroupFilter(id)) => { if id.is_default() { - Rc::downgrade(self.session().borrow().default_group()) + Rc::downgrade(self.session().borrow().default_group(compartment)) } else { let session = self.session(); let session = session.borrow(); - let group = session.find_group_by_id(id).expect("group not existing"); + let group = session + .find_group_by_id(compartment, id) + .expect("group not existing"); Rc::downgrade(group) } } @@ -1072,6 +1103,7 @@ impl HeaderPanel { } fn update_group(&self) { + let compartment = self.active_compartment(); let group_filter = match self .view .require_control(root::ID_GROUP_COMBO_BOX) @@ -1083,14 +1115,16 @@ impl HeaderPanel { let session = self.session(); let session = session.borrow(); let group = session - .find_group_by_index_sorted(i as usize) + .find_group_by_index_sorted(compartment, i as usize) .expect("group not existing") .borrow(); Some(GroupFilter(group.id())) } _ => unreachable!(), }; - self.main_state.borrow_mut().group_filter.set(group_filter); + self.main_state + .borrow_mut() + .set_group_filter_for_active_compartment(group_filter); } fn update_preset_auto_load_mode(&self) { @@ -1154,7 +1188,7 @@ impl HeaderPanel { match compartment { MappingCompartment::ControllerMappings => { session - .activate_controller(preset_id, self.session.clone()) + .activate_controller_preset(preset_id, self.session.clone()) .unwrap(); } MappingCompartment::MainMappings => session @@ -1290,7 +1324,7 @@ impl HeaderPanel { match compartment { MappingCompartment::ControllerMappings => ( Box::new(App::get().controller_preset_manager()), - session.active_controller_id(), + session.active_controller_preset_id(), ), MappingCompartment::MainMappings => ( Box::new(App::get().main_preset_manager()), @@ -1300,7 +1334,7 @@ impl HeaderPanel { let active_preset_id = active_preset_id.ok_or("no preset selected")?.to_string(); match compartment { MappingCompartment::ControllerMappings => { - session.activate_controller(None, self.session.clone())? + session.activate_controller_preset(None, self.session.clone())? } MappingCompartment::MainMappings => { session.activate_main_preset(None, self.session.clone())? @@ -1324,7 +1358,7 @@ impl HeaderPanel { let session = session.borrow(); let compartment = self.active_compartment(); let preset_id = match compartment { - MappingCompartment::ControllerMappings => session.active_controller_id(), + MappingCompartment::ControllerMappings => session.active_controller_preset_id(), MappingCompartment::MainMappings => session.active_main_preset_id(), }; let preset_id = match preset_id { @@ -1346,22 +1380,27 @@ impl HeaderPanel { let extended_context = ExtendedProcessorContext::new(&context, ¶ms); self.make_mappings_project_independent_if_desired(extended_context, &mut mappings); let session = session.borrow(); + let default_group = session.default_group(compartment).borrow().clone(); + let groups = session + .groups(compartment) + .map(|ptr| ptr.borrow().clone()) + .collect(); match compartment { MappingCompartment::ControllerMappings => { let preset_manager = App::get().controller_preset_manager(); - let mut controller = preset_manager + let mut controller_preset = preset_manager .find_by_id(&preset_id) - .ok_or("controller not found")?; - controller.update_realearn_data(mappings); - preset_manager.borrow_mut().update_preset(controller)?; + .ok_or("controller preset not found")?; + controller_preset.update_realearn_data(default_group, groups, mappings); + preset_manager + .borrow_mut() + .update_preset(controller_preset)?; } MappingCompartment::MainMappings => { let preset_manager = App::get().main_preset_manager(); let mut main_preset = preset_manager .find_by_id(&preset_id) .ok_or("main preset not found")?; - let default_group = session.default_group().borrow().clone(); - let groups = session.groups().map(|ptr| ptr.borrow().clone()).collect(); main_preset.update_data(default_group, groups, mappings); preset_manager.borrow_mut().update_preset(main_preset)?; } @@ -1432,23 +1471,32 @@ impl HeaderPanel { }; let preset_id = slug::slugify(&preset_name); let mut session = session.borrow_mut(); + let default_group = session.default_group(compartment).borrow().clone(); + let groups = session + .groups(compartment) + .map(|ptr| ptr.borrow().clone()) + .collect(); match compartment { MappingCompartment::ControllerMappings => { let custom_data = session .active_controller() .map(|c| c.custom_data().clone()) .unwrap_or_default(); - let controller = - ControllerPreset::new(preset_id.clone(), preset_name, mappings, custom_data); + let controller = ControllerPreset::new( + preset_id.clone(), + preset_name, + default_group, + groups, + mappings, + custom_data, + ); App::get() .controller_preset_manager() .borrow_mut() .add_preset(controller)?; - session.activate_controller(Some(preset_id), self.session.clone())?; + session.activate_controller_preset(Some(preset_id), self.session.clone())?; } MappingCompartment::MainMappings => { - let default_group = session.default_group().borrow().clone(); - let groups = session.groups().map(|ptr| ptr.borrow().clone()).collect(); let main_preset = MainPreset::new( preset_id.clone(), preset_name, @@ -1469,8 +1517,7 @@ impl HeaderPanel { fn reset(&self) { self.main_state .borrow_mut() - .group_filter - .set(Some(GroupFilter(GroupId::default()))); + .set_group_filter_for_active_compartment(Some(GroupFilter(GroupId::default()))); if let Some(already_open_panel) = self.group_panel.borrow().as_ref() { already_open_panel.close(); } @@ -1518,16 +1565,16 @@ impl HeaderPanel { fn register_listeners(self: SharedView) { let shared_session = self.session(); let session = shared_session.borrow(); - self.when(session.everything_changed(), |view| { + self.when(session.everything_changed(), |view, _| { view.reset(); }); - self.when(session.let_matched_events_through.changed(), |view| { + self.when(session.let_matched_events_through.changed(), |view, _| { view.invalidate_let_matched_events_through_check_box(); }); - self.when(session.let_unmatched_events_through.changed(), |view| { + self.when(session.let_unmatched_events_through.changed(), |view, _| { view.invalidate_let_unmatched_events_through_check_box(); }); - self.when(session.learn_many_state_changed(), |view| { + self.when(session.learn_many_state_changed(), |view, _| { view.invalidate_all_controls(); }); self.when( @@ -1535,7 +1582,7 @@ impl HeaderPanel { .midi_control_input .changed() .merge(session.osc_input_device_id.changed()), - |view| { + |view, _| { view.invalidate_control_input_combo_box(); view.invalidate_let_matched_events_through_check_box(); view.invalidate_let_unmatched_events_through_check_box(); @@ -1556,16 +1603,19 @@ impl HeaderPanel { .midi_feedback_output .changed() .merge(session.osc_output_device_id.changed()), - |view| view.invalidate_feedback_output_combo_box(), + |view, _| view.invalidate_feedback_output_combo_box(), ); - self.when(session.group_changed(), |view| { + self.when(session.group_changed(), |view, _| { view.invalidate_group_controls(); }); let main_state = self.main_state.borrow(); - self.when(main_state.group_filter.changed(), |view| { - view.invalidate_group_controls(); - }); - self.when(main_state.search_expression.changed(), |view| { + self.when( + main_state.group_filter_for_any_compartment_changed(), + |view, _| { + view.invalidate_group_controls(); + }, + ); + self.when(main_state.search_expression.changed(), |view, _| { view.invoke_programmatically(|| { view.invalidate_search_expression(); }); @@ -1575,7 +1625,7 @@ impl HeaderPanel { .is_learning_target_filter .changed() .merge(main_state.target_filter.changed()), - |view| { + |view, _| { view.invalidate_target_filter_buttons(); }, ); @@ -1584,17 +1634,17 @@ impl HeaderPanel { .is_learning_source_filter .changed() .merge(main_state.source_filter.changed()), - |view| { + |view, _| { view.invalidate_source_filter_buttons(); }, ); - self.when(main_state.active_compartment.changed(), |view| { + self.when(main_state.active_compartment.changed(), |view, _| { view.invalidate_all_controls(); }); - self.when(session.main_preset_auto_load_mode.changed(), |view| { + self.when(session.main_preset_auto_load_mode.changed(), |view, _| { view.invalidate_all_controls(); }); - self.when(session.group_list_changed(), |view| { + self.when(session.group_list_changed(), |view, _| { view.invalidate_group_controls(); }); when( @@ -1630,8 +1680,8 @@ impl HeaderPanel { .mapping_list_changed() .map_to(()) .merge(session.mapping_changed().map_to(())) - .merge(session.group_list_changed()) - .merge(session.group_changed()) + .merge(session.group_list_changed().map_to(())) + .merge(session.group_changed().map_to(())) .take_until(self.view.closed()), ) .with(Rc::downgrade(&self)) @@ -1640,14 +1690,14 @@ impl HeaderPanel { }); } - fn when( + fn when( self: &SharedView, - event: impl UnitEvent, - reaction: impl Fn(SharedView) + 'static + Copy, + event: impl SharedItemEvent, + reaction: impl Fn(SharedView, I) + 'static + Clone, ) { when(event.take_until(self.view.closed())) .with(Rc::downgrade(self)) - .do_sync(move |panel, _| reaction(panel)); + .do_sync(move |panel, item| reaction(panel, item)); } fn is_invoked_programmatically(&self) -> bool { diff --git a/main/src/infrastructure/ui/mapping_header_panel.rs b/main/src/infrastructure/ui/mapping_header_panel.rs index 6185eae3e..74ab27b67 100644 --- a/main/src/infrastructure/ui/mapping_header_panel.rs +++ b/main/src/infrastructure/ui/mapping_header_panel.rs @@ -11,7 +11,7 @@ use crate::application::{ ActivationType, GroupModel, MappingModel, ModifierConditionModel, ProgramConditionModel, SharedSession, WeakSession, }; -use crate::domain::{MappingCompartment, PLUGIN_PARAMETER_COUNT}; +use crate::domain::PLUGIN_PARAMETER_COUNT; use std::fmt::Debug; use swell_ui::{DialogUnits, Point, SharedView, View, ViewContext, Window}; @@ -603,7 +603,7 @@ impl Item for MappingModel { } fn supports_activation(&self) -> bool { - self.compartment() != MappingCompartment::ControllerMappings + true } fn name(&self) -> &str { diff --git a/main/src/infrastructure/ui/mapping_row_panel.rs b/main/src/infrastructure/ui/mapping_row_panel.rs index a1234a760..c3d6e3b7e 100644 --- a/main/src/infrastructure/ui/mapping_row_panel.rs +++ b/main/src/infrastructure/ui/mapping_row_panel.rs @@ -342,7 +342,11 @@ impl MappingRowPanel { // When we route keyboard input to ReaLearn and press space, it presses the "Up" button, // even if we don't display the rows. Don't know why, but suppress a panic here. let mapping = self.optional_mapping().ok_or("row has no mapping")?; - let within_same_group = self.main_state.borrow().group_filter.get().is_some(); + let within_same_group = self + .main_state + .borrow() + .group_filter_for_active_compartment() + .is_some(); let _ = self.session().borrow_mut().move_mapping_within_list( self.active_compartment(), mapping.borrow().id(), @@ -546,7 +550,7 @@ impl MappingRowPanel { ) }, )) - .chain(session.groups_sorted().map(move |g| { + .chain(session.groups_sorted(compartment).map(move |g| { let session = session_4.clone(); let g = g.borrow(); let g_id = g.id(); diff --git a/main/src/infrastructure/ui/mapping_rows_panel.rs b/main/src/infrastructure/ui/mapping_rows_panel.rs index 47eb0f1ad..b26cb32b8 100644 --- a/main/src/infrastructure/ui/mapping_rows_panel.rs +++ b/main/src/infrastructure/ui/mapping_rows_panel.rs @@ -335,7 +335,7 @@ impl MappingRowsPanel { mapping: &SharedMapping, ) -> bool { let mapping = mapping.borrow(); - if let Some(group_filter) = main_state.group_filter.get() { + if let Some(group_filter) = main_state.group_filter_for_active_compartment() { if !group_filter.matches(&mapping) { return false; } @@ -404,8 +404,8 @@ impl MappingRowsPanel { .merge(main_state.target_filter.changed()) .merge(main_state.search_expression.changed()) .merge(main_state.active_compartment.changed()) - .merge(main_state.group_filter.changed()) - .merge(session.group_list_changed()), + .merge(main_state.group_filter_for_any_compartment_changed()) + .merge(session.group_list_changed().map_to(())), |view, _| { if !view.scroll(0) { // No scrolling was necessary. But that also means, the rows were not @@ -425,8 +425,7 @@ impl MappingRowsPanel { let clipboard_object = get_object_from_clipboard(); let main_state = self.main_state.borrow(); let group_id = main_state - .group_filter - .get() + .group_filter_for_active_compartment() .map(|f| f.group_id()) .unwrap_or_default(); let compartment = main_state.active_compartment.get(); diff --git a/main/src/infrastructure/ui/state.rs b/main/src/infrastructure/ui/state.rs index 02bfbaf3f..18ba35fa8 100644 --- a/main/src/infrastructure/ui/state.rs +++ b/main/src/infrastructure/ui/state.rs @@ -2,6 +2,8 @@ use crate::core::{prop, Prop}; use crate::domain::{CompoundMappingSource, MappingCompartment, ReaperTarget}; use crate::application::{GroupId, MappingModel}; +use enum_map::{enum_map, EnumMap}; +use rx_util::UnitEvent; use std::cell::RefCell; use std::rc::Rc; @@ -14,12 +16,12 @@ pub struct MainState { pub source_filter: Prop>, pub is_learning_source_filter: Prop, pub active_compartment: Prop, - pub group_filter: Prop>, + pub group_filter: EnumMap>>, pub search_expression: Prop, pub status_msg: Prop, } -#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)] pub struct GroupFilter(pub GroupId); impl GroupFilter { @@ -40,7 +42,10 @@ impl Default for MainState { source_filter: prop(None), is_learning_source_filter: prop(false), active_compartment: prop(MappingCompartment::MainMappings), - group_filter: prop(Some(GroupFilter(GroupId::default()))), + group_filter: enum_map! { + ControllerMappings => prop(Some(GroupFilter::default())), + MainMappings => prop(Some(GroupFilter::default())), + }, search_expression: Default::default(), status_msg: Default::default(), } @@ -50,7 +55,23 @@ impl Default for MainState { impl MainState { pub fn clear_all_filters(&mut self) { self.clear_all_filters_except_group(); - self.clear_group_filter(); + for c in MappingCompartment::enum_iter() { + self.clear_group_filter(c); + } + } + + pub fn group_filter_for_any_compartment_changed(&self) -> impl UnitEvent { + self.group_filter[MappingCompartment::ControllerMappings] + .changed() + .merge(self.group_filter[MappingCompartment::MainMappings].changed()) + } + + pub fn group_filter_for_active_compartment(&self) -> Option { + self.group_filter[self.active_compartment.get()].get() + } + + pub fn set_group_filter_for_active_compartment(&mut self, filter: Option) { + self.group_filter[self.active_compartment.get()].set(filter); } pub fn clear_all_filters_except_group(&mut self) { @@ -60,8 +81,8 @@ impl MainState { self.stop_filter_learning(); } - pub fn clear_group_filter(&mut self) { - self.group_filter.set(None); + pub fn clear_group_filter(&mut self, compartment: MappingCompartment) { + self.group_filter[compartment].set(None); } pub fn clear_search_expression_filter(&mut self) { @@ -77,7 +98,9 @@ impl MainState { } pub fn filter_is_active(&self) -> bool { - self.group_filter.get_ref().is_some() + self.group_filter[self.active_compartment.get()] + .get_ref() + .is_some() || self.source_filter.get_ref().is_some() || self.target_filter.get_ref().is_some() || !self.search_expression.get_ref().trim().is_empty()