use gled_model::{
asset::{
animation::{Animation, argument::ArgumentKindId},
curve::Curve,
midi_controller::MidiController,
output_device::routing::OutputRoutings,
palette::Palette,
project::{
GridHighlight, Project,
io::{ArtnetConfig, ArtnetControlConfig, ChannelOverwrites, OscConfig},
scene_instance_path::SceneInstanceUnion,
},
scene::{Scene, color::SceneInstanceColor, grid::GridLocation, instance::SceneInstance},
},
asset_id::AssetId,
input::event::InputEvent,
patching::Patch,
sound::AudioDeviceId,
};
use kanal::{Receiver, Sender, unbounded};
use once_cell::sync::OnceCell;
use serde::{Deserialize, Serialize};
use std::{
cell::RefCell,
collections::{BTreeSet, HashMap},
sync::Arc,
};
static ACTION_SENDER: OnceCell<Sender<UiAction>> = OnceCell::new();
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TriggerInputs {
pub tap: BTreeSet<InputEvent>,
pub blackout: BTreeSet<InputEvent>,
pub blackout_hold: BTreeSet<InputEvent>,
pub half: BTreeSet<InputEvent>,
pub double: BTreeSet<InputEvent>,
}
impl TriggerInputs {
pub fn of(project: &Project) -> Self {
Self {
tap: project.tap_input_events.clone(),
blackout: project.blackout_input_events.clone(),
blackout_hold: project.blackout_hold_input_events.clone(),
half: project.half_input_events.clone(),
double: project.double_input_events.clone(),
}
}
pub fn apply_to(self, project: &mut Project) {
project.tap_input_events = self.tap;
project.blackout_input_events = self.blackout;
project.blackout_hold_input_events = self.blackout_hold;
project.half_input_events = self.half;
project.double_input_events = self.double;
}
}
type ActionRoute = Box<dyn Fn(UiAction)>;
thread_local! {
static ACTION_ROUTE: RefCell<Option<ActionRoute>> = const { RefCell::new(None) };
}
pub fn route_actions_on_this_thread(route: impl Fn(UiAction) + 'static) {
ACTION_ROUTE.with(|cell| *cell.borrow_mut() = Some(Box::new(route)));
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum UiAction {
SetProjectPalette(Box<Option<Palette>>),
SetProjectPatch(Box<Patch>),
SetGridSize(usize, usize),
SetGridHighlight(GridHighlight),
SetProjectChannelOverwrites(ChannelOverwrites),
SetProjectOutputRoutings(Arc<OutputRoutings>),
SetProjectArtnetConfig(Box<ArtnetConfig>),
SetProjectTriggerInputs(Box<TriggerInputs>),
SetMidiActiveMappings(HashMap<String, Option<AssetId<MidiController>>>),
SetProjectArtnetControl(ArtnetControlConfig),
SetProjectOsc(OscConfig),
ReplaceSceneInstance(GridLocation, Option<Box<SceneInstance>>),
CloseProject,
SetOutputRoutings(Arc<OutputRoutings>),
AddScene(GridLocation, AssetId<Scene>),
SetProject(AssetId<Project>),
SelectScene(SceneInstanceUnion),
SelectSceneByLocation(GridLocation),
SelectSceneRelative(i32),
SelectSceneEffect(usize),
SelectSceneEffectRelative(i32),
DeleteSceneInstance {
location: GridLocation,
},
DeleteSceneInstancePath(SceneInstanceUnion),
CloneSceneInstance(GridLocation),
CloneSceneInstancePath(SceneInstanceUnion),
ReloadShaderCode(Option<AssetId<Animation>>),
Tap,
SpeedAdd(f32),
SpeedMultiply(f32),
SetBlackout(bool),
ToggleBlackout,
SetSceneOpacity(SceneInstanceUnion, f32),
ToggleSceneActive(SceneInstanceUnion),
SetSceneActive(SceneInstanceUnion, bool),
SetSceneFlash(SceneInstanceUnion, bool),
SetMainDimmer(f32),
AddMainDimmer(f32),
MidiOutputActive(bool),
SwapScenes(GridLocation, GridLocation),
SetSceneName(SceneInstanceUnion, String),
SetSceneColor(SceneInstanceUnion, SceneInstanceColor),
SetSceneInputDimmer(SceneInstanceUnion, f32),
SetSceneIgnoreMainDimmer(SceneInstanceUnion, bool),
SetSceneBeatOffset(SceneInstanceUnion, f32),
SetSceneSetOffsetOnFlash(SceneInstanceUnion, bool),
SetSceneOpacityMultiplier(SceneInstanceUnion, f32),
SetSceneOpacityCurve(SceneInstanceUnion, Option<AssetId<Curve>>),
SetSceneBeatOffsetMultiplier(SceneInstanceUnion, f32),
SetSceneBeatOffsetCurve(SceneInstanceUnion, Option<AssetId<Curve>>),
SetSceneActivationInput(SceneInstanceUnion, Option<InputEvent>),
SetSceneFlashInput(SceneInstanceUnion, Option<InputEvent>),
SetSceneDimmerInput(SceneInstanceUnion, Option<InputEvent>),
SetScenePaletteOverwriteFromAsset(SceneInstanceUnion, Option<Option<AssetId<Palette>>>),
SetScenePaletteOverwritePrimary(SceneInstanceUnion, [f32; 3]),
SetScenePaletteOverwriteSecondary(SceneInstanceUnion, [f32; 3]),
SetScenePaletteOverwriteGradient(SceneInstanceUnion, usize, [f32; 3]),
SetProjectPaletteFromAsset(Option<AssetId<Palette>>),
SetProjectPalettePrimary([f32; 3]),
SetProjectPaletteSecondary([f32; 3]),
SetProjectPaletteGradient(usize, [f32; 3]),
SetProjectArtnetControlActive(bool),
SetProjectArtnetControlUniverse(u16),
SetProjectOscActive(bool),
SetProjectOscPort(u16),
AddProjectTapInputArtnet(u16),
RemoveProjectTapInputArtnet(u16),
ClearProjectTapInput,
AddProjectBlackoutInputArtnet(u16),
RemoveProjectBlackoutInputArtnet(u16),
ClearProjectBlackoutInput,
AddProjectBlackoutHoldInputArtnet(u16),
RemoveProjectBlackoutHoldInputArtnet(u16),
ClearProjectBlackoutHoldInput,
AddProjectHalfInputArtnet(u16),
RemoveProjectHalfInputArtnet(u16),
ClearProjectHalfInput,
AddProjectDoubleInputArtnet(u16),
RemoveProjectDoubleInputArtnet(u16),
ClearProjectDoubleInput,
SetSceneEffectOpacity(SceneInstanceUnion, usize, f32),
SetSceneEffectOpacityMultiplier(SceneInstanceUnion, usize, f32),
SetSceneEffectOpacityCurve(SceneInstanceUnion, usize, Option<AssetId<Curve>>),
SetSceneEffectColorShift(SceneInstanceUnion, usize, f32),
SetSceneEffectColorShiftMultiplier(SceneInstanceUnion, usize, f32),
SetSceneEffectColorShiftCurve(SceneInstanceUnion, usize, Option<AssetId<Curve>>),
SetSceneEffectBeatProgression(SceneInstanceUnion, usize, f32),
SetSceneEffectBeatProgressionMultiplier(SceneInstanceUnion, usize, f32),
SetSceneEffectBeatProgressionCurve(SceneInstanceUnion, usize, Option<AssetId<Curve>>),
SetSceneEffectBeatOffset(SceneInstanceUnion, usize, f32),
SetSceneEffectBeatOffsetMultiplier(SceneInstanceUnion, usize, f32),
SetSceneEffectBeatOffsetCurve(SceneInstanceUnion, usize, Option<AssetId<Curve>>),
SetSceneEffectSpeedExponent(SceneInstanceUnion, usize, i32),
SetSceneEffectLayout(SceneInstanceUnion, usize, Option<usize>),
SetSceneEffectAnimation(SceneInstanceUnion, usize, Option<AssetId<Animation>>),
SetSceneEffectAnimationConfigU32(SceneInstanceUnion, usize, usize, u32),
SetSceneEffectAnimationConfigF32(SceneInstanceUnion, usize, usize, f32),
SetSceneEffectAnimationConfigF32Multiplier(SceneInstanceUnion, usize, usize, f32),
AddSceneEffect(SceneInstanceUnion),
RemoveSceneEffect(SceneInstanceUnion, usize),
CloneSceneEffect(SceneInstanceUnion, usize),
SetAnimationShaderCode(AssetId<Animation>, String),
AddAnimationArgument(AssetId<Animation>),
RemoveAnimationArgument(AssetId<Animation>, usize),
SetAnimationArgumentName(AssetId<Animation>, usize, String),
SetAnimationArgumentKind(AssetId<Animation>, usize, ArgumentKindId),
Error(String),
SetAudioDevice(Option<AudioDeviceId>),
}
impl UiAction {
pub fn init_queue() -> Option<Receiver<UiAction>> {
let (sender, receiver) = unbounded();
ACTION_SENDER.set(sender).ok()?;
Some(receiver)
}
pub fn enqueue(self) {
let action = ACTION_ROUTE.with(|cell| match cell.borrow().as_ref() {
Some(route) => {
route(self);
None
}
None => Some(self),
});
let Some(action) = action else {
return;
};
match ACTION_SENDER.get() {
Some(sender) => {
if sender.send(action).is_err() {
tracing::warn!("The action queue has no receiver any more");
}
}
None => tracing::warn!("Dropped {action:?}: the action queue is not set up"),
}
}
}