use bevy::input::ButtonInput;
use bevy::input_focus::tab_navigation::TabIndex;
use bevy::input_focus::{FocusCause, InputFocus, InputFocusVisible};
use bevy::prelude::*;
use bevy::ui::Selected;
use bevy::ui_widgets::{SelectedTab, ValueChange};
use crate::model::tab_step;
use crate::nav::{FocusScopes, show_focus};
use crate::skin::Role;
#[derive(Component, Clone, Copy, Debug, Default, PartialEq, Eq, Reflect)]
#[reflect(Component, Default, Debug, PartialEq, Clone)]
pub struct TabsValue(pub usize);
#[derive(Component, Clone, Copy, Debug, Default, PartialEq, Eq, Reflect)]
#[reflect(Component, Default, Debug, PartialEq, Clone)]
pub struct TabsCount(pub usize);
pub(crate) fn tabs_on_select(
change: On<ValueChange<Option<Entity>>>,
strips: Query<(&ChildOf, &Children)>,
values: Query<&TabsValue>,
mut commands: Commands,
) {
let Some(tab) = change.value else {
return;
};
let Ok((strip, tabs)) = strips.get(change.source) else {
return;
};
let root = strip.parent();
let Some(index) = tabs.iter().position(|t| t == tab) else {
return;
};
if values.get(root).is_ok_and(|value| value.0 != index) {
commands.trigger(ValueChange {
source: root,
value: index,
is_final: true,
});
}
}
pub(crate) fn tabs_self_update(change: On<ValueChange<usize>>, mut values: Query<&mut TabsValue>) {
if let Ok(mut value) = values.get_mut(change.source) {
value.set_if_neq(TabsValue(change.value));
}
}
pub fn update_tabs(
mut roots: Query<(&mut TabsValue, &TabsCount, &Children), Changed<TabsValue>>,
roles: Query<&Role>,
children: Query<&Children>,
mut strips: Query<&mut SelectedTab>,
mut visibility: Query<&mut Visibility>,
lit: Query<(), With<Selected>>,
mut commands: Commands,
) {
for (mut value, count, kids) in &mut roots {
let last = count.0.saturating_sub(1);
if value.0 > last {
value.0 = last;
}
let part = |role: Role| kids.iter().find(|&kid| roles.get(kid) == Ok(&role));
if let Some(strip) = part(Role::TabList)
&& let Ok(tabs) = children.get(strip)
{
let chosen = tabs.get(value.0).copied();
if let Ok(mut selection) = strips.get_mut(strip) {
selection.set_if_neq(SelectedTab(chosen));
}
for &tab in tabs {
match (Some(tab) == chosen, lit.contains(tab)) {
(true, false) => {
commands.entity(tab).insert(Selected);
}
(false, true) => {
commands.entity(tab).remove::<Selected>();
}
_ => {}
}
}
}
if let Some(container) = part(Role::TabPages)
&& let Ok(pages) = children.get(container)
{
for (index, page) in pages.iter().enumerate() {
if let Ok(mut page) = visibility.get_mut(page) {
page.set_if_neq(if index == value.0 {
Visibility::Inherited
} else {
Visibility::Hidden
});
}
}
}
}
}
pub fn step_tabs(
keys: Res<ButtonInput<KeyCode>>,
gamepads: Query<&Gamepad>,
mut focus: ResMut<InputFocus>,
mut shown: ResMut<InputFocusVisible>,
scopes: FocusScopes,
mut commands: Commands,
) {
let pad = |button: GamepadButton| gamepads.iter().any(|g| g.just_pressed(button));
let ctrl = keys.any_pressed([KeyCode::ControlLeft, KeyCode::ControlRight]);
let step = |plus: bool, minus: bool| i32::from(plus) - i32::from(minus);
let mut delta = step(
pad(GamepadButton::RightTrigger),
pad(GamepadButton::LeftTrigger),
);
if ctrl {
delta += step(
keys.just_pressed(KeyCode::PageDown),
keys.just_pressed(KeyCode::PageUp),
);
}
let Some(from) = focus.get() else {
return;
};
let on_tab = scopes.is_tab(from);
if on_tab {
delta += step(pad(GamepadButton::DPadRight), pad(GamepadButton::DPadLeft));
}
let delta = delta.clamp(-1, 1);
if delta == 0 {
return;
}
let Some((root, selected, count)) = scopes.tabs_for(from).and_then(|root| {
scopes
.tabs_state(root)
.map(|(value, count)| (root, value, count))
}) else {
return;
};
show_focus(&mut shown);
let to = tab_step(selected, count, delta);
if to == selected {
return;
}
commands.trigger(ValueChange {
source: root,
value: to,
is_final: true,
});
if on_tab && let Some(tab) = scopes.tab_at(root, to) {
focus.set(tab, FocusCause::Navigated);
}
}
pub(crate) type StopQuery = (
Option<&'static TabIndex>,
Option<&'static InheritedVisibility>,
Option<&'static Children>,
);
pub fn land_tab_focus(
mut focus: ResMut<InputFocus>,
parents: Query<&ChildOf>,
roles: Query<&Role>,
children: Query<&Children>,
values: Query<(&TabsValue, &TabsCount)>,
visible: Query<&InheritedVisibility>,
stops: Query<StopQuery>,
) {
let Some(from) = focus.get() else {
return;
};
let Some(page) = parents
.iter_ancestors(from)
.find(|&a| roles.get(a) == Ok(&Role::TabPage))
else {
return;
};
let Some(container) = parents.get(page).ok().map(ChildOf::parent) else {
return;
};
let Some(root) = parents.get(container).ok().map(ChildOf::parent) else {
return;
};
let (Ok((value, count)), Ok(pages)) = (values.get(root), children.get(container)) else {
return;
};
let chosen = value.0.min(count.0.saturating_sub(1));
if pages.get(chosen).is_none_or(|&shown| shown == page) {
return;
}
let Some(&shown) = pages.get(chosen) else {
return;
};
if !visible.get(shown).is_ok_and(|v| v.get()) {
return;
}
let to = first_stop(shown, &stops).or_else(|| {
let strip = children
.get(root)
.ok()?
.iter()
.find(|&kid| roles.get(kid) == Ok(&Role::TabList))?;
children.get(strip).ok()?.get(chosen).copied()
});
if let Some(to) = to {
focus.set(to, FocusCause::Navigated);
}
}
pub(crate) fn first_stop(page: Entity, stops: &Query<StopQuery>) -> Option<Entity> {
let mut best: Option<(i32, Entity)> = None;
let mut pending = vec![page];
while let Some(entity) = pending.pop() {
let Ok((index, visible, kids)) = stops.get(entity) else {
continue;
};
if visible.is_some_and(|v| !v.get()) {
continue;
}
if let Some(index) = index.filter(|index| index.0 >= 0)
&& best.is_none_or(|(lowest, _)| index.0 < lowest)
{
best = Some((index.0, entity));
}
if let Some(kids) = kids {
pending.extend(kids.iter().rev());
}
}
best.map(|(_, entity)| entity)
}