use bevy_ecs::hierarchy::ChildOf;
use bevy_ecs::prelude::{Commands, Component, Entity};
use bevy_input_focus::tab_navigation::TabGroup;
use bevy_ui::{FlexDirection, Node, Val};
use plurimus::core::UiWidget;
use plurimus::widgets::button;
use plurimus::widgets::ratatui_widgets::paragraph::Paragraph;
use super::domain::{FieldSpec, Ops};
use super::field;
use super::form::{handle_button, handle_form_key};
use super::group::{Dependent, gate_of};
use crate::commands::tui::layout::{
Emphasis, button_node, fixed, growing, placed, rule, sized, spawn_button_row,
};
use crate::commands::tui::overlay::{self, Centred};
use crate::commands::tui::scope::KeyScope;
#[derive(Component)]
pub struct EditForm {
pub ops: Ops,
}
#[derive(Component, Clone, Copy)]
#[require(KeyScope = KeyScope::Plain)]
pub struct FormField {
pub spec: FieldSpec,
}
#[derive(Component, Clone, Copy, PartialEq, Eq, Debug)]
#[require(KeyScope = KeyScope::Plain)]
pub enum FormButton {
Discard,
Apply,
}
pub const FORM_COLS: u16 = 67;
const LABEL_MARGIN: u16 = 3;
const HELP_ROWS: u16 = 3;
const FOOT_ROWS: u16 = 3;
const FRAME_COLS: usize = 2;
const WRAP_SLACK: usize = 10;
pub const fn help_fits(ops: Ops) -> bool {
let room = (FORM_COLS as usize - FRAME_COLS - WRAP_SLACK) * HELP_ROWS as usize;
let mut at = 0;
while at < ops.fields.len() {
let spec = ops.fields[at];
if spec.help.is_empty() || spec.help.len() > room {
return false;
}
at += 1;
}
true
}
pub fn centred(ops: Ops) -> (Node, Centred) {
overlay::centred(FORM_COLS, rows_of(ops))
}
pub(super) const fn rows_of(ops: Ops) -> u16 {
(ops.fields.len() as u16).saturating_add(HELP_ROWS + FOOT_ROWS)
}
const GUTTER: &str = " │ ";
const GUTTER_COLS: u16 = 3;
fn gutter_cols(fields: &[FieldSpec], spec: &FieldSpec) -> u16 {
gate_of(fields, spec.key).map_or(0, |_| GUTTER_COLS)
}
fn label_cols(fields: &[FieldSpec]) -> u16 {
let cols = fields
.iter()
.map(|spec| spec.label.len() as u16 + gutter_cols(fields, spec));
cols.max().unwrap_or(0).saturating_add(LABEL_MARGIN)
}
#[derive(Component, Clone, Copy)]
pub struct FieldLabel {
pub key: &'static str,
pub label: &'static str,
pub is_marked: bool,
}
#[derive(Component)]
pub struct HelpFoot;
pub fn spawn_form(commands: &mut Commands, form: Entity, ops: Ops, is_alone: bool) {
let label_cols = label_cols(ops.fields);
commands
.entity(form)
.insert((EditForm { ops }, TabGroup::modal()))
.observe(handle_form_key);
for &spec in ops.fields {
let gutter = gutter_cols(ops.fields, &spec);
let row = spawn_row(commands, form, spec, gutter, label_cols - gutter);
if let Some(dependent) = Dependent::of(ops.fields, &spec) {
commands.entity(row).insert(dependent);
}
}
commands.spawn((growing(), ChildOf(form)));
commands.spawn((rule(), ChildOf(form)));
commands.spawn((
HelpFoot,
fixed(f32::from(HELP_ROWS)),
UiWidget::new(Paragraph::new("")),
placed(),
ChildOf(form),
));
commands.spawn((rule(), ChildOf(form)));
spawn_buttons(commands, form, ops.actions, is_alone);
}
fn spawn_row(
commands: &mut Commands,
form: Entity,
spec: FieldSpec,
gutter: u16,
label_cols: u16,
) -> Entity {
let row = commands
.spawn((
Node {
flex_direction: FlexDirection::Row,
..fixed(1.0)
},
ChildOf(form),
))
.id();
if gutter > 0 {
commands.spawn((
sized(f32::from(gutter), 1.0),
UiWidget::new(Paragraph::new(GUTTER)),
placed(),
ChildOf(row),
));
}
commands.spawn((
FieldLabel {
key: spec.key,
label: spec.label,
is_marked: false,
},
sized(f32::from(label_cols), 1.0),
UiWidget::new(Paragraph::new(spec.label)),
placed(),
ChildOf(row),
));
let value = commands
.spawn((
Node {
flex_direction: FlexDirection::Row,
min_width: Val::Px(0.0),
..growing()
},
ChildOf(row),
))
.id();
field::spawn_field(commands, value, spec);
row
}
fn spawn_buttons(
commands: &mut Commands,
form: Entity,
actions: [&'static str; 2],
is_alone: bool,
) {
let row = spawn_button_row(commands, form);
for (which, label) in [FormButton::Discard, FormButton::Apply]
.into_iter()
.zip(actions)
.filter(|(which, _)| !(is_alone && *which == FormButton::Discard))
{
let mut spawned = commands.spawn((
button(label),
which,
button_node(label),
placed(),
ChildOf(row),
));
spawned.observe(handle_button);
if which == FormButton::Apply {
spawned.insert(Emphasis::Primary);
}
}
}