1use crate::state::TouchedControl;
10use gled_model::{
11 asset::{
12 animation::{Animation, argument::ArgumentKindId},
13 curve::Curve,
14 midi_controller::{MidiController, MidiModeHighlight},
15 output_device::routing::OutputRoutings,
16 palette::Palette,
17 project::{
18 GridHighlight, Project,
19 io::{ArtnetConfig, ArtnetControlConfig, ChannelOverwrites, OscConfig},
20 scene_instance_path::SceneInstanceUnion,
21 },
22 scene::{Scene, color::SceneInstanceColor, grid::GridLocation, instance::SceneInstance},
23 },
24 asset_id::AssetId,
25 input::event::InputEvent,
26 patching::Patch,
27 sound::AudioDeviceId,
28};
29use kanal::{Receiver, Sender, unbounded};
30use once_cell::sync::OnceCell;
31use serde::{Deserialize, Serialize};
32use std::{
33 cell::RefCell,
34 collections::{BTreeSet, HashMap},
35 sync::Arc,
36};
37
38static ACTION_SENDER: OnceCell<Sender<UiAction>> = OnceCell::new();
39
40#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
42pub struct TriggerInputs {
43 pub tap: BTreeSet<InputEvent>,
44 pub blackout: BTreeSet<InputEvent>,
45 pub blackout_hold: BTreeSet<InputEvent>,
46 pub half: BTreeSet<InputEvent>,
47 pub double: BTreeSet<InputEvent>,
48}
49
50impl TriggerInputs {
51 pub fn of(project: &Project) -> Self {
52 Self {
53 tap: project.tap_input_events.clone(),
54 blackout: project.blackout_input_events.clone(),
55 blackout_hold: project.blackout_hold_input_events.clone(),
56 half: project.half_input_events.clone(),
57 double: project.double_input_events.clone(),
58 }
59 }
60
61 pub fn apply_to(self, project: &mut Project) {
62 project.tap_input_events = self.tap;
63 project.blackout_input_events = self.blackout;
64 project.blackout_hold_input_events = self.blackout_hold;
65 project.half_input_events = self.half;
66 project.double_input_events = self.double;
67 }
68}
69
70type ActionRoute = Box<dyn Fn(UiAction)>;
72
73thread_local! {
74 static ACTION_ROUTE: RefCell<Option<ActionRoute>> = const { RefCell::new(None) };
78}
79
80pub fn route_actions_on_this_thread(route: impl Fn(UiAction) + 'static) {
82 ACTION_ROUTE.with(|cell| *cell.borrow_mut() = Some(Box::new(route)));
83}
84
85#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
86pub enum UiAction {
87 SetProjectPalette(Box<Option<Palette>>),
90 SetProjectPatch(Box<Patch>),
92 SetGridSize(usize, usize),
93 SetGridHighlight(GridHighlight),
94 SetProjectChannelOverwrites(ChannelOverwrites),
95 SetProjectOutputRoutings(Arc<OutputRoutings>),
96 SetProjectArtnetConfig(Box<ArtnetConfig>),
97 SetProjectTriggerInputs(Box<TriggerInputs>),
98 SetMidiActiveMappings(HashMap<String, Option<AssetId<MidiController>>>),
99 SetProjectArtnetControl(ArtnetControlConfig),
100 SetProjectOsc(OscConfig),
101 ReplaceSceneInstance(GridLocation, Option<Box<SceneInstance>>),
103 CloseProject,
104 SetOutputRoutings(Arc<OutputRoutings>),
106
107 AddScene(GridLocation, AssetId<Scene>),
108 SetProject(AssetId<Project>),
109 SelectScene(SceneInstanceUnion),
110 SelectSceneByLocation(GridLocation),
111 SelectSceneRelative(i32),
114 SelectSceneRowRelative(i32),
117 SelectSceneColumnRelative(i32),
120 SetMidiMode {
124 port: String,
125 mode: u8,
126 highlight: Option<MidiModeHighlight>,
127 },
128 SetTouchedControl {
131 touch: String,
132 control: Option<TouchedControl>,
133 },
134 SelectSceneEffect(usize),
137 SelectSceneEffectRelative(i32),
140 DeleteSceneInstance {
141 location: GridLocation,
142 },
143 DeleteSceneInstancePath(SceneInstanceUnion),
144 CloneSceneInstance(GridLocation),
145 CloneSceneInstancePath(SceneInstanceUnion),
146 ReloadShaderCode(Option<AssetId<Animation>>),
147 Tap,
148 SpeedAdd(f32),
149 SpeedMultiply(f32),
150 SetBlackout(bool),
151 ToggleBlackout,
152 SetSceneOpacity(SceneInstanceUnion, f32),
156 ToggleSceneActive(SceneInstanceUnion),
157 SetSceneActive(SceneInstanceUnion, bool),
158 SetSceneFlash(SceneInstanceUnion, bool),
159 SetMainDimmer(f32),
160 AddMainDimmer(f32),
162 MidiOutputActive(bool),
163 SwapScenes(GridLocation, GridLocation),
164 SetSceneName(SceneInstanceUnion, String),
165 SetSceneColor(SceneInstanceUnion, SceneInstanceColor),
166 SetSceneInputDimmer(SceneInstanceUnion, f32),
167 SetSceneIgnoreMainDimmer(SceneInstanceUnion, bool),
168 SetSceneBeatOffset(SceneInstanceUnion, f32),
169 SetSceneSetOffsetOnFlash(SceneInstanceUnion, bool),
170 SetSceneOpacityMultiplier(SceneInstanceUnion, f32),
172 SetSceneOpacityCurve(SceneInstanceUnion, Option<AssetId<Curve>>),
174 SetSceneBeatOffsetMultiplier(SceneInstanceUnion, f32),
176 SetSceneBeatOffsetCurve(SceneInstanceUnion, Option<AssetId<Curve>>),
178 SetSceneActivationInput(SceneInstanceUnion, Option<InputEvent>),
179 SetSceneFlashInput(SceneInstanceUnion, Option<InputEvent>),
180 SetSceneDimmerInput(SceneInstanceUnion, Option<InputEvent>),
181 SetScenePaletteOverwriteFromAsset(SceneInstanceUnion, Option<Option<AssetId<Palette>>>),
182 SetScenePaletteOverwritePrimary(SceneInstanceUnion, [f32; 3]),
183 SetScenePaletteOverwriteSecondary(SceneInstanceUnion, [f32; 3]),
184 SetScenePaletteOverwriteGradient(SceneInstanceUnion, usize, [f32; 3]),
185 SetProjectPaletteFromAsset(Option<AssetId<Palette>>),
186 SetProjectPalettePrimary([f32; 3]),
187 SetProjectPaletteSecondary([f32; 3]),
188 SetProjectPaletteGradient(usize, [f32; 3]),
189 SetProjectArtnetControlActive(bool),
190 SetProjectArtnetControlUniverse(u16),
191 SetProjectOscActive(bool),
192 SetProjectOscPort(u16),
193 AddProjectTapInputArtnet(u16),
194 RemoveProjectTapInputArtnet(u16),
195 ClearProjectTapInput,
196 AddProjectBlackoutInputArtnet(u16),
197 RemoveProjectBlackoutInputArtnet(u16),
198 ClearProjectBlackoutInput,
199 AddProjectBlackoutHoldInputArtnet(u16),
200 RemoveProjectBlackoutHoldInputArtnet(u16),
201 ClearProjectBlackoutHoldInput,
202 AddProjectHalfInputArtnet(u16),
203 RemoveProjectHalfInputArtnet(u16),
204 ClearProjectHalfInput,
205 AddProjectDoubleInputArtnet(u16),
206 RemoveProjectDoubleInputArtnet(u16),
207 ClearProjectDoubleInput,
208 SetSceneEffectOpacity(SceneInstanceUnion, usize, f32),
209 SetSceneEffectOpacityMultiplier(SceneInstanceUnion, usize, f32),
210 SetSceneEffectOpacityCurve(SceneInstanceUnion, usize, Option<AssetId<Curve>>),
211 SetSceneEffectColorShift(SceneInstanceUnion, usize, f32),
212 SetSceneEffectColorShiftMultiplier(SceneInstanceUnion, usize, f32),
213 SetSceneEffectColorShiftCurve(SceneInstanceUnion, usize, Option<AssetId<Curve>>),
214 SetSceneEffectBeatProgression(SceneInstanceUnion, usize, f32),
215 SetSceneEffectBeatProgressionMultiplier(SceneInstanceUnion, usize, f32),
216 SetSceneEffectBeatProgressionCurve(SceneInstanceUnion, usize, Option<AssetId<Curve>>),
217 SetSceneEffectBeatOffset(SceneInstanceUnion, usize, f32),
218 SetSceneEffectBeatOffsetMultiplier(SceneInstanceUnion, usize, f32),
219 SetSceneEffectBeatOffsetCurve(SceneInstanceUnion, usize, Option<AssetId<Curve>>),
220 SetSceneEffectSpeedExponent(SceneInstanceUnion, usize, i32),
221 SetSceneEffectLayout(SceneInstanceUnion, usize, Option<usize>),
225 SetSceneEffectAnimation(SceneInstanceUnion, usize, Option<AssetId<Animation>>),
226 SetSceneEffectAnimationConfigU32(SceneInstanceUnion, usize, usize, u32),
227 SetSceneEffectAnimationConfigF32(SceneInstanceUnion, usize, usize, f32),
228 SetSceneEffectAnimationConfigF32Multiplier(SceneInstanceUnion, usize, usize, f32),
231 AddSceneEffect(SceneInstanceUnion),
232 RemoveSceneEffect(SceneInstanceUnion, usize),
233 CloneSceneEffect(SceneInstanceUnion, usize),
234 SetAnimationShaderCode(AssetId<Animation>, String),
235 AddAnimationArgument(AssetId<Animation>),
236 RemoveAnimationArgument(AssetId<Animation>, usize),
237 SetAnimationArgumentName(AssetId<Animation>, usize, String),
238 SetAnimationArgumentKind(AssetId<Animation>, usize, ArgumentKindId),
239 Error(String),
240 SetAudioDevice(Option<AudioDeviceId>),
241}
242
243impl UiAction {
244 pub fn init_queue() -> Option<Receiver<UiAction>> {
246 let (sender, receiver) = unbounded();
247 ACTION_SENDER.set(sender).ok()?;
248 Some(receiver)
249 }
250
251 pub fn enqueue(self) {
252 let action = ACTION_ROUTE.with(|cell| match cell.borrow().as_ref() {
253 Some(route) => {
254 route(self);
255 None
256 }
257 None => Some(self),
258 });
259 let Some(action) = action else {
260 return;
261 };
262 match ACTION_SENDER.get() {
263 Some(sender) => {
264 if sender.send(action).is_err() {
265 tracing::warn!("The action queue has no receiver any more");
266 }
267 }
268 None => tracing::warn!("Dropped {action:?}: the action queue is not set up"),
269 }
270 }
271}