use std::cell::RefCell;
use std::rc::Rc;
use bevy_ecs::prelude::*;
use yakui::event::Event;
use yakui::font::Fonts;
use yakui::geometry::{Rect, Vec2};
use yakui::input::MouseButton;
use yakui::{WidgetId, Yakui};
use super::font::{Face, Family, Font};
use super::resources::{CursorPosition, MouseInput, PointerCapture, ScreenSize};
pub const SCROLL_LINE: f32 = 100.0 / 3.0;
#[derive(Clone, Default)]
pub(crate) struct Panels(pub Rc<RefCell<Vec<WidgetId>>>);
pub struct Ui {
pub yakui: Yakui,
left: bool,
right: bool,
viewport: Vec2,
open: bool,
}
impl Default for Ui {
fn default() -> Self {
Self::new()
}
}
impl Ui {
pub fn new() -> Self {
let yakui = Yakui::new();
let fonts = yakui.dom().get_global_or_init(Fonts::default);
for family in Family::ALL {
fonts.add(family.load(), Some(family.name()));
}
fonts.add(Family::default().load(), Some("default"));
Self {
yakui,
left: false,
right: false,
viewport: Vec2::ZERO,
open: false,
}
}
pub fn begin_frame(world: &mut World) {
let Some(mut ui) = world.remove_non_send::<Ui>() else {
return;
};
if ui.open {
ui.yakui.finish();
}
let screen = *world.resource::<ScreenSize>();
ui.set_viewport(screen.width, screen.height);
let cursor = *world.resource::<CursorPosition>();
let mouse = *world.resource::<MouseInput>();
ui.feed(cursor, mouse);
let panels = ui.yakui.dom().get_global_or_init(Panels::default);
let over_panel = panels.0.borrow().iter().any(|&id| {
ui.yakui
.layout_dom()
.get(id)
.is_some_and(|node| node.rect.contains_point(Vec2::new(cursor.x, cursor.y)))
});
panels.0.borrow_mut().clear();
world.resource_mut::<PointerCapture>().over_panel = over_panel;
let family = world
.get_resource::<Font>()
.map(Font::family)
.unwrap_or_default();
ui.yakui.dom().get_global_or_init(Face::default).set(family);
ui.yakui.start();
ui.open = true;
world.insert_non_send(ui);
}
pub fn end_frame(world: &mut World) {
if let Some(mut ui) = world.get_non_send_mut::<Ui>()
&& ui.open
{
ui.yakui.finish();
ui.open = false;
}
}
pub fn rect_of(&self, id: WidgetId) -> Option<Rect> {
self.yakui.layout_dom().get(id).map(|node| node.rect)
}
fn set_viewport(&mut self, width: f32, height: f32) {
let size = Vec2::new(width, height);
if size == self.viewport {
return;
}
self.viewport = size;
self.yakui.set_surface_size(size);
self.yakui
.set_unscaled_viewport(Rect::from_pos_size(Vec2::ZERO, size));
self.yakui.set_scale_factor(1.0);
}
fn feed(&mut self, cursor: CursorPosition, mouse: MouseInput) {
self.yakui
.handle_event(Event::CursorMoved(Some(Vec2::new(cursor.x, cursor.y))));
let mut button = |button: MouseButton, down: bool| {
self.yakui
.handle_event(Event::MouseButtonChanged { button, down });
};
if mouse.just_pressed {
if self.left {
button(MouseButton::One, false);
}
self.left = true;
button(MouseButton::One, true);
}
if mouse.left_down != self.left {
self.left = mouse.left_down;
button(MouseButton::One, mouse.left_down);
}
if mouse.right_down != self.right {
self.right = mouse.right_down;
button(MouseButton::Two, mouse.right_down);
}
if mouse.scroll != 0.0 {
self.yakui.handle_event(Event::MouseScroll {
delta: Vec2::new(0.0, -mouse.scroll * SCROLL_LINE),
});
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ecs::Application;
use crate::ui::plugin::UiPlugin;
fn app() -> Application {
let mut app = Application::new();
app.add_plugin(UiPlugin);
app.world.insert_resource(ScreenSize {
width: 640.0,
height: 480.0,
});
app
}
#[test]
fn every_face_is_registered_with_yakui() {
let ui = Ui::new();
let fonts = ui.yakui.dom().get_global_or_init(Fonts::default);
for family in Family::ALL {
assert!(
fonts.get(&family.name().into()).is_some(),
"{} is not registered",
family.label()
);
}
assert!(fonts.get(&"default".into()).is_some());
}
#[test]
fn frames_open_and_close_cleanly() {
let mut app = app();
for _ in 0..3 {
Ui::begin_frame(&mut app.world);
crate::ui::text(14.0, "hello");
app.update();
Ui::end_frame(&mut app.world);
}
Ui::begin_frame(&mut app.world);
Ui::begin_frame(&mut app.world);
Ui::end_frame(&mut app.world);
Ui::end_frame(&mut app.world);
}
#[test]
fn the_pointer_over_a_panel_is_the_panel_s() {
let mut app = app();
let draw = |app: &mut Application| {
Ui::begin_frame(&mut app.world);
crate::ui::corner(yakui::Alignment::TOP_LEFT, || {
crate::ui::panel(|| {
crate::ui::text(20.0, "A PANEL IN THE CORNER");
});
});
app.update();
Ui::end_frame(&mut app.world);
};
draw(&mut app);
app.world
.insert_resource(CursorPosition { x: 40.0, y: 30.0 });
draw(&mut app);
assert!(app.world.resource::<PointerCapture>().taken());
app.world
.insert_resource(CursorPosition { x: 600.0, y: 400.0 });
draw(&mut app);
assert!(!app.world.resource::<PointerCapture>().taken());
}
}