diff --git a/Cargo.lock b/Cargo.lock index d0c04b76e..0b25cb6ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2804,7 +2804,7 @@ dependencies = [ [[package]] name = "helgobox" -version = "2.16.2" +version = "2.16.3" dependencies = [ "anyhow", "approx", @@ -5064,7 +5064,7 @@ dependencies = [ [[package]] name = "reaper-common-types" version = "0.1.0" -source = "git+https://github.com/helgoboss/reaper-rs.git?branch=master#502c84c463dc3863ed5282eef839ccd6df553f73" +source = "git+https://github.com/helgoboss/reaper-rs.git?branch=master#f86bc31b59943a943b9d048022fabfaccbc74a4a" dependencies = [ "hex-literal", "nutype", @@ -5075,7 +5075,7 @@ dependencies = [ [[package]] name = "reaper-fluent" version = "0.1.0" -source = "git+https://github.com/helgoboss/reaper-rs.git?branch=master#502c84c463dc3863ed5282eef839ccd6df553f73" +source = "git+https://github.com/helgoboss/reaper-rs.git?branch=master#f86bc31b59943a943b9d048022fabfaccbc74a4a" dependencies = [ "fragile", "reaper-low", @@ -5086,7 +5086,7 @@ dependencies = [ [[package]] name = "reaper-high" version = "0.1.0" -source = "git+https://github.com/helgoboss/reaper-rs.git?branch=master#502c84c463dc3863ed5282eef839ccd6df553f73" +source = "git+https://github.com/helgoboss/reaper-rs.git?branch=master#f86bc31b59943a943b9d048022fabfaccbc74a4a" dependencies = [ "backtrace", "base64 0.13.0", @@ -5115,7 +5115,7 @@ dependencies = [ [[package]] name = "reaper-low" version = "0.1.0" -source = "git+https://github.com/helgoboss/reaper-rs.git?branch=master#502c84c463dc3863ed5282eef839ccd6df553f73" +source = "git+https://github.com/helgoboss/reaper-rs.git?branch=master#f86bc31b59943a943b9d048022fabfaccbc74a4a" dependencies = [ "c_str_macro", "cc 1.0.83", @@ -5129,7 +5129,7 @@ dependencies = [ [[package]] name = "reaper-macros" version = "0.1.0" -source = "git+https://github.com/helgoboss/reaper-rs.git?branch=master#502c84c463dc3863ed5282eef839ccd6df553f73" +source = "git+https://github.com/helgoboss/reaper-rs.git?branch=master#f86bc31b59943a943b9d048022fabfaccbc74a4a" dependencies = [ "darling 0.10.2", "quote", @@ -5139,7 +5139,7 @@ dependencies = [ [[package]] name = "reaper-medium" version = "0.1.0" -source = "git+https://github.com/helgoboss/reaper-rs.git?branch=master#502c84c463dc3863ed5282eef839ccd6df553f73" +source = "git+https://github.com/helgoboss/reaper-rs.git?branch=master#f86bc31b59943a943b9d048022fabfaccbc74a4a" dependencies = [ "c_str_macro", "camino", @@ -5160,7 +5160,7 @@ dependencies = [ [[package]] name = "reaper-rx" version = "0.1.0" -source = "git+https://github.com/helgoboss/reaper-rs.git?branch=master#502c84c463dc3863ed5282eef839ccd6df553f73" +source = "git+https://github.com/helgoboss/reaper-rs.git?branch=master#f86bc31b59943a943b9d048022fabfaccbc74a4a" dependencies = [ "crossbeam-channel", "helgoboss-midi", @@ -5414,7 +5414,7 @@ checksum = "3cd14fd5e3b777a7422cca79358c57a8f6e3a703d9ac187448d0daf220c2407f" [[package]] name = "rppxml-parser" version = "0.1.0" -source = "git+https://github.com/helgoboss/reaper-rs.git?branch=master#502c84c463dc3863ed5282eef839ccd6df553f73" +source = "git+https://github.com/helgoboss/reaper-rs.git?branch=master#f86bc31b59943a943b9d048022fabfaccbc74a4a" dependencies = [ "splitty", ] diff --git a/api/src/persistence/target.rs b/api/src/persistence/target.rs index 3882991ee..4c814a295 100644 --- a/api/src/persistence/target.rs +++ b/api/src/persistence/target.rs @@ -5,7 +5,7 @@ use num_enum::{IntoPrimitive, TryFromPrimitive}; use serde::{Deserialize, Serialize}; use std::collections::HashSet; use std::fmt::{Display, Formatter}; -use strum::IntoEnumIterator; +use strum::{EnumIter, IntoEnumIterator}; #[derive( Copy, @@ -229,6 +229,8 @@ pub struct ReaperActionTarget { #[serde(flatten)] pub commons: TargetCommons, #[serde(skip_serializing_if = "Option::is_none")] + pub scope: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub command: Option, #[serde(skip_serializing_if = "Option::is_none")] pub invocation: Option, @@ -236,6 +238,54 @@ pub struct ReaperActionTarget { pub track: Option, } +#[derive( + Copy, + Clone, + Eq, + PartialEq, + Hash, + Debug, + Default, + EnumIter, + TryFromPrimitive, + IntoPrimitive, + Display, + Serialize, + Deserialize, +)] +#[repr(usize)] +pub enum ActionScope { + #[display(fmt = "Main")] + #[default] + Main, + #[display(fmt = "Active MIDI editor")] + ActiveMidiEditor, + #[display(fmt = "Active MIDI event list editor")] + ActiveMidiEventListEditor, + #[display(fmt = "Media explorer")] + MediaExplorer, +} + +impl ActionScope { + pub fn guess_from_section_id(section_id: u32) -> Self { + match section_id { + 32060 => ActionScope::ActiveMidiEditor, + 32061 => ActionScope::ActiveMidiEventListEditor, + 32063 => ActionScope::MediaExplorer, + _ => ActionScope::Main, + } + } + + pub fn section_id(&self) -> u32 { + match self { + ActionScope::Main => 0, + ActionScope::ActiveMidiEditor => 32060, + ActionScope::ActiveMidiEventListEditor => 32061, + ActionScope::MediaExplorer => 32063, + } + } +} + #[derive(Eq, PartialEq, Serialize, Deserialize)] pub struct TransportActionTarget { #[serde(flatten)] diff --git a/main/Cargo.toml b/main/Cargo.toml index b31ba7e05..2d21fc097 100644 --- a/main/Cargo.toml +++ b/main/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "helgobox" -version = "2.16.2" +version = "2.16.3" authors = ["Benjamin Klum "] edition = "2021" build = "build.rs" diff --git a/main/src/application/actions.rs b/main/src/application/actions.rs new file mode 100644 index 000000000..37274c125 --- /dev/null +++ b/main/src/application/actions.rs @@ -0,0 +1,35 @@ +use crate::base::notification; +use reaper_high::{Action, Reaper}; +use reaper_medium::SectionId; + +pub fn build_smart_command_name_from_action(action: &Action) -> Option { + match action.command_name() { + // Built-in actions don't have a command name but a persistent command ID. + // Use command ID as string. + None => action.command_id().ok().map(|id| id.to_string()), + // ReaScripts and custom actions have a command name as persistent identifier. + Some(name) => Some(name.into_string()), + } +} + +pub fn build_action_from_smart_command_name( + section_id: SectionId, + smart_command_name: &str, +) -> Option { + match smart_command_name.parse::() { + // Could parse this as command ID integer. This is a built-in action. + Ok(command_id_int) => match command_id_int.try_into() { + Ok(command_id) => Some( + Reaper::get() + .section_by_id(section_id) + .action_by_command_id(command_id), + ), + Err(_) => { + notification::warn(format!("Invalid command ID {command_id_int}")); + None + } + }, + // Couldn't parse this as integer. This is a ReaScript or custom action. + Err(_) => Some(Reaper::get().action_by_command_name(smart_command_name)), + } +} diff --git a/main/src/application/mod.rs b/main/src/application/mod.rs index 8f15d0ccc..a84c2769e 100644 --- a/main/src/application/mod.rs +++ b/main/src/application/mod.rs @@ -38,4 +38,8 @@ mod props; pub use props::*; mod auto_units; + +mod actions; +pub use actions::*; + pub use auto_units::*; diff --git a/main/src/application/target_model.rs b/main/src/application/target_model.rs index fc46961cf..a36bc17ef 100644 --- a/main/src/application/target_model.rs +++ b/main/src/application/target_model.rs @@ -13,7 +13,8 @@ use reaper_high::{ use serde::{Deserialize, Serialize}; use crate::application::{ - Affected, Change, GetProcessingRelevance, ProcessingRelevance, UnitModel, + build_action_from_smart_command_name, build_smart_command_name_from_action, Affected, Change, + GetProcessingRelevance, ProcessingRelevance, UnitModel, }; use crate::domain::{ find_bookmark, get_fx_name, get_fx_params, get_non_present_virtual_route_label, @@ -59,10 +60,10 @@ use std::error::Error; use crate::domain::ui_util::format_tags_as_csv; use base::hash_util::NonCryptoHashSet; use helgobox_api::persistence::{ - Axis, BrowseTracksMode, ClipColumnTrackContext, FxChainDescriptor, FxDescriptorCommons, - FxToolAction, LearnTargetMappingModification, LearnableTargetKind, MappingModification, - MappingSnapshotDescForLoad, MappingSnapshotDescForTake, MonitoringMode, MouseAction, - MouseButton, PlaytimeColumnAction, PlaytimeColumnDescriptor, PlaytimeMatrixAction, + ActionScope, Axis, BrowseTracksMode, ClipColumnTrackContext, FxChainDescriptor, + FxDescriptorCommons, FxToolAction, LearnTargetMappingModification, LearnableTargetKind, + MappingModification, MappingSnapshotDescForLoad, MappingSnapshotDescForTake, MonitoringMode, + MouseAction, MouseButton, PlaytimeColumnAction, PlaytimeColumnDescriptor, PlaytimeMatrixAction, PlaytimeRowAction, PlaytimeRowDescriptor, PlaytimeSlotDescriptor, PlaytimeSlotManagementAction, PlaytimeSlotTransportAction, PotFilterKind, SeekBehavior, SetTargetToLastTouchedMappingModification, TargetTouchCause, TrackDescriptorCommons, @@ -70,8 +71,8 @@ use helgobox_api::persistence::{ }; use playtime_api::persistence::ColumnAddress; use reaper_medium::{ - AutomationMode, BookmarkId, GlobalAutomationModeOverride, InputMonitoringMode, TrackArea, - TrackLocation, TrackSendDirection, + AutomationMode, BookmarkId, GlobalAutomationModeOverride, InputMonitoringMode, SectionId, + TrackArea, TrackLocation, TrackSendDirection, }; use std::fmt; use std::fmt::{Display, Formatter}; @@ -87,7 +88,8 @@ pub enum TargetCommand { SetControlElementId(VirtualControlElementId), SetLearnable(bool), SetTargetType(ReaperTargetType), - SetAction(Option), + SetActionScope(ActionScope), + SetSmartCommandName(Option), SetActionInvocationType(ActionInvocationType), SetWithTrack(bool), SetTrackName(String), @@ -182,7 +184,8 @@ pub enum TargetProp { ControlElementId, Learnable, TargetType, - Action, + ActionScope, + SmartCommandName, ActionInvocationType, WithTrack, TrackType, @@ -313,9 +316,13 @@ impl<'a> Change<'a> for TargetModel { self.r#type = v; One(P::TargetType) } - C::SetAction(v) => { - self.action = v; - One(P::Action) + C::SetActionScope(v) => { + self.action_scope = v; + One(P::ActionScope) + } + C::SetSmartCommandName(v) => { + self.smart_command_name = v; + One(P::SmartCommandName) } C::SetActionInvocationType(v) => { self.action_invocation_type = v; @@ -672,8 +679,8 @@ pub struct TargetModel { // TODO-low Rename this to reaper_target_type r#type: ReaperTargetType, // # For action targets only - // TODO-low Maybe replace Action with just command ID and/or command name - action: Option, + action_scope: ActionScope, + smart_command_name: Option, action_invocation_type: ActionInvocationType, with_track: bool, // # For track targets @@ -847,7 +854,8 @@ impl Default for TargetModel { control_element_id: Default::default(), learnable: true, r#type: ReaperTargetType::Dummy, - action: None, + action_scope: Default::default(), + smart_command_name: None, action_invocation_type: ActionInvocationType::default(), track_type: Default::default(), track_id: None, @@ -966,8 +974,12 @@ impl TargetModel { self.r#type } - pub fn action(&self) -> Option<&Action> { - self.action.as_ref() + pub fn action_scope(&self) -> ActionScope { + self.action_scope + } + + pub fn smart_command_name(&self) -> Option<&str> { + self.smart_command_name.as_deref() } pub fn action_invocation_type(&self) -> ActionInvocationType { @@ -1730,7 +1742,9 @@ impl TargetModel { match target { Action(t) => { - self.action = Some(t.action.clone()); + let section_id = t.action.section().map(|s| s.id()).unwrap_or_default(); + self.action_scope = ActionScope::guess_from_section_id(section_id.get()); + self.smart_command_name = build_smart_command_name_from_action(&t.action); self.action_invocation_type = t.invocation_type; } FxParameter(t) => { @@ -2243,7 +2257,8 @@ impl TargetModel { }, ), Action => UnresolvedReaperTarget::Action(UnresolvedActionTarget { - action: self.resolved_action()?, + action: self.resolved_available_action()?, + scope: self.action_scope, invocation_type: self.action_invocation_type, track_descriptor: if self.with_track { Some(self.track_descriptor()?) @@ -2962,7 +2977,7 @@ impl TargetModel { } fn command_id_label(&self) -> Cow { - match self.action.as_ref() { + match self.resolve_action() { None => "-".into(), Some(action) => { if action.is_available() { @@ -2980,8 +2995,16 @@ impl TargetModel { } } - pub fn resolved_action(&self) -> Result { - let action = self.action.as_ref().ok_or("action not set")?; + pub fn resolve_action(&self) -> Option { + let command_name = self.smart_command_name.as_deref()?; + build_action_from_smart_command_name( + SectionId::new(self.action_scope.section_id()), + command_name, + ) + } + + pub fn resolved_available_action(&self) -> Result { + let action = self.resolve_action().ok_or("action not set")?; if !action.is_available() { return Err("action not available"); } @@ -2989,7 +3012,7 @@ impl TargetModel { } pub fn action_name_label(&self) -> Cow { - match self.resolved_action().ok() { + match self.resolved_available_action().ok() { None => "-".into(), Some(a) => a.name().expect("should be available").into_string().into(), } @@ -3006,7 +3029,7 @@ impl<'a> Display for TargetModelFormatVeryShort<'a> { use ReaperTargetType::*; let tt = self.0.r#type; match tt { - Action => match self.0.resolved_action().ok() { + Action => match self.0.resolved_available_action().ok() { None => write!(f, "Action {}", self.0.command_id_label()), Some(a) => f.write_str(a.name().expect("should be available").to_str()), }, diff --git a/main/src/domain/realearn_target.rs b/main/src/domain/realearn_target.rs index e1ec8eb7b..ed2d87679 100644 --- a/main/src/domain/realearn_target.rs +++ b/main/src/domain/realearn_target.rs @@ -75,7 +75,7 @@ pub trait RealearnTarget { Reaper::get() .main_section() .action_by_command_id(CommandId::new(40913)) - .invoke_as_trigger(Some(track.project())) + .invoke_as_trigger(Some(track.project()), None) .expect("built-in action should exist"); } } diff --git a/main/src/domain/reaper_target.rs b/main/src/domain/reaper_target.rs index ad9537bf9..b2ace99b2 100644 --- a/main/src/domain/reaper_target.rs +++ b/main/src/domain/reaper_target.rs @@ -21,7 +21,7 @@ use strum::EnumIter; use helgoboss_learn::{ AbsoluteValue, ControlType, ControlValue, NumericValue, PropValue, Target, UnitValue, }; -use helgobox_api::persistence::{SeekBehavior, TrackScope}; +use helgobox_api::persistence::{ActionScope, SeekBehavior, TrackScope}; use crate::domain::ui_util::convert_bool_to_unit_value; use crate::domain::{ @@ -1056,12 +1056,16 @@ fn determine_target_for_action(action: Action) -> ReaperTarget { project, action: TransportAction::Repeat, }), - _ => ReaperTarget::Action(ActionTarget { - action, - invocation_type: ActionInvocationType::Trigger, - project, - track: None, - }), + _ => { + let section_id = action.section().map(|s| s.id()).unwrap_or_default(); + ReaperTarget::Action(ActionTarget { + action, + scope: ActionScope::guess_from_section_id(section_id.get()), + invocation_type: ActionInvocationType::Trigger, + project, + track: None, + }) + } } } diff --git a/main/src/domain/targets/action_target.rs b/main/src/domain/targets/action_target.rs index 6cc447d52..09616c068 100644 --- a/main/src/domain/targets/action_target.rs +++ b/main/src/domain/targets/action_target.rs @@ -5,16 +5,21 @@ use crate::domain::{ MappingControlContext, RealearnTarget, ReaperTarget, ReaperTargetType, TargetCharacter, TargetSection, TargetTypeDef, TrackDescriptor, UnresolvedReaperTargetDef, DEFAULT_TARGET, }; +use camino::Utf8Path; use helgoboss_learn::{AbsoluteValue, ControlType, ControlValue, Fraction, Target, UnitValue}; use helgoboss_midi::{U14, U7}; +use helgobox_api::persistence::ActionScope; use reaper_high::{Action, ActionCharacter, Project, Reaper, Track}; -use reaper_medium::{ActionValueChange, CommandId, MasterTrackBehavior, WindowContext}; +use reaper_medium::{ + ActionValueChange, CommandId, Hwnd, MasterTrackBehavior, OpenMediaExplorerMode, +}; use std::borrow::Cow; use std::convert::TryFrom; #[derive(Debug)] pub struct UnresolvedActionTarget { pub action: Action, + pub scope: ActionScope, pub invocation_type: ActionInvocationType, pub track_descriptor: Option, } @@ -32,6 +37,7 @@ impl UnresolvedReaperTargetDef for UnresolvedActionTarget { .map(|track| { ReaperTarget::Action(ActionTarget { action: self.action.clone(), + scope: self.scope, invocation_type: self.invocation_type, project, track: Some(track), @@ -41,6 +47,7 @@ impl UnresolvedReaperTargetDef for UnresolvedActionTarget { } else { vec![ReaperTarget::Action(ActionTarget { action: self.action.clone(), + scope: self.scope, invocation_type: self.invocation_type, project, track: None, @@ -57,6 +64,7 @@ impl UnresolvedReaperTargetDef for UnresolvedActionTarget { #[derive(Clone, Debug, PartialEq)] pub struct ActionTarget { pub action: Action, + pub scope: ActionScope, pub invocation_type: ActionInvocationType, pub project: Project, pub track: Option, @@ -89,7 +97,7 @@ impl RealearnTarget for ActionTarget { Reaper::get() .main_section() .action_by_command_id(CommandId::new(40605)) - .invoke_as_trigger(Some(self.project)) + .invoke_as_trigger(Some(self.project), None) .expect("built-in action should exist"); } @@ -157,7 +165,11 @@ impl RealearnTarget for ActionTarget { }, ControlValue::RelativeDiscrete(i) => { if let ActionInvocationType::Relative = self.invocation_type { - self.action.invoke_relative(i.get(), Some(self.project))?; + self.action.invoke_relative( + i.get(), + Some(self.project), + self.get_context_window(), + )?; HitResponse::processed_with_effect() } else { return Err("relative values need relative invocation type"); @@ -166,7 +178,11 @@ impl RealearnTarget for ActionTarget { ControlValue::RelativeContinuous(i) => { if let ActionInvocationType::Relative = self.invocation_type { let i = i.to_discrete_increment(); - self.action.invoke_relative(i.get(), Some(self.project))?; + self.action.invoke_relative( + i.get(), + Some(self.project), + self.get_context_window(), + )?; HitResponse::processed_with_effect() } else { return Err("relative values need relative invocation type"); @@ -249,7 +265,7 @@ impl ActionTarget { }; self.action.invoke_directly( value_change, - WindowContext::Win(Reaper::get().main_window()), + self.get_context_window(), self.project.context(), )?; Ok(()) @@ -260,10 +276,26 @@ impl ActionTarget { v: UnitValue, enforce_7_bit: bool, ) -> Result<(), &'static str> { - self.action - .invoke_absolute(v.get(), Some(self.project), enforce_7_bit)?; + self.action.invoke_absolute( + v.get(), + Some(self.project), + enforce_7_bit, + self.get_context_window(), + )?; Ok(()) } + + fn get_context_window(&self) -> Option { + match self.scope { + ActionScope::Main => None, + ActionScope::ActiveMidiEditor | ActionScope::ActiveMidiEventListEditor => { + Reaper::get().medium_reaper().midi_editor_get_active() + } + ActionScope::MediaExplorer => Reaper::get() + .medium_reaper() + .open_media_explorer(Utf8Path::new(""), OpenMediaExplorerMode::Select), + } + } } pub const ACTION_TARGET: TargetTypeDef = TargetTypeDef { diff --git a/main/src/domain/targets/browse_tracks_target.rs b/main/src/domain/targets/browse_tracks_target.rs index 689e6be15..657363b02 100644 --- a/main/src/domain/targets/browse_tracks_target.rs +++ b/main/src/domain/targets/browse_tracks_target.rs @@ -116,7 +116,7 @@ impl RealearnTarget for BrowseTracksTarget { Reaper::get() .main_section() .action_by_command_id(CommandId::new(40913)) - .invoke_as_trigger(Some(track.project())) + .invoke_as_trigger(Some(track.project()), None) .expect("built-in action should exist"); } if self.scroll_mixer { diff --git a/main/src/domain/targets/track_selection_target.rs b/main/src/domain/targets/track_selection_target.rs index f040e2212..5653f18d7 100644 --- a/main/src/domain/targets/track_selection_target.rs +++ b/main/src/domain/targets/track_selection_target.rs @@ -90,7 +90,7 @@ impl RealearnTarget for TrackSelectionTarget { Reaper::get() .main_section() .action_by_command_id(CommandId::new(40913)) - .invoke_as_trigger(Some(self.track.project())) + .invoke_as_trigger(Some(self.track.project()), None) .expect("built-in action should exist"); } if self.scroll_mixer { diff --git a/main/src/infrastructure/api/convert/from_data/target.rs b/main/src/infrastructure/api/convert/from_data/target.rs index 6e237e115..778bb190e 100644 --- a/main/src/infrastructure/api/convert/from_data/target.rs +++ b/main/src/infrastructure/api/convert/from_data/target.rs @@ -79,6 +79,7 @@ fn convert_real_target( } Action => T::ReaperAction(ReaperActionTarget { commons, + scope: style.required_value(data.action_scope), command: { if let Some(n) = data.command_name { let v = match n.parse::() { diff --git a/main/src/infrastructure/api/convert/to_data/target.rs b/main/src/infrastructure/api/convert/to_data/target.rs index 174152cfe..d25cbe37e 100644 --- a/main/src/infrastructure/api/convert/to_data/target.rs +++ b/main/src/infrastructure/api/convert/to_data/target.rs @@ -68,6 +68,7 @@ pub fn convert_target(t: Target) -> ConversionResult { TargetModelData { category: TargetCategory::Reaper, r#type: ReaperTargetType::Action, + action_scope: d.scope.unwrap_or_default(), command_name: d.command.map(|cmd| match cmd { ReaperCommand::Id(id) => id.to_string(), ReaperCommand::Name(n) => n, diff --git a/main/src/infrastructure/data/target_model_data.rs b/main/src/infrastructure/data/target_model_data.rs index ed78553f5..7969b25ee 100644 --- a/main/src/infrastructure/data/target_model_data.rs +++ b/main/src/infrastructure/data/target_model_data.rs @@ -1,8 +1,5 @@ use super::f32_as_u32; use super::none_if_minus_one; -use reaper_high::{BookmarkType, Fx, Guid, Reaper}; -use std::collections::HashSet; - use crate::application::{ AutomationModeOverrideType, BookmarkAnchorType, Change, FxParameterPropValues, FxPropValues, FxSnapshot, MappingModificationKind, MappingRefModel, MappingSnapshotTypeForLoad, @@ -10,7 +7,6 @@ use crate::application::{ TargetCommand, TargetModel, TargetUnit, TrackPropValues, TrackRoutePropValues, TrackRouteSelectorType, VirtualFxParameterType, VirtualFxType, VirtualTrackType, }; -use crate::base::notification; use crate::domain::{ get_fx_chains, ActionInvocationType, AnyOnParameter, CompartmentKind, Exclusivity, ExtendedProcessorContext, FxDisplayType, GroupKey, MappingKey, OscDeviceId, ReaperTargetType, @@ -29,10 +25,13 @@ use base::default_util::{ }; use helgoboss_learn::{AbsoluteValue, Fraction, OscTypeTag, UnitValue}; use helgobox_api::persistence::{ - Axis, BrowseTracksMode, FxToolAction, LearnableTargetKind, MappingSnapshotDescForLoad, - MappingSnapshotDescForTake, MonitoringMode, MouseAction, PotFilterKind, SeekBehavior, - TargetTouchCause, TargetValue, TrackScope, TrackToolAction, VirtualControlElementCharacter, + ActionScope, Axis, BrowseTracksMode, FxToolAction, LearnableTargetKind, + MappingSnapshotDescForLoad, MappingSnapshotDescForTake, MonitoringMode, MouseAction, + PotFilterKind, SeekBehavior, TargetTouchCause, TargetValue, TrackScope, TrackToolAction, + VirtualControlElementCharacter, }; +use reaper_high::{BookmarkType, Fx, Guid}; +use std::collections::HashSet; use base::hash_util::NonCryptoHashSet; use helgobox_api::persistence::{ @@ -42,7 +41,6 @@ use helgobox_api::persistence::{ }; use semver::Version; use serde::{Deserialize, Serialize}; -use std::convert::TryInto; #[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -72,6 +70,12 @@ pub struct TargetModelData { deserialize_with = "deserialize_null_default", skip_serializing_if = "is_default" )] + pub action_scope: ActionScope, + #[serde( + default, + deserialize_with = "deserialize_null_default", + skip_serializing_if = "is_default" + )] pub command_name: Option, #[serde( default, @@ -538,13 +542,8 @@ impl TargetModelData { category: model.category(), unit: model.unit(), r#type: model.target_type(), - command_name: model.action().and_then(|a| match a.command_name() { - // Built-in actions don't have a command name but a persistent command ID. - // Use command ID as string. - None => a.command_id().ok().map(|id| id.to_string()), - // ReaScripts and custom actions have a command name as persistent identifier. - Some(name) => Some(name.into_string()), - }), + action_scope: model.action_scope(), + command_name: model.smart_command_name().map(|n| n.to_string()), invocation_type: model.action_invocation_type(), // Not serialized anymore because deprecated invoke_relative: None, @@ -685,25 +684,8 @@ impl TargetModelData { model.change(C::SetUnit(self.unit)); model.change(C::SetTargetType(self.r#type)); if self.category == TargetCategory::Reaper && self.r#type == ReaperTargetType::Action { - let reaper = Reaper::get(); - let action = match self.command_name.as_ref() { - None => None, - Some(command_name) => match command_name.parse::() { - // Could parse this as command ID integer. This is a built-in action. - Ok(command_id_int) => match command_id_int.try_into() { - Ok(command_id) => { - Some(reaper.main_section().action_by_command_id(command_id)) - } - Err(_) => { - notification::warn(format!("Invalid command ID {command_id_int}")); - None - } - }, - // Couldn't parse this as integer. This is a ReaScript or custom action. - Err(_) => Some(reaper.action_by_command_name(command_name.as_str())), - }, - }; - model.change(C::SetAction(action)); + model.change(C::SetActionScope(self.action_scope)); + model.change(C::SetSmartCommandName(self.command_name.clone())); } let invocation_type = if let Some(invoke_relative) = self.invoke_relative { // Very old ReaLearn version diff --git a/main/src/infrastructure/proto/request_handler.rs b/main/src/infrastructure/proto/request_handler.rs index 49a42c52a..ac35e15f1 100644 --- a/main/src/infrastructure/proto/request_handler.rs +++ b/main/src/infrastructure/proto/request_handler.rs @@ -296,7 +296,7 @@ impl ProtoRequestHandler { let _ = Reaper::get() .main_section() .action_by_command_id(CommandId::new(40345)) - .invoke_as_trigger(None); + .invoke_as_trigger(None, None); BackboneShell::get() .proto_hub() .notify_about_global_info_event(GlobalInfoEvent::generic( @@ -366,7 +366,7 @@ impl ProtoRequestHandler { Reaper::get() .main_section() .action_by_command_id(save_project_command_id) - .invoke_as_trigger(Some(project))?; + .invoke_as_trigger(Some(project), None)?; BackboneShell::get() .proto_hub() .notify_about_instance_info_event( diff --git a/main/src/infrastructure/ui/mapping_panel.rs b/main/src/infrastructure/ui/mapping_panel.rs index 3cda242a5..b92fb6127 100644 --- a/main/src/infrastructure/ui/mapping_panel.rs +++ b/main/src/infrastructure/ui/mapping_panel.rs @@ -28,8 +28,8 @@ use helgoboss_learn::{ TakeoverMode, Target, UnitValue, ValueSequence, VirtualColor, DEFAULT_OSC_ARG_VALUE_RANGE, }; use helgobox_api::persistence::{ - Axis, BrowseTracksMode, FxDescriptor, FxToolAction, LearnableTargetKind, MidiScriptKind, - MonitoringMode, MouseButton, PlaytimeColumnAction, PlaytimeColumnDescriptor, + ActionScope, Axis, BrowseTracksMode, FxDescriptor, FxToolAction, LearnableTargetKind, + MidiScriptKind, MonitoringMode, MouseButton, PlaytimeColumnAction, PlaytimeColumnDescriptor, PlaytimeColumnDescriptorKind, PlaytimeMatrixAction, PlaytimeRowAction, PlaytimeRowDescriptor, PlaytimeRowDescriptorKind, PlaytimeSlotDescriptor, PlaytimeSlotDescriptorKind, PlaytimeSlotManagementAction, PlaytimeSlotTransportAction, PotFilterKind, SeekBehavior, @@ -449,7 +449,7 @@ impl MappingPanel { P::TrackType | P::TrackIndex | P::TrackId | P::TrackName | P::TrackExpression | P::BookmarkType | P::BookmarkAnchorType | P::BookmarkRef | P::TransportAction | P::AnyOnParameter - | P::Action => { + | P::SmartCommandName | P::ActionScope => { view.invalidate_window_title(); view.invalidate_target_controls(initiator); view.invalidate_mode_controls(); @@ -926,10 +926,12 @@ impl MappingPanel { ReaperTargetType::Action => { let reaper = Reaper::get().medium_reaper(); use InitialAction::*; - let initial_action = match mapping.borrow().target_model.action() { + let initial_action = match mapping.borrow().target_model.resolve_action() { None => NoneSelected, Some(a) => Selected(a.command_id()?), }; + let action_scope = mapping.borrow().target_model.action_scope(); + let section_id = SectionId::new(action_scope.section_id()); // TODO-low Add this to reaper-high with rxRust if reaper.low().pointers().PromptForAction.is_none() { self.view.require_window().alert( @@ -938,16 +940,16 @@ impl MappingPanel { ); return Ok(()); } - reaper.prompt_for_action_create(initial_action, SectionId::new(0)); + reaper.prompt_for_action_create(initial_action, section_id); let shared_mapping = self.mapping(); let weak_session = self.session.clone(); Global::control_surface_rx() .main_thread_idle() .take_until(self.party_is_over()) - .map(|_| { + .map(move |_| { Reaper::get() .medium_reaper() - .prompt_for_action_poll(SectionId::new(0)) + .prompt_for_action_poll(section_id) }) .filter(|r| *r != PromptForActionResult::NoneSelected) .take_while(|r| *r != PromptForActionResult::ActionWindowGone) @@ -955,13 +957,12 @@ impl MappingPanel { move |r| { if let PromptForActionResult::Selected(command_id) = r { let session = weak_session.upgrade().expect("session gone"); - let action = Reaper::get() - .main_section() - .action_by_command_id(command_id); let mut mapping = shared_mapping.borrow_mut(); - let cmd = MappingCommand::ChangeTarget(TargetCommand::SetAction( - Some(action), - )); + let cmd = MappingCommand::ChangeTarget( + TargetCommand::SetSmartCommandName(Some( + command_id.get().to_string(), + )), + ); session.borrow_mut().change_mapping_from_ui_expert( &mut mapping, cmd, @@ -970,10 +971,10 @@ impl MappingPanel { ); } }, - || { + move || { Reaper::get() .medium_reaper() - .prompt_for_action_finish(SectionId::new(0)); + .prompt_for_action_finish(section_id); }, ); } @@ -3022,6 +3023,15 @@ impl<'a> MutableMappingPanel<'a> { .require_control(root::ID_TARGET_LINE_3_COMBO_BOX_1); match self.target_category() { TargetCategory::Reaper => match self.reaper_target_type() { + ReaperTargetType::Action => { + let scope: ActionScope = combo + .selected_combo_box_item_index() + .try_into() + .unwrap_or_default(); + self.change_mapping(MappingCommand::ChangeTarget( + TargetCommand::SetActionScope(scope), + )); + } ReaperTargetType::PlaytimeColumnAction => { let kind = combo .selected_combo_box_item_index() @@ -5584,6 +5594,11 @@ impl<'a> ImmutableMappingPanel<'a> { .require_control(root::ID_TARGET_LINE_3_COMBO_BOX_1); match self.target_category() { TargetCategory::Reaper => match self.target.target_type() { + ReaperTargetType::Action => { + combo.show(); + combo.fill_combo_box_indexed(ActionScope::iter()); + combo.select_combo_box_item_by_index(self.target.action_scope().into()); + } ReaperTargetType::PlaytimeColumnAction => { combo.show(); combo.fill_combo_box_indexed(PlaytimeColumnDescriptorKind::iter()); diff --git a/playtime-clip-engine b/playtime-clip-engine index 75542f3d8..4c785f8a4 160000 --- a/playtime-clip-engine +++ b/playtime-clip-engine @@ -1 +1 @@ -Subproject commit 75542f3d861b5c51fdef3f025596cab500bd9001 +Subproject commit 4c785f8a45a60dad5f1def051cd61e37f7bf32aa diff --git a/pot/src/preview_recorder.rs b/pot/src/preview_recorder.rs index b6fb81d58..0f6570863 100644 --- a/pot/src/preview_recorder.rs +++ b/pot/src/preview_recorder.rs @@ -164,7 +164,7 @@ fn render_to_file(project: Project, full_path: &Utf8Path) -> Result<(), Box