#[path = "checklist.rs"] mod checklist;
#[path = "verify_log.rs"] mod verify_log;
#[path = "demo_cat01_init.rs"] mod demo_cat01_init;
#[path = "demo_cat02_sprites.rs"] mod demo_cat02_sprites;
#[path = "demo_cat03_shapes.rs"] mod demo_cat03_shapes;
#[path = "demo_cat04_blend_mask.rs"] mod demo_cat04_blend_mask;
#[path = "demo_cat05_subscreen.rs"] mod demo_cat05_subscreen;
#[path = "demo_cat06_keyboard.rs"] mod demo_cat06_keyboard;
#[path = "demo_cat07_mouse.rs"] mod demo_cat07_mouse;
#[path = "demo_cat08_gamepad.rs"] mod demo_cat08_gamepad;
#[path = "demo_cat09_sound.rs"] mod demo_cat09_sound;
#[path = "demo_cat10_text.rs"] mod demo_cat10_text;
#[path = "demo_cat11_overlay.rs"] mod demo_cat11_overlay;
#[path = "demo_cat12_debuglog.rs"] mod demo_cat12_debuglog;
use rustraight::prelude::*;
use checklist::{Category, ChecklistItem, VerifyMethod, CHECKLIST, RUN_ORDER};
use verify_log::{Verdict, VerifyLog};
pub(crate) const COMMON_W: i32 = 960;
pub(crate) const COMMON_H: i32 = 720;
const Y0: i32 = 110;
pub(crate) fn asset(rel: &str) -> String {
format!("{}/検証素材/{}", env!("CARGO_MANIFEST_DIR"), rel)
}
pub(crate) fn missing_assets(rels: &[&str]) -> Vec<String> {
rels.iter()
.map(|r| asset(r))
.filter(|p| !std::path::Path::new(p).exists())
.collect()
}
enum CategoryState {
Init(demo_cat01_init::State),
Sprites(demo_cat02_sprites::State),
Shapes,
BlendMask(demo_cat04_blend_mask::State),
Subscreen(demo_cat05_subscreen::State),
Keyboard,
Mouse(demo_cat07_mouse::State),
Gamepad,
Sound(demo_cat09_sound::State),
Text(demo_cat10_text::State),
Overlay(demo_cat11_overlay::State),
DebugLog,
}
fn enter_category(cat: Category) -> Result<CategoryState, String> {
set_window_size(COMMON_W, COMMON_H);
set_screen_size(COMMON_W, COMMON_H);
match cat {
Category::Init => demo_cat01_init::setup().map(CategoryState::Init),
Category::Sprites => demo_cat02_sprites::setup().map(CategoryState::Sprites),
Category::Shapes => demo_cat03_shapes::setup().map(|_| CategoryState::Shapes),
Category::BlendMask => demo_cat04_blend_mask::setup().map(CategoryState::BlendMask),
Category::Subscreen => demo_cat05_subscreen::setup().map(CategoryState::Subscreen),
Category::Keyboard => demo_cat06_keyboard::setup().map(|_| CategoryState::Keyboard),
Category::Mouse => demo_cat07_mouse::setup().map(CategoryState::Mouse),
Category::Gamepad => demo_cat08_gamepad::setup().map(|_| CategoryState::Gamepad),
Category::Sound => demo_cat09_sound::setup().map(CategoryState::Sound),
Category::Text => demo_cat10_text::setup().map(CategoryState::Text),
Category::Overlay => demo_cat11_overlay::setup().map(CategoryState::Overlay),
Category::DebugLog => demo_cat12_debuglog::setup().map(|_| CategoryState::DebugLog),
}
}
fn render_category(state: &mut CategoryState) {
match state {
CategoryState::Init(s) => demo_cat01_init::render(s),
CategoryState::Sprites(s) => demo_cat02_sprites::render(s),
CategoryState::Shapes => demo_cat03_shapes::render(),
CategoryState::BlendMask(s) => demo_cat04_blend_mask::render(s),
CategoryState::Subscreen(s) => demo_cat05_subscreen::render(s),
CategoryState::Keyboard => demo_cat06_keyboard::render(),
CategoryState::Mouse(s) => demo_cat07_mouse::render(s),
CategoryState::Gamepad => demo_cat08_gamepad::render(),
CategoryState::Sound(s) => demo_cat09_sound::render(s),
CategoryState::Text(s) => demo_cat10_text::render(s),
CategoryState::Overlay(s) => demo_cat11_overlay::render(s),
CategoryState::DebugLog => demo_cat12_debuglog::render(),
}
}
fn run_auto_checks(log: &mut VerifyLog) {
for item in CHECKLIST.iter().filter(|i| i.method == VerifyMethod::Auto) {
let (ok, reason) = auto_check(item);
log.record(item, if ok { Verdict::Pass } else { Verdict::Fail }, &reason);
}
}
fn auto_check(item: &ChecklistItem) -> (bool, String) {
match item.id {
27 => {
let c = Color::rgb(10, 20, 30);
let ok = c.r == 10 && c.g == 20 && c.b == 30 && c.a == 255;
(ok, format!("r={} g={} b={} a={}", c.r, c.g, c.b, c.a))
}
28 => {
let c = Color::rgba(10, 20, 30, 40);
let ok = c.r == 10 && c.g == 20 && c.b == 30 && c.a == 40;
(ok, format!("r={} g={} b={} a={}", c.r, c.g, c.b, c.a))
}
29 => {
let ok = Color::WHITE.r == 255 && Color::WHITE.g == 255 && Color::WHITE.b == 255 && Color::WHITE.a == 255
&& Color::BLACK.r == 0 && Color::BLACK.g == 0 && Color::BLACK.b == 0
&& Color::RED.r == 255 && Color::RED.g == 0 && Color::RED.b == 0
&& Color::GREEN.r == 0 && Color::GREEN.g == 255 && Color::GREEN.b == 0
&& Color::BLUE.r == 0 && Color::BLUE.g == 0 && Color::BLUE.b == 255
&& Color::YELLOW.r == 255 && Color::YELLOW.g == 255 && Color::YELLOW.b == 0
&& Color::CYAN.r == 0 && Color::CYAN.g == 255 && Color::CYAN.b == 255
&& Color::MAGENTA.r == 255 && Color::MAGENTA.g == 0 && Color::MAGENTA.b == 255;
(ok, String::new())
}
55 => {
let missing = missing_assets(&["fonts/Mamelon-3.5-Hi-Regular.otf"]);
if !missing.is_empty() {
return (false, format!("アセット不足のため検証不可: {}", missing.join(", ")));
}
let ok_handle = load_font(&asset("fonts/Mamelon-3.5-Hi-Regular.otf"), 20);
let bad_handle = load_font("no_such_font_for_verify_all.ttf", 20);
let ok = ok_handle != 0 && bad_handle == 0;
(ok, format!("ok_handle={ok_handle} bad_handle={bad_handle}"))
}
56 => {
let missing = missing_assets(&["fonts/Mamelon-3.5-Hi-Regular.otf"]);
if !missing.is_empty() {
return (false, format!("アセット不足のため検証不可: {}", missing.join(", ")));
}
let font = load_font(&asset("fonts/Mamelon-3.5-Hi-Regular.otf"), 20);
let w_short = get_text_width("A", font);
let w_long = get_text_width("AAAA", font);
let ok = w_short > 0 && w_long > w_short;
(ok, format!("w_short={w_short} w_long={w_long}"))
}
_ => (false, "未対応の自動チェック項目".to_string()),
}
}
fn draw_hud(item: &ChecklistItem, item_idx: usize, cat_total: usize, global_done: usize, global_total: usize) {
draw_rectangle(MAIN_SCREEN, 0, 0, COMMON_W, Y0, Color::rgb(30, 30, 45), true);
draw_text(
MAIN_SCREEN, 10, 6,
format!(
"[{}/{}] 全体 {}/{} カテゴリ: {}",
item_idx + 1, cat_total, global_done, global_total, item.category.name_ja()
),
Color::CYAN,
);
draw_text(MAIN_SCREEN, 10, 30, item.api, Color::YELLOW);
draw_text(MAIN_SCREEN, 10, 54, item.description, Color::WHITE);
draw_text(MAIN_SCREEN, 10, 84, "F1:合格(PASS) F2:不合格(FAIL) F3:スキップ(SKIP) Esc:中断して終了", Color::GREEN);
}
fn draw_done_screen(log: &VerifyLog) {
let (pass, fail, skip, notrun) = log.summary_counts();
draw_text(MAIN_SCREEN, 20, 40, "検証完了", Color::CYAN);
draw_text(MAIN_SCREEN, 20, 80, format!("PASS={pass} FAIL={fail} SKIP={skip} (未実施={notrun})"), Color::WHITE);
draw_text(MAIN_SCREEN, 20, 120, format!("詳細ログ: {}", log.path()), Color::YELLOW);
draw_text(MAIN_SCREEN, 20, 150, "ウィンドウを閉じるとこのプログラムは終了します。", Color::YELLOW);
}
enum Phase {
EnterCategory,
Judging,
Done,
}
struct Runner {
order_idx: usize,
items: Vec<&'static ChecklistItem>,
item_idx: usize,
phase: Phase,
state: Option<CategoryState>,
global_done: usize,
global_total: usize,
}
fn main() {
let mut log = VerifyLog::new();
run_auto_checks(&mut log);
init(WindowConfig {
title: String::from("rustraight verify_all"),
width: COMMON_W,
height: COMMON_H,
screen_width: COMMON_W,
screen_height: COMMON_H,
overlay_enabled: true,
..Default::default()
});
let mut runner = Runner {
order_idx: 0,
items: Vec::new(),
item_idx: 0,
phase: Phase::EnterCategory,
state: None,
global_done: 0,
global_total: checklist::manual_total(),
};
while advance_frame() {
draw_fill(MAIN_SCREEN, Color::rgb(15, 15, 20));
match runner.phase {
Phase::EnterCategory => {
if runner.order_idx >= RUN_ORDER.len() {
runner.phase = Phase::Done;
} else {
let cat = RUN_ORDER[runner.order_idx];
let items = checklist::manual_items_for(cat);
match enter_category(cat) {
Ok(state) => {
runner.state = Some(state);
runner.items = items;
runner.item_idx = 0;
if runner.items.is_empty() {
runner.order_idx += 1;
} else {
runner.phase = Phase::Judging;
}
}
Err(reason) => {
for item in &items {
log.record(item, Verdict::Skip, &reason);
runner.global_done += 1;
}
runner.order_idx += 1;
}
}
}
}
Phase::Judging => {
if let Some(state) = runner.state.as_mut() {
render_category(state);
}
let item = runner.items[runner.item_idx];
draw_hud(item, runner.item_idx, runner.items.len(), runner.global_done, runner.global_total);
if is_just_pressed(KeyCode::F1) {
log.record(item, Verdict::Pass, "");
runner.item_idx += 1;
runner.global_done += 1;
} else if is_just_pressed(KeyCode::F2) {
log.record(item, Verdict::Fail, "");
runner.item_idx += 1;
runner.global_done += 1;
} else if is_just_pressed(KeyCode::F3) {
log.record(item, Verdict::Skip, "手動スキップ");
runner.item_idx += 1;
runner.global_done += 1;
}
if runner.item_idx >= runner.items.len() {
runner.state = None;
runner.order_idx += 1;
runner.phase = Phase::EnterCategory;
}
}
Phase::Done => {
draw_done_screen(&log);
}
}
if is_just_pressed(KeyCode::Escape) {
runner.phase = Phase::Done;
}
}
log.sweep_notrun_all();
log.write_summary();
println!("検証終了。詳細は {} を参照してください。", log.path());
}