use std::collections::BTreeMap;
use bevy::input_focus::{FocusCause, InputFocus};
use bevy::prelude::*;
use bevy::ui::InteractionDisabled;
use bevy::ui_widgets::{Activate, ValueChange};
use crate::skin::Role;
#[derive(Component, Clone, Copy, Debug, Default, PartialEq, Eq, Reflect)]
#[reflect(Component, Default, Debug, PartialEq, Clone)]
pub struct SectionOpen(pub bool);
#[derive(Component, Clone, Debug, PartialEq, Eq, Hash, Reflect)]
#[reflect(Component, Debug, PartialEq, Hash, Clone)]
pub struct SectionId(pub String);
impl SectionId {
pub fn new(id: impl Into<String>) -> Self {
Self(id.into())
}
}
#[derive(Resource, Clone, Debug, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SectionStates {
open: BTreeMap<String, bool>,
}
impl SectionStates {
pub fn open(&self, id: &str) -> Option<bool> {
self.open.get(id).copied()
}
pub fn set(&mut self, id: impl Into<String>, open: bool) {
self.open.insert(id.into(), open);
}
pub fn clear(&mut self) {
self.open.clear();
}
}
#[derive(Component, Clone, Copy, Debug, Default)]
pub struct SectionHeader;
pub(crate) fn header_on_activate(
activate: On<Activate>,
parents: Query<&ChildOf>,
sections: Query<&SectionOpen, Without<InteractionDisabled>>,
mut commands: Commands,
) {
let Ok(parent) = parents.get(activate.entity) else {
return;
};
if let Ok(open) = sections.get(parent.parent()) {
commands.trigger(ValueChange {
source: parent.parent(),
value: !open.0,
is_final: true,
});
}
}
pub(crate) fn section_self_update(
change: On<ValueChange<bool>>,
mut sections: Query<&mut SectionOpen>,
) {
if let Ok(mut open) = sections.get_mut(change.source) {
open.set_if_neq(SectionOpen(change.value));
}
}
#[allow(clippy::too_many_arguments)]
pub fn update_sections(
mut sections: Query<(&mut SectionOpen, Option<&SectionId>, &Children)>,
headers: Query<&Children, With<SectionHeader>>,
roles: Query<&Role>,
bodies: Query<Option<&Children>, With<Role>>,
mut nodes: Query<&mut Node>,
mut visibility: Query<&mut Visibility>,
parents: Query<&ChildOf>,
states: Option<ResMut<SectionStates>>,
mut focus: ResMut<InputFocus>,
) {
let mut states = states;
for (mut open, id, children) in &mut sections {
if let (Some(id), Some(states)) = (id, states.as_deref_mut()) {
if open.is_added() {
match states.open(&id.0) {
Some(saved) if saved != open.0 => open.0 = saved,
Some(_) => {}
None => states.set(id.0.clone(), open.0),
}
} else if open.is_changed() && states.open(&id.0) != Some(open.0) {
states.set(id.0.clone(), open.0);
}
}
let is_open = open.0;
let body = children
.iter()
.find(|&child| roles.get(child) == Ok(&Role::SectionBody));
let empty = body.is_some_and(|body| {
bodies
.get(body)
.is_ok_and(|kids| kids.is_none_or(|kids| kids.is_empty()))
});
for &child in children {
if let Ok(header) = headers.get(child) {
for &part in header {
let shown = match roles.get(part) {
Ok(Role::TreeClosed) => !is_open,
Ok(Role::TreeOpen) => is_open,
_ => continue,
};
show(part, shown, &mut nodes, None);
}
} else {
match roles.get(child) {
Ok(Role::SectionBody) => {
show(child, is_open, &mut nodes, Some(&mut visibility))
}
Ok(Role::SectionNote) => show(child, is_open && empty, &mut nodes, None),
_ => {}
}
}
}
if !is_open
&& let Some(body) = body
&& let Some(focused) = focus.get()
&& parents.iter_ancestors(focused).any(|a| a == body)
&& let Some(header) = children.iter().find(|&c| headers.contains(c))
{
focus.set(header, FocusCause::Navigated);
}
}
}
fn show(
entity: Entity,
shown: bool,
nodes: &mut Query<&mut Node>,
visibility: Option<&mut Query<&mut Visibility>>,
) {
if let Ok(mut node) = nodes.get_mut(entity) {
let display = if shown { Display::Flex } else { Display::None };
if node.display != display {
node.display = display;
}
}
if let Some(visibility) = visibility
&& let Ok(mut visible) = visibility.get_mut(entity)
{
let want = if shown {
Visibility::Inherited
} else {
Visibility::Hidden
};
if *visible != want {
*visible = want;
}
}
}