mod layout;
use concinnity_core::components::{InputKey, KeyEvent, Window};
use concinnity_core::ecs::FontHandle;
use concinnity_core::input::snapshot::InputSnapshot;
use concinnity_core::render::backend::{FrameParams, RenderBackend};
use concinnity_core::render::backend_init::BackendInit;
use concinnity_core::render::text::{FontSet, build_text_calls};
const FONT_HANDLE: FontHandle = FontHandle::new(0);
const IDENTITY_VIEW: [[f32; 4]; 4] = [
[1.0, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[0.0, 0.0, 0.0, 1.0],
];
pub(crate) fn show(title: &str, message: &str) -> bool {
let Some(builtin) = crate::gfx::builtin_font::load(FONT_HANDLE) else {
return false;
};
let atlases = vec![builtin.atlas];
let mut fonts = FontSet::default();
fonts.insert(FONT_HANDLE, builtin.loaded);
let window = Window {
title: title.to_string(),
resizable: true,
..Window::default()
};
#[cfg(target_os = "macos")]
crate::app::runloop::activate_app_macos();
let init = BackendInit::minimal(&window, atlases);
let mut backend = match crate::device::init_backend(init) {
Ok(backend) => backend,
Err(e) => {
tracing::error!(
"error screen: no render backend ({e}); reporting on the console instead"
);
return false;
}
};
run_loop(backend.as_mut(), message, &fonts);
backend.wait_idle();
true
}
fn run_loop(backend: &mut dyn RenderBackend, message: &str, fonts: &FontSet) {
let started = std::time::Instant::now();
let mut hovered = false;
loop {
if backend.window_closed() {
return;
}
let (win_w, win_h) = backend.logical_size();
let screen = layout::build(message, win_w, win_h, fonts, FONT_HANDLE, hovered);
let text_calls = build_text_calls(&screen.labels, fonts, [win_w, win_h]);
backend.update_view(IDENTITY_VIEW);
if let Err(e) = backend.draw_frame(FrameParams {
elapsed: started.elapsed().as_secs_f32(),
fov_y_radians: 1.0,
near: 0.1,
view_distance: None,
cam_pos: [0.0, 0.0, 0.0],
text_calls: &text_calls,
lines: &[],
world_hidden: true,
view_mode: Default::default(),
show: Default::default(),
sky_rot: concinnity_core::sky::SkyOrientation::IDENTITY_ROWS,
history_reset: false,
}) {
tracing::error!("error screen: draw failed, closing: {e}");
return;
}
let input = backend.take_input();
hovered = screen.quit_hit(input.mouse_x, input.mouse_y);
if dismissed(&input, hovered) {
return;
}
}
}
fn dismissed(input: &InputSnapshot, hovered: bool) -> bool {
input.escape
|| (input.left_click && hovered)
|| input
.key_events
.iter()
.any(|e| matches!(e, KeyEvent::Press(p) if p.key == InputKey::Enter))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn escape_dismisses() {
let input = InputSnapshot {
escape: true,
..Default::default()
};
assert!(dismissed(&input, false));
}
#[test]
fn a_click_over_quit_dismisses() {
let input = InputSnapshot {
left_click: true,
..Default::default()
};
assert!(dismissed(&input, true));
}
#[test]
fn a_click_away_from_quit_does_not_dismiss() {
let input = InputSnapshot {
left_click: true,
..Default::default()
};
assert!(!dismissed(&input, false));
}
#[test]
fn enter_dismisses() {
let input = InputSnapshot {
key_events: vec![KeyEvent::press(InputKey::Enter)],
..Default::default()
};
assert!(dismissed(&input, false));
}
#[test]
fn a_frame_without_a_dismissal_does_not_dismiss() {
assert!(!dismissed(&InputSnapshot::default(), true));
}
}