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