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 DeleteSceneInstance {
111 location: GridLocation,
112 },
113 DeleteSceneInstancePath(SceneInstanceUnion),
114 CloneSceneInstance(GridLocation),
115 CloneSceneInstancePath(SceneInstanceUnion),
116 ReloadShaderCode(Option<AssetId<Animation>>),
117 Tap,
118 SpeedAdd(f32),
119 SpeedMultiply(f32),
120 SetBlackout(bool),
121 SetSceneOpacity(SceneInstanceUnion, f32),
125 ToggleSceneActive(SceneInstanceUnion),
126 SetSceneActive(SceneInstanceUnion, bool),
127 SetSceneFlash(SceneInstanceUnion, bool),
128 SetMainDimmer(f32),
129 MidiOutputActive(bool),
130 SwapScenes(GridLocation, GridLocation),
131 SetSceneName(SceneInstanceUnion, String),
132 SetSceneColor(SceneInstanceUnion, SceneInstanceColor),
133 SetSceneInputDimmer(SceneInstanceUnion, f32),
134 SetSceneIgnoreMainDimmer(SceneInstanceUnion, bool),
135 SetSceneBeatOffset(SceneInstanceUnion, f32),
136 SetSceneSetOffsetOnFlash(SceneInstanceUnion, bool),
137 SetSceneOpacityMultiplier(SceneInstanceUnion, f32),
139 SetSceneOpacityCurve(SceneInstanceUnion, Option<AssetId<Curve>>),
141 SetSceneBeatOffsetMultiplier(SceneInstanceUnion, f32),
143 SetSceneBeatOffsetCurve(SceneInstanceUnion, Option<AssetId<Curve>>),
145 SetSceneActivationInput(SceneInstanceUnion, Option<InputEvent>),
146 SetSceneFlashInput(SceneInstanceUnion, Option<InputEvent>),
147 SetSceneDimmerInput(SceneInstanceUnion, Option<InputEvent>),
148 SetScenePaletteOverwriteFromAsset(SceneInstanceUnion, Option<Option<AssetId<Palette>>>),
149 SetScenePaletteOverwritePrimary(SceneInstanceUnion, [f32; 3]),
150 SetScenePaletteOverwriteSecondary(SceneInstanceUnion, [f32; 3]),
151 SetScenePaletteOverwriteGradient(SceneInstanceUnion, usize, [f32; 3]),
152 SetProjectPaletteFromAsset(Option<AssetId<Palette>>),
153 SetProjectPalettePrimary([f32; 3]),
154 SetProjectPaletteSecondary([f32; 3]),
155 SetProjectPaletteGradient(usize, [f32; 3]),
156 SetProjectArtnetControlActive(bool),
157 SetProjectArtnetControlUniverse(u16),
158 SetProjectOscActive(bool),
159 SetProjectOscPort(u16),
160 AddProjectTapInputArtnet(u16),
161 RemoveProjectTapInputArtnet(u16),
162 ClearProjectTapInput,
163 AddProjectBlackoutInputArtnet(u16),
164 RemoveProjectBlackoutInputArtnet(u16),
165 ClearProjectBlackoutInput,
166 AddProjectBlackoutHoldInputArtnet(u16),
167 RemoveProjectBlackoutHoldInputArtnet(u16),
168 ClearProjectBlackoutHoldInput,
169 AddProjectHalfInputArtnet(u16),
170 RemoveProjectHalfInputArtnet(u16),
171 ClearProjectHalfInput,
172 AddProjectDoubleInputArtnet(u16),
173 RemoveProjectDoubleInputArtnet(u16),
174 ClearProjectDoubleInput,
175 SetSceneEffectOpacity(SceneInstanceUnion, usize, f32),
176 SetSceneEffectOpacityMultiplier(SceneInstanceUnion, usize, f32),
177 SetSceneEffectOpacityCurve(SceneInstanceUnion, usize, Option<AssetId<Curve>>),
178 SetSceneEffectColorShift(SceneInstanceUnion, usize, f32),
179 SetSceneEffectColorShiftMultiplier(SceneInstanceUnion, usize, f32),
180 SetSceneEffectColorShiftCurve(SceneInstanceUnion, usize, Option<AssetId<Curve>>),
181 SetSceneEffectBeatProgression(SceneInstanceUnion, usize, f32),
182 SetSceneEffectBeatProgressionMultiplier(SceneInstanceUnion, usize, f32),
183 SetSceneEffectBeatProgressionCurve(SceneInstanceUnion, usize, Option<AssetId<Curve>>),
184 SetSceneEffectBeatOffset(SceneInstanceUnion, usize, f32),
185 SetSceneEffectBeatOffsetMultiplier(SceneInstanceUnion, usize, f32),
186 SetSceneEffectBeatOffsetCurve(SceneInstanceUnion, usize, Option<AssetId<Curve>>),
187 SetSceneEffectSpeedExponent(SceneInstanceUnion, usize, i32),
188 SetSceneEffectLayout(SceneInstanceUnion, usize, Option<usize>),
192 SetSceneEffectAnimation(SceneInstanceUnion, usize, Option<AssetId<Animation>>),
193 SetSceneEffectAnimationConfigU32(SceneInstanceUnion, usize, usize, u32),
194 SetSceneEffectAnimationConfigF32(SceneInstanceUnion, usize, usize, f32),
195 AddSceneEffect(SceneInstanceUnion),
196 RemoveSceneEffect(SceneInstanceUnion, usize),
197 CloneSceneEffect(SceneInstanceUnion, usize),
198 SetAnimationShaderCode(AssetId<Animation>, String),
199 AddAnimationArgument(AssetId<Animation>),
200 RemoveAnimationArgument(AssetId<Animation>, usize),
201 SetAnimationArgumentName(AssetId<Animation>, usize, String),
202 SetAnimationArgumentKind(AssetId<Animation>, usize, ArgumentKindId),
203 Error(String),
204 SetAudioDevice(Option<AudioDeviceId>),
205}
206
207impl UiAction {
208 pub fn init_queue() -> Option<Receiver<UiAction>> {
210 let (sender, receiver) = unbounded();
211 ACTION_SENDER.set(sender).ok()?;
212 Some(receiver)
213 }
214
215 pub fn enqueue(self) {
216 let action = ACTION_ROUTE.with(|cell| match cell.borrow().as_ref() {
217 Some(route) => {
218 route(self);
219 None
220 }
221 None => Some(self),
222 });
223 let Some(action) = action else {
224 return;
225 };
226 match ACTION_SENDER.get() {
227 Some(sender) => {
228 if sender.send(action).is_err() {
229 tracing::warn!("The action queue has no receiver any more");
230 }
231 }
232 None => tracing::warn!("Dropped {action:?}: the action queue is not set up"),
233 }
234 }
235}