mirror of
https://gitlab.com/cleanflash/installer.git
synced 2026-08-12 18:31:37 +08:00
Remove PNG loading
This commit is contained in:
@@ -2,7 +2,7 @@ use crate::install_flags::{self, InstallFlags};
|
|||||||
use crate::installer;
|
use crate::installer;
|
||||||
use clean_flash_common::{uninstaller, redirection, update_checker, ProgressCallback};
|
use clean_flash_common::{uninstaller, redirection, update_checker, ProgressCallback};
|
||||||
use clean_flash_ui::font::FontManager;
|
use clean_flash_ui::font::FontManager;
|
||||||
use clean_flash_ui::renderer::{Renderer, RgbaImage};
|
use clean_flash_ui::renderer::Renderer;
|
||||||
use clean_flash_ui::widgets::button::GradientButton;
|
use clean_flash_ui::widgets::button::GradientButton;
|
||||||
use clean_flash_ui::widgets::checkbox::ImageCheckBox;
|
use clean_flash_ui::widgets::checkbox::ImageCheckBox;
|
||||||
use clean_flash_ui::widgets::label::Label;
|
use clean_flash_ui::widgets::label::Label;
|
||||||
@@ -59,10 +59,7 @@ pub struct InstallForm {
|
|||||||
// Header
|
// Header
|
||||||
pub title_text: String,
|
pub title_text: String,
|
||||||
pub subtitle_text: String,
|
pub subtitle_text: String,
|
||||||
pub flash_logo: RgbaImage,
|
flash_logo_cache: clean_flash_ui::flash_logo::FlashLogoCache,
|
||||||
// Checkbox images
|
|
||||||
pub checkbox_on: RgbaImage,
|
|
||||||
pub checkbox_off: RgbaImage,
|
|
||||||
// Navigation buttons
|
// Navigation buttons
|
||||||
pub prev_button: GradientButton,
|
pub prev_button: GradientButton,
|
||||||
pub next_button: GradientButton,
|
pub next_button: GradientButton,
|
||||||
@@ -132,12 +129,6 @@ impl InstallForm {
|
|||||||
let title_text = "Clean Flash Player".to_string();
|
let title_text = "Clean Flash Player".to_string();
|
||||||
let subtitle_text = format!("built from version {} (China)", version);
|
let subtitle_text = format!("built from version {} (China)", version);
|
||||||
|
|
||||||
// Load images from the common resources folder.
|
|
||||||
// These are loaded from the C# project's assets alongside the binary.
|
|
||||||
let flash_logo = load_resource_image("flashLogo.png");
|
|
||||||
let checkbox_on = load_resource_image("checkboxOn.png");
|
|
||||||
let checkbox_off = load_resource_image("checkboxOff.png");
|
|
||||||
|
|
||||||
let fonts = FontManager::new();
|
let fonts = FontManager::new();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
@@ -145,9 +136,7 @@ impl InstallForm {
|
|||||||
panel: Panel::Disclaimer,
|
panel: Panel::Disclaimer,
|
||||||
title_text,
|
title_text,
|
||||||
subtitle_text,
|
subtitle_text,
|
||||||
flash_logo,
|
flash_logo_cache: clean_flash_ui::flash_logo::FlashLogoCache::new(),
|
||||||
checkbox_on,
|
|
||||||
checkbox_off,
|
|
||||||
prev_button: btn(90, 286, 138, 31, "QUIT"),
|
prev_button: btn(90, 286, 138, 31, "QUIT"),
|
||||||
next_button: btn(497, 286, 138, 31, "AGREE"),
|
next_button: btn(497, 286, 138, 31, "AGREE"),
|
||||||
// Disclaimer panel
|
// Disclaimer panel
|
||||||
@@ -240,10 +229,10 @@ The following details could be useful. Press the Retry button to try again.",
|
|||||||
// ----- Draw -----
|
// ----- Draw -----
|
||||||
renderer.clear(BG_COLOR);
|
renderer.clear(BG_COLOR);
|
||||||
|
|
||||||
// Header: flash logo.
|
// Header: flash logo (cached software render).
|
||||||
let lw = (self.flash_logo.width as f32 * self.scale) as i32;
|
self.flash_logo_cache.draw(
|
||||||
let lh = (self.flash_logo.height as f32 * self.scale) as i32;
|
renderer, self.s(90), self.s(36), self.s(109), self.s(107),
|
||||||
renderer.draw_image_scaled(self.s(90), self.s(36), lw, lh, &self.flash_logo);
|
);
|
||||||
|
|
||||||
// Title.
|
// Title.
|
||||||
self.fonts.draw_text(
|
self.fonts.draw_text(
|
||||||
@@ -561,34 +550,27 @@ including Clean Flash Player and older versions of Adobe Flash Player."
|
|||||||
// ---- Drawing helpers ----
|
// ---- Drawing helpers ----
|
||||||
|
|
||||||
fn draw_disclaimer(&self, r: &mut Renderer) {
|
fn draw_disclaimer(&self, r: &mut Renderer) {
|
||||||
self.disclaimer_box
|
self.disclaimer_box.draw(r);
|
||||||
.draw(r, &self.checkbox_on, &self.checkbox_off);
|
|
||||||
self.disclaimer_label.draw(r, &self.fonts);
|
self.disclaimer_label.draw(r, &self.fonts);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_choice(&self, r: &mut Renderer) {
|
fn draw_choice(&self, r: &mut Renderer) {
|
||||||
self.browser_ask_label.draw(r, &self.fonts);
|
self.browser_ask_label.draw(r, &self.fonts);
|
||||||
self.pepper_box
|
self.pepper_box.draw(r);
|
||||||
.draw(r, &self.checkbox_on, &self.checkbox_off);
|
|
||||||
self.pepper_label.draw(r, &self.fonts);
|
self.pepper_label.draw(r, &self.fonts);
|
||||||
self.netscape_box
|
self.netscape_box.draw(r);
|
||||||
.draw(r, &self.checkbox_on, &self.checkbox_off);
|
|
||||||
self.netscape_label.draw(r, &self.fonts);
|
self.netscape_label.draw(r, &self.fonts);
|
||||||
self.activex_box
|
self.activex_box.draw(r);
|
||||||
.draw(r, &self.checkbox_on, &self.checkbox_off);
|
|
||||||
self.activex_label.draw(r, &self.fonts);
|
self.activex_label.draw(r, &self.fonts);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_player_choice(&self, r: &mut Renderer) {
|
fn draw_player_choice(&self, r: &mut Renderer) {
|
||||||
self.player_ask_label.draw(r, &self.fonts);
|
self.player_ask_label.draw(r, &self.fonts);
|
||||||
self.player_box
|
self.player_box.draw(r);
|
||||||
.draw(r, &self.checkbox_on, &self.checkbox_off);
|
|
||||||
self.player_label.draw(r, &self.fonts);
|
self.player_label.draw(r, &self.fonts);
|
||||||
self.player_desktop_box
|
self.player_desktop_box.draw(r);
|
||||||
.draw(r, &self.checkbox_on, &self.checkbox_off);
|
|
||||||
self.player_desktop_label.draw(r, &self.fonts);
|
self.player_desktop_label.draw(r, &self.fonts);
|
||||||
self.player_start_menu_box
|
self.player_start_menu_box.draw(r);
|
||||||
.draw(r, &self.checkbox_on, &self.checkbox_off);
|
|
||||||
self.player_start_menu_label.draw(r, &self.fonts);
|
self.player_start_menu_label.draw(r, &self.fonts);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -664,13 +646,3 @@ fn join_with_and(items: &[&str]) -> String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Try to load a resource image from the original C# project's asset folder.
|
|
||||||
fn load_resource_image(name: &str) -> RgbaImage {
|
|
||||||
let bytes: &[u8] = match name {
|
|
||||||
"flashLogo.png" => include_bytes!("../../../resources/flashLogo.png"),
|
|
||||||
"checkboxOn.png" => include_bytes!("../../../resources/checkboxOn.png"),
|
|
||||||
"checkboxOff.png" => include_bytes!("../../../resources/checkboxOff.png"),
|
|
||||||
_ => return RgbaImage::empty(),
|
|
||||||
};
|
|
||||||
RgbaImage::from_png_bytes(bytes)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -5,6 +5,5 @@ edition = "2021"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
ab_glyph = { workspace = true }
|
ab_glyph = { workspace = true }
|
||||||
image = { workspace = true }
|
|
||||||
minifb = { workspace = true }
|
minifb = { workspace = true }
|
||||||
windows-sys = { workspace = true }
|
windows-sys = { workspace = true }
|
||||||
|
|||||||
@@ -0,0 +1,265 @@
|
|||||||
|
use crate::renderer::Renderer;
|
||||||
|
|
||||||
|
/// Cached flash logo bitmap. Stores a pre-rendered pixel buffer so the
|
||||||
|
/// expensive MSAA polygon fill only runs when the target size changes.
|
||||||
|
pub struct FlashLogoCache {
|
||||||
|
width: i32,
|
||||||
|
height: i32,
|
||||||
|
/// Pre-rendered pixels in 0x00RRGGBB format, row-major, size = width * height.
|
||||||
|
pixels: Vec<u32>,
|
||||||
|
/// Shadow offset derived from width.
|
||||||
|
shadow_off: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FlashLogoCache {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
width: 0,
|
||||||
|
height: 0,
|
||||||
|
pixels: Vec::new(),
|
||||||
|
shadow_off: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draw the flash logo at (x, y) with size (w, h).
|
||||||
|
/// Re-renders into the cache only if the size changed.
|
||||||
|
pub fn draw(&mut self, renderer: &mut Renderer, x: i32, y: i32, w: i32, h: i32) {
|
||||||
|
if w <= 0 || h <= 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.width != w || self.height != h {
|
||||||
|
self.rebuild(w, h);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw subtle drop shadow first.
|
||||||
|
let shadow_color = Renderer::rgb(0, 0, 0);
|
||||||
|
let shadow_alpha: u8 = 40;
|
||||||
|
let so = self.shadow_off;
|
||||||
|
for dy in 0..h {
|
||||||
|
for dx in 0..w {
|
||||||
|
renderer.blend_pixel(x + dx + so, y + dy + so, shadow_color, shadow_alpha);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blit cached pixels onto the renderer.
|
||||||
|
for dy in 0..h {
|
||||||
|
for dx in 0..w {
|
||||||
|
let px = self.pixels[dy as usize * w as usize + dx as usize];
|
||||||
|
renderer.set_pixel(x + dx, y + dy, px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Render the logo into the internal cache at the given size.
|
||||||
|
fn rebuild(&mut self, w: i32, h: i32) {
|
||||||
|
self.width = w;
|
||||||
|
self.height = h;
|
||||||
|
self.shadow_off = ((w as f32 * 0.02).round() as i32).max(1);
|
||||||
|
|
||||||
|
// Render into a temporary offscreen renderer.
|
||||||
|
let mut tmp = Renderer::new(w as usize, h as usize);
|
||||||
|
|
||||||
|
let vw = 500.0_f32;
|
||||||
|
let vh = 487.0_f32;
|
||||||
|
let sx = w as f32 / vw;
|
||||||
|
let sy = h as f32 / vh;
|
||||||
|
|
||||||
|
let blue = Renderer::rgb(0x00, 0x3A, 0x74);
|
||||||
|
tmp.fill_rect(0, 0, w, h, blue);
|
||||||
|
|
||||||
|
let white = Renderer::rgb(255, 255, 255);
|
||||||
|
let points = build_f_polygon();
|
||||||
|
fill_polygon(&mut tmp, &points, 0.0, 0.0, sx, sy, white);
|
||||||
|
|
||||||
|
self.pixels = tmp.buffer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Evaluate a cubic bezier curve and return `segments` points (excluding the start point).
|
||||||
|
fn cubic_bezier(
|
||||||
|
p0: (f32, f32),
|
||||||
|
p1: (f32, f32),
|
||||||
|
p2: (f32, f32),
|
||||||
|
p3: (f32, f32),
|
||||||
|
segments: usize,
|
||||||
|
) -> Vec<(f32, f32)> {
|
||||||
|
let mut pts = Vec::with_capacity(segments);
|
||||||
|
for i in 1..=segments {
|
||||||
|
let t = i as f32 / segments as f32;
|
||||||
|
let u = 1.0 - t;
|
||||||
|
let x = u * u * u * p0.0
|
||||||
|
+ 3.0 * u * u * t * p1.0
|
||||||
|
+ 3.0 * u * t * t * p2.0
|
||||||
|
+ t * t * t * p3.0;
|
||||||
|
let y = u * u * u * p0.1
|
||||||
|
+ 3.0 * u * u * t * p1.1
|
||||||
|
+ 3.0 * u * t * t * p2.1
|
||||||
|
+ t * t * t * p3.1;
|
||||||
|
pts.push((x, y));
|
||||||
|
}
|
||||||
|
pts
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build the "f" shape polygon by tracing the SVG path data, flattening beziers
|
||||||
|
/// into line segments. Coordinates are in the SVG viewBox space (500×487).
|
||||||
|
fn build_f_polygon() -> Vec<(f32, f32)> {
|
||||||
|
let mut pts = Vec::with_capacity(128);
|
||||||
|
const N: usize = 16; // segments per bezier curve
|
||||||
|
|
||||||
|
// M 269.2,138.7
|
||||||
|
pts.push((269.2, 138.7));
|
||||||
|
|
||||||
|
// c -22.5,27.5 -35.8,61.8 -48.7,95
|
||||||
|
pts.extend(cubic_bezier(
|
||||||
|
(269.2, 138.7),
|
||||||
|
(246.7, 166.2),
|
||||||
|
(233.4, 200.5),
|
||||||
|
(220.5, 233.7),
|
||||||
|
N,
|
||||||
|
));
|
||||||
|
|
||||||
|
// c -26.1,67.3 -43.6,105.8 -99.7,105.8
|
||||||
|
pts.extend(cubic_bezier(
|
||||||
|
(220.5, 233.7),
|
||||||
|
(194.4, 301.0),
|
||||||
|
(176.9, 339.5),
|
||||||
|
(120.8, 339.5),
|
||||||
|
N,
|
||||||
|
));
|
||||||
|
|
||||||
|
// V 402
|
||||||
|
pts.push((120.8, 402.0));
|
||||||
|
|
||||||
|
// c 46.4,0 84.1,-17.2 112,-51.2
|
||||||
|
pts.extend(cubic_bezier(
|
||||||
|
(120.8, 402.0),
|
||||||
|
(167.2, 402.0),
|
||||||
|
(204.9, 384.8),
|
||||||
|
(232.8, 350.8),
|
||||||
|
N,
|
||||||
|
));
|
||||||
|
|
||||||
|
// c 17.9,-21.9 30.3,-49.3 40.9,-75.8
|
||||||
|
pts.extend(cubic_bezier(
|
||||||
|
(232.8, 350.8),
|
||||||
|
(250.7, 328.9),
|
||||||
|
(263.1, 301.5),
|
||||||
|
(273.7, 275.0),
|
||||||
|
N,
|
||||||
|
));
|
||||||
|
|
||||||
|
// h 74.1
|
||||||
|
pts.push((347.8, 275.0));
|
||||||
|
|
||||||
|
// v -62.5
|
||||||
|
pts.push((347.8, 212.5));
|
||||||
|
|
||||||
|
// h -48.8
|
||||||
|
pts.push((299.0, 212.5));
|
||||||
|
|
||||||
|
// c 18.9,-40.6 39.2,-62.5 82.1,-62.5
|
||||||
|
pts.extend(cubic_bezier(
|
||||||
|
(299.0, 212.5),
|
||||||
|
(317.9, 171.9),
|
||||||
|
(338.2, 150.0),
|
||||||
|
(381.1, 150.0),
|
||||||
|
N,
|
||||||
|
));
|
||||||
|
|
||||||
|
// V 87.5
|
||||||
|
pts.push((381.1, 87.5));
|
||||||
|
|
||||||
|
// C 334.8,87.5 297.1,104.7 269.2,138.7 (absolute cubic, closes the shape)
|
||||||
|
pts.extend(cubic_bezier(
|
||||||
|
(381.1, 87.5),
|
||||||
|
(334.8, 87.5),
|
||||||
|
(297.1, 104.7),
|
||||||
|
(269.2, 138.7),
|
||||||
|
N,
|
||||||
|
));
|
||||||
|
|
||||||
|
pts
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test whether a point is inside the polygon using the even-odd (parity) rule.
|
||||||
|
fn point_in_polygon(transformed: &[(f32, f32)], px: f32, py: f32) -> bool {
|
||||||
|
let n = transformed.len();
|
||||||
|
let mut inside = false;
|
||||||
|
let mut j = n - 1;
|
||||||
|
for i in 0..n {
|
||||||
|
let (xi, yi) = transformed[i];
|
||||||
|
let (xj, yj) = transformed[j];
|
||||||
|
if ((yi > py) != (yj > py)) && (px < (xj - xi) * (py - yi) / (yj - yi) + xi) {
|
||||||
|
inside = !inside;
|
||||||
|
}
|
||||||
|
j = i;
|
||||||
|
}
|
||||||
|
inside
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Scanline-fill a polygon with 4×4 MSAA antialiasing after transforming from
|
||||||
|
/// viewBox to screen coordinates: screen_x = ox + vx * sx, screen_y = oy + vy * sy.
|
||||||
|
fn fill_polygon(
|
||||||
|
renderer: &mut Renderer,
|
||||||
|
points: &[(f32, f32)],
|
||||||
|
ox: f32,
|
||||||
|
oy: f32,
|
||||||
|
sx: f32,
|
||||||
|
sy: f32,
|
||||||
|
color: u32,
|
||||||
|
) {
|
||||||
|
if points.len() < 3 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transform to screen space.
|
||||||
|
let transformed: Vec<(f32, f32)> = points
|
||||||
|
.iter()
|
||||||
|
.map(|&(px, py)| (ox + px * sx, oy + py * sy))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// Bounding box.
|
||||||
|
let mut min_x = f32::MAX;
|
||||||
|
let mut max_x = f32::MIN;
|
||||||
|
let mut min_y = f32::MAX;
|
||||||
|
let mut max_y = f32::MIN;
|
||||||
|
for &(x, y) in &transformed {
|
||||||
|
if x < min_x { min_x = x; }
|
||||||
|
if x > max_x { max_x = x; }
|
||||||
|
if y < min_y { min_y = y; }
|
||||||
|
if y > max_y { max_y = y; }
|
||||||
|
}
|
||||||
|
|
||||||
|
let x_start = (min_x.floor() as i32).max(0);
|
||||||
|
let x_end = (max_x.ceil() as i32).min(renderer.width as i32 - 1);
|
||||||
|
let y_start = (min_y.floor() as i32).max(0);
|
||||||
|
let y_end = (max_y.ceil() as i32).min(renderer.height as i32 - 1);
|
||||||
|
|
||||||
|
// 4×4 sub-pixel grid offsets (16 samples per pixel).
|
||||||
|
const GRID: usize = 4;
|
||||||
|
const SAMPLES: usize = GRID * GRID;
|
||||||
|
let step = 1.0 / GRID as f32;
|
||||||
|
let half_step = step / 2.0;
|
||||||
|
|
||||||
|
for y in y_start..=y_end {
|
||||||
|
for x in x_start..=x_end {
|
||||||
|
let mut hits = 0u32;
|
||||||
|
for sy_i in 0..GRID {
|
||||||
|
let sample_y = y as f32 + half_step + sy_i as f32 * step;
|
||||||
|
for sx_i in 0..GRID {
|
||||||
|
let sample_x = x as f32 + half_step + sx_i as f32 * step;
|
||||||
|
if point_in_polygon(&transformed, sample_x, sample_y) {
|
||||||
|
hits += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if hits == SAMPLES as u32 {
|
||||||
|
renderer.set_pixel(x, y, color);
|
||||||
|
} else if hits > 0 {
|
||||||
|
let alpha = ((hits * 255 + SAMPLES as u32 / 2) / SAMPLES as u32) as u8;
|
||||||
|
renderer.blend_pixel(x, y, color, alpha);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
pub mod flash_logo;
|
||||||
pub mod font;
|
pub mod font;
|
||||||
pub mod renderer;
|
pub mod renderer;
|
||||||
pub mod widgets;
|
pub mod widgets;
|
||||||
|
|||||||
@@ -108,77 +108,6 @@ impl Renderer {
|
|||||||
self.fill_rect(x + dx, y, 1, h, c);
|
self.fill_rect(x + dx, y, 1, h, c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draw an RGBA image scaled to (w, h) at (x, y) using nearest-neighbor interpolation.
|
|
||||||
pub fn draw_image_scaled(&mut self, x: i32, y: i32, w: i32, h: i32, img: &RgbaImage) {
|
|
||||||
if w <= 0 || h <= 0 || img.width == 0 || img.height == 0 {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for dy in 0..h {
|
|
||||||
for dx in 0..w {
|
|
||||||
let src_x = (dx as f32 * img.width as f32 / w as f32) as usize;
|
|
||||||
let src_y = (dy as f32 * img.height as f32 / h as f32) as usize;
|
|
||||||
let idx = (src_y * img.width + src_x) * 4;
|
|
||||||
let r = img.data[idx];
|
|
||||||
let g = img.data[idx + 1];
|
|
||||||
let b = img.data[idx + 2];
|
|
||||||
let a = img.data[idx + 3];
|
|
||||||
if a == 255 {
|
|
||||||
self.set_pixel(x + dx, y + dy, Self::rgb(r, g, b));
|
|
||||||
} else if a > 0 {
|
|
||||||
self.blend_pixel(x + dx, y + dy, Self::rgb(r, g, b), a);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Draw an RGBA image onto the framebuffer at (x, y).
|
|
||||||
pub fn draw_image(&mut self, x: i32, y: i32, img: &RgbaImage) {
|
|
||||||
for iy in 0..img.height as i32 {
|
|
||||||
for ix in 0..img.width as i32 {
|
|
||||||
let idx = (iy as usize * img.width + ix as usize) * 4;
|
|
||||||
let r = img.data[idx];
|
|
||||||
let g = img.data[idx + 1];
|
|
||||||
let b = img.data[idx + 2];
|
|
||||||
let a = img.data[idx + 3];
|
|
||||||
if a == 255 {
|
|
||||||
self.set_pixel(x + ix, y + iy, Self::rgb(r, g, b));
|
|
||||||
} else if a > 0 {
|
|
||||||
self.blend_pixel(x + ix, y + iy, Self::rgb(r, g, b), a);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Simple RGBA image stored as raw bytes.
|
|
||||||
pub struct RgbaImage {
|
|
||||||
pub width: usize,
|
|
||||||
pub height: usize,
|
|
||||||
pub data: Vec<u8>, // RGBA, row-major
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RgbaImage {
|
|
||||||
/// Load a PNG from embedded bytes.
|
|
||||||
pub fn from_png_bytes(bytes: &[u8]) -> Self {
|
|
||||||
let img = image::load_from_memory_with_format(bytes, image::ImageFormat::Png)
|
|
||||||
.expect("Failed to decode PNG")
|
|
||||||
.to_rgba8();
|
|
||||||
Self {
|
|
||||||
width: img.width() as usize,
|
|
||||||
height: img.height() as usize,
|
|
||||||
data: img.into_raw(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create an empty (transparent) image.
|
|
||||||
pub fn empty() -> Self {
|
|
||||||
Self {
|
|
||||||
width: 0,
|
|
||||||
height: 0,
|
|
||||||
data: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- helpers ----
|
// ---- helpers ----
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use super::Rect;
|
use super::Rect;
|
||||||
use crate::renderer::{Renderer, RgbaImage};
|
use crate::renderer::Renderer;
|
||||||
|
|
||||||
/// An image-based checkbox matching the C# ImageCheckBox control.
|
/// A software-rendered checkbox with a gray gradient, black outline, and white checkmark.
|
||||||
pub struct ImageCheckBox {
|
pub struct ImageCheckBox {
|
||||||
pub rect: Rect,
|
pub rect: Rect,
|
||||||
pub checked: bool,
|
pub checked: bool,
|
||||||
@@ -32,59 +32,34 @@ impl ImageCheckBox {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw(
|
pub fn draw(&self, renderer: &mut Renderer) {
|
||||||
&self,
|
|
||||||
renderer: &mut Renderer,
|
|
||||||
checked_img: &RgbaImage,
|
|
||||||
unchecked_img: &RgbaImage,
|
|
||||||
) {
|
|
||||||
if !self.visible {
|
if !self.visible {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let img = if self.checked {
|
|
||||||
checked_img
|
let r = self.rect;
|
||||||
} else {
|
|
||||||
unchecked_img
|
// Gray gradient background (matching button style).
|
||||||
};
|
let color1 = Renderer::rgb(118, 118, 118);
|
||||||
if img.width > 0 && img.height > 0 {
|
let color2 = Renderer::rgb(81, 81, 81);
|
||||||
renderer.draw_image_scaled(self.rect.x, self.rect.y, self.rect.w, self.rect.h, img);
|
renderer.fill_gradient_v(r.x, r.y, r.w, r.h, color1, color2);
|
||||||
} else {
|
|
||||||
// Fallback: draw a simple square.
|
// Black 1px outline.
|
||||||
let bg = if self.checked {
|
renderer.draw_rect(r.x, r.y, r.w, r.h, Renderer::rgb(0, 0, 0));
|
||||||
Renderer::rgb(97, 147, 232)
|
|
||||||
} else {
|
// White checkmark when checked.
|
||||||
Renderer::rgb(80, 80, 80)
|
if self.checked {
|
||||||
};
|
let white = Renderer::rgb(255, 255, 255);
|
||||||
renderer.fill_rect(self.rect.x, self.rect.y, self.rect.w, self.rect.h, bg);
|
Self::draw_checkmark(renderer, r.x, r.y, r.w, r.h, white);
|
||||||
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 {
|
if !self.enabled {
|
||||||
// Dim overlay.
|
// Dim overlay.
|
||||||
for dy in 0..self.rect.h {
|
for dy in 0..r.h {
|
||||||
for dx in 0..self.rect.w {
|
for dx in 0..r.w {
|
||||||
renderer.blend_pixel(
|
renderer.blend_pixel(
|
||||||
self.rect.x + dx,
|
r.x + dx,
|
||||||
self.rect.y + dy,
|
r.y + dy,
|
||||||
Renderer::rgb(50, 51, 51),
|
Renderer::rgb(50, 51, 51),
|
||||||
100,
|
100,
|
||||||
);
|
);
|
||||||
@@ -92,4 +67,72 @@ impl ImageCheckBox {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Draw a checkmark scaled to fit within the given box.
|
||||||
|
fn draw_checkmark(renderer: &mut Renderer, bx: i32, by: i32, bw: i32, bh: i32, color: u32) {
|
||||||
|
// Checkmark geometry defined in a normalised coordinate space.
|
||||||
|
// The check goes from bottom-left, down to a valley, then up to top-right.
|
||||||
|
// Key points (in fractions of size):
|
||||||
|
// start: (0.20, 0.50)
|
||||||
|
// valley: (0.40, 0.72)
|
||||||
|
// end: (0.80, 0.25)
|
||||||
|
// We draw two thick line segments between these points.
|
||||||
|
|
||||||
|
let w = bw as f32;
|
||||||
|
let h = bh as f32;
|
||||||
|
|
||||||
|
// Absolute coordinates of the three key points.
|
||||||
|
let x0 = bx as f32 + w * 0.20;
|
||||||
|
let y0 = by as f32 + h * 0.48;
|
||||||
|
let x1 = bx as f32 + w * 0.40;
|
||||||
|
let y1 = by as f32 + h * 0.72;
|
||||||
|
let x2 = bx as f32 + w * 0.80;
|
||||||
|
let y2 = by as f32 + h * 0.25;
|
||||||
|
|
||||||
|
// Line thickness scales with checkbox size.
|
||||||
|
let thickness = (w * 0.14).max(1.5);
|
||||||
|
|
||||||
|
draw_thick_line(renderer, x0, y0, x1, y1, thickness, color);
|
||||||
|
draw_thick_line(renderer, x1, y1, x2, y2, thickness, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draw a thick line between two points using filled circles along the path (round caps).
|
||||||
|
fn draw_thick_line(
|
||||||
|
renderer: &mut Renderer,
|
||||||
|
x0: f32,
|
||||||
|
y0: f32,
|
||||||
|
x1: f32,
|
||||||
|
y1: f32,
|
||||||
|
thickness: f32,
|
||||||
|
color: u32,
|
||||||
|
) {
|
||||||
|
let dx = x1 - x0;
|
||||||
|
let dy = y1 - y0;
|
||||||
|
let dist = (dx * dx + dy * dy).sqrt();
|
||||||
|
let steps = (dist * 2.0).ceil() as i32;
|
||||||
|
let half = thickness / 2.0;
|
||||||
|
|
||||||
|
for i in 0..=steps {
|
||||||
|
let t = i as f32 / steps.max(1) as f32;
|
||||||
|
let cx = x0 + dx * t;
|
||||||
|
let cy = y0 + dy * t;
|
||||||
|
|
||||||
|
// Fill a small circle at (cx, cy).
|
||||||
|
let min_x = (cx - half).floor() as i32;
|
||||||
|
let max_x = (cx + half).ceil() as i32;
|
||||||
|
let min_y = (cy - half).floor() as i32;
|
||||||
|
let max_y = (cy + half).ceil() as i32;
|
||||||
|
let r_sq = half * half;
|
||||||
|
|
||||||
|
for py in min_y..=max_y {
|
||||||
|
for px in min_x..=max_x {
|
||||||
|
let fdx = px as f32 + 0.5 - cx;
|
||||||
|
let fdy = py as f32 + 0.5 - cy;
|
||||||
|
if fdx * fdx + fdy * fdy <= r_sq {
|
||||||
|
renderer.set_pixel(px, py, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use clean_flash_common::{redirection, uninstaller, update_checker, ProgressCallback};
|
use clean_flash_common::{redirection, uninstaller, update_checker, ProgressCallback};
|
||||||
use clean_flash_ui::font::FontManager;
|
use clean_flash_ui::font::FontManager;
|
||||||
use clean_flash_ui::renderer::{Renderer, RgbaImage};
|
use clean_flash_ui::renderer::Renderer;
|
||||||
use clean_flash_ui::widgets::button::GradientButton;
|
use clean_flash_ui::widgets::button::GradientButton;
|
||||||
use clean_flash_ui::widgets::label::Label;
|
use clean_flash_ui::widgets::label::Label;
|
||||||
use clean_flash_ui::widgets::progress_bar::ProgressBar;
|
use clean_flash_ui::widgets::progress_bar::ProgressBar;
|
||||||
@@ -43,7 +43,7 @@ pub struct UninstallForm {
|
|||||||
panel: Panel,
|
panel: Panel,
|
||||||
title_text: String,
|
title_text: String,
|
||||||
subtitle_text: String,
|
subtitle_text: String,
|
||||||
flash_logo: RgbaImage,
|
flash_logo_cache: clean_flash_ui::flash_logo::FlashLogoCache,
|
||||||
prev_button: GradientButton,
|
prev_button: GradientButton,
|
||||||
next_button: GradientButton,
|
next_button: GradientButton,
|
||||||
// Before uninstall
|
// Before uninstall
|
||||||
@@ -79,7 +79,7 @@ impl UninstallForm {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let version = update_checker::FLASH_VERSION;
|
let version = update_checker::FLASH_VERSION;
|
||||||
let flash_logo = load_resource_image("flashLogo.png");
|
|
||||||
let fonts = FontManager::new();
|
let fonts = FontManager::new();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
@@ -87,7 +87,7 @@ impl UninstallForm {
|
|||||||
panel: Panel::BeforeInstall,
|
panel: Panel::BeforeInstall,
|
||||||
title_text: "Clean Flash Player".into(),
|
title_text: "Clean Flash Player".into(),
|
||||||
subtitle_text: format!("built from version {} (China)", version),
|
subtitle_text: format!("built from version {} (China)", version),
|
||||||
flash_logo,
|
flash_logo_cache: clean_flash_ui::flash_logo::FlashLogoCache::new(),
|
||||||
prev_button: btn(90, 286, 138, 31, "QUIT"),
|
prev_button: btn(90, 286, 138, 31, "QUIT"),
|
||||||
next_button: btn(497, 286, 138, 31, "UNINSTALL"),
|
next_button: btn(497, 286, 138, 31, "UNINSTALL"),
|
||||||
before_label: lbl(PANEL_X + 3, PANEL_Y + 2, BEFORE_TEXT, 15.0),
|
before_label: lbl(PANEL_X + 3, PANEL_Y + 2, BEFORE_TEXT, 15.0),
|
||||||
@@ -156,9 +156,9 @@ The following details could be useful. Press the Retry button to try again.",
|
|||||||
|
|
||||||
// Draw.
|
// Draw.
|
||||||
renderer.clear(BG_COLOR);
|
renderer.clear(BG_COLOR);
|
||||||
let lw = (self.flash_logo.width as f32 * self.scale) as i32;
|
self.flash_logo_cache.draw(
|
||||||
let lh = (self.flash_logo.height as f32 * self.scale) as i32;
|
renderer, self.s(90), self.s(36), self.s(109), self.s(107),
|
||||||
renderer.draw_image_scaled(self.s(90), self.s(36), lw, lh, &self.flash_logo);
|
);
|
||||||
|
|
||||||
self.fonts
|
self.fonts
|
||||||
.draw_text(renderer, self.s(233), self.s(54), &self.title_text, self.sf(32.0), FG_COLOR);
|
.draw_text(renderer, self.s(233), self.s(54), &self.title_text, self.sf(32.0), FG_COLOR);
|
||||||
@@ -288,10 +288,3 @@ impl ProgressCallback for ThreadProgressCallback {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_resource_image(name: &str) -> RgbaImage {
|
|
||||||
let bytes: &[u8] = match name {
|
|
||||||
"flashLogo.png" => include_bytes!("../../../resources/flashLogo.png"),
|
|
||||||
_ => return RgbaImage::empty(),
|
|
||||||
};
|
|
||||||
RgbaImage::from_png_bytes(bytes)
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user