use std::cell::RefCell;
use std::process::ExitCode;
use std::rc::Rc;
use smudge_core::geometry::{Color, Size};
use smudge_core::reactive::{self, RwSignal, prelude::*};
use smudge_core::style::{Align, Length, px};
use smudge_core::theme::Theme;
use smudge_core::widget::panel;
use smudge_platform::{PlatformError, PresentStatsHandle, WindowOptions, run_with};
use smudge_text::{LabelExt, label};
const BOXES: usize = 120;
const BOX_SIZE: f32 = 36.0;
const GAP: f32 = 4.0;
const PADDING: f32 = 16.0;
const CAPTION_EVERY: u64 = 120;
type SetColor = Box<dyn Fn(Color)>;
const PALETTE: [Color; 6] = [
Color::rgb(232, 93, 76),
Color::rgb(238, 160, 62),
Color::rgb(236, 214, 96),
Color::rgb(94, 191, 122),
Color::rgb(72, 155, 226),
Color::rgb(158, 112, 226),
];
fn main() -> ExitCode {
match run() {
Ok(()) => ExitCode::SUCCESS,
Err(error) => {
eprintln!("smudge: {error}");
ExitCode::FAILURE
}
}
}
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen(start)]
pub fn start_web() -> Result<(), wasm_bindgen::JsValue> {
std::panic::set_hook(Box::new(|info| console_error(&info.to_string())));
match run() {
Ok(()) => Ok(()),
Err(error) => {
console_error(&error.to_string());
Err(error.to_string().into())
}
}
}
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::prelude::wasm_bindgen;
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
unsafe extern "C" {
#[wasm_bindgen(js_namespace = console)]
fn console_error(message: &str);
}
fn run() -> Result<(), PlatformError> {
let setters: Rc<RefCell<Vec<SetColor>>> = Rc::new(RefCell::new(Vec::with_capacity(BOXES)));
let grid_setters = Rc::clone(&setters);
let stats = PresentStatsHandle::new();
let caption = RwSignal::new(String::from("damage: waiting for the first present"));
let caption_read = caption.clone();
let build = move || {
let mut grid = panel()
.row()
.wrap(true)
.gap(Size {
width: GAP,
height: GAP,
})
.width(Length::pct(100.0));
let mut setters = grid_setters.borrow_mut();
for index in 0..BOXES {
let (color, set_color) = reactive::signal_local(base_color(index));
setters.push(Box::new(move |color| set_color.set(color)));
grid = grid.child(
panel()
.width(px(BOX_SIZE))
.height(px(BOX_SIZE))
.corner_radius(8.0)
.border(2.0)
.border_color(Color::rgb(70, 74, 88))
.background(move || color.get().into()),
);
}
drop(setters);
panel()
.column()
.align_items(Align::Start)
.padding(px(PADDING))
.gap(Size {
width: 0.0,
height: 12.0,
})
.background(Color::rgb(24, 26, 32))
.child(
label("Smudge boxes")
.text_size(20.0)
.text_color(Color::WHITE),
)
.child(
label(move || caption_read.get())
.text_size(13.0)
.text_color(Color::rgb(160, 164, 178)),
)
.child(grid)
};
run_with(
WindowOptions {
title: String::from("Smudge boxes"),
size: Size {
width: 960.0,
height: 640.0,
},
clear_color: Color::rgb(16, 17, 21),
stats: Some(stats.clone()),
..WindowOptions::default()
},
build,
move |app| {
app.set_theme(Theme::dark());
let mut next = 0usize;
let mut frames = 0_u64;
app.set_tick(move |_| {
let index = next % BOXES;
let pass = next / BOXES;
if let Some(set_color) = setters.borrow().get(index) {
set_color(PALETTE[(pass + index) % PALETTE.len()]);
}
next = next.wrapping_add(1);
frames += 1;
if frames.is_multiple_of(CAPTION_EVERY) {
let present = stats.get();
caption.set(format!(
"frame {frames}: {} present copied {} of {} kilobytes",
if present.with_damage {
"damage"
} else {
"full"
},
present.bytes_copied / 1024,
u64::from(present.surface_width) * u64::from(present.surface_height) * 4
/ 1024,
));
}
});
},
)
}
fn base_color(index: usize) -> Color {
let shade = 40 + (index % 8) as u8 * 6;
Color::rgb(shade, shade, shade + 20)
}