mirror of
https://gitlab.com/cleanflash/installer.git
synced 2026-08-13 02:41:38 +08:00
Add Rust port
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user