use glam::Vec2;
use crate::camera::Rect;
use crate::color::Color;
use crate::ctx::Ctx;
use crate::input::MouseButton;
use crate::render::batch::SpriteInstance;
use crate::text::TextParams;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct UiStyle {
pub panel: Color,
pub button: Color,
pub button_hovered: Color,
pub button_pressed: Color,
pub accent: Color,
pub text: Color,
pub text_size: f32,
pub padding: f32,
}
impl Default for UiStyle {
fn default() -> Self {
Self {
panel: Color::rgba(0.08, 0.09, 0.12, 0.85),
button: Color::rgba(0.20, 0.22, 0.28, 1.0),
button_hovered: Color::rgba(0.27, 0.30, 0.38, 1.0),
button_pressed: Color::rgba(0.15, 0.16, 0.21, 1.0),
accent: Color::hex(0xf28c28),
text: Color::rgba(0.93, 0.93, 0.95, 1.0),
text_size: 16.0,
padding: 10.0,
}
}
}
#[derive(Default)]
pub(crate) struct UiState {
this_frame: Vec<Rect>,
last_frame: Vec<Rect>,
white: Option<crate::Texture>,
circle: Option<crate::Texture>,
pub overlay_has_pointer: bool,
retained_spare: Option<Ui>,
retained_input_text: String,
#[cfg(feature = "3d")]
pub panels: panel3d::Panels,
}
impl UiState {
pub fn begin_frame(&mut self) {
std::mem::swap(&mut self.this_frame, &mut self.last_frame);
self.this_frame.clear();
}
}
const CIRCLE: u32 = 128;
#[derive(Clone, Copy)]
enum Layer {
World,
Screen,
}
impl Ctx {
fn white_texture(&mut self) -> crate::Texture {
if let Some(t) = self.ui.white {
return t;
}
let t = self.create_texture(1, 1, &[255; 4]);
self.ui.white = Some(t);
t
}
fn circle_texture(&mut self) -> crate::Texture {
if let Some(t) = self.ui.circle {
return t;
}
let r = CIRCLE as f32 / 2.0;
let mut rgba = Vec::with_capacity((CIRCLE * CIRCLE * 4) as usize);
for y in 0..CIRCLE {
for x in 0..CIRCLE {
let d = Vec2::new(x as f32 + 0.5 - r, y as f32 + 0.5 - r).length();
let alpha = (r - d).clamp(0.0, 1.0); rgba.extend_from_slice(&[255, 255, 255, (alpha * 255.0) as u8]);
}
}
let t = self.create_texture(CIRCLE, CIRCLE, &rgba);
self.ui.circle = Some(t);
t
}
#[allow(clippy::too_many_arguments)]
fn shape(
&mut self,
layer: Layer,
texture: u32,
pos: Vec2,
size: Vec2,
rotation: f32,
pivot: Vec2,
color: Color,
z: f32,
) {
let instance = SpriteInstance::new(pos.into(), size.into(), pivot.into(), rotation, color.to_rgba8());
match layer {
Layer::World => self.draw_list.push(texture, z, instance),
Layer::Screen => self.ui_list.push(texture, z, instance),
}
}
pub fn draw_rect(&mut self, rect: Rect, color: Color) {
let white = self.white_texture().id;
self.shape(Layer::World, white, rect.min, rect.size(), 0.0, Vec2::ZERO, color, 0.0);
}
pub fn draw_line(&mut self, a: Vec2, b: Vec2, width: f32, color: Color) {
let white = self.white_texture().id;
let d = b - a;
self.shape(
Layer::World,
white,
a,
Vec2::new(d.length(), width),
d.to_angle(),
Vec2::new(0.0, 0.5),
color,
0.0,
);
}
pub fn draw_circle(&mut self, center: Vec2, radius: f32, color: Color) {
let circle = self.circle_texture().id;
self.shape(
Layer::World,
circle,
center,
Vec2::splat(radius * 2.0),
0.0,
Vec2::splat(0.5),
color,
0.0,
);
}
pub fn mouse_over_ui(&self) -> bool {
let m = self.mouse_pos();
self.ui_retained.wants_pointer()
|| self.ui.overlay_has_pointer
|| self
.ui
.this_frame
.iter()
.chain(&self.ui.last_frame)
.any(|r| r.contains(m))
}
pub fn ui_panel(&mut self, rect: Rect) {
let color = self.ui_style.panel;
self.ui_fill(rect, color, -1.0);
self.ui.this_frame.push(rect);
}
pub fn ui_label(&mut self, pos: Vec2, text: &str) -> Vec2 {
let style = self.ui_style;
self.draw_text_ex(
text,
pos,
TextParams {
size: style.text_size,
color: style.text,
z: 1.0,
..Default::default()
},
);
self.measure_text(text, style.text_size)
}
pub fn ui_button(&mut self, rect: Rect, label: &str) -> bool {
let style = self.ui_style;
let mouse = self.mouse_pos();
if self.mouse_pressed(MouseButton::Left) {
self.ui_press_at = mouse;
}
let hovered = rect.contains(mouse);
let held = hovered && self.mouse_down(MouseButton::Left);
let clicked = hovered && self.mouse_released(MouseButton::Left) && rect.contains(self.ui_press_at);
let fill = if held {
style.button_pressed
} else if hovered {
style.button_hovered
} else {
style.button
};
if hovered {
self.ui_fill(rect.expand(1.0), style.accent, -0.5);
}
self.ui_fill(rect, fill, 0.0);
let size = self.measure_text(label, style.text_size);
let at = (rect.center() - size / 2.0).round();
self.draw_text_ex(
label,
at,
TextParams {
size: style.text_size,
color: style.text,
z: 1.0,
..Default::default()
},
);
self.ui.this_frame.push(rect);
clicked
}
pub(crate) fn ui_fill(&mut self, rect: Rect, color: Color, z: f32) {
let white = self.white_texture().id;
self.shape(Layer::Screen, white, rect.min, rect.size(), 0.0, Vec2::ZERO, color, z);
}
}
#[cfg(feature = "3d")]
mod panel3d;
mod retained;
mod retained_draw;
#[cfg(feature = "3d")]
pub use panel3d::UiPanel3D;
pub use retained::{Ui, UiEvent, UiId, UiInput, UiPaint, UiTextAlign, UiWidget};