/*
* This file is part of laurelin_client
* Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
*
* Licensed under GPL-3.0-only.
* See LICENSE for licensing information.
*/
use bevy::{
a11y::{accesskit::NodeBuilder, AccessibilityNode},
input::mouse::{MouseScrollUnit, MouseWheel},
prelude::*,
};
use crate::{game_status::LogSection, plugins::GameData, AppState};
pub struct LogPlugin;
impl Plugin for LogPlugin {
fn build(&self, app: &mut App) {
app.add_event::<ToggleLogEvent>()
.add_event::<UpdateLogEvent>()
.add_system(setup_log.in_schedule(OnEnter(AppState::InGame)))
.add_system(update_log_button)
.add_system(toggle_log.run_if(on_event::<ToggleLogEvent>()))
.add_system(update_log.run_if(on_event::<UpdateLogEvent>()))
.add_system(log_mouse_scroll);
}
}
struct ToggleLogEvent;
pub struct UpdateLogEvent;
const NORMAL_BUTTON: Color = Color::rgb(0.15, 0.15, 0.15);
const HOVERED_BUTTON: Color = Color::rgb(0.25, 0.25, 0.25);
const PRESSED_BUTTON: Color = Color::rgb(0.35, 0.75, 0.35);
#[derive(Component)]
struct LogButton;
#[derive(Component)]
struct LogListParent;
#[derive(Component)]
struct LogList;
#[derive(Component)]
struct LogListEntry;
fn setup_log(mut commands: Commands, asset_server: Res<AssetServer>) {
let font = asset_server.load("fonts/FiraMono-Bold.ttf");
// log toggle button
commands
.spawn(NodeBundle {
style: Style {
position_type: PositionType::Absolute,
position: UiRect {
top: Val::Px(20.),
right: Val::Px(20.),
..Default::default()
},
..Default::default()
},
..Default::default()
})
.with_children(|parent| {
parent
.spawn((
ButtonBundle {
style: Style {
size: Size::new(Val::Px(75.), Val::Px(35.)),
// center child text
justify_content: JustifyContent::Center,
align_items: AlignItems::Center,
..Default::default()
},
background_color: NORMAL_BUTTON.into(),
..Default::default()
},
LogButton,
))
.with_children(|parent| {
parent.spawn((TextBundle::from_section(
"Log",
TextStyle {
font: font.clone(),
font_size: 20.,
color: Color::WHITE,
},
),));
});
});
// log window
commands
.spawn((
NodeBundle {
style: Style {
flex_direction: FlexDirection::Column,
justify_content: JustifyContent::Center,
align_items: AlignItems::Center,
size: Size {
width: Val::Px(475.),
height: Val::Percent(75.),
},
position_type: PositionType::Absolute,
position: UiRect {
bottom: Val::Percent(12.5),
right: Val::Px(100.),
..Default::default()
},
..Default::default()
},
background_color: Color::rgb(0.15, 0.15, 0.15).into(),
visibility: Visibility::Hidden,
..Default::default()
},
LogListParent,
))
.with_children(|parent| {
parent
.spawn(NodeBundle {
style: Style {
flex_direction: FlexDirection::Column,
//align_self: AlignSelf::Stretch,
size: Size {
width: Val::Px(450.),
height: Val::Percent(95.),
},
overflow: Overflow::Hidden,
..Default::default()
},
..Default::default()
})
.with_children(|parent| {
parent
.spawn((
NodeBundle {
style: Style {
flex_direction: FlexDirection::Column,
max_size: Size::UNDEFINED,
..Default::default()
},
..Default::default()
},
AccessibilityNode(NodeBuilder::new(bevy::a11y::accesskit::Role::List)),
ScrollingList::default(),
LogList,
))
.with_children(|parent| {
parent.spawn((
TextBundle::from_section(
"Empty".to_string(),
TextStyle {
font: font.clone(),
font_size: 20.,
color: Color::WHITE,
},
),
AccessibilityNode(NodeBuilder::new(
bevy::a11y::accesskit::Role::ListItem,
)),
LogListEntry,
));
});
});
});
}
fn update_log_button(
mut interaction_query: Query<
(&Interaction, &mut BackgroundColor),
(Changed<Interaction>, With<LogButton>),
>,
mut tl_ev_w: EventWriter<ToggleLogEvent>,
) {
for (interaction, mut color) in &mut interaction_query {
match interaction {
Interaction::Clicked => {
*color = PRESSED_BUTTON.into();
tl_ev_w.send(ToggleLogEvent);
}
Interaction::Hovered => {
*color = HOVERED_BUTTON.into();
}
Interaction::None => {
*color = NORMAL_BUTTON.into();
}
}
}
}
fn toggle_log(mut log_parent_query: Query<&mut Visibility, With<LogListParent>>) {
for mut visibility in &mut log_parent_query {
if *visibility == Visibility::Hidden {
*visibility = Visibility::Inherited;
} else {
*visibility = Visibility::Hidden;
}
}
}
fn update_log(
mut commands: Commands,
asset_server: Res<AssetServer>,
log_query: Query<Entity, With<LogList>>,
log_entries_query: Query<Entity, With<LogListEntry>>,
game_data: Res<GameData>,
) {
for entity in &log_entries_query {
commands.entity(entity).despawn_recursive();
}
for entity in &log_query {
let Some(status) = game_data.clone().game_status else {
return;
};
let font = asset_server.load("fonts/FiraMono-Regular.ttf");
let font_bold = asset_server.load("fonts/FiraMono-Bold.ttf");
let style = TextStyle {
font: font.clone(),
font_size: 20.,
color: Color::WHITE,
};
let index_spaces = status.actions.len().to_string().len();
for (i, log) in status.log.iter().enumerate() {
let spaces = " ".repeat(index_spaces - i.to_string().len());
let index_text = TextSection {
value: format!("{}{}. ", spaces, i),
style: TextStyle {
font: font_bold.clone(),
..style
},
};
let mut sections = vec![index_text];
for section in &log.sections {
match section {
LogSection::Normal(value) => {
sections.push(TextSection {
value: value.to_string(),
style: style.clone(),
});
}
LogSection::Bold(value) => {
sections.push(TextSection {
value: value.to_string(),
style: TextStyle {
font: font_bold.clone(),
..style.clone()
},
});
}
}
}
commands
.spawn((
TextBundle::from_sections(sections).with_style(Style {
max_size: Size {
width: Val::Px(450.),
height: Val::Undefined,
},
..Default::default()
}),
AccessibilityNode(NodeBuilder::new(bevy::a11y::accesskit::Role::ListItem)),
LogListEntry,
))
.set_parent(entity);
}
}
}
#[derive(Component, Default)]
struct ScrollingList {
position: f32,
}
fn log_mouse_scroll(
mut mouse_wheel_events: EventReader<MouseWheel>,
mut query_list: Query<(&mut ScrollingList, &mut Style, &Parent, &Node)>,
query_node: Query<&Node>,
) {
for mouse_wheel_event in mouse_wheel_events.iter() {
for (mut scrolling_list, mut style, parent, list_node) in &mut query_list {
let items_height = list_node.size().y;
let container_height = query_node.get(parent.get()).unwrap().size().y;
let max_scroll = (items_height - container_height).max(0.);
let dy = match mouse_wheel_event.unit {
MouseScrollUnit::Line => mouse_wheel_event.y * 20.,
MouseScrollUnit::Pixel => mouse_wheel_event.y,
};
scrolling_list.position += dy;
scrolling_list.position = scrolling_list.position.clamp(-max_scroll, 0.);
style.position.top = Val::Px(scrolling_list.position);
}
}
}