use bevy_ecs::prelude::*;
use super::button::{ButtonColors, ButtonMarker, OnClick};
use super::color::{Color, linear_rgba};
use super::font;
use super::geometry::{
Background, ButtonIcons, Icon, Label, LabelAlign, LabelSize, Position, Size, TextColor,
};
use super::heading::HeadingText;
use super::atlas::Atlas;
use super::font::Font;
use super::interaction::Interaction;
use super::layout::{self, LABEL_PIXEL_SIZE};
use super::outliner::OutlinerRow;
use super::panel::{Closed, ClosesPanel, Dragging, Movable, PanelChild, PanelMarker, TitleBar};
use super::progress::{OnFull, Progress};
use super::renderer2d::{NO_ICON, QuadInstance};
use super::rect::Rect;
use super::resources::{
CursorPosition, MenuLayout, MouseInput, PointerCapture, ScreenSize, UiDrawList,
};
use super::text::TextLine;
use super::visibility::{Visibility, visible};
use crate::time::Time;
pub fn relayout_menu_system(
screen: Res<ScreenSize>,
menus: Query<&MenuLayout>,
mut positions: Query<&mut Position>,
mut sizes: Query<&mut Size>,
) {
if !screen.is_changed() {
return;
}
for menu in &menus {
let (panel_rect, button_rects) = layout::vertical_menu_offset(
screen.width,
screen.height,
menu.buttons.len(),
menu.anchor,
menu.metrics,
menu.offset,
);
let panel_rect = Rect::new(
panel_rect.x,
panel_rect.y,
panel_rect.width,
panel_rect.height + menu.bar_height,
);
let mut place = |entity: Entity, rect: Rect| {
if let Ok(mut pos) = positions.get_mut(entity) {
(pos.x, pos.y) = (rect.x, rect.y);
}
if let Ok(mut size) = sizes.get_mut(entity) {
(size.width, size.height) = (rect.width, rect.height);
}
};
place(menu.panel, panel_rect);
if let Some(bar) = menu.bar {
let rect = Rect::new(
panel_rect.x,
panel_rect.y,
panel_rect.width,
menu.bar_height,
);
place(bar, rect);
if let Some(close) = menu.close {
let side = menu.bar_height - layout::TITLE_INSET * 2.0;
place(
close,
Rect::new(
rect.x + rect.width - side - layout::TITLE_INSET,
rect.y + layout::TITLE_INSET,
side,
side,
),
);
}
}
for (&entity, rect) in menu.buttons.iter().zip(button_rects) {
place(
entity,
Rect::new(rect.x, rect.y + menu.bar_height, rect.width, rect.height),
);
}
}
}
pub fn advance_progress_system(time: Res<Time>, mut bars: Query<&mut Progress>) {
for mut bar in &mut bars {
if bar.fill_over.is_some() {
bar.advance(time.delta);
}
}
}
pub fn dispatch_progress_full_system(mut bars: Query<(&Progress, &mut OnFull)>) {
for (progress, mut on_full) in &mut bars {
if progress.value >= 1.0 && on_full.is_armed() {
on_full.fire();
}
}
}
pub fn update_interaction_system(
cursor: Res<CursorPosition>,
mouse: Res<MouseInput>,
mut buttons: Query<
(&Position, &Size, &mut Interaction, Option<&Visibility>),
With<ButtonMarker>,
>,
) {
for (pos, size, mut interaction, visibility) in &mut buttons {
let hovered = visible(visibility) && pos.rect(size).contains(cursor.x, cursor.y);
interaction.hovered = hovered;
interaction.pressed = hovered && mouse.left_down;
interaction.clicked = hovered && mouse.just_pressed;
}
}
pub fn update_pointer_capture_system(
cursor: Res<CursorPosition>,
panels: Query<(&Position, &Size, Option<&Visibility>), With<PanelMarker>>,
mut capture: ResMut<PointerCapture>,
) {
capture.over_panel = panels.iter().any(|(pos, size, visibility)| {
visible(visibility) && pos.rect(size).contains(cursor.x, cursor.y)
});
}
pub fn dispatch_clicks_system(mut buttons: Query<(&Interaction, &mut OnClick)>) {
for (interaction, mut on_click) in &mut buttons {
if interaction.clicked {
(on_click.0)();
}
}
}
pub fn dispatch_panel_close_system(
buttons: Query<(&Interaction, &ClosesPanel)>,
mut commands: Commands,
) {
for (interaction, closes) in &buttons {
if interaction.clicked {
commands.entity(closes.0).insert(Closed);
}
}
}
pub fn drag_panel_system(
mouse: Res<MouseInput>,
cursor: Res<CursorPosition>,
movable: Query<(), With<Movable>>,
dragging: Query<(Entity, &Dragging)>,
children: Query<(Entity, &PanelChild)>,
mut rects: ParamSet<(Query<(&Position, &Size, &TitleBar)>, Query<&mut Position>)>,
mut commands: Commands,
) {
if !mouse.left_down {
for (panel, _) in &dragging {
commands.entity(panel).remove::<Dragging>();
}
return;
}
if mouse.just_pressed && dragging.is_empty() {
let grabbed = rects
.p0()
.iter()
.find(|(pos, size, bar)| {
pos.rect(size).contains(cursor.x, cursor.y) && movable.get(bar.panel).is_ok()
})
.map(|(_, _, bar)| bar.panel);
if let Some(panel) = grabbed {
if let Ok(origin) = rects.p1().get(panel) {
commands.entity(panel).insert(Dragging {
from: (cursor.x, cursor.y),
origin: (origin.x, origin.y),
});
}
}
return;
}
for (panel, drag) in &dragging {
let mut positions = rects.p1();
let Ok(current) = positions.get(panel) else {
continue;
};
let wanted = (
drag.origin.0 + cursor.x - drag.from.0,
drag.origin.1 + cursor.y - drag.from.1,
);
let by = (wanted.0 - current.x, wanted.1 - current.y);
if by == (0.0, 0.0) {
continue;
}
let moving: Vec<Entity> = std::iter::once(panel)
.chain(
children
.iter()
.filter(|(_, child)| child.0 == panel)
.map(|(entity, _)| entity),
)
.collect();
for entity in moving {
if let Ok(mut pos) = positions.get_mut(entity) {
pos.x += by.0;
pos.y += by.1;
}
}
}
}
pub fn clear_input_edge_system(mut mouse: ResMut<MouseInput>) {
mouse.just_pressed = false;
mouse.just_released = false;
mouse.scroll = 0.0;
}
pub fn collect_quads_system(
screen: Res<ScreenSize>,
headings: Query<&HeadingText>,
lines: Query<&TextLine>,
progress_bars: Query<&Progress>,
panels: Query<(&Position, &Size, &Background, Option<&Visibility>), With<PanelMarker>>,
bars: Query<(&Position, &Size, &Background), With<TitleBar>>,
icons: Query<(&Position, &Size, &Icon, Option<&Visibility>)>,
rows: Query<
(
&Position,
&Size,
&Label,
Option<&LabelSize>,
Option<&TextColor>,
),
With<OutlinerRow>,
>,
titles: Query<(&Position, &Size, &Label, Option<&LabelSize>), With<TitleBar>>,
mut atlas: ResMut<Atlas>,
mut font: ResMut<Font>,
buttons: Query<
(
&Position,
&Size,
&ButtonColors,
&Label,
&Interaction,
Option<&TextColor>,
Option<&LabelSize>,
Option<&Visibility>,
Option<&LabelAlign>,
Option<&ButtonIcons>,
),
With<ButtonMarker>,
>,
mut draw_list: ResMut<UiDrawList>,
) {
if font.take_changed() {
atlas.forget_glyphs();
}
let face = font.face();
let quads = &mut draw_list.0;
quads.clear();
for (pos, size, background, visibility) in &panels {
if visible(visibility) {
quads.push(colored_quad(pos, size, background.0));
}
}
for (pos, size, background) in &bars {
quads.push(colored_quad(pos, size, background.0));
}
for (pos, size, icon, visibility) in &icons {
if !visible(visibility) {
continue;
}
let Some(uv) = atlas.icon(icon.path) else {
continue;
};
quads.push(QuadInstance {
pos: [pos.x, pos.y],
size: [size.width, size.height],
color: linear_rgba(icon.color),
uv: [uv.min[0], uv.min[1], uv.max[0], uv.max[1]],
});
}
for (pos, size, colors, label, interaction, text_color, label_size, visibility, align, icons) in
&buttons
{
if !visible(visibility) {
continue;
}
let color = colors.current(interaction.hovered, interaction.pressed);
quads.push(colored_quad(pos, size, color));
let text_color = text_color.map(|c| c.0).unwrap_or(Color::WHITE);
let label_size = label_size.map(|size| size.0).unwrap_or(LABEL_PIXEL_SIZE);
let label_width = font::text_width(&label.0, label_size);
let label_height = font::text_height(label_size);
let icons = icons.copied().unwrap_or_default();
let icon_side = (label_size / layout::MENU_LABEL_SIZE) * layout::MENU_ICON;
let margin = (label_size / layout::MENU_LABEL_SIZE) * layout::MENU_ROW_MARGIN;
let gap = (label_size / layout::MENU_LABEL_SIZE) * layout::MENU_ICON_GAP;
let icon_y = pos.y + size.height * 0.5 - icon_side * 0.5;
let mut icon_quad = |x: f32, path: &'static str, atlas: &mut Atlas| {
if let Some(uv) = atlas.icon(path) {
quads.push(QuadInstance {
pos: [x, icon_y],
size: [icon_side, icon_side],
color: linear_rgba(text_color),
uv: [uv.min[0], uv.min[1], uv.max[0], uv.max[1]],
});
}
};
if let Some(leading) = icons.leading {
icon_quad(pos.x + margin, leading, atlas.as_mut());
}
if let Some(trailing) = icons.trailing {
icon_quad(
pos.x + size.width - margin - icon_side,
trailing,
atlas.as_mut(),
);
}
let label_x = match align.copied().unwrap_or_default() {
LabelAlign::Center => pos.x + size.width * 0.5 - label_width * 0.5,
LabelAlign::Left => {
pos.x
+ margin
+ match icons.leading {
Some(_) => icon_side + gap,
None => 0.0,
}
}
};
font::push_text(
quads,
atlas.as_mut(),
face,
&label.0,
label_x,
pos.y + size.height * 0.5 - label_height * 0.5,
label_size,
text_color,
);
}
for (pos, size, label, label_size) in &titles {
let label_size = label_size
.map(|size| size.0)
.unwrap_or(layout::TITLE_LABEL_SIZE);
let height = font::text_height(label_size);
font::push_text(
quads,
atlas.as_mut(),
face,
&label.0,
pos.x + layout::PANEL_PADDING * 2.0,
pos.y + size.height * 0.5 - height * 0.5,
label_size,
Color::srgb(0.82, 0.82, 0.88),
);
}
for (pos, size, label, label_size, text_color) in &rows {
let label_size = label_size.map(|size| size.0).unwrap_or(LABEL_PIXEL_SIZE);
let height = font::text_height(label_size);
font::push_text(
quads,
atlas.as_mut(),
face,
&label.0,
pos.x,
pos.y + size.height * 0.5 - height * 0.5,
label_size,
text_color.map(|c| c.0).unwrap_or(Color::WHITE),
);
}
for heading in &headings {
let width = font::text_width(&heading.text, heading.pixel_size);
let height = font::text_height(heading.pixel_size);
font::push_text(
quads,
atlas.as_mut(),
face,
&heading.text,
screen.width * heading.x_ratio - width * 0.5,
screen.height * heading.y_ratio + heading.y_offset - height * 0.5,
heading.pixel_size,
heading.color,
);
}
for line in &lines {
font::push_text(
quads,
atlas.as_mut(),
face,
&line.text,
line.x,
line.y,
line.pixel_size,
line.color,
);
}
for progress in &progress_bars {
let width = screen.width * progress.width_ratio;
let x = screen.width * 0.5 - width * 0.5;
let y = screen.height * progress.y_ratio + progress.y_offset - progress.height * 0.5;
quads.push(quad(x, y, width, progress.height, progress.track));
let filled = progress.filled_width(width);
if filled > 0.0 {
quads.push(quad(
x + progress.padding,
y + progress.padding,
filled,
(progress.height - progress.padding * 2.0).max(0.0),
progress.fill,
));
}
}
}
fn quad(x: f32, y: f32, width: f32, height: f32, color: Color) -> QuadInstance {
QuadInstance {
pos: [x, y],
size: [width, height],
color: linear_rgba(color),
uv: NO_ICON,
}
}
fn colored_quad(pos: &Position, size: &Size, color: Color) -> QuadInstance {
QuadInstance {
pos: [pos.x, pos.y],
size: [size.width, size.height],
color: linear_rgba(color),
uv: NO_ICON,
}
}