use egui::{Popup, PopupCloseBehavior, RichText, Ui};
use egui_phosphor_icons::icons;
use gled_model::asset::midi_controller::{MidiColorSource, MidiOutputBindingKind, MidiValueSource};
use super::intent::Intent;
use super::pickers::target_picker;
use super::rule_card::{Cards, Shown};
use super::rules::{
FeedbackRole, Place, TokenKind, ValueKind, feedback_role, roles_for, source_sentence,
target_text,
};
use super::token_editors::source_token_editor;
use super::widgets::token::{sentence, token, token_color};
pub(crate) fn feedback_lines(ui: &mut Ui, cards: &mut Cards, shown: &Shown) {
let first = shown.first().clone();
let mapping = cards.mapping;
for (position, &output) in first.feedback.iter().enumerate() {
let kind = &mapping.output_bindings[output].kind;
let role = feedback_role(cards.controls, &first, kind);
let line = ui.horizontal_wrapped(|ui| {
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
ui.add_space(22.0);
ui.weak(RichText::new(role.label()).color(ui.visuals().weak_text_color()));
match kind {
MidiOutputBindingKind::SceneColorValue(color) => {
pad_color_line(ui, cards, shown, position, color);
}
kind => {
if let Some(source) = kind.source() {
source_line(ui, cards, shown, position, source, role);
}
}
}
if ui
.small_button(icons::X)
.on_hover_text(format!("Remove the {}", role.name().to_lowercase()))
.clicked()
{
cards.intents.push(Intent::RemoveFeedback {
members: shown.members.clone(),
position,
});
}
});
if ui.rect_contains_pointer(line.response.rect) {
cards.previewed.extend(
shown
.members
.rules
.iter()
.filter_map(|rule| rule.feedback.get(position)),
);
}
}
add_feedback_button(ui, cards, shown);
}
fn source_line(
ui: &mut Ui,
cards: &mut Cards,
shown: &Shown,
position: usize,
source: &MidiValueSource,
role: FeedbackRole,
) {
let mapping = cards.mapping;
let place = Place::Feedback(position);
let numbers = shown.numbers(place);
let pieces = source_sentence(source, &mapping.modes, numbers.as_ref());
let prefer = if matches!(role, FeedbackRole::Light | FeedbackRole::PadColor) {
ValueKind::OnOff
} else {
ValueKind::Level
};
let numbering = shown.numbering(place);
for (kind, response) in sentence(ui, &pieces) {
let edited = source_token_editor(
&response,
kind,
source,
&numbering,
&mapping.modes,
prefer,
shown.group_first(cards.controls),
);
if let Some((source, numbering)) = edited {
let numbering = if kind == TokenKind::Main {
shown.numbering(place)
} else {
numbering
};
cards.intents.push(Intent::SetSource {
members: shown.members.clone(),
position,
source,
numbering,
});
}
}
}
fn pad_color_line(
ui: &mut Ui,
cards: &mut Cards,
shown: &Shown,
position: usize,
color: &gled_model::asset::midi_controller::MidiSceneColorValueOutput,
) {
let mapping = cards.mapping;
let MidiColorSource::SceneColor { target } = &color.source;
let numbers = shown.numbers(Place::Feedback(position));
let text = target_text(target, numbers.as_ref());
let response = token(ui, &text, token_color(TokenKind::Target));
let numbering = shown.numbering(Place::Feedback(position));
let group_first = shown.group_first(cards.controls);
Popup::menu(&response)
.close_behavior(PopupCloseBehavior::CloseOnClickOutside)
.show(|ui| {
let mut edited = target.clone();
let mut numbering = numbering.clone();
if target_picker(ui, &mut edited, &mut numbering, group_first) {
for rule in &shown.members.rules {
if let Some(&output) = rule.feedback.get(position)
&& let MidiOutputBindingKind::SceneColorValue(mut color) =
mapping.output_bindings[output].kind.clone()
{
color.source = MidiColorSource::SceneColor {
target: edited.clone(),
};
cards.intents.push(Intent::SetFeedbackKind {
output,
kind: MidiOutputBindingKind::SceneColorValue(color),
});
}
}
}
});
ui.label("using");
let name = if color.mapping_name.is_empty() {
"no color mapping"
} else {
&color.mapping_name
};
let response = token(ui, name, token_color(TokenKind::Parameter));
Popup::menu(&response)
.close_behavior(PopupCloseBehavior::CloseOnClickOutside)
.show(|ui| {
if mapping.color_mappings.is_empty() {
ui.weak("No color mappings yet — add one in the Colors tab");
}
for named in &mapping.color_mappings {
if ui
.selectable_label(named.name == color.mapping_name, &named.name)
.clicked()
{
for rule in &shown.members.rules {
if let Some(&output) = rule.feedback.get(position)
&& let MidiOutputBindingKind::SceneColorValue(mut color) =
mapping.output_bindings[output].kind.clone()
{
color.mapping_name = named.name.clone();
cards.intents.push(Intent::SetFeedbackKind {
output,
kind: MidiOutputBindingKind::SceneColorValue(color),
});
}
}
ui.close();
}
}
});
}
fn add_feedback_button(ui: &mut Ui, cards: &mut Cards, shown: &Shown) {
let first = shown.first();
let control = &cards.controls[first.control];
let present: Vec<FeedbackRole> = first
.feedback
.iter()
.map(|&output| {
feedback_role(
cards.controls,
first,
&cards.mapping.output_bindings[output].kind,
)
})
.collect();
let roles: Vec<FeedbackRole> = roles_for(control.kind)
.iter()
.copied()
.filter(|role| !present.contains(role) || *role == FeedbackRole::Value)
.collect();
ui.horizontal(|ui| {
ui.add_space(22.0);
let suggested = roles
.first()
.copied()
.filter(|role| *role != FeedbackRole::Value);
if let Some(role) = suggested
&& ui
.small_button(format!(
"+ {} for {}",
role.name().to_lowercase(),
control.name
))
.on_hover_text(role.description())
.clicked()
{
cards.intents.push(Intent::AddFeedback {
members: shown.members.clone(),
role,
});
}
let more = ui.small_button("+ other feedback");
Popup::menu(&more).show(|ui| {
for role in roles_for_any() {
if ui
.button(role.name())
.on_hover_text(role.description())
.clicked()
{
cards.intents.push(Intent::AddFeedback {
members: shown.members.clone(),
role,
});
}
}
});
});
}
fn roles_for_any() -> [FeedbackRole; 6] {
[
FeedbackRole::Light,
FeedbackRole::PadColor,
FeedbackRole::MotorFader,
FeedbackRole::Meter,
FeedbackRole::Ring,
FeedbackRole::Value,
]
}