Add Rust port

This commit is contained in:
darktohka
2026-03-17 03:10:07 +02:00
parent 64de0ade31
commit f8a1dde721
38 changed files with 4806 additions and 1 deletions
@@ -0,0 +1,97 @@
use super::Rect;
use crate::font::FontManager;
use crate::renderer::Renderer;
/// A gradient button matching the C# GradientButton control.
pub struct GradientButton {
pub rect: Rect,
pub text: String,
pub color1: u32,
pub color2: u32,
pub back_color: u32,
pub fore_color: u32,
pub hover_alpha: f64,
pub disable_alpha: f64,
pub enabled: bool,
pub visible: bool,
pub hovered: bool,
pub pressed: bool,
}
impl GradientButton {
pub fn new(x: i32, y: i32, w: i32, h: i32, text: &str) -> Self {
Self {
rect: Rect::new(x, y, w, h),
text: text.to_string(),
color1: Renderer::rgb(118, 118, 118),
color2: Renderer::rgb(81, 81, 81),
back_color: Renderer::rgb(0, 0, 0),
fore_color: Renderer::rgb(227, 227, 227),
hover_alpha: 0.875,
disable_alpha: 0.644,
enabled: true,
visible: true,
hovered: false,
pressed: false,
}
}
/// Update hover / pressed state from mouse position and button state.
pub fn update(&mut self, mx: i32, my: i32, mouse_down: bool) {
if !self.visible || !self.enabled {
self.hovered = false;
self.pressed = false;
return;
}
self.hovered = self.rect.contains(mx, my);
self.pressed = self.hovered && mouse_down;
}
/// Returns true if mouse was just released inside this button.
pub fn clicked(&self, mx: i32, my: i32, mouse_released: bool) -> bool {
self.visible && self.enabled && self.rect.contains(mx, my) && mouse_released
}
pub fn draw(&self, renderer: &mut Renderer, fonts: &FontManager) {
if !self.visible {
return;
}
let (mut c1, mut c2, mut bg, mut fg) = (self.color1, self.color2, self.back_color, self.fore_color);
if !self.enabled {
c1 = dim_color(c1, self.disable_alpha);
c2 = dim_color(c2, self.disable_alpha);
bg = dim_color(bg, self.disable_alpha);
fg = dim_color(fg, self.disable_alpha);
} else if self.pressed {
c1 = dim_color(c1, self.hover_alpha);
c2 = dim_color(c2, self.hover_alpha);
} else if !self.hovered {
c1 = dim_color(c1, self.hover_alpha);
c2 = dim_color(c2, self.hover_alpha);
}
let r = self.rect;
renderer.fill_gradient_v(r.x, r.y, r.w, r.h, c1, c2);
renderer.draw_rect(r.x, r.y, r.w, r.h, bg);
// Measure text to centre it.
let font_size = 13.0;
let (tw, th) = fonts.measure_text(&self.text, font_size);
let tx = r.x + ((r.w as f32 - tw) / 2.0) as i32;
let ty = r.y + ((r.h as f32 - th) / 2.0) as i32;
// Shadow.
fonts.draw_text(renderer, tx + 1, ty + 1, &self.text, font_size, bg);
// Foreground.
fonts.draw_text(renderer, tx, ty, &self.text, font_size, fg);
}
}
fn dim_color(c: u32, alpha: f64) -> u32 {
let r = (((c >> 16) & 0xFF) as f64 * alpha) as u8;
let g = (((c >> 8) & 0xFF) as f64 * alpha) as u8;
let b = ((c & 0xFF) as f64 * alpha) as u8;
Renderer::rgb(r, g, b)
}
@@ -0,0 +1,91 @@
use super::Rect;
use crate::renderer::{Renderer, RgbaImage};
/// An image-based checkbox matching the C# ImageCheckBox control.
pub struct ImageCheckBox {
pub rect: Rect,
pub checked: bool,
pub enabled: bool,
pub visible: bool,
}
impl ImageCheckBox {
pub fn new(x: i32, y: i32) -> Self {
Self {
rect: Rect::new(x, y, 21, 21),
checked: true,
enabled: true,
visible: true,
}
}
pub fn toggle_if_clicked(&mut self, mx: i32, my: i32, mouse_released: bool) -> bool {
if self.visible && self.enabled && self.rect.contains(mx, my) && mouse_released {
self.checked = !self.checked;
true
} else {
false
}
}
pub fn draw(
&self,
renderer: &mut Renderer,
checked_img: &RgbaImage,
unchecked_img: &RgbaImage,
) {
if !self.visible {
return;
}
let img = if self.checked {
checked_img
} else {
unchecked_img
};
if img.width > 0 && img.height > 0 {
renderer.draw_image(self.rect.x, self.rect.y, img);
} else {
// Fallback: draw a simple square.
let bg = if self.checked {
Renderer::rgb(97, 147, 232)
} else {
Renderer::rgb(80, 80, 80)
};
renderer.fill_rect(self.rect.x, self.rect.y, self.rect.w, self.rect.h, bg);
renderer.draw_rect(
self.rect.x,
self.rect.y,
self.rect.w,
self.rect.h,
Renderer::rgb(160, 160, 160),
);
if self.checked {
// Draw a simple checkmark.
let cx = self.rect.x + 5;
let cy = self.rect.y + 10;
for i in 0..4 {
renderer.set_pixel(cx + i, cy + i, Renderer::rgb(255, 255, 255));
renderer.set_pixel(cx + i, cy + i + 1, Renderer::rgb(255, 255, 255));
}
for i in 0..8 {
renderer.set_pixel(cx + 3 + i, cy + 3 - i, Renderer::rgb(255, 255, 255));
renderer.set_pixel(cx + 3 + i, cy + 4 - i, Renderer::rgb(255, 255, 255));
}
}
}
if !self.enabled {
// Dim overlay.
for dy in 0..self.rect.h {
for dx in 0..self.rect.w {
renderer.blend_pixel(
self.rect.x + dx,
self.rect.y + dy,
Renderer::rgb(50, 51, 51),
100,
);
}
}
}
}
}
@@ -0,0 +1,51 @@
use super::Rect;
use crate::font::FontManager;
use crate::renderer::Renderer;
/// Simple static label for drawing text.
pub struct Label {
pub rect: Rect,
pub text: String,
pub color: u32,
pub font_size: f32,
pub visible: bool,
}
impl Label {
pub fn new(x: i32, y: i32, text: &str, font_size: f32) -> Self {
Self {
rect: Rect::new(x, y, 0, 0),
text: text.to_string(),
color: Renderer::rgb(245, 245, 245),
font_size,
visible: true,
}
}
pub fn draw(&self, renderer: &mut Renderer, fonts: &FontManager) {
if !self.visible || self.text.is_empty() {
return;
}
fonts.draw_text_multiline(
renderer,
self.rect.x,
self.rect.y,
&self.text,
self.font_size,
self.color,
2.0,
);
}
/// Check if a click at (mx, my) is within a rough bounding box of the label text.
pub fn clicked(&self, mx: i32, my: i32, mouse_released: bool, fonts: &FontManager) -> bool {
if !self.visible || !mouse_released {
return false;
}
let (tw, _th) = fonts.measure_text(&self.text, self.font_size);
let lines = self.text.lines().count().max(1);
let approx_h = (self.font_size * lines as f32 + 2.0 * lines as f32) as i32;
let r = Rect::new(self.rect.x, self.rect.y, tw as i32 + 5, approx_h);
r.contains(mx, my)
}
}
@@ -0,0 +1,23 @@
pub mod button;
pub mod checkbox;
pub mod label;
pub mod progress_bar;
/// A rectangle region on screen.
#[derive(Clone, Copy, Debug, Default)]
pub struct Rect {
pub x: i32,
pub y: i32,
pub w: i32,
pub h: i32,
}
impl Rect {
pub const fn new(x: i32, y: i32, w: i32, h: i32) -> Self {
Self { x, y, w, h }
}
pub fn contains(&self, px: i32, py: i32) -> bool {
px >= self.x && px < self.x + self.w && py >= self.y && py < self.y + self.h
}
}
@@ -0,0 +1,68 @@
use super::Rect;
use crate::renderer::Renderer;
/// A smooth gradient progress bar matching the C# SmoothProgressBar control.
pub struct ProgressBar {
pub rect: Rect,
pub minimum: i32,
pub maximum: i32,
pub value: i32,
pub color1: u32,
pub color2: u32,
pub visible: bool,
}
impl ProgressBar {
pub fn new(x: i32, y: i32, w: i32, h: i32) -> Self {
Self {
rect: Rect::new(x, y, w, h),
minimum: 0,
maximum: 100,
value: 0,
color1: Renderer::rgb(97, 147, 232),
color2: Renderer::rgb(28, 99, 232),
visible: true,
}
}
pub fn draw(&self, renderer: &mut Renderer) {
if !self.visible {
return;
}
let range = (self.maximum - self.minimum).max(1) as f32;
let percent = (self.value - self.minimum) as f32 / range;
let fill_w = (self.rect.w as f32 * percent) as i32;
if fill_w > 0 {
renderer.fill_gradient_h(
self.rect.x,
self.rect.y,
fill_w,
self.rect.h,
self.color1,
self.color2,
);
}
// 3-D border.
let r = self.rect;
let dark = Renderer::rgb(105, 105, 105);
let light = Renderer::rgb(255, 255, 255);
// Top
for dx in 0..r.w {
renderer.set_pixel(r.x + dx, r.y, dark);
}
// Left
for dy in 0..r.h {
renderer.set_pixel(r.x, r.y + dy, dark);
}
// Bottom
for dx in 0..r.w {
renderer.set_pixel(r.x + dx, r.y + r.h - 1, light);
}
// Right
for dy in 0..r.h {
renderer.set_pixel(r.x + r.w - 1, r.y + dy, light);
}
}
}