use crate::primitives2d::Primitives2D;
use crate::input::InputHandler;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WidgetState {
Normal,
Hovered,
Pressed,
Disabled,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UITheme {
pub primary_color: [u8; 4],
pub secondary_color: [u8; 4],
pub background_color: [u8; 4],
pub text_color: [u8; 4],
pub border_color: [u8; 4],
pub hover_color: [u8; 4],
pub pressed_color: [u8; 4],
pub disabled_color: [u8; 4],
pub border_width: u32,
pub padding: u32,
pub corner_radius: u32,
}
impl UITheme {
pub fn dark() -> Self {
Self {
primary_color: [70, 130, 180, 255],
secondary_color: [100, 149, 237, 255],
background_color: [30, 30, 40, 255],
text_color: [255, 255, 255, 255],
border_color: [100, 100, 120, 255],
hover_color: [90, 150, 200, 255],
pressed_color: [50, 100, 150, 255],
disabled_color: [80, 80, 90, 255],
border_width: 2,
padding: 8,
corner_radius: 4,
}
}
pub fn light() -> Self {
Self {
primary_color: [100, 149, 237, 255],
secondary_color: [135, 206, 250, 255],
background_color: [240, 240, 245, 255],
text_color: [30, 30, 30, 255],
border_color: [180, 180, 190, 255],
hover_color: [120, 169, 255, 255],
pressed_color: [80, 129, 217, 255],
disabled_color: [200, 200, 210, 255],
border_width: 2,
padding: 8,
corner_radius: 4,
}
}
pub fn neon() -> Self {
Self {
primary_color: [255, 0, 255, 255],
secondary_color: [0, 255, 255, 255],
background_color: [10, 10, 20, 255],
text_color: [255, 255, 255, 255],
border_color: [255, 0, 255, 255],
hover_color: [255, 100, 255, 255],
pressed_color: [200, 0, 200, 255],
disabled_color: [50, 50, 60, 255],
border_width: 3,
padding: 10,
corner_radius: 8,
}
}
}
pub struct Button {
pub x: i32,
pub y: i32,
pub width: u32,
pub height: u32,
pub text: String,
pub state: WidgetState,
pub enabled: bool,
pub visible: bool,
}
impl Button {
pub fn new(x: i32, y: i32, width: u32, height: u32, text: &str) -> Self {
Self {
x,
y,
width,
height,
text: text.to_string(),
state: WidgetState::Normal,
enabled: true,
visible: true,
}
}
pub fn update(&mut self, input: &InputHandler) -> bool {
if !self.enabled || !self.visible {
self.state = WidgetState::Disabled;
return false;
}
let (mx, my) = input.get_mouse_position();
let mx = mx as i32;
let my = my as i32;
let hovered = mx >= self.x
&& mx <= self.x + self.width as i32
&& my >= self.y
&& my <= self.y + self.height as i32;
if hovered {
if input.is_mouse_button_pressed(crate::input::MouseBtn::Left) {
self.state = WidgetState::Pressed;
} else {
self.state = WidgetState::Hovered;
}
if input.is_mouse_button_just_released(crate::input::MouseBtn::Left) {
return true; }
} else {
self.state = WidgetState::Normal;
}
false
}
pub fn draw(&self, prims: &mut Primitives2D, theme: &UITheme) {
if !self.visible {
return;
}
let color = match self.state {
WidgetState::Normal => theme.primary_color,
WidgetState::Hovered => theme.hover_color,
WidgetState::Pressed => theme.pressed_color,
WidgetState::Disabled => theme.disabled_color,
};
prims.draw_rect_filled(self.x, self.y, self.width, self.height, color);
prims.draw_rect(self.x, self.y, self.width, self.height, theme.border_color);
}
}
pub struct Slider {
pub x: i32,
pub y: i32,
pub width: u32,
pub height: u32,
pub value: f32,
pub min: f32,
pub max: f32,
pub dragging: bool,
pub enabled: bool,
pub visible: bool,
}
impl Slider {
pub fn new(x: i32, y: i32, width: u32, min: f32, max: f32) -> Self {
Self {
x,
y,
width,
height: 20,
value: (min + max) / 2.0,
min,
max,
dragging: false,
enabled: true,
visible: true,
}
}
pub fn update(&mut self, input: &InputHandler) {
if !self.enabled || !self.visible {
return;
}
let (mx, my) = input.get_mouse_position();
let mx = mx as i32;
let my = my as i32;
let hovered = mx >= self.x
&& mx <= self.x + self.width as i32
&& my >= self.y
&& my <= self.y + self.height as i32;
if input.is_mouse_button_pressed(crate::input::MouseBtn::Left) && hovered {
self.dragging = true;
}
if !input.is_mouse_button_pressed(crate::input::MouseBtn::Left) {
self.dragging = false;
}
if self.dragging {
let relative_x = (mx - self.x).max(0).min(self.width as i32);
let t = relative_x as f32 / self.width as f32;
self.value = self.min + t * (self.max - self.min);
}
}
pub fn draw(&self, prims: &mut Primitives2D, theme: &UITheme) {
if !self.visible {
return;
}
prims.draw_rect_filled(
self.x,
self.y + self.height as i32 / 4,
self.width,
self.height / 2,
theme.background_color,
);
let t = (self.value - self.min) / (self.max - self.min);
let handle_x = self.x + (t * self.width as f32) as i32;
let handle_size = self.height;
let color = if self.dragging {
theme.pressed_color
} else {
theme.primary_color
};
prims.draw_circle_filled(handle_x, self.y + self.height as i32 / 2, handle_size as i32 / 2, color);
}
pub fn get_value(&self) -> f32 {
self.value
}
pub fn set_value(&mut self, value: f32) {
self.value = value.clamp(self.min, self.max);
}
}
pub struct Checkbox {
pub x: i32,
pub y: i32,
pub size: u32,
pub checked: bool,
pub enabled: bool,
pub visible: bool,
}
impl Checkbox {
pub fn new(x: i32, y: i32, size: u32) -> Self {
Self {
x,
y,
size,
checked: false,
enabled: true,
visible: true,
}
}
pub fn update(&mut self, input: &InputHandler) -> bool {
if !self.enabled || !self.visible {
return false;
}
let (mx, my) = input.get_mouse_position();
let mx = mx as i32;
let my = my as i32;
let hovered = mx >= self.x
&& mx <= self.x + self.size as i32
&& my >= self.y
&& my <= self.y + self.size as i32;
if hovered && input.is_mouse_button_just_pressed(crate::input::MouseBtn::Left) {
self.checked = !self.checked;
return true;
}
false
}
pub fn draw(&self, prims: &mut Primitives2D, theme: &UITheme) {
if !self.visible {
return;
}
prims.draw_rect_filled(self.x, self.y, self.size, self.size, theme.background_color);
prims.draw_rect(self.x, self.y, self.size, self.size, theme.border_color);
if self.checked {
let padding = self.size / 4;
prims.draw_rect_filled(
self.x + padding as i32,
self.y + padding as i32,
self.size - padding * 2,
self.size - padding * 2,
theme.primary_color,
);
}
}
}
pub struct Panel {
pub x: i32,
pub y: i32,
pub width: u32,
pub height: u32,
pub title: String,
pub visible: bool,
pub draggable: bool,
pub dragging: bool,
pub drag_offset: (i32, i32),
}
impl Panel {
pub fn new(x: i32, y: i32, width: u32, height: u32, title: &str) -> Self {
Self {
x,
y,
width,
height,
title: title.to_string(),
visible: true,
draggable: true,
dragging: false,
drag_offset: (0, 0),
}
}
pub fn update(&mut self, input: &InputHandler) {
if !self.visible || !self.draggable {
return;
}
let (mx, my) = input.get_mouse_position();
let mx = mx as i32;
let my = my as i32;
let title_bar_height = 30;
let title_bar_hovered = mx >= self.x
&& mx <= self.x + self.width as i32
&& my >= self.y
&& my <= self.y + title_bar_height;
if title_bar_hovered && input.is_mouse_button_just_pressed(crate::input::MouseBtn::Left) {
self.dragging = true;
self.drag_offset = (mx - self.x, my - self.y);
}
if !input.is_mouse_button_pressed(crate::input::MouseBtn::Left) {
self.dragging = false;
}
if self.dragging {
self.x = mx - self.drag_offset.0;
self.y = my - self.drag_offset.1;
}
}
pub fn draw(&self, prims: &mut Primitives2D, theme: &UITheme) {
if !self.visible {
return;
}
let title_bar_height = 30;
prims.draw_rect_filled(self.x, self.y, self.width, title_bar_height, theme.primary_color);
prims.draw_rect_filled(
self.x,
self.y + title_bar_height as i32,
self.width,
self.height - title_bar_height,
theme.background_color,
);
prims.draw_rect(self.x, self.y, self.width, self.height, theme.border_color);
}
}
pub struct ProgressBar {
pub x: i32,
pub y: i32,
pub width: u32,
pub height: u32,
pub value: f32,
pub max: f32,
pub visible: bool,
}
impl ProgressBar {
pub fn new(x: i32, y: i32, width: u32, height: u32, max: f32) -> Self {
Self {
x,
y,
width,
height,
value: 0.0,
max,
visible: true,
}
}
pub fn set_value(&mut self, value: f32) {
self.value = value.clamp(0.0, self.max);
}
pub fn draw(&self, prims: &mut Primitives2D, theme: &UITheme) {
if !self.visible {
return;
}
prims.draw_rect_filled(self.x, self.y, self.width, self.height, theme.background_color);
let progress_width = (self.width as f32 * (self.value / self.max)) as u32;
prims.draw_rect_filled(self.x, self.y, progress_width, self.height, theme.primary_color);
prims.draw_rect(self.x, self.y, self.width, self.height, theme.border_color);
}
}
pub struct Label {
pub x: i32,
pub y: i32,
pub text: String,
pub color: [u8; 4],
pub visible: bool,
}
impl Label {
pub fn new(x: i32, y: i32, text: &str) -> Self {
Self {
x,
y,
text: text.to_string(),
color: [255, 255, 255, 255],
visible: true,
}
}
pub fn draw(&self, _prims: &mut Primitives2D, _theme: &UITheme) {
if !self.visible {
return;
}
}
}
pub struct UIManager {
pub theme: UITheme,
pub buttons: Vec<Button>,
pub sliders: Vec<Slider>,
pub checkboxes: Vec<Checkbox>,
pub panels: Vec<Panel>,
pub progress_bars: Vec<ProgressBar>,
pub labels: Vec<Label>,
}
impl UIManager {
pub fn new(theme: UITheme) -> Self {
Self {
theme,
buttons: Vec::new(),
sliders: Vec::new(),
checkboxes: Vec::new(),
panels: Vec::new(),
progress_bars: Vec::new(),
labels: Vec::new(),
}
}
pub fn add_button(&mut self, button: Button) -> usize {
self.buttons.push(button);
self.buttons.len() - 1
}
pub fn add_slider(&mut self, slider: Slider) -> usize {
self.sliders.push(slider);
self.sliders.len() - 1
}
pub fn add_checkbox(&mut self, checkbox: Checkbox) -> usize {
self.checkboxes.push(checkbox);
self.checkboxes.len() - 1
}
pub fn add_panel(&mut self, panel: Panel) -> usize {
self.panels.push(panel);
self.panels.len() - 1
}
pub fn add_progress_bar(&mut self, bar: ProgressBar) -> usize {
self.progress_bars.push(bar);
self.progress_bars.len() - 1
}
pub fn add_label(&mut self, label: Label) -> usize {
self.labels.push(label);
self.labels.len() - 1
}
pub fn update(&mut self, input: &InputHandler) -> Vec<UIEvent> {
let mut events = Vec::new();
for panel in self.panels.iter_mut() {
panel.update(input);
}
for (i, button) in self.buttons.iter_mut().enumerate() {
if button.update(input) {
events.push(UIEvent::ButtonClicked(i));
}
}
for (i, slider) in self.sliders.iter_mut().enumerate() {
let old_value = slider.value;
slider.update(input);
if (slider.value - old_value).abs() > 0.001 {
events.push(UIEvent::SliderChanged(i, slider.value));
}
}
for (i, checkbox) in self.checkboxes.iter_mut().enumerate() {
if checkbox.update(input) {
events.push(UIEvent::CheckboxToggled(i, checkbox.checked));
}
}
events
}
pub fn draw(&self, prims: &mut Primitives2D) {
for panel in &self.panels {
panel.draw(prims, &self.theme);
}
for bar in &self.progress_bars {
bar.draw(prims, &self.theme);
}
for button in &self.buttons {
button.draw(prims, &self.theme);
}
for slider in &self.sliders {
slider.draw(prims, &self.theme);
}
for checkbox in &self.checkboxes {
checkbox.draw(prims, &self.theme);
}
for label in &self.labels {
label.draw(prims, &self.theme);
}
}
pub fn set_theme(&mut self, theme: UITheme) {
self.theme = theme;
}
}
#[derive(Debug, Clone)]
pub enum UIEvent {
ButtonClicked(usize),
SliderChanged(usize, f32),
CheckboxToggled(usize, bool),
}