1use gled_model::{
10 asset::{
11 animation::{Animation, argument::ArgumentKindId},
12 curve::Curve,
13 group::Groups,
14 midi_controller::MidiController,
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 svg::Svg,
24 },
25 asset_id::AssetId,
26 input::event::InputEvent,
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 SetProjectGroups(Groups),
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 SetSvg(Option<Svg>),
118 Tap,
119 SpeedAdd(f32),
120 SpeedMultiply(f32),
121 SetBlackout(bool),
122 SetSceneOpacity(SceneInstanceUnion, f32),
126 ToggleSceneActive(SceneInstanceUnion),
127 SetSceneActive(SceneInstanceUnion, bool),
128 SetSceneFlash(SceneInstanceUnion, bool),
129 SetMainDimmer(f32),
130 MidiOutputActive(bool),
131 SwapScenes(GridLocation, GridLocation),
132 SetSceneName(SceneInstanceUnion, String),
133 SetSceneColor(SceneInstanceUnion, SceneInstanceColor),
134 SetSceneInputDimmer(SceneInstanceUnion, f32),
135 SetSceneIgnoreMainDimmer(SceneInstanceUnion, bool),
136 SetSceneBeatOffset(SceneInstanceUnion, f32),
137 SetSceneSetOffsetOnFlash(SceneInstanceUnion, bool),
138 SetSceneOpacityMultiplier(SceneInstanceUnion, f32),
140 SetSceneOpacityCurve(SceneInstanceUnion, Option<AssetId<Curve>>),
142 SetSceneBeatOffsetMultiplier(SceneInstanceUnion, f32),
144 SetSceneBeatOffsetCurve(SceneInstanceUnion, Option<AssetId<Curve>>),
146 SetSceneActivationInput(SceneInstanceUnion, Option<InputEvent>),
147 SetSceneFlashInput(SceneInstanceUnion, Option<InputEvent>),
148 SetSceneDimmerInput(SceneInstanceUnion, Option<InputEvent>),
149 SetScenePaletteOverwriteFromAsset(SceneInstanceUnion, Option<Option<AssetId<Palette>>>),
150 SetScenePaletteOverwritePrimary(SceneInstanceUnion, [f32; 3]),
151 SetScenePaletteOverwriteSecondary(SceneInstanceUnion, [f32; 3]),
152 SetScenePaletteOverwriteGradient(SceneInstanceUnion, usize, [f32; 3]),
153 ClearSceneGroupsOverwrite(SceneInstanceUnion),
154 SetSceneGroupsOverwriteEntry(SceneInstanceUnion, usize, String),
155 RemoveSceneGroupsOverwriteEntry(SceneInstanceUnion, usize),
156 SetProjectPaletteFromAsset(Option<AssetId<Palette>>),
157 SetProjectPalettePrimary([f32; 3]),
158 SetProjectPaletteSecondary([f32; 3]),
159 SetProjectPaletteGradient(usize, [f32; 3]),
160 SetProjectArtnetControlActive(bool),
161 SetProjectArtnetControlUniverse(u16),
162 SetProjectOscActive(bool),
163 SetProjectOscPort(u16),
164 SetProjectGroup(usize, String),
165 RemoveProjectGroup(usize),
166 ClearProjectGroups,
167 AddProjectTapInputArtnet(u16),
168 RemoveProjectTapInputArtnet(u16),
169 ClearProjectTapInput,
170 AddProjectBlackoutInputArtnet(u16),
171 RemoveProjectBlackoutInputArtnet(u16),
172 ClearProjectBlackoutInput,
173 AddProjectBlackoutHoldInputArtnet(u16),
174 RemoveProjectBlackoutHoldInputArtnet(u16),
175 ClearProjectBlackoutHoldInput,
176 AddProjectHalfInputArtnet(u16),
177 RemoveProjectHalfInputArtnet(u16),
178 ClearProjectHalfInput,
179 AddProjectDoubleInputArtnet(u16),
180 RemoveProjectDoubleInputArtnet(u16),
181 ClearProjectDoubleInput,
182 SetSceneEffectOpacity(SceneInstanceUnion, usize, f32),
183 SetSceneEffectOpacityMultiplier(SceneInstanceUnion, usize, f32),
184 SetSceneEffectOpacityCurve(SceneInstanceUnion, usize, Option<AssetId<Curve>>),
185 SetSceneEffectColorShift(SceneInstanceUnion, usize, f32),
186 SetSceneEffectColorShiftMultiplier(SceneInstanceUnion, usize, f32),
187 SetSceneEffectColorShiftCurve(SceneInstanceUnion, usize, Option<AssetId<Curve>>),
188 SetSceneEffectBeatProgression(SceneInstanceUnion, usize, f32),
189 SetSceneEffectBeatProgressionMultiplier(SceneInstanceUnion, usize, f32),
190 SetSceneEffectBeatProgressionCurve(SceneInstanceUnion, usize, Option<AssetId<Curve>>),
191 SetSceneEffectBeatOffset(SceneInstanceUnion, usize, f32),
192 SetSceneEffectBeatOffsetMultiplier(SceneInstanceUnion, usize, f32),
193 SetSceneEffectBeatOffsetCurve(SceneInstanceUnion, usize, Option<AssetId<Curve>>),
194 SetSceneEffectSpeedExponent(SceneInstanceUnion, usize, i32),
195 SetSceneEffectGroupIndex(SceneInstanceUnion, usize, usize),
196 SetSceneEffectAnimation(SceneInstanceUnion, usize, Option<AssetId<Animation>>),
197 SetSceneEffectAnimationConfigU32(SceneInstanceUnion, usize, usize, u32),
198 SetSceneEffectAnimationConfigF32(SceneInstanceUnion, usize, usize, f32),
199 AddSceneEffect(SceneInstanceUnion),
200 RemoveSceneEffect(SceneInstanceUnion, usize),
201 CloneSceneEffect(SceneInstanceUnion, usize),
202 SetAnimationShaderCode(AssetId<Animation>, String),
203 AddAnimationArgument(AssetId<Animation>),
204 RemoveAnimationArgument(AssetId<Animation>, usize),
205 SetAnimationArgumentName(AssetId<Animation>, usize, String),
206 SetAnimationArgumentKind(AssetId<Animation>, usize, ArgumentKindId),
207 Error(String),
208 SetAudioDevice(Option<AudioDeviceId>),
209}
210
211impl UiAction {
212 pub fn init_queue() -> Option<Receiver<UiAction>> {
214 let (sender, receiver) = unbounded();
215 ACTION_SENDER.set(sender).ok()?;
216 Some(receiver)
217 }
218
219 pub fn enqueue(self) {
220 let action = ACTION_ROUTE.with(|cell| match cell.borrow().as_ref() {
221 Some(route) => {
222 route(self);
223 None
224 }
225 None => Some(self),
226 });
227 let Some(action) = action else {
228 return;
229 };
230 match ACTION_SENDER.get() {
231 Some(sender) => {
232 if sender.send(action).is_err() {
233 tracing::warn!("The action queue has no receiver any more");
234 }
235 }
236 None => tracing::warn!("Dropped {action:?}: the action queue is not set up"),
237 }
238 }
239}