diff --git a/.github/workflows/example-report.yml b/.github/workflows/example-report.yml index d9bd189..9ea4d14 100644 --- a/.github/workflows/example-report.yml +++ b/.github/workflows/example-report.yml @@ -584,7 +584,7 @@ jobs: cd .. - curl https://pixel-eagle.vleue.com/$project/runs/$run/compare/auto --json '{"os":""}' | jq '{from: .from, to: .to}' > pixeleagle-${{ matrix.os }}.json + curl https://pixel-eagle.vleue.com/$project/runs/$run/compare/auto --json '{"os":""}' | jq '{project_id: .project_id, from: .from, to: .to}' > pixeleagle-${{ matrix.os }}.json cat pixeleagle-${{ matrix.os }}.json echo "created run $run" diff --git a/src/main.rs b/src/main.rs index 132bb9a..db677a1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,23 +1,21 @@ use chrono::NaiveDateTime; -use percy::read_percy_results; use serde::Serialize; use std::{ collections::{HashMap, HashSet}, fs, hash::Hash, - thread, - time::Duration, + str::FromStr, }; -use tera::{Context, Tera}; -use crate::percy::{ScreenshotData, ScreenshotState}; +use crate::screenshot::{percy, pixeleagle, ScreenshotData, ScreenshotState}; -mod percy; +mod screenshot; +mod template; #[derive(Debug, Clone, Serialize)] struct Example { name: String, - category: String, + category: ExampleCategory, flaky: bool, } @@ -38,11 +36,79 @@ impl Hash for Example { struct Run { date: String, commit: String, - results: HashMap>, - screenshots: HashMap>, + results: HashMap>, + screenshots: HashMap>, logs: HashMap>, } +#[derive(Debug, Serialize, Clone, PartialEq, Eq, Hash)] +struct ExampleCategory(String); + +#[derive(Debug, Serialize, Clone, PartialEq, Eq, Hash)] +struct ImageUrl(String); + +#[derive(Debug, Serialize, Clone, PartialEq, Eq, Hash)] +struct SnapshotViewerUrl(String); + +#[derive(Debug, Serialize, PartialEq, Eq, Hash, Clone)] +enum Platform { + Linux, + Macos, + Windows, + Mobile, + Tag(String), +} + +impl ToString for Platform { + fn to_string(&self) -> String { + match self { + Platform::Linux => String::from("Linux"), + Platform::Macos => String::from("macOS"), + Platform::Windows => String::from("Windows"), + Platform::Mobile => String::from("Mobile"), + Platform::Tag(tag) => tag.clone(), + } + } +} + +impl FromStr for Platform { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "Linux" => Ok(Platform::Linux), + "macOS" => Ok(Platform::Macos), + "Windows" => Ok(Platform::Windows), + "mobile" => Ok(Platform::Mobile), + _ => Err(s.to_string()), + } + } +} + +#[derive(Debug, Serialize, PartialEq, Eq, Hash, Clone)] +enum Kind { + Successes, + Failures, + NoScreenshots, + Percy, + PixelEagle, +} + +impl FromStr for Kind { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "successes" => Ok(Kind::Successes), + "failures" => Ok(Kind::Failures), + "no_screenshots" => Ok(Kind::NoScreenshots), + "percy" => Ok(Kind::Percy), + "pixeleagle" => Ok(Kind::PixelEagle), + _ => Err(s.to_string()), + } + } +} + fn main() { let paths = fs::read_dir(std::env::args().nth(1).as_deref().unwrap()).unwrap(); @@ -58,7 +124,7 @@ fn main() { folders.sort(); folders.reverse(); - for (i, run_path) in folders.iter().take(30).enumerate() { + for (i, run_path) in folders.iter().take(15).enumerate() { let file_name = run_path.file_name().unwrap().to_str().unwrap(); if file_name.starts_with(".") { continue; @@ -75,42 +141,45 @@ fn main() { }; for file in fs::read_dir(run_path).unwrap() { - let path = file.as_ref().unwrap().path(); + let file = file.as_ref().unwrap(); + if file.file_type().unwrap().is_dir() { + continue; + } + let path = file.path(); let mut name = path.file_name().unwrap().to_str().unwrap().split('-'); - let platform = name.next().unwrap(); - let kind = name.next().unwrap(); + let platform = Platform::from_str(name.next().unwrap()).unwrap(); + let kind = Kind::from_str(name.next().unwrap()).unwrap(); - if ["successes", "failures", "no_screenshots"].contains(&kind) { - println!(" - {} / {}", kind, platform); - fs::read_to_string(file.as_ref().unwrap().path()) - .unwrap() - .lines() - .for_each(|line| { - let mut line = line.split(" - "); - let mut details = line.next().unwrap().split('/'); - let example = Example { - category: details.next().unwrap().to_string(), - name: details.next().unwrap().to_string(), - flaky: kind != "successes", - }; - let previous = all_examples.take(&example); - all_examples.insert(Example { - flaky: previous.map(|ex: Example| ex.flaky).unwrap_or(false) - || example.flaky, - ..example.clone() - }); - run.results - .entry(example.name) - .or_insert_with(HashMap::new) - .insert(platform.to_string(), kind.to_string()); + if [Kind::Successes, Kind::Failures, Kind::NoScreenshots].contains(&kind) { + println!(" - {:?} / {:?}", kind, platform); + fs::read_to_string(&path).unwrap().lines().for_each(|line| { + let mut line = line.split(" - "); + let mut details = line.next().unwrap().split('/'); + let example = Example { + category: ExampleCategory(details.next().unwrap().to_string()), + name: details.next().unwrap().to_string(), + flaky: kind != Kind::Successes, + }; + let previous = all_examples.take(&example); + all_examples.insert(Example { + flaky: previous.map(|ex: Example| ex.flaky).unwrap_or(false) + || example.flaky, + ..example.clone() }); + run.results + .entry(example.name) + .or_insert_with(HashMap::new) + .insert(platform.to_string().clone(), kind.clone()); + }); } - if kind == "percy" { - println!(" - {} / {}", kind, platform); - let screenshots = - read_percy_results(fs::read_to_string(file.as_ref().unwrap().path()).unwrap()); - // sleep to limit how hard Percy API are used - thread::sleep(Duration::from_secs(1)); + if [Kind::Percy, Kind::PixelEagle].contains(&kind) { + println!(" - {:?} / {:?}", kind, platform); + let content = fs::read_to_string(&path).unwrap(); + let screenshots = match kind { + Kind::Percy => percy::read_results(content), + Kind::PixelEagle => pixeleagle::read_results(content), + _ => unreachable!(), + }; for ScreenshotData { example, screenshot, @@ -120,15 +189,15 @@ fn main() { snapshot_url, } in screenshots.into_iter() { - let (category, name) = if platform == "mobile" { + let (category, name) = if platform == Platform::Mobile { if let Some(tag) = tag.as_ref() { all_mobile_platforms.insert(tag.clone()); } - ("Mobile".to_string(), example) + (ExampleCategory("Mobile".to_string()), example) } else { let mut split = example.split('.').next().unwrap().split('/'); ( - split.next().unwrap().to_string(), + ExampleCategory(split.next().unwrap().to_string()), split.next().unwrap().to_string(), ) }; @@ -147,35 +216,46 @@ fn main() { if diff_ratio == 0.0 && changed == ScreenshotState::Changed { println!( " - setting {} / {} ({:?}) as unchanged", - example.category, example.name, tag + example.category.0, example.name, tag ); changed = ScreenshotState::Similar; } + let platform = tag + .clone() + .map(|tag| Platform::Tag(tag.clone())) + .unwrap_or_else(|| platform.clone()) + .to_string(); // If there is a screenshot but no results, mark as success run.results .entry(example.name.clone()) .or_insert_with(HashMap::new) - .entry(tag.clone().unwrap_or_else(|| platform.to_string())) - .or_insert_with(|| "successes".to_string()); + .entry(platform.clone()) + .or_insert_with(|| Kind::Successes); + // Keeping Percy results over PixelEagle for now + // TODO: remove + if let Some(existing_screenshots) = run.screenshots.get(&example.name) { + if existing_screenshots.contains_key(&platform) { + if kind == Kind::PixelEagle { + continue; + } + } + } run.screenshots .entry(example.name) .or_insert_with(HashMap::new) - .insert( - tag.unwrap_or_else(|| platform.to_string()), - (screenshot, changed, snapshot_url), - ); + .insert(platform.clone(), (screenshot, changed, snapshot_url)); } } } - for platform in ["Windows", "Linux", "macOS"] { - let rerun = run_path.join(format!("status-rerun-{}", platform)); + for rerun_platform in [Platform::Linux, Platform::Windows, Platform::Macos] { + let rerun = run_path.join(format!("status-rerun-{:?}", rerun_platform)); if rerun.exists() { - println!(" - rerun {}", platform); + println!(" - rerun {:?}", rerun_platform); for file in fs::read_dir(rerun.as_path()).unwrap() { let path = file.as_ref().unwrap().path(); let kind = path.file_name().unwrap().to_str().unwrap(); if kind == "successes" { - println!(" - {} / {}", kind, platform); + println!(" - {} / {:?}", kind, rerun_platform); fs::read_to_string(file.as_ref().unwrap().path()) .unwrap() .lines() @@ -183,19 +263,19 @@ fn main() { let mut line = line.split(" - "); let mut details = line.next().unwrap().split('/'); let example = Example { - category: details.next().unwrap().to_string(), + category: ExampleCategory(details.next().unwrap().to_string()), name: details.next().unwrap().to_string(), flaky: false, }; run.results .entry(example.name) .or_insert_with(HashMap::new) - .insert(platform.to_string(), "no_screenshots".to_string()); + .insert(rerun_platform.to_string(), Kind::NoScreenshots); }); } if kind.ends_with(".log") { let example_name = kind.strip_suffix(".log").unwrap(); - println!(" - log / {} ({})", platform, example_name); + println!(" - log / {:?} ({})", rerun_platform, example_name); let mut log = fs::read_to_string(file.as_ref().unwrap().path()).unwrap(); log = log.replace("", ""); log = log.replace("", ""); @@ -206,7 +286,7 @@ fn main() { run.logs .entry(example_name.to_string()) .or_insert_with(HashMap::new) - .insert(platform.to_string(), log); + .insert(rerun_platform.to_string(), log); } } } @@ -223,7 +303,7 @@ fn main() { let has_failures = runs.iter().any(|run| { run.results .get(&example.name) - .map(|platforms| platforms.values().any(|v| v == "failures")) + .map(|platforms| platforms.values().any(|v| v == &Kind::Failures)) .unwrap_or(false) }); if !has_screenshot && !has_failures { @@ -232,33 +312,7 @@ fn main() { all_examples_cleaned.push(example); } - all_examples_cleaned.sort_by_key(|a| format!("{}/{}", a.category, a.name)); - - let mut context = Context::new(); - context.insert("runs".to_string(), &runs); - context.insert("all_examples".to_string(), &all_examples_cleaned); - context.insert("all_mobile_platforms".to_string(), &all_mobile_platforms); - - let mut tera = Tera::default(); - tera.add_raw_template( - "macros.html", - &std::fs::read_to_string("./templates/macros.html").unwrap(), - ) - .unwrap(); - tera.add_raw_template( - "index.html", - &std::fs::read_to_string("./templates/index.html").unwrap(), - ) - .unwrap(); - tera.add_raw_template( - "about.html", - &std::fs::read_to_string("./templates/about.html").unwrap(), - ) - .unwrap(); - - let rendered = tera.render("index.html", &context).unwrap(); - std::fs::write("./site/index.html", &rendered).unwrap(); + all_examples_cleaned.sort_by_key(|a| format!("{}/{}", a.category.0, a.name)); - let rendered = tera.render("about.html", &context).unwrap(); - std::fs::write("./site/about.html", &rendered).unwrap(); + template::build_site(runs, all_examples_cleaned, all_mobile_platforms) } diff --git a/src/screenshot/mod.rs b/src/screenshot/mod.rs new file mode 100644 index 0000000..167a461 --- /dev/null +++ b/src/screenshot/mod.rs @@ -0,0 +1,25 @@ +use serde::{Deserialize, Serialize}; + +use crate::{ImageUrl, SnapshotViewerUrl}; + +pub mod percy; +pub mod pixeleagle; + +#[derive(Debug)] +pub struct ScreenshotData { + /// category / name + pub example: String, + /// URL to the screenshot + pub screenshot: ImageUrl, + pub changed: ScreenshotState, + /// Tag used to differentiate for mobile + pub tag: Option, + pub diff_ratio: f32, + pub snapshot_url: SnapshotViewerUrl, +} + +#[derive(Debug, Deserialize, PartialEq, Eq, Clone, Copy, Serialize)] +pub enum ScreenshotState { + Similar, + Changed, +} diff --git a/src/percy.rs b/src/screenshot/percy.rs similarity index 93% rename from src/percy.rs rename to src/screenshot/percy.rs index 383176b..ed8983f 100644 --- a/src/percy.rs +++ b/src/screenshot/percy.rs @@ -1,6 +1,10 @@ use std::{thread, time::Duration}; -use serde::{Deserialize, Serialize}; +use serde::Deserialize; + +use crate::{ImageUrl, SnapshotViewerUrl}; + +use super::{ScreenshotData, ScreenshotState}; #[derive(Deserialize, Debug)] #[serde(rename_all = "kebab-case")] @@ -38,7 +42,10 @@ fn get_snapshots_with_retry(build_id: &str) -> SnapshotsData { } } -pub fn read_percy_results(results: String) -> Vec { +pub fn read_results(results: String) -> Vec { + // sleep to limit how hard Percy API are used + thread::sleep(Duration::from_secs(1)); + let Ok(main) = serde_json::from_str::
(&results) else { return vec![]; }; @@ -173,11 +180,11 @@ fn snapshots_to_images(snapshots: SnapshotsData, build_url: &str) -> Vec for ScreenshotState { + fn from(reason: &ReviewStateReason) -> Self { + match reason { + ReviewStateReason::NoDiffs => ScreenshotState::Similar, + _ => ScreenshotState::Changed, + } + } +} + #[derive(Deserialize, Debug)] #[serde(rename_all = "kebab-case")] struct SnapshotRelationship { @@ -301,11 +317,13 @@ struct ComparisonTagAttributes { mod tests { use std::fs; - use crate::percy::{snapshots_to_images, SnapshotsData}; + use crate::screenshot::percy::SnapshotsData; + + use super::*; #[test] fn read_file_native() { - let file = fs::read_to_string("src/test-percy.json").unwrap(); + let file = fs::read_to_string("src/screenshot/test-percy.json").unwrap(); // dbg!(read_percy_results(file)); let read = serde_json::from_str::(&file).unwrap(); dbg!(read.data.len()); @@ -317,7 +335,7 @@ mod tests { #[test] fn read_file_mobile() { - let file = fs::read_to_string("src/test-percy-mobile.json").unwrap(); + let file = fs::read_to_string("src/screenshot/test-percy-mobile.json").unwrap(); // dbg!(read_percy_results(file)); let read = serde_json::from_str::(&file).unwrap(); dbg!(read.data.len()); @@ -327,28 +345,3 @@ mod tests { // assert!(false); } } - -#[derive(Debug)] -pub struct ScreenshotData { - pub example: String, - pub screenshot: String, - pub changed: ScreenshotState, - pub tag: Option, - pub diff_ratio: f32, - pub snapshot_url: String, -} - -impl From<&ReviewStateReason> for ScreenshotState { - fn from(reason: &ReviewStateReason) -> Self { - match reason { - ReviewStateReason::NoDiffs => ScreenshotState::Similar, - _ => ScreenshotState::Changed, - } - } -} - -#[derive(Debug, Deserialize, PartialEq, Eq, Clone, Copy, Serialize)] -pub enum ScreenshotState { - Similar, - Changed, -} diff --git a/src/screenshot/pixeleagle.rs b/src/screenshot/pixeleagle.rs new file mode 100644 index 0000000..4175fe4 --- /dev/null +++ b/src/screenshot/pixeleagle.rs @@ -0,0 +1,136 @@ +use serde::Deserialize; + +use crate::{ImageUrl, SnapshotViewerUrl}; + +use super::{ScreenshotData, ScreenshotState}; + +#[derive(Deserialize)] +struct ComparisonTarget { + project_id: String, + from: u32, + to: u32, +} + +#[derive(Deserialize, Debug)] +struct Comparison { + project_id: String, + from: u32, + to: u32, + // missing: Vec, + new: Vec, + diff: Vec, + unchanged: Vec, +} + +#[derive(Deserialize, Debug)] +struct Screenshot { + name: String, + hash: String, + // previous_hash: Option, + diff: Option, +} + +#[derive(Deserialize, Debug)] +enum Difference { + Unknown, + Processing, + Done(f32), +} + +pub fn read_results(results: String) -> Vec { + let Ok(target) = serde_json::from_str::(&results) else { + return vec![]; + }; + + let screenshots = ureq::get(&format!( + "https://pixel-eagle.vleue.com/{}/runs/{}/compare/{}", + target.project_id, target.from, target.to + )) + .call() + .unwrap() + .into_json::() + .unwrap(); + + comparison_to_screenshot_data(screenshots) +} + +fn comparison_to_screenshot_data(comparison: Comparison) -> Vec { + let mut result = vec![]; + + for screenshot in comparison.new { + result.push(ScreenshotData { + example: screenshot.name.clone(), + screenshot: ImageUrl(format!( + "https://pixel-eagle.vleue.com/{}/screenshot/{}", + comparison.project_id.clone(), + screenshot.hash.clone() + )), + changed: ScreenshotState::Changed, + tag: None, + diff_ratio: 0.0, + snapshot_url: SnapshotViewerUrl(format!( + "https://pixel-eagle.vleue.com/project/{}/run/{}/compare/{}?screenshot={}", + comparison.project_id, comparison.from, comparison.to, screenshot.name + )), + }); + } + + for screenshot in comparison.unchanged { + result.push(ScreenshotData { + example: screenshot.name.clone(), + screenshot: ImageUrl(format!( + "https://pixel-eagle.vleue.com/{}/screenshot/{}", + comparison.project_id.clone(), + screenshot.hash.clone() + )), + changed: ScreenshotState::Similar, + tag: None, + diff_ratio: 0.0, + snapshot_url: SnapshotViewerUrl(format!( + "https://pixel-eagle.vleue.com/project/{}/run/{}/compare/{}?screenshot={}", + comparison.project_id, comparison.from, comparison.to, screenshot.name + )), + }); + } + + for screenshot in comparison.diff { + result.push(ScreenshotData { + example: screenshot.name.clone(), + screenshot: ImageUrl(format!( + "https://pixel-eagle.vleue.com/{}/screenshot/{}", + comparison.project_id.clone(), + screenshot.hash.clone() + )), + changed: ScreenshotState::Changed, + tag: None, + diff_ratio: screenshot + .diff + .map(|diff| match diff { + Difference::Done(ratio) => ratio, + _ => 1.0, + }) + .unwrap(), + snapshot_url: SnapshotViewerUrl(format!( + "https://pixel-eagle.vleue.com/project/{}/run/{}/compare/{}?screenshot={}", + comparison.project_id, comparison.from, comparison.to, screenshot.name + )), + }); + } + result +} + +#[cfg(test)] +mod tests { + use std::fs; + + use super::*; + + #[test] + fn read_file_native() { + let file = fs::read_to_string("src/screenshot/test-pixeleagle.json").unwrap(); + let read = serde_json::from_str::(&file).unwrap(); + // dbg!(read.diff); + dbg!(comparison_to_screenshot_data(read)); + // assert!(false); + } +} diff --git a/src/test-percy-mobile.json b/src/screenshot/test-percy-mobile.json similarity index 100% rename from src/test-percy-mobile.json rename to src/screenshot/test-percy-mobile.json diff --git a/src/test-percy.json b/src/screenshot/test-percy.json similarity index 100% rename from src/test-percy.json rename to src/screenshot/test-percy.json diff --git a/src/screenshot/test-pixeleagle.json b/src/screenshot/test-pixeleagle.json new file mode 100644 index 0000000..f36bb52 --- /dev/null +++ b/src/screenshot/test-pixeleagle.json @@ -0,0 +1,745 @@ +{ + "project_id": "test", + "from": 30, + "to": 27, + "missing": [ + { + "name": "3D Rendering/transmission.png", + "hash": "6b8503bf6ed8f09c5829ef12af8df51fae95ee7df7f00114fea79e91a0db18de" + } + ], + "new": [ + { + "name": "Transforms/align.png", + "hash": "8cf8d6c8fd51fe3e5da628bfe71e228504432631393fc829684f6513e9f4554b" + } + ], + "diff": [ + { + "name": "Application/log_layers_ecs.png", + "hash": "176b2c6f077214dc4db35d8ac5687106b164e83d6b7f22440330c19296fb6692", + "previous_hash": "2e07e75ed675cd0f916d1c8439b1ae9286a921c90b4a55571516f89f3607c450", + "diff": { + "Done": 0.0009071181 + } + }, + { + "name": "Async Tasks/external_source_external_thread.png", + "hash": "f85c34573bca219c44306076eb3328c3aefc98aece9457bcd0e2ea16971c2e8f", + "previous_hash": "e2c4e58f5fc84aba211705411b348ec5c61444f62415f6588bb7d79bcb3be0bf", + "diff": { + "Done": 0.01773763 + } + }, + { + "name": "Games/alien_cake_addict.png", + "hash": "51e6c70041eee1a0dc3f1ff63f2e68bad83ced32e889dfddeec59ca6c902924d", + "previous_hash": "3550da0daa8f9f322c499eac2f273df829dfdb678ca1a5d326400a2a82e1d8cb", + "diff": { + "Done": 0.52488935 + } + }, + { + "name": "Games/contributors.png", + "hash": "b8afc409f3970179d18dbda7b258a1d8fd5c242c97d06e7158c55a41195ce851", + "previous_hash": "64c416968e9900e4932e5bfa682e7858892d7bd4aaf5867db0ffb5e57ad2caa4", + "diff": { + "Done": 0.017224392 + } + }, + { + "name": "Gizmos/axes.png", + "hash": "aed55438437e78e8a1a8ede9074682f40a519ca9093d6bc6303665344741fbaf", + "previous_hash": "db5a3b4d1c45d9845c70a5e40c6b60b6fe619362aa8ef801d106fe400b96146a", + "diff": { + "Done": 0.0009809028 + } + }, + { + "name": "Shaders/compute_shader_game_of_life.png", + "hash": "3540f760eafbaf55f39582e5bbbe796de6a26b59e1fc567085d1686db7a52aba", + "previous_hash": "d6cfb11b31c09a7f5d9b4008ea4984250e82e066ace555be2d72c46540b8589e", + "diff": { + "Done": 0.47716364 + } + }, + { + "name": "UI (User Interface)/font_atlas_debug.png", + "hash": "fc8176ecb44854f1f1d8dda6fa3f910d58154d2efecabc4def11371aa23c3425", + "previous_hash": "5439dac82b701435d354871cce12e976ce59792a0e677bc12c51eae522e5406f", + "diff": { + "Done": 0.08128798 + } + } + ], + "unchanged": [ + { + "name": "2D Rendering/2d_shapes.png", + "hash": "8496ddbb6a1d8cfb68e1e1cdad3817c43c6cf7fac5e16eddcc1796ad48e27576" + }, + { + "name": "2D Rendering/2d_viewport_to_world.png", + "hash": "f02fbb5581d3fe546a8f836c0e8823325045807d957c3b657476f9fac8bd2a4a" + }, + { + "name": "2D Rendering/bloom_2d.png", + "hash": "5aca9ae4bf24e5025e97578bcc9f8466338e74751f7ddf80560464864a7ea78a" + }, + { + "name": "2D Rendering/bounding_2d.png", + "hash": "317e85821ae0811c9dd187468cecc1b7a2d3027253d6c8225e8b806a2f6835a0" + }, + { + "name": "2D Rendering/custom_gltf_vertex_attribute.png", + "hash": "ad52aa204c7f17239a0aa63c46cf87932152386d0f79f3251f4aed5880449dde" + }, + { + "name": "2D Rendering/mesh2d_manual.png", + "hash": "9e6237cecb8db69980c4c2feff99b50cff9cabfb1b6a60dc1968c8a454aa2910" + }, + { + "name": "2D Rendering/mesh2d.png", + "hash": "e509771af89825f3ffba830af53562b1a2c19776dea9c26fcbfd4e8fd1fa1ce5" + }, + { + "name": "2D Rendering/mesh2d_vertex_color_texture.png", + "hash": "49a5536f088eaaa0150d41ca125eb6753598a9599b71590822fee47ecba0e593" + }, + { + "name": "2D Rendering/move_sprite.png", + "hash": "bc8cf1fce83de54e34cf54dfbcb66813e50504f3861ae007a28aced517d1cdf8" + }, + { + "name": "2D Rendering/pixel_grid_snap.png", + "hash": "98a3d87a4e0e4f1d02e2e5fa8ca448c7461b71827f96b5a2d7d205136c4bb45b" + }, + { + "name": "2D Rendering/rotation.png", + "hash": "8ffbb656ca00eaa5daabefee25055b5f9cb77acb6994da8e3bf81b089033da9e" + }, + { + "name": "2D Rendering/sprite_flipping.png", + "hash": "584197a8595e7582c6d52edd65c7c629f701afaaa90638fad5c393b40121a7c0" + }, + { + "name": "2D Rendering/sprite.png", + "hash": "bd1e73a3cc5580835bb1debcdd2fd06ede4070065c515b0f2fd00685225d1e23" + }, + { + "name": "2D Rendering/sprite_sheet.png", + "hash": "b32a0b98bf210f7338cb40e30ed6c71260c121f7c351fcd78c86b65bb2621613" + }, + { + "name": "2D Rendering/sprite_slice.png", + "hash": "8cd3592da5a43870679f8e9296f703ddd3ea645dc785ca3c2db3e5476184eba5" + }, + { + "name": "2D Rendering/sprite_tile.png", + "hash": "93480eded026bb6fe3e0a083a2bcbe1e1fc7c931a73478575a91b0283a58a05d" + }, + { + "name": "2D Rendering/text2d.png", + "hash": "cf6db047b5a672aa876323c8153766db4940eeffde57c2c2b7687c3631a33659" + }, + { + "name": "2D Rendering/texture_atlas.png", + "hash": "c9df978c0cc55602a0918839f9acc7e3d6990fa888b209298dfe8ee9ac1c272e" + }, + { + "name": "2D Rendering/transparency_2d.png", + "hash": "0339693cad049ad8dc84f4b675e4fde55eb35d79a8c4e179757e4ac250b28474" + }, + { + "name": "3D Rendering/3d_scene.png", + "hash": "60f52c6bc53b30561432e6406daf36d20b3b251e138b6fb35a0c55de84f93431" + }, + { + "name": "3D Rendering/3d_shapes.png", + "hash": "9e34c8556678a61f477894338ed88103802c77d4df8cfd8866befc40f98cb9ea" + }, + { + "name": "3D Rendering/3d_viewport_to_world.png", + "hash": "cb4e871feff426a9f158d9fb3b694e6ce78ed9f80ba1fe9080d666845393e6be" + }, + { + "name": "3D Rendering/animated_material.png", + "hash": "c24a1344c9815eef921961a47046658eaf4593060b5e2c6fdedc5089fb5414e5" + }, + { + "name": "3D Rendering/blend_modes.png", + "hash": "5b0b2990c687fff14771518f4727ddebb218909522227cef88abacac62bc59be" + }, + { + "name": "3D Rendering/bloom_3d.png", + "hash": "844ac0518a0d2f980d9cf83dd9e807dd4d597ef543a874910d1c0a68c75201df" + }, + { + "name": "3D Rendering/deterministic.png", + "hash": "f3dda3ef2b15bdcdb98509e4488d57463c4aeb2015fef8fb4b1b1783dcfdd705" + }, + { + "name": "3D Rendering/fog.png", + "hash": "7320c3a13c296fbacb1d71883843a4dc8dc247584fcce47b1ce4b14352ac5588" + }, + { + "name": "3D Rendering/generate_custom_mesh.png", + "hash": "74199ef24235a539c6f7b36d785e5ea28b231d416b8d5d26faf463d9f99eb1f6" + }, + { + "name": "3D Rendering/lightmaps.png", + "hash": "020d309d512ca85ad087f33235f347a5c92f6b179903dc28cc85f3a936eb1e61" + }, + { + "name": "3D Rendering/lines.png", + "hash": "6d1100e1f66551d4d4997d2eebe6ce57c7cfd1e86668cc87540afa305be62ef4" + }, + { + "name": "3D Rendering/orthographic.png", + "hash": "12051d5d36caa7583b242fe2d2f91700edb810a3660b9074e28b5a9b12e11964" + }, + { + "name": "3D Rendering/parenting.png", + "hash": "3e53845f67d9f4b64d353be8bc75c55ee1657e93509fc2bbdd12133a3d5651f8" + }, + { + "name": "3D Rendering/pbr.png", + "hash": "55915393d2302fc25bcebafcc7adf760edf59a78e1f4ace3c2407563bb00e706" + }, + { + "name": "3D Rendering/reflection_probes.png", + "hash": "807e8d61bdeaa644d5f5f627efb32ffb3f7c3eabb9a018305302e242673ead95" + }, + { + "name": "3D Rendering/render_to_texture.png", + "hash": "2027a8f152613141d2b8f74e6350fc1f90077896a6588ad2621cf8c06a87b32e" + }, + { + "name": "3D Rendering/shadow_biases.png", + "hash": "51cf779a1fe9a48f4b85ab9dda59898e871aa4c1efe224151792689649056463" + }, + { + "name": "3D Rendering/shadow_caster_receiver.png", + "hash": "edef6ebfeb8cb5b6656ca29333d1bb1a17248e6a6351448be8bf2e70157ef2bf" + }, + { + "name": "3D Rendering/skybox.png", + "hash": "7fb83c2e976564bb1d3a8f3d1cc0391173ee43f670bdfb48056ed84547e84d7f" + }, + { + "name": "3D Rendering/spherical_area_lights.png", + "hash": "253f4355f812c5253e0b19438e7547d6ae5dcc07d566b8543dc7f7926e8660bd" + }, + { + "name": "3D Rendering/split_screen.png", + "hash": "cadcbdd3b51347a31fea238928af5919488d8a8426770eb2c9b7508b2186a38e" + }, + { + "name": "3D Rendering/ssao.png", + "hash": "0f6ad0f8b2cc82467a7d8a02626e472cdb168bb4f537e49992c467e6c44b01b1" + }, + { + "name": "3D Rendering/texture.png", + "hash": "27b5c3549b3ba05357615361905d742eafe2350ce7fbf56c393293506bdf32a2" + }, + { + "name": "3D Rendering/transparency_3d.png", + "hash": "5a05892393588619a677e8e0f230873f38388662ff7b63952bb89fcd886ae81b" + }, + { + "name": "3D Rendering/two_passes.png", + "hash": "8ac7f8b949177a88fc4905309114a58c6e1f0d51d851f55658379ccd50db47ac" + }, + { + "name": "3D Rendering/vertex_colors.png", + "hash": "868ddc70b5dd353603d3376cf3def945b79db4b9d3c5f9e9fe4aeb56dd822895" + }, + { + "name": "3D Rendering/wireframe.png", + "hash": "4ca46380c47a5ad52e7ffe8a983cae59357238a3a99553ab8fa2021a8c72f271" + }, + { + "name": "Animation/animated_fox.png", + "hash": "1a05e553e39b80afdb947ac1317525e23c8bcc2fa92ca4fba574d46487482b26" + }, + { + "name": "Animation/animated_transform.png", + "hash": "263c95def26570a746f9fa42b19634b2a2dbac79fd2895fde98d559e9a8240a1" + }, + { + "name": "Animation/animation_graph.png", + "hash": "be1b005021e7c28c0d83ee926d0f098a32ec7a2a1973813a7e6780f42243524a" + }, + { + "name": "Animation/cubic_curve.png", + "hash": "2a3e2b984b01e7dabb54eb7e2f75fdd6faf187be16cd256b2dcafe514794d7cd" + }, + { + "name": "Animation/custom_skinned_mesh.png", + "hash": "0262e103858c8c3e77098b1a0f8e2de7a7c502028847381507f6d8107dd119f8" + }, + { + "name": "Animation/gltf_skinned_mesh.png", + "hash": "6c0586716b28ec0862e888636a8b00126be7377b2605de1be341af0c70b06b68" + }, + { + "name": "Animation/morph_targets.png", + "hash": "84c3474f41a0a28856087c3ba926686ef18b65478f2780cd68c5dfe02e1c31f0" + }, + { + "name": "Application/drag_and_drop.png", + "hash": "7bf77c0560d2232c2741419adcaca30fb15d6827fffae5e3c79c88b078be7935" + }, + { + "name": "Application/empty_defaults.png", + "hash": "7bf77c0560d2232c2741419adcaca30fb15d6827fffae5e3c79c88b078be7935" + }, + { + "name": "Application/log_layers.png", + "hash": "7bf77c0560d2232c2741419adcaca30fb15d6827fffae5e3c79c88b078be7935" + }, + { + "name": "Application/logs.png", + "hash": "5b8a7e9bca4af29a9b5a95d245d81869139a2dfd11f6d6914e61576a57a23fb3" + }, + { + "name": "Application/plugin_group.png", + "hash": "7bf77c0560d2232c2741419adcaca30fb15d6827fffae5e3c79c88b078be7935" + }, + { + "name": "Application/plugin.png", + "hash": "7bf77c0560d2232c2741419adcaca30fb15d6827fffae5e3c79c88b078be7935" + }, + { + "name": "Application/return_after_run.png", + "hash": "7bf77c0560d2232c2741419adcaca30fb15d6827fffae5e3c79c88b078be7935" + }, + { + "name": "Application/thread_pool_resources.png", + "hash": "7bf77c0560d2232c2741419adcaca30fb15d6827fffae5e3c79c88b078be7935" + }, + { + "name": "Assets/asset_decompression.png", + "hash": "b49bcadefe31319a8cdce11ce2dcf88f442a0da6fcd71eef8009756c45bd8763" + }, + { + "name": "Assets/asset_loading.png", + "hash": "6e26de218b5190a8940db16ec9c2c63300cf37038984cb515abc0f1fc719ff12" + }, + { + "name": "Assets/asset_processing.png", + "hash": "7bf77c0560d2232c2741419adcaca30fb15d6827fffae5e3c79c88b078be7935" + }, + { + "name": "Assets/custom_asset.png", + "hash": "7bf77c0560d2232c2741419adcaca30fb15d6827fffae5e3c79c88b078be7935" + }, + { + "name": "Assets/custom_asset_reader.png", + "hash": "b49bcadefe31319a8cdce11ce2dcf88f442a0da6fcd71eef8009756c45bd8763" + }, + { + "name": "Assets/embedded_asset.png", + "hash": "c302ef81107adba58e87f0ecfca046782c947b09e28f2554a78c15a668e735f1" + }, + { + "name": "Assets/extra_asset_source.png", + "hash": "c302ef81107adba58e87f0ecfca046782c947b09e28f2554a78c15a668e735f1" + }, + { + "name": "Assets/hot_asset_reloading.png", + "hash": "4a61e67ab696920b69e04fe87085b78a6e8b976446f3e5f4b2dd119a5641cf1f" + }, + { + "name": "Async Tasks/async_compute.png", + "hash": "f99cceda94658a312c0c3053a5ad60cc4156692c3c2c33b6a4376451ef5da606" + }, + { + "name": "Audio/audio_control.png", + "hash": "7bf77c0560d2232c2741419adcaca30fb15d6827fffae5e3c79c88b078be7935" + }, + { + "name": "Audio/audio.png", + "hash": "7bf77c0560d2232c2741419adcaca30fb15d6827fffae5e3c79c88b078be7935" + }, + { + "name": "Audio/decodable.png", + "hash": "7bf77c0560d2232c2741419adcaca30fb15d6827fffae5e3c79c88b078be7935" + }, + { + "name": "Audio/pitch.png", + "hash": "7bf77c0560d2232c2741419adcaca30fb15d6827fffae5e3c79c88b078be7935" + }, + { + "name": "Audio/spatial_audio_2d.png", + "hash": "44d1e6fb4d075c72ca4cd5edaebfeb8b997050d538a681019668a5ff3e412b5a" + }, + { + "name": "Audio/spatial_audio_3d.png", + "hash": "649e170482dc149fabdf98a6ddfbc1b182626517ebb69b1b6c1daa8029f279c7" + }, + { + "name": "Dev tools/fps_overlay.png", + "hash": "11f8f88200335ffa0f2bb8c826d6b858bdcd2d63b8a9829a6e953ab3e38b1931" + }, + { + "name": "Diagnostics/custom_diagnostic.png", + "hash": "7bf77c0560d2232c2741419adcaca30fb15d6827fffae5e3c79c88b078be7935" + }, + { + "name": "Diagnostics/log_diagnostics.png", + "hash": "7bf77c0560d2232c2741419adcaca30fb15d6827fffae5e3c79c88b078be7935" + }, + { + "name": "ECS (Entity Component System)/component_change_detection.png", + "hash": "7bf77c0560d2232c2741419adcaca30fb15d6827fffae5e3c79c88b078be7935" + }, + { + "name": "ECS (Entity Component System)/component_hooks.png", + "hash": "7bf77c0560d2232c2741419adcaca30fb15d6827fffae5e3c79c88b078be7935" + }, + { + "name": "ECS (Entity Component System)/event.png", + "hash": "7bf77c0560d2232c2741419adcaca30fb15d6827fffae5e3c79c88b078be7935" + }, + { + "name": "ECS (Entity Component System)/fixed_timestep.png", + "hash": "7bf77c0560d2232c2741419adcaca30fb15d6827fffae5e3c79c88b078be7935" + }, + { + "name": "ECS (Entity Component System)/generic_system.png", + "hash": "7bf77c0560d2232c2741419adcaca30fb15d6827fffae5e3c79c88b078be7935" + }, + { + "name": "ECS (Entity Component System)/hierarchy.png", + "hash": "c24a1344c9815eef921961a47046658eaf4593060b5e2c6fdedc5089fb5414e5" + }, + { + "name": "ECS (Entity Component System)/iter_combinations.png", + "hash": "f32284597560a24fe74d7682f4e855f32495f4a1deb3de819f0bf9407f859d87" + }, + { + "name": "ECS (Entity Component System)/nondeterministic_system_order.png", + "hash": "7bf77c0560d2232c2741419adcaca30fb15d6827fffae5e3c79c88b078be7935" + }, + { + "name": "ECS (Entity Component System)/parallel_query.png", + "hash": "2c4043cbf6a74b6173a30c554107c4243507286c87ece230291c6cf6623cc791" + }, + { + "name": "ECS (Entity Component System)/removal_detection.png", + "hash": "b352e3615ee9a530512baf225ae8efebb198962478464c7eed727c2093c65584" + }, + { + "name": "ECS (Entity Component System)/run_conditions.png", + "hash": "7bf77c0560d2232c2741419adcaca30fb15d6827fffae5e3c79c88b078be7935" + }, + { + "name": "ECS (Entity Component System)/state.png", + "hash": "e8668ff06a703b619a7682cd597fcbfcd97a66ce6167586b7945d2086a066b14" + }, + { + "name": "Games/breakout.png", + "hash": "013abd441dc24a4c0092fb1c5a628ad80e13c9fd16ee08dce029a3b05536ec8b" + }, + { + "name": "Games/desk_toy.png", + "hash": "989f9297510e38d5b8afd13d27059a673d473b73b9bc920bd0813192de7c5e45" + }, + { + "name": "Games/game_menu.png", + "hash": "613daad53091d29ace518bfebd7ca0bad625e81f5b95c681c18794cba60efcff" + }, + { + "name": "Gizmos/2d_gizmos.png", + "hash": "2c0bcfb7cb09f2e70e6de0d03df707821092c1c2f4207122076ecb0f9e85e2e9" + }, + { + "name": "Gizmos/3d_gizmos.png", + "hash": "708a54b408558136cdfba72b5ec30b06f260f55c8a41717bfc6293f265d3ca1b" + }, + { + "name": "Gizmos/light_gizmos.png", + "hash": "ac17ceba55f072e3663899fe8f3b9de195c7546d9bd945207051386ec734130e" + }, + { + "name": "Input/char_input_events.png", + "hash": "7bf77c0560d2232c2741419adcaca30fb15d6827fffae5e3c79c88b078be7935" + }, + { + "name": "Input/gamepad_input_events.png", + "hash": "7bf77c0560d2232c2741419adcaca30fb15d6827fffae5e3c79c88b078be7935" + }, + { + "name": "Input/gamepad_input.png", + "hash": "7bf77c0560d2232c2741419adcaca30fb15d6827fffae5e3c79c88b078be7935" + }, + { + "name": "Input/gamepad_rumble.png", + "hash": "7bf77c0560d2232c2741419adcaca30fb15d6827fffae5e3c79c88b078be7935" + }, + { + "name": "Input/keyboard_input_events.png", + "hash": "7bf77c0560d2232c2741419adcaca30fb15d6827fffae5e3c79c88b078be7935" + }, + { + "name": "Input/keyboard_input.png", + "hash": "7bf77c0560d2232c2741419adcaca30fb15d6827fffae5e3c79c88b078be7935" + }, + { + "name": "Input/keyboard_modifiers.png", + "hash": "7bf77c0560d2232c2741419adcaca30fb15d6827fffae5e3c79c88b078be7935" + }, + { + "name": "Input/mouse_grab.png", + "hash": "7bf77c0560d2232c2741419adcaca30fb15d6827fffae5e3c79c88b078be7935" + }, + { + "name": "Input/mouse_input_events.png", + "hash": "7bf77c0560d2232c2741419adcaca30fb15d6827fffae5e3c79c88b078be7935" + }, + { + "name": "Input/mouse_input.png", + "hash": "7bf77c0560d2232c2741419adcaca30fb15d6827fffae5e3c79c88b078be7935" + }, + { + "name": "Input/text_input.png", + "hash": "738abd4d04e4a6daa7be4f016cb87e6742a1968c229ef157dee8ee8ff32744d8" + }, + { + "name": "Input/touch_input_events.png", + "hash": "7bf77c0560d2232c2741419adcaca30fb15d6827fffae5e3c79c88b078be7935" + }, + { + "name": "Input/touch_input.png", + "hash": "7bf77c0560d2232c2741419adcaca30fb15d6827fffae5e3c79c88b078be7935" + }, + { + "name": "Math/render_primitives.png", + "hash": "8ac7dccf0b5ab2f2e8a30adfee0864ec74c9e0dc3b87b0e63a24c92cfc901aa1" + }, + { + "name": "Reflection/generic_reflection.png", + "hash": "7bf77c0560d2232c2741419adcaca30fb15d6827fffae5e3c79c88b078be7935" + }, + { + "name": "Reflection/reflection.png", + "hash": "7bf77c0560d2232c2741419adcaca30fb15d6827fffae5e3c79c88b078be7935" + }, + { + "name": "Reflection/reflection_types.png", + "hash": "7bf77c0560d2232c2741419adcaca30fb15d6827fffae5e3c79c88b078be7935" + }, + { + "name": "Reflection/trait_reflection.png", + "hash": "7bf77c0560d2232c2741419adcaca30fb15d6827fffae5e3c79c88b078be7935" + }, + { + "name": "Scene/scene.png", + "hash": "22c5e44f11cfbb677da1e2a3b615a6a48f3a648d87a0ca7664288dc88a2fea53" + }, + { + "name": "Shaders/animate_shader.png", + "hash": "329de1d7e07158910e061bbdbb2659ff535ab5702aaaa07b28bbfbb2826a3ba0" + }, + { + "name": "Shaders/array_texture.png", + "hash": "12143d646d354224e12d6c2ee375350bf809e63ee5b4f7e55ec853a309c407d2" + }, + { + "name": "Shaders/custom_vertex_attribute.png", + "hash": "e27fd120bfc373e10cedd91bec07c3beb8014efc0d15dae011dc1a5faa25109f" + }, + { + "name": "Shaders/extended_material.png", + "hash": "f39de5c93c4b5c1fea92231b031e2a2cdd142e378b448613ea0e7d6d4ba6c7ab" + }, + { + "name": "Shaders/post_processing.png", + "hash": "eeaf144ed20f94bea0c5d0f51f6b37a2b651bb54d997676751bb423f2b3fba32" + }, + { + "name": "Shaders/shader_defs.png", + "hash": "0b9e9ec6c00b4dfbfd0337cef42810389d947b7d3621a2fda8175d6b6e6d1cec" + }, + { + "name": "Shaders/shader_instancing.png", + "hash": "9a9d9dea9d47e642b340677b2aa3b41e8dc3bdb8f97c09fc6e6b70b9aac13f26" + }, + { + "name": "Shaders/shader_material_2d.png", + "hash": "c83db3421534c5a9e0965b1976bf3a16ffd4a1dd25dac9496d54309bc18cf4f9" + }, + { + "name": "Shaders/shader_material_glsl.png", + "hash": "9ccfac984c87bb6029de8178cc588dea046a3e0cbe6b410cc8876da925d85355" + }, + { + "name": "Shaders/shader_material.png", + "hash": "416ffe96f6b59f4520a934af423a006f288271a3ee931738566668972273bfb1" + }, + { + "name": "Shaders/shader_material_screenspace_texture.png", + "hash": "0483eecbd62a70935c1ed07ebb86bd56b9872629400aa7d7111a1a28a25952f2" + }, + { + "name": "Shaders/shader_prepass.png", + "hash": "15cfd135af7f2441d4a76d139fb52327c052031794b1b49ec7bd975a14448a0b" + }, + { + "name": "Shaders/texture_binding_array.png", + "hash": "8124de29e3280c623ea3950b096265caaa2c6af0039a950a48bf300f9bc78642" + }, + { + "name": "Time/timers.png", + "hash": "7bf77c0560d2232c2741419adcaca30fb15d6827fffae5e3c79c88b078be7935" + }, + { + "name": "Time/virtual_time.png", + "hash": "21fe4f34d8c20e5fd64e20e9c3545ba676a4d2d39d75281c4598bdd1e274e90a" + }, + { + "name": "Tools/gamepad_viewer.png", + "hash": "bdd4903de588c8a2d76b243f6d3d6e73086591804d2a47eeaae2b46ebb74dbe4" + }, + { + "name": "Transforms/3d_rotation.png", + "hash": "aa593caaa587e5ccaa435b569447d7537fdf1043026298beebed83b36def6fed" + }, + { + "name": "Transforms/scale.png", + "hash": "e840b405762d3bed5c41beea2100b15fe1336f64fd2646771a15d731c8bae2a8" + }, + { + "name": "Transforms/transform.png", + "hash": "01adcea97b1974a18cf89ff14c2859d6cf2f76f25e2454852694a9d8e007b31b" + }, + { + "name": "Transforms/translation.png", + "hash": "7624ef7aa15c68c3e891f15906d61b73c458ce52f23d296ee6b1207e2aaaf51b" + }, + { + "name": "UI (User Interface)/borders.png", + "hash": "6f233e5b589c20a989de41fb21987becfb89e9fd72b3a5bd16b7bd933c7e6f83" + }, + { + "name": "UI (User Interface)/button.png", + "hash": "3b6d612f9710422c1e92baa1164f36fa190c0faeb81b9a746d4c5d7c01a72759" + }, + { + "name": "UI (User Interface)/display_and_visibility.png", + "hash": "61f1757be4ab83bf29e263a17e192b1a4f07c911039fed418026f67e4b2ed3d0" + }, + { + "name": "UI (User Interface)/flex_layout.png", + "hash": "87da27556c3df9ac2a811ba411b6709aadbd8f92a5643010f05e8ba917161772" + }, + { + "name": "UI (User Interface)/grid.png", + "hash": "81d5ffa577c4500026ac8ec6c28b3a6acade3b4acf9c5ef4278a9a1ad69c7d10" + }, + { + "name": "UI (User Interface)/overflow_debug.png", + "hash": "459d6d5c9ae0658b78284f0e3ac59ca655cb80a1e27039d656d4a28b87b754bb" + }, + { + "name": "UI (User Interface)/overflow.png", + "hash": "fa1d68eb9b94ae483261824d0ee870be7a2a0e828d4fe5f3c2ffa7b0b4e61b88" + }, + { + "name": "UI (User Interface)/relative_cursor_position.png", + "hash": "2a3f2ab5d32946be8dbed36cdef2bd462d1443e8ee5bb066d489578f17a881e7" + }, + { + "name": "UI (User Interface)/render_ui_to_texture.png", + "hash": "79d3cab2d698cd721dbdeaa0ec3c3293b6f64c070a7b9fa7abfd0d27517ef85d" + }, + { + "name": "UI (User Interface)/size_constraints.png", + "hash": "1f1c24c866a07a6b3767ef282c42307bbccaffa8176bb272e428e697ca5995ed" + }, + { + "name": "UI (User Interface)/text_debug.png", + "hash": "375e18d6062b262ef40e95a06ef4e1528574dbbbd613fe8fb25df1426a86f8fc" + }, + { + "name": "UI (User Interface)/text.png", + "hash": "e42f7093a1ececb33a3855bb384f2d99317e4697e313c83abcd6d4d9f1c18a3e" + }, + { + "name": "UI (User Interface)/text_wrap_debug.png", + "hash": "3c05154c8ee4394e2da660dad31b1fc26db1a618eb254199a1f603aaa1ecfa9c" + }, + { + "name": "UI (User Interface)/transparency_ui.png", + "hash": "7fcd41f6755a976589ef6c03313dce6113628bc29322caa6c6f0616bc981175a" + }, + { + "name": "UI (User Interface)/ui_material.png", + "hash": "4cdc674c873d28d25ad9fa4c889f456209eeb97db97de0c63f3527125f3931f8" + }, + { + "name": "UI (User Interface)/ui.png", + "hash": "fd8ad5fc210a3c841df398f10fb2333bc5b8e0d59f5ac6ca97dcd3a4cc02e0f9" + }, + { + "name": "UI (User Interface)/ui_scaling.png", + "hash": "8f4804837e349444e9dd38300bac92cbb69c5c420de1f7d2b87102dc674dc1be" + }, + { + "name": "UI (User Interface)/ui_texture_atlas.png", + "hash": "0387ece70dc603606a0ca24ed8e3c1b91bf558af27f675feeb00dffad87e2868" + }, + { + "name": "UI (User Interface)/ui_texture_atlas_slice.png", + "hash": "4a164cdd6d32f703bd613a2aecd086bc3b7f23198cc7c27f9992a4fa0251548c" + }, + { + "name": "UI (User Interface)/ui_texture_slice.png", + "hash": "8480eaa8a7313df01dd23d6dc36b1a54b37ab1170bbce2d8c8150aec6ad5b5e2" + }, + { + "name": "UI (User Interface)/viewport_debug.png", + "hash": "8b6f3b9345f5a6c6ca75dc9861141795792cfd54bdf240876fbcd8d117943b4b" + }, + { + "name": "UI (User Interface)/window_fallthrough.png", + "hash": "54f30b918a75a63742b4148a4134786194e630f04d89dfe70b9ee782d1f7b38a" + }, + { + "name": "UI (User Interface)/z_index.png", + "hash": "487af1a2a2a1ff42bed74584ff374805e4fefa094c60a1129db8ca355fee5a24" + }, + { + "name": "Window/clear_color.png", + "hash": "11f8899fd87c17d37b15eb59125d548336c2899a856e33032159f4cf66c58b39" + }, + { + "name": "Window/low_power.png", + "hash": "058ce3b176fa037afc869068ed6a033739027ef2258d467ce523b498bf97a38e" + }, + { + "name": "Window/multiple_windows.png", + "hash": "fdfdb3e06e7da440add841913fdaf14e10847747c7bc94da9fc5515b043d0dc3" + }, + { + "name": "Window/scale_factor_override.png", + "hash": "f0ca69bb77e63abf21d7ffbebef531d8b717bb37617b81a1b186448b864d42f8" + }, + { + "name": "Window/screenshot.png", + "hash": "91f873a57001fa65085a30e07e7b253c543e4b7479b4343c991a28819caf6bcc" + }, + { + "name": "Window/transparent_window.png", + "hash": "60c5bdc9658535dfb60d2f8a1fc1ad3ced9b2828d6c212f48ea67b59f1979c0b" + }, + { + "name": "Window/window_resizing.png", + "hash": "e04cf5469c5f5fb63c5ddd76dc824a675cdc108a5c953e651ededaa27cb775bb" + }, + { + "name": "Window/window_settings.png", + "hash": "3d813e293725e420e402f6e400b1737ab0e06409d809ae5ef0008264eda3e591" + } + ] +} \ No newline at end of file diff --git a/src/template.rs b/src/template.rs new file mode 100644 index 0000000..a4c4f7b --- /dev/null +++ b/src/template.rs @@ -0,0 +1,39 @@ +use std::collections::HashSet; + +use tera::{Context, Tera}; + +use crate::{Example, Run}; + +pub fn build_site( + runs: Vec, + all_examples: Vec, + all_mobile_platforms: HashSet, +) { + let mut context = Context::new(); + context.insert("runs".to_string(), &runs); + context.insert("all_examples".to_string(), &all_examples); + context.insert("all_mobile_platforms".to_string(), &all_mobile_platforms); + + let mut tera = Tera::default(); + tera.add_raw_template( + "macros.html", + &std::fs::read_to_string("./templates/macros.html").unwrap(), + ) + .unwrap(); + tera.add_raw_template( + "index.html", + &std::fs::read_to_string("./templates/index.html").unwrap(), + ) + .unwrap(); + tera.add_raw_template( + "about.html", + &std::fs::read_to_string("./templates/about.html").unwrap(), + ) + .unwrap(); + + let rendered = tera.render("index.html", &context).unwrap(); + std::fs::write("./site/index.html", &rendered).unwrap(); + + let rendered = tera.render("about.html", &context).unwrap(); + std::fs::write("./site/about.html", &rendered).unwrap(); +} diff --git a/templates/macros.html b/templates/macros.html index 923ea3d..d0fcd09 100644 --- a/templates/macros.html +++ b/templates/macros.html @@ -1,6 +1,6 @@ {% macro status(example_name, platform, run) %} {% if run.results[example_name][platform] -%} -{% if run.results[example_name][platform] == "successes" -%} +{% if run.results[example_name][platform] == "Successes" -%} {% if run.screenshots[example_name][platform] -%}
@@ -15,7 +15,7 @@ {% else -%} {% endif -%} -{% elif run.results[example_name][platform] == "failures" -%} +{% elif run.results[example_name][platform] == "Failures" -%} {% if run.logs[example_name] -%}
@@ -24,7 +24,7 @@ {% else -%} {% endif -%} -{% elif run.results[example_name][platform] == "no_screenshots" -%} +{% elif run.results[example_name][platform] == "NoScreenshots" -%} {% endif -%} {% else -%}