use crate::app::art::Art;
use crate::app::company::{self, Perch};
use crate::app::cycle::Cycle;
use crate::app::i18n::{ALL_LANGS, Lang};
use crate::app::settings::GameSettings;
use crate::app::{Screen, menu_ui, palette};
use bevy::prelude::*;
#[derive(Component)]
pub struct LanguageUi;
#[derive(Component)]
pub struct LanguageRow(pub usize);
#[derive(Component)]
pub struct LanguageNote;
const FLAG_W: f32 = 33.0;
const FLAG_H: f32 = 22.0;
const ROW_FONT: f32 = 22.0;
const NAME_W: f32 = 190.0;
#[cfg(test)]
const CARD_W: f32 = FLAG_W + 12.0 + NAME_W + 2.0 * 22.0;
pub fn enter_language(mut commands: Commands, settings: Res<GameSettings>, art: Res<Art>) {
commands
.spawn((LanguageUi, menu_ui::between_bars()))
.with_children(|wrap| {
company::flock(wrap, &art, &FLOCK);
wrap.spawn(Node {
align_items: AlignItems::Center,
column_gap: Val::Px(company::CRITTER_GAP),
..default()
})
.with_children(|line| {
company::shoulder(line, &art, false, 0.0);
line.spawn(menu_ui::screen_card()).with_children(|card| {
for (index, lang) in ALL_LANGS.iter().enumerate() {
card.spawn((LanguageRow(index), menu_ui::card_row()))
.with_children(|row| {
row.spawn((
ImageNode::new(art.flag(*lang)),
Node {
width: Val::Px(FLAG_W),
height: Val::Px(FLAG_H),
flex_shrink: 0.0,
margin: UiRect::right(Val::Px(12.0)),
..default()
},
));
row.spawn((
Text::new(lang.native_name()),
TextFont {
font_size: FontSize::Px(ROW_FONT),
..default()
},
TextLayout::no_wrap(),
TextColor(palette::IDLE_ROW),
Node {
width: Val::Px(NAME_W),
..default()
},
));
});
}
});
company::shoulder(line, &art, true, 1.7);
});
wrap.spawn((
LanguageNote,
Text::new(settings.tr().pick_language_later),
TextFont {
font_size: FontSize::Px(15.0),
..default()
},
TextLayout::no_wrap(),
TextColor(palette::PARCHMENT.with_alpha(0.40)),
));
});
}
const FLOCK: [Perch; 7] = [
(0.09, 0.13, 46.0, true),
(0.25, 0.04, 30.0, true),
(0.88, 0.09, 40.0, true),
(0.70, 0.02, 26.0, true),
(0.12, 0.80, 42.0, false),
(0.35, 0.92, 28.0, false),
(0.86, 0.84, 48.0, false),
];
pub fn opening_screen(saved: bool) -> Screen {
if saved {
Screen::default()
} else {
Screen::Language
}
}
pub fn may_save(screen: &Screen) -> bool {
!matches!(screen, Screen::Language)
}
fn step(keys: &ButtonInput<KeyCode>, current: Lang) -> (Lang, bool) {
let at = menu_ui::nav(keys, current.index(), ALL_LANGS.len());
let taken = keys.just_pressed(KeyCode::Enter) || keys.just_pressed(KeyCode::NumpadEnter);
(ALL_LANGS[at], taken)
}
pub fn language_input(
keys: Res<ButtonInput<KeyCode>>,
mut settings: ResMut<GameSettings>,
mut next_screen: ResMut<NextState<Screen>>,
) {
let (lang, taken) = step(&keys, settings.language);
if lang != settings.language {
settings.set_language(lang);
}
if taken {
settings.save();
next_screen.set(Screen::Menu);
}
}
pub fn update_language_ui(
settings: Res<GameSettings>,
mut rows: Query<(&LanguageRow, &mut BackgroundColor, &Children)>,
mut text: Query<(&mut TextColor, &mut Node)>,
mut note: Query<&mut Text, With<LanguageNote>>,
) {
for mut line in &mut note {
menu_ui::set_text(&mut line, settings.tr().pick_language_later);
}
let picked = settings.language.index();
for (row, mut fill, children) in &mut rows {
let on = row.0 == picked;
menu_ui::set_bg(&mut fill, menu_ui::band(on));
for child in children {
let Ok((mut colour, _)) = text.get_mut(*child) else {
continue;
};
menu_ui::set_color(
&mut colour,
if on {
palette::SELECTED_ROW
} else {
palette::IDLE_ROW
},
);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn pressing(key: KeyCode) -> ButtonInput<KeyCode> {
let mut keys = ButtonInput::default();
keys.press(key);
keys
}
#[test]
fn walking_down_reaches_every_language() {
let mut at = Lang::default();
let mut seen = vec![at];
for _ in 1..ALL_LANGS.len() {
at = step(&pressing(KeyCode::KeyS), at).0;
seen.push(at);
}
for lang in ALL_LANGS {
assert!(seen.contains(&lang), "{lang:?} cannot be walked to");
}
assert_eq!(step(&pressing(KeyCode::KeyS), at).0, Lang::default());
}
#[test]
fn walking_up_wraps_to_the_end() {
let up = step(&pressing(KeyCode::KeyW), Lang::default()).0;
assert_eq!(up, *ALL_LANGS.last().unwrap());
}
#[test]
fn only_enter_takes_it() {
for key in [
KeyCode::KeyW,
KeyCode::KeyS,
KeyCode::ArrowUp,
KeyCode::Escape,
] {
assert!(!step(&pressing(key), Lang::default()).1, "{key:?} took it");
}
for key in [KeyCode::Enter, KeyCode::NumpadEnter] {
let (lang, taken) = step(&pressing(key), Lang::Es);
assert!(taken, "{key:?} did not take it");
assert_eq!(lang, Lang::Es);
}
}
#[test]
fn only_a_fresh_install_is_asked() {
assert_eq!(opening_screen(false), Screen::Language);
assert_eq!(opening_screen(true), Screen::default());
assert_ne!(Screen::default(), Screen::Language);
}
#[test]
fn the_flock_leaves_the_card_alone() {
company::flock_is_hung_clear(&FLOCK, company::keep_clear(CARD_W), (0.15, 0.80));
}
#[test]
fn the_default_language_is_on_the_list() {
assert!(ALL_LANGS.contains(&Lang::default()));
assert_eq!(ALL_LANGS[Lang::default().index()], Lang::default());
}
}