use bevy_ecs::prelude::*;
use tracing::{debug, info};
#[derive(Resource, Default)]
pub struct App {
window_count: usize,
display_configuration_changed: bool,
}
impl App {
pub fn new() -> Self {
Self::default()
}
pub fn mark_display_change(&mut self) {
crate::ecs::world::tick_wake::mark(crate::ecs::world::tick_wake::WM_GEOMETRY);
self.display_configuration_changed = true;
info!("[App] Display configuration changed");
}
pub fn reset_display_change(&mut self) {
self.display_configuration_changed = false;
}
pub fn display_configuration_changed(&self) -> bool {
self.display_configuration_changed
}
pub fn on_window_created(&mut self, entity: Entity) {
self.window_count += 1;
debug!(
entity = ?entity,
total_windows = self.window_count,
"[App] Window created"
);
}
pub fn on_window_destroyed(&mut self, entity: Entity) -> bool {
self.window_count = self.window_count.saturating_sub(1);
debug!(
entity = ?entity,
remaining_windows = self.window_count,
"[App] Window destroyed"
);
if self.window_count == 0 {
info!("[App] Last window closed.");
true
} else {
false
}
}
pub fn window_count(&self) -> usize {
self.window_count
}
}
#[cfg(test)]
mod tests {
use super::*;
fn entity(idx: u32) -> Entity {
Entity::from_raw_u32(idx).expect("valid entity index")
}
#[test]
fn test_default_and_new_start_with_zero_windows_and_no_display_change() {
let from_default = App::default();
assert_eq!(from_default.window_count(), 0);
assert!(!from_default.display_configuration_changed());
let from_new = App::new();
assert_eq!(from_new.window_count(), 0);
assert!(!from_new.display_configuration_changed());
}
#[test]
fn test_on_window_created_increments_window_count() {
let mut app = App::new();
app.on_window_created(entity(1));
assert_eq!(app.window_count(), 1);
app.on_window_created(entity(2));
assert_eq!(app.window_count(), 2);
app.on_window_created(entity(3));
assert_eq!(app.window_count(), 3);
}
#[test]
fn test_on_window_destroyed_decrements_and_returns_false_while_windows_remain() {
let mut app = App::new();
app.on_window_created(entity(1));
app.on_window_created(entity(2));
assert_eq!(app.window_count(), 2);
let was_last = app.on_window_destroyed(entity(2));
assert!(!was_last, "破棄後も1ウィンドウ残るため false");
assert_eq!(app.window_count(), 1);
}
#[test]
fn test_on_window_destroyed_returns_true_when_last_window_closed() {
let mut app = App::new();
app.on_window_created(entity(1));
assert_eq!(app.window_count(), 1);
let was_last = app.on_window_destroyed(entity(1));
assert!(was_last, "最後のウィンドウ破棄で true");
assert_eq!(app.window_count(), 0);
}
#[test]
fn test_on_window_destroyed_saturates_at_zero_and_returns_true() {
let mut app = App::new();
assert_eq!(app.window_count(), 0);
let was_last = app.on_window_destroyed(entity(7));
assert!(
was_last,
"0 から破棄しても window_count==0 のため true(最後のウィンドウ扱い)"
);
assert_eq!(
app.window_count(),
0,
"saturating_sub でアンダーフローしない"
);
}
#[test]
fn test_window_count_tracks_mixed_create_destroy_sequence() {
let mut app = App::new();
app.on_window_created(entity(1));
app.on_window_created(entity(2));
app.on_window_created(entity(3));
assert_eq!(app.window_count(), 3);
assert!(!app.on_window_destroyed(entity(3)));
assert_eq!(app.window_count(), 2);
app.on_window_created(entity(4));
assert_eq!(app.window_count(), 3);
assert!(!app.on_window_destroyed(entity(4)));
assert!(!app.on_window_destroyed(entity(2)));
assert_eq!(app.window_count(), 1);
assert!(app.on_window_destroyed(entity(1)));
assert_eq!(app.window_count(), 0);
}
}