Checkpoint 3: deliver playable samples and complete native authoring workflows
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
#include <faset/core/io.hpp>
|
||||
#include <faset/editor/editor_ui.hpp>
|
||||
#include <iostream>
|
||||
#include <thread>
|
||||
using namespace faset;
|
||||
namespace {
|
||||
void check(bool value, const std::string& message) {
|
||||
if (!value)
|
||||
throw std::runtime_error(message);
|
||||
}
|
||||
render::Event key(std::string name, bool control = false) {
|
||||
render::Event e;
|
||||
e.type = render::Event::Type::KeyDown;
|
||||
e.key = std::move(name);
|
||||
e.control = control;
|
||||
return e;
|
||||
}
|
||||
void click(editor::EditorUI& ui, const std::string& id) {
|
||||
const auto* w = ui.widgets().find(id);
|
||||
check(w, "Missing widget: " + id);
|
||||
const auto r = w->rect.intersection(w->clip);
|
||||
check(r.width > 0 && r.height > 0, "Hidden widget: " + id);
|
||||
render::Event down;
|
||||
down.type = render::Event::Type::MouseDown;
|
||||
down.button = 1;
|
||||
down.x = r.x + r.width * .5f;
|
||||
down.y = r.y + r.height * .5f;
|
||||
auto up = down;
|
||||
up.type = render::Event::Type::MouseUp;
|
||||
ui.frame({down, up});
|
||||
}
|
||||
void poll(editor::EditorUI& ui) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(550));
|
||||
ui.frame({});
|
||||
}
|
||||
} // namespace
|
||||
int main() {
|
||||
const auto root = std::filesystem::temp_directory_path() / ("faset-ui-reload-" + new_id());
|
||||
try {
|
||||
const auto styles = root / "styles/dark.json",
|
||||
layout_path = root / "styles/editor-layout.json";
|
||||
auto theme = read_json(std::filesystem::path(FASET_TEST_ENGINE) / "assets/ui/dark.json");
|
||||
auto layout =
|
||||
read_json(std::filesystem::path(FASET_TEST_ENGINE) / "assets/ui/editor-layout.json");
|
||||
atomic_write_json(styles, theme);
|
||||
atomic_write_json(layout_path, layout);
|
||||
editor::Session session({root / "project", FASET_TEST_ENGINE, root});
|
||||
render::Renderer renderer({1280, 800, "Style reload acceptance", true, true});
|
||||
editor::EditorUI ui(session, renderer,
|
||||
std::filesystem::path(FASET_TEST_ENGINE) / "assets/fonts/NotoSans.ttf",
|
||||
styles);
|
||||
ui.frame({});
|
||||
click(ui, "add-cube");
|
||||
click(ui, "object-name");
|
||||
render::Event input;
|
||||
input.type = render::Event::Type::TextInput;
|
||||
input.text = "Unfinished name";
|
||||
ui.frame({key("A", true), input});
|
||||
const auto revision = session.authoring().query(ui.current_document()).at("revision");
|
||||
const auto width = ui.widgets().find("scene_panel")->rect.width;
|
||||
renderer.render(ui.snapshot());
|
||||
const auto pixels_before = renderer.pixels();
|
||||
theme["surface"] = {.18, .24, .12, 1};
|
||||
layout["root"]["children"][2]["children"][0]["layout"]["width"] = 270;
|
||||
atomic_write_json(styles, theme);
|
||||
atomic_write_json(layout_path, layout);
|
||||
poll(ui);
|
||||
check(ui.widgets().find("scene_panel")->rect.width == 270 && width != 270,
|
||||
"Valid layout edit changes live panel geometry");
|
||||
check(ui.widgets().focused_id() == "object-name" &&
|
||||
ui.widgets().find("object-name")->text == "Unfinished name",
|
||||
"Valid reload preserves focus and unfinished text");
|
||||
renderer.render(ui.snapshot());
|
||||
const auto pixels_after = renderer.pixels();
|
||||
const auto pixel = (500 * renderer.width() + 10) * 4;
|
||||
check(pixels_before.at(pixel) != pixels_after.at(pixel) ||
|
||||
pixels_before.at(pixel + 1) != pixels_after.at(pixel + 1),
|
||||
"Valid theme edit changes actual Vulkan pixels");
|
||||
check(session.authoring().query(ui.current_document()).at("revision") == revision,
|
||||
"Presentation reload does not author the scene");
|
||||
const auto working_theme = ui.widgets().theme().to_json();
|
||||
const auto working_menubar = ui.widgets().find("menubar")->layout.height;
|
||||
auto bad_layout = layout;
|
||||
bad_layout["root"]["children"][0]["layout"]["height"] = 68;
|
||||
bad_layout["root"]["children"].back()["text"] = 7;
|
||||
theme["surface"] = {.3, .08, .2, 1};
|
||||
atomic_write_json(styles, theme);
|
||||
atomic_write_json(layout_path, bad_layout);
|
||||
poll(ui);
|
||||
check(ui.widgets().theme().to_json() == working_theme &&
|
||||
ui.widgets().find("menubar")->layout.height == working_menubar,
|
||||
"Invalid layout rejects both candidate files atomically");
|
||||
check(ui.widgets().find("scene_panel")->rect.width == 270 &&
|
||||
ui.widgets().focused_id() == "object-name" &&
|
||||
ui.widgets().find("object-name")->text == "Unfinished name",
|
||||
"Invalid reload retains last good layout and editing state");
|
||||
const auto log_count = session.logs().size();
|
||||
poll(ui);
|
||||
check(session.logs().size() == log_count,
|
||||
"Unchanged invalid file does not flood editor logs");
|
||||
atomic_write_json(layout_path, layout);
|
||||
poll(ui);
|
||||
check(ui.widgets().theme().surface[0] == .3f,
|
||||
"Fixing invalid layout publishes pending theme candidate");
|
||||
atomic_write(styles, "{ broken json");
|
||||
poll(ui);
|
||||
check(ui.widgets().theme().surface[0] == .3f,
|
||||
"Partially written JSON keeps last working theme");
|
||||
atomic_write_json(styles, theme);
|
||||
poll(ui);
|
||||
ui.frame({key("Return")});
|
||||
const auto saved = session.authoring().query(ui.current_document());
|
||||
check(saved.at("revision") == revision.get<std::uint64_t>() + 1 &&
|
||||
saved["scene"]["entities"][0]["name"] == "Unfinished name",
|
||||
"Retained callback commits preserved edit once after failed and valid reloads");
|
||||
renderer.render(ui.snapshot());
|
||||
renderer.capture(root / "reloaded.ppm");
|
||||
check(renderer.stats().validation_errors == 0, "Reload Vulkan validation");
|
||||
std::cout << "Theme/layout live reload pixels+geometry, atomic rejection, focus+draft "
|
||||
"preservation, callback and diagnostic dedup passed. "
|
||||
<< root << '\n';
|
||||
return 0;
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << e.what() << "\nRetained: " << root << '\n';
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user