use egui::{Id, Popup, PopupCloseBehavior, Response, Ui};
use gled_model::asset::midi_controller::{
MidiInputAction, MidiInputActionKind, MidiMode, MidiValueSource, MidiValueSourceKind,
};
use super::action_converters::input_action_from_kind;
use super::intent::SlotNumbering;
use super::pickers::{
GroupFirst, animation_picker, beat_picker, blackout_picker, effect_picker, encoder_picker,
mode_picker, parameter_picker, setting_picker, target_picker,
};
use super::rules::{ControlKind, TokenKind, ValueKind, actions, sources};
use super::value_source_converters::value_source_from_kind;
use super::widgets::palette::{PaletteEntry, palette};
pub(crate) enum ActionChoice {
Set(MidiInputAction, SlotNumbering),
RemoveAction,
DeleteRule,
}
#[derive(Clone)]
enum Pick<T> {
Kind(T),
RemoveAction,
DeleteRule,
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn action_token_editor(
ui: &mut Ui,
response: &Response,
kind: TokenKind,
action: Option<&MidiInputAction>,
numbering: &SlotNumbering,
modes: &[MidiMode],
control: ControlKind,
group: Option<GroupFirst>,
open: bool,
) -> Option<ActionChoice> {
let popup_id = Popup::default_response_id(response);
if open {
Popup::open_id(ui.ctx(), popup_id);
}
Popup::menu(response)
.close_behavior(PopupCloseBehavior::CloseOnClickOutside)
.show(|ui| {
if kind == TokenKind::Main {
return action_palette(ui, popup_id, action, control);
}
let mut edited = action?.clone();
let mut numbering = numbering.clone();
let changed = edit_action_part(ui, kind, &mut edited, &mut numbering, modes, group);
changed.then_some(ActionChoice::Set(edited, numbering))
})
.and_then(|inner| inner.inner)
}
fn action_palette(
ui: &mut Ui,
id: Id,
action: Option<&MidiInputAction>,
control: ControlKind,
) -> Option<ActionChoice> {
let current = action.map(MidiInputActionKind::from);
let mut entries: Vec<PaletteEntry<Pick<MidiInputActionKind>>> = actions()
.map(|info| PaletteEntry {
group: info.group.label(),
label: info.label.to_owned(),
description: info.description,
suggested: info.suits(control),
current: current == Some(info.kind),
found: Box::new(move |query| info.matches(query)),
value: Pick::Kind(info.kind),
})
.collect();
if action.is_some() {
entries.push(PaletteEntry {
group: "Remove",
label: "Does nothing — remove the action".to_owned(),
description: "Keeps the rule's lights",
suggested: false,
current: false,
found: Box::new(|query| "does nothing remove none clear".contains(query)),
value: Pick::RemoveAction,
});
}
entries.push(PaletteEntry {
group: "Remove",
label: "Delete this rule".to_owned(),
description: "Removes the action and the lights",
suggested: false,
current: false,
found: Box::new(|query| "delete remove rule".contains(query)),
value: Pick::DeleteRule,
});
match palette(ui, id, &entries)? {
Pick::Kind(kind) if Some(kind) == current => None,
Pick::Kind(kind) => {
let base = action.cloned().unwrap_or(MidiInputAction::Tap);
Some(ActionChoice::Set(
input_action_from_kind(kind, base),
SlotNumbering::default(),
))
}
Pick::RemoveAction => Some(ActionChoice::RemoveAction),
Pick::DeleteRule => Some(ActionChoice::DeleteRule),
}
}
fn edit_action_part(
ui: &mut Ui,
kind: TokenKind,
action: &mut MidiInputAction,
numbering: &mut SlotNumbering,
modes: &[MidiMode],
group: Option<GroupFirst>,
) -> bool {
use MidiInputAction as A;
match (kind, action) {
(TokenKind::Target, action) => {
target_of(action).is_some_and(|target| target_picker(ui, target, numbering, group))
}
(TokenKind::Effect, action) => {
effect_of(action).is_some_and(|effect| effect_picker(ui, effect, numbering, group))
}
(TokenKind::Setting, A::SetSceneEffectSettingF32 { setting_index, .. }) => {
setting_picker(ui, setting_index, numbering, group)
}
(TokenKind::Animation, A::SelectSceneEffect { index }) => {
animation_picker(ui, index, numbering, group)
}
(TokenKind::Mode, A::SelectMode { mode }) => mode_picker(ui, mode, modes),
(
TokenKind::Encoder,
A::SetMainDimmerRelative { encoder }
| A::SetSpeedRelative { encoder }
| A::SelectSceneRelative { encoder }
| A::SelectSceneRow { encoder }
| A::SelectSceneColumn { encoder }
| A::SelectSceneEffectRelative { encoder },
) => encoder_picker(ui, encoder),
_ => false,
}
}
fn target_of(
action: &mut MidiInputAction,
) -> Option<&mut gled_model::asset::midi_controller::MidiSceneTarget> {
use MidiInputAction as A;
match action {
A::SelectScene { target }
| A::ToggleSceneActive { target }
| A::SetSceneActive { target }
| A::FlashScene { target }
| A::SetSceneOpacity { target }
| A::SetSceneInputDimmer { target }
| A::SetSceneBeatOffset { target }
| A::SetSceneIgnoreMainDimmer { target }
| A::SetSceneSetOffsetOnFlash { target }
| A::SetSceneEffectSettingF32 { target, .. }
| A::SetSceneEffectOpacity { target, .. }
| A::SetSceneEffectColorShift { target, .. }
| A::SetSceneEffectBeatProgression { target, .. }
| A::SetSceneEffectBeatOffset { target, .. }
| A::SetSceneEffectSpeedExponent { target, .. }
| A::SetSceneEffectLayout { target, .. } => Some(target),
_ => None,
}
}
fn effect_of(
action: &mut MidiInputAction,
) -> Option<&mut gled_model::asset::midi_controller::MidiEffectTarget> {
use MidiInputAction as A;
match action {
A::SetSceneEffectSettingF32 { effect, .. }
| A::SetSceneEffectOpacity { effect, .. }
| A::SetSceneEffectColorShift { effect, .. }
| A::SetSceneEffectBeatProgression { effect, .. }
| A::SetSceneEffectBeatOffset { effect, .. }
| A::SetSceneEffectSpeedExponent { effect, .. }
| A::SetSceneEffectLayout { effect, .. } => Some(effect),
_ => None,
}
}
pub(crate) fn source_token_editor(
response: &Response,
kind: TokenKind,
source: &MidiValueSource,
numbering: &SlotNumbering,
modes: &[MidiMode],
prefer: ValueKind,
group: Option<GroupFirst>,
) -> Option<(MidiValueSource, SlotNumbering)> {
let popup_id = Popup::default_response_id(response);
Popup::menu(response)
.close_behavior(PopupCloseBehavior::CloseOnClickOutside)
.show(|ui| {
if kind == TokenKind::Main {
let current = MidiValueSourceKind::from(source);
let entries: Vec<PaletteEntry<MidiValueSourceKind>> = sources()
.map(|info| PaletteEntry {
group: info.group,
label: info.label.to_owned(),
description: "",
suggested: info.value == prefer,
current: current == info.kind,
found: Box::new(move |query| {
query.split_whitespace().all(|word| {
info.label.to_lowercase().contains(word)
|| info.group.to_lowercase().contains(word)
})
}),
value: info.kind,
})
.collect();
let picked = palette(ui, popup_id, &entries)?;
return (picked != current).then(|| {
(
value_source_from_kind(picked, source.clone()),
SlotNumbering::default(),
)
});
}
let mut edited = source.clone();
let mut numbering = numbering.clone();
let changed = edit_source_part(ui, kind, &mut edited, &mut numbering, modes, group);
changed.then_some((edited, numbering))
})
.and_then(|inner| inner.inner)
}
fn edit_source_part(
ui: &mut Ui,
kind: TokenKind,
source: &mut MidiValueSource,
numbering: &mut SlotNumbering,
modes: &[MidiMode],
group: Option<GroupFirst>,
) -> bool {
use MidiValueSource as S;
match (kind, source) {
(
TokenKind::Target,
S::SceneOpacity { target }
| S::SceneOpacityLive { target }
| S::SceneInputDimmer { target }
| S::SceneBeatOffset { target }
| S::SceneIgnoreMainDimmer { target }
| S::SceneSetOffsetOnFlash { target }
| S::SceneActive { target }
| S::SceneFlashed { target }
| S::SceneSelected { target }
| S::SceneEffectSettingF32 { target, .. }
| S::SceneEffectOpacity { target, .. }
| S::SceneEffectColorShift { target, .. }
| S::SceneEffectBeatProgression { target, .. }
| S::SceneEffectBeatOffset { target, .. }
| S::SceneEffectSpeedExponent { target, .. }
| S::SceneEffectLayout { target, .. },
) => target_picker(ui, target, numbering, group),
(
TokenKind::Effect,
S::SelectedSceneEffectLive { effect, .. }
| S::SceneEffectSettingF32 { effect, .. }
| S::SceneEffectOpacity { effect, .. }
| S::SceneEffectColorShift { effect, .. }
| S::SceneEffectBeatProgression { effect, .. }
| S::SceneEffectBeatOffset { effect, .. }
| S::SceneEffectSpeedExponent { effect, .. }
| S::SceneEffectLayout { effect, .. },
) => effect_picker(ui, effect, numbering, group),
(TokenKind::Setting, S::SceneEffectSettingF32 { setting_index, .. }) => {
setting_picker(ui, setting_index, numbering, group)
}
(TokenKind::Animation, S::EffectSelected { index }) => {
animation_picker(ui, index, numbering, group)
}
(TokenKind::Parameter, S::SelectedSceneEffectLive { parameter, .. }) => {
parameter_picker(ui, parameter)
}
(TokenKind::Mode, S::ModeActive { mode }) => mode_picker(ui, mode, modes),
(
TokenKind::Beat,
S::BeatFlankPulse {
start_beat,
end_beat,
blackout_blink_value,
every_beat,
},
) => beat_picker(ui, start_beat, end_beat, every_beat, blackout_blink_value),
(TokenKind::Blackout, S::Blackout { inverted, blink }) => {
blackout_picker(ui, inverted, blink)
}
_ => false,
}
}