use egui::{DragValue, Ui};
use gled_model::asset::midi_controller::{
MidiEffectParameter, MidiEffectTarget, MidiEncoder, MidiMode, MidiRelativeEncoding,
MidiSceneTarget,
};
use strum::IntoEnumIterator;
use super::intent::SlotNumbering;
use super::rules::{Numbering, Numbers, Slot};
#[derive(Debug, Clone, Copy)]
pub(crate) struct GroupFirst<'a> {
pub(crate) n: i32,
pub(crate) name: &'a str,
}
fn counted_number(
ui: &mut Ui,
label: &str,
value: &mut u8,
slot: Slot,
numbering: &mut SlotNumbering,
group: Option<GroupFirst>,
max: u8,
) -> bool {
let mut changed = false;
let mut current = numbering.numbering(slot);
ui.horizontal(|ui| {
ui.label(label);
match (group, current) {
(Some(first), Numbering::Counting { offset }) => {
let mut start = first.n + offset;
ui.label(format!("counts up: {} gets", first.name));
if ui
.add(DragValue::new(&mut start).range(1..=i32::from(max) + 1))
.changed()
{
current = Numbering::Counting {
offset: start - first.n,
};
changed = true;
}
}
_ => {
let mut shown = u16::from(*value) + 1;
if ui
.add(DragValue::new(&mut shown).range(1..=u16::from(max) + 1))
.changed()
{
*value = u8::try_from(shown - 1).unwrap_or(max);
changed = true;
}
}
}
if let Some(first) = group {
let mut counting = matches!(current, Numbering::Counting { .. });
if ui
.checkbox(&mut counting, "counts up")
.on_hover_text(format!(
"Each control gets its own number: {} gets the number shown, the next one more",
first.name
))
.changed()
{
current = if counting {
Numbering::Counting {
offset: i32::from(*value) + 1 - first.n,
}
} else {
Numbering::Same
};
changed = true;
}
}
});
if changed {
numbering.0.retain(|(numbered, _)| *numbered != slot);
numbering.0.push((slot, current));
}
changed
}
pub(crate) fn target_picker(
ui: &mut Ui,
target: &mut MidiSceneTarget,
numbering: &mut SlotNumbering,
group: Option<GroupFirst>,
) -> bool {
let mut changed = false;
ui.horizontal_wrapped(|ui| {
let options = [
("The selected scene", MidiSceneTarget::Selected),
("In the selected row", MidiSceneTarget::Row { col: 0 }),
("In the selected column", MidiSceneTarget::Column { row: 0 }),
("Grid position", MidiSceneTarget::Grid { row: 0, col: 0 }),
("Quick scene", MidiSceneTarget::Quick { index: 0 }),
];
for (label, option) in options {
let same_kind = std::mem::discriminant(target) == std::mem::discriminant(&option);
if ui.selectable_label(same_kind, label).clicked() && !same_kind {
*target = keep_numbers(target, option);
changed = true;
}
}
});
match target {
MidiSceneTarget::Selected => {
ui.weak("Follows the scene selected in gled");
}
MidiSceneTarget::Row { col } => {
changed |= counted_number(ui, "Column", col, Slot::Column, numbering, group, 63);
}
MidiSceneTarget::Column { row } => {
changed |= counted_number(ui, "Row", row, Slot::Row, numbering, group, 63);
}
MidiSceneTarget::Grid { row, col } => {
changed |= counted_number(ui, "Row", row, Slot::Row, numbering, group, 63);
changed |= counted_number(ui, "Column", col, Slot::Column, numbering, group, 63);
}
MidiSceneTarget::Quick { index } => {
changed |= counted_number(ui, "Quick scene", index, Slot::Quick, numbering, group, 63);
}
}
changed
}
fn keep_numbers(current: &MidiSceneTarget, option: MidiSceneTarget) -> MidiSceneTarget {
let (row, col) = match current {
MidiSceneTarget::Grid { row, col } => (*row, *col),
MidiSceneTarget::Row { col } => (0, *col),
MidiSceneTarget::Column { row } => (*row, 0),
MidiSceneTarget::Quick { index } => (0, *index),
MidiSceneTarget::Selected => (0, 0),
};
match option {
MidiSceneTarget::Row { .. } => MidiSceneTarget::Row { col },
MidiSceneTarget::Column { .. } => MidiSceneTarget::Column { row },
MidiSceneTarget::Grid { .. } => MidiSceneTarget::Grid { row, col },
MidiSceneTarget::Quick { .. } => MidiSceneTarget::Quick { index: col },
MidiSceneTarget::Selected => MidiSceneTarget::Selected,
}
}
pub(crate) fn effect_picker(
ui: &mut Ui,
effect: &mut MidiEffectTarget,
numbering: &mut SlotNumbering,
group: Option<GroupFirst>,
) -> bool {
let mut changed = false;
ui.horizontal(|ui| {
let selected = *effect == MidiEffectTarget::Selected;
if ui
.selectable_label(selected, "The selected animation")
.clicked()
&& !selected
{
*effect = MidiEffectTarget::Selected;
changed = true;
}
if ui.selectable_label(!selected, "Animation number").clicked() && selected {
*effect = MidiEffectTarget::Index { index: 0 };
changed = true;
}
});
if let MidiEffectTarget::Index { index } = effect {
changed |= counted_number(ui, "Animation", index, Slot::Effect, numbering, group, 31);
}
changed
}
pub(crate) fn setting_picker(
ui: &mut Ui,
setting: &mut u8,
numbering: &mut SlotNumbering,
group: Option<GroupFirst>,
) -> bool {
let changed = counted_number(ui, "Setting", setting, Slot::Setting, numbering, group, 5);
ui.weak("The animation's own sliders, in the order its editor shows them");
changed
}
pub(crate) fn animation_picker(
ui: &mut Ui,
index: &mut u8,
numbering: &mut SlotNumbering,
group: Option<GroupFirst>,
) -> bool {
counted_number(
ui,
"Animation",
index,
Slot::Animation,
numbering,
group,
31,
)
}
pub(crate) fn mode_picker(ui: &mut Ui, mode: &mut u8, modes: &[MidiMode]) -> bool {
let mut changed = false;
if modes.is_empty() {
ui.weak("No modes yet — add one in the Modes tab");
}
for (index, entry) in modes.iter().enumerate() {
let index = u8::try_from(index).unwrap_or(u8::MAX);
if ui.selectable_label(*mode == index, &entry.name).clicked() && *mode != index {
*mode = index;
changed = true;
}
}
changed
}
pub(crate) fn encoder_picker(ui: &mut Ui, encoder: &mut MidiEncoder) -> bool {
let mut changed = false;
ui.horizontal(|ui| {
ui.label("Clicks per step");
changed |= ui
.add(DragValue::new(&mut encoder.steps_per_unit).range(1..=64))
.changed();
ui.weak("higher = slower");
});
ui.horizontal(|ui| {
ui.label("The encoder sends");
for encoding in MidiRelativeEncoding::iter() {
let label = match encoding {
MidiRelativeEncoding::TwosComplement => "127 for one click left",
MidiRelativeEncoding::SignMagnitude => "65 for one click left",
};
if ui
.selectable_label(encoder.encoding == encoding, label)
.on_hover_text(encoding.to_string())
.clicked()
&& encoder.encoding != encoding
{
encoder.encoding = encoding;
changed = true;
}
}
});
changed
}
pub(crate) fn beat_picker(
ui: &mut Ui,
start: &mut f32,
end: &mut f32,
every_beat: &mut bool,
blackout_blink: &mut Option<u8>,
) -> bool {
let mut changed = false;
ui.horizontal_wrapped(|ui| {
for (label, preset) in [
("Every beat, first half", (0.0, 0.5, true)),
("Once per bar: beat 1", (0.0, 1.0, false)),
("First half of the bar", (0.0, 2.0, false)),
] {
let current = (*start, *end, *every_beat) == preset;
if ui.selectable_label(current, label).clicked() && !current {
(*start, *end, *every_beat) = preset;
changed = true;
}
}
});
ui.horizontal(|ui| {
changed |= ui.radio_value(every_beat, true, "every beat").changed();
changed |= ui
.radio_value(every_beat, false, "once per bar (4 beats)")
.changed();
});
let period = if *every_beat { 1.0 } else { 4.0 };
ui.horizontal(|ui| {
ui.label("Lit from");
changed |= ui
.add(DragValue::new(start).range(0.0..=period).speed(0.05))
.changed();
ui.label("to");
changed |= ui
.add(DragValue::new(end).range(0.0..=period).speed(0.05))
.changed();
ui.weak(if *every_beat {
"of each beat"
} else {
"beats into the bar"
});
});
let mut blink = blackout_blink.is_some();
if ui.checkbox(&mut blink, "Blink during blackout").changed() {
*blackout_blink = blink.then_some(127);
changed = true;
}
changed
}
pub(crate) fn blackout_picker(ui: &mut Ui, inverted: &mut bool, blink: &mut bool) -> bool {
let mut changed = false;
changed |= ui
.radio_value(inverted, false, "Lit during blackout")
.changed();
changed |= ui
.radio_value(inverted, true, "Lit when not in blackout")
.changed();
changed |= ui.checkbox(blink, "Blinking").changed();
changed
}
pub(crate) fn parameter_picker(ui: &mut Ui, parameter: &mut MidiEffectParameter) -> bool {
let mut changed = false;
for option in MidiEffectParameter::iter() {
if ui
.selectable_label(*parameter == option, option.to_string().to_lowercase())
.clicked()
&& *parameter != option
{
*parameter = option;
changed = true;
}
}
changed
}