The crash screen in the app's own look (logo with smoke, the report in a well, Open it again / Copy / Report it on GitHub); sculpt mode's panels and status bar say what sculpting does; fry_scriptpanel; twelve new previews on the site (sculpt, materials, layout, render, scripts, light and the crash screen added)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
@ -101,7 +101,8 @@ add_library(imgui STATIC
|
||||||
${imgui_SOURCE_DIR}/imgui_demo.cpp
|
${imgui_SOURCE_DIR}/imgui_demo.cpp
|
||||||
${imgui_SOURCE_DIR}/backends/imgui_impl_sdl3.cpp
|
${imgui_SOURCE_DIR}/backends/imgui_impl_sdl3.cpp
|
||||||
${imgui_SOURCE_DIR}/backends/imgui_impl_vulkan.cpp
|
${imgui_SOURCE_DIR}/backends/imgui_impl_vulkan.cpp
|
||||||
${imgui_SOURCE_DIR}/backends/imgui_impl_opengl3.cpp)
|
${imgui_SOURCE_DIR}/backends/imgui_impl_opengl3.cpp
|
||||||
|
${imgui_SOURCE_DIR}/backends/imgui_impl_sdlrenderer3.cpp) # (the crash screen)
|
||||||
target_include_directories(imgui SYSTEM PUBLIC ${imgui_SOURCE_DIR} ${imgui_SOURCE_DIR}/backends)
|
target_include_directories(imgui SYSTEM PUBLIC ${imgui_SOURCE_DIR} ${imgui_SOURCE_DIR}/backends)
|
||||||
target_compile_definitions(imgui PUBLIC IMGUI_IMPL_VULKAN_USE_VOLK)
|
target_compile_definitions(imgui PUBLIC IMGUI_IMPL_VULKAN_USE_VOLK)
|
||||||
target_link_libraries(imgui PUBLIC volk SDL3::SDL3)
|
target_link_libraries(imgui PUBLIC volk SDL3::SDL3)
|
||||||
|
|
|
||||||
|
|
@ -46,12 +46,18 @@ window.FRY = {
|
||||||
],
|
],
|
||||||
|
|
||||||
shots: [
|
shots: [
|
||||||
{ img: "img/model.webp", title: "Model", text: "Starter models, a folder of parts, a shape drawn onto a cube's face, rendered with sky and shadows." },
|
{ img: "img/model.webp", title: "Model", text: "Starter models with materials (wood, glossy glass, ceramic), a folder for what's on the table, rendered with sky and shadows while you work." },
|
||||||
{ img: "img/paint.webp", title: "Paint", text: "A picture pasted on as a sticker: it bends over the cube's edge like a real one." },
|
{ img: "img/sculpt.webp", title: "Sculpt", text: "A ball pushed into a head with the brushes: a nose raised, eyes dug in, a smile pinched, ears grabbed out. Tab in the Paint tab." },
|
||||||
{ img: "img/print.webp", title: "Print", text: "The printer's plate, sizes in millimetres, checks and overhangs before you slice." },
|
{ img: "img/materials.webp", title: "Materials", text: "Gold, chrome, wood, rubber, copper, ceramic, steel: picked in Properties, shown in the view, exported with the model." },
|
||||||
{ img: "img/phone.webp", title: "Phone", text: "The phone layout: big buttons along the bottom, panels when you want them, fingers for the view." },
|
{ img: "img/paint.webp", title: "Paint", text: "The logo pasted onto a box as a sticker: drag its corners to fit, Enter to lock it on. The Picture panel shows where it lands." },
|
||||||
{ img: "img/combine.webp", title: "Combine", text: "A cylinder taken out of a cube, copies in a row in a folder, a plane made thick." },
|
{ img: "img/layout.webp", title: "Layout", text: "The box's surface laid out flat: move, turn and size the frames, and the paint follows." },
|
||||||
{ img: "img/crash.webp", title: "Even the crash screen", text: "If something does get burnt, your work comes back from the last autosave." },
|
{ img: "img/render.webp", title: "Render", text: "Sun, sky and ground, brightness, soft shadows and glow, and F12 for a picture of just the view." },
|
||||||
|
{ img: "img/print.webp", title: "Print", text: "Two gears and an arch on a Prusa's plate: sizes in millimetres, checked to be closed, the arch's overhang flagged, weight and filament." },
|
||||||
|
{ img: "img/combine.webp", title: "Combine", text: "A hole taken out of a box, pegs as copies in a folder, a plane made thick: everything stays closed." },
|
||||||
|
{ img: "img/scripts.webp", title: "Scripts", text: "A little town from a dozen lines of Lua: random houses, each a folder of walls and roof. F5 runs it, Ctrl+Z takes it back." },
|
||||||
|
{ img: "img/light.webp", title: "Light", text: "The light appearance, warm paper instead of graphite. Or let it follow your desktop (View > Appearance)." },
|
||||||
|
{ img: "img/phone.webp", title: "Phone", text: "The phone layout: big buttons along the bottom, Shift and Ctrl that stay down, panels when you want them." },
|
||||||
|
{ img: "img/crash.webp", title: "Even the crash screen", text: "If something does get burnt, it says so plainly, keeps a report for you, and your work comes back from the last autosave." },
|
||||||
],
|
],
|
||||||
|
|
||||||
scripting: {
|
scripting: {
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 104 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 27 KiB |
BIN
docs/img/layout.webp
Normal file
|
After Width: | Height: | Size: 85 KiB |
BIN
docs/img/light.webp
Normal file
|
After Width: | Height: | Size: 102 KiB |
BIN
docs/img/materials.webp
Normal file
|
After Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 109 KiB After Width: | Height: | Size: 99 KiB |
BIN
docs/img/render.webp
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
docs/img/scripts.webp
Normal file
|
After Width: | Height: | Size: 81 KiB |
BIN
docs/img/sculpt.webp
Normal file
|
After Width: | Height: | Size: 56 KiB |
|
|
@ -94,7 +94,7 @@
|
||||||
<section class="shell section" id="look">
|
<section class="shell section" id="look">
|
||||||
<div class="section__head">
|
<div class="section__head">
|
||||||
<span class="nv-overline">How it looks</span>
|
<span class="nv-overline">How it looks</span>
|
||||||
<h2 class="nv-heading">Five tabs: what you're doing, not where it's hidden.</h2>
|
<h2 class="nv-heading">What you're doing, not where it's hidden.</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="nv-nav-tabs" role="tablist" aria-label="Screenshots" id="shot-tabs" data-nv-tabs style="--nv-selection-name: nv-shot-tabs"></div>
|
<div class="nv-nav-tabs" role="tablist" aria-label="Screenshots" id="shot-tabs" data-nv-tabs style="--nv-selection-name: nv-shot-tabs"></div>
|
||||||
<div id="shot-panels" class="tab-box shots"></div>
|
<div id="shot-panels" class="tab-box shots"></div>
|
||||||
|
|
|
||||||
323
engine/crash.cpp
|
|
@ -2,6 +2,13 @@
|
||||||
|
|
||||||
#include <core/log.h>
|
#include <core/log.h>
|
||||||
#include <core/sysinfo.h>
|
#include <core/sysinfo.h>
|
||||||
|
#include <fs/vfs.h>
|
||||||
|
#include <ui/ui.h>
|
||||||
|
|
||||||
|
#include <imgui.h>
|
||||||
|
#include <imgui_impl_sdl3.h>
|
||||||
|
#include <imgui_impl_sdlrenderer3.h>
|
||||||
|
#include <stb_image.h>
|
||||||
|
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
|
@ -190,6 +197,9 @@ Busy::~Busy() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── The crash screen: SDL's 2D renderer and its built-in 8×8 font, scaled up ──
|
// ── The crash screen: SDL's 2D renderer and its built-in 8×8 font, scaled up ──
|
||||||
|
// The crash screen: its own small program (this one, started again), drawn with SDL's own
|
||||||
|
// renderer rather than Vulkan or OpenGL (what crashed may have been the graphics driver), in the
|
||||||
|
// app's look: the same fonts, colours and keys.
|
||||||
int runReporter(int argc, char** argv) {
|
int runReporter(int argc, char** argv) {
|
||||||
if (argc < 3 || std::strcmp(argv[1], "-crashreport") != 0) return -1;
|
if (argc < 3 || std::strcmp(argv[1], "-crashreport") != 0) return -1;
|
||||||
std::string report;
|
std::string report;
|
||||||
|
|
@ -208,173 +218,200 @@ int runReporter(int argc, char** argv) {
|
||||||
for (std::string l; std::getline(ss, l);) lines.push_back(l);
|
for (std::string l; std::getline(ss, l);) lines.push_back(l);
|
||||||
}
|
}
|
||||||
const std::string headline = lines.empty() ? "Fry3DX crashed." : lines[0];
|
const std::string headline = lines.empty() ? "Fry3DX crashed." : lines[0];
|
||||||
|
std::string details; // (the report without its headline)
|
||||||
|
for (size_t i = 1; i < lines.size(); ++i) details += lines[i] + "\n";
|
||||||
|
while (!details.empty() && details.front() == '\n') details.erase(0, 1);
|
||||||
|
|
||||||
if (!SDL_Init(SDL_INIT_VIDEO)) return 1;
|
if (!SDL_Init(SDL_INIT_VIDEO)) return 1;
|
||||||
SDL_Window* win = nullptr;
|
SDL_Window* win = nullptr;
|
||||||
SDL_Renderer* ren = nullptr;
|
SDL_Renderer* ren = nullptr;
|
||||||
if (!SDL_CreateWindowAndRenderer("Fry3DX - something got burnt", 1000, 620, SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY, &win, &ren)) return 1;
|
if (!SDL_CreateWindowAndRenderer("Fry3DX - something got burnt", 940, 640, SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY, &win, &ren)) return 1;
|
||||||
SDL_SetRenderVSync(ren, 1);
|
SDL_SetRenderVSync(ren, 1);
|
||||||
SDL_SetRenderDrawBlendMode(ren, SDL_BLENDMODE_BLEND);
|
const char* base = SDL_GetBasePath();
|
||||||
auto text = [&](float x, float y, float scale, const std::string& s, SDL_Color c) {
|
const std::string exeDir = base ? base : "./";
|
||||||
SDL_SetRenderScale(ren, scale, scale);
|
fs::mountGameDir(exeDir + "engine"); // (the fonts)
|
||||||
SDL_SetRenderDrawColor(ren, c.r, c.g, c.b, c.a);
|
fs::mountGameDir(exeDir + "Fry3DX"); // (the logo)
|
||||||
SDL_RenderDebugText(ren, x / scale, y / scale, s.c_str());
|
|
||||||
SDL_SetRenderScale(ren, 1, 1);
|
IMGUI_CHECKVERSION();
|
||||||
};
|
ImGui::CreateContext();
|
||||||
// Shapes for the picture: an ellipse (filled), a box.
|
ImGui::GetIO().IniFilename = nullptr;
|
||||||
auto ellipse = [&](float cx, float cy, float rx, float ry, SDL_FColor c) {
|
ImGui_ImplSDL3_InitForSDLRenderer(win, ren);
|
||||||
std::vector<SDL_Vertex> v;
|
ImGui_ImplSDLRenderer3_Init(ren);
|
||||||
const int n = 48;
|
ui::init();
|
||||||
for (int i = 0; i < n; ++i) {
|
const float density = SDL_GetWindowDisplayScale(win);
|
||||||
const float a0 = 6.2831853f * i / n, a1 = 6.2831853f * (i + 1) / n;
|
ImGui::GetStyle().ScaleAllSizes(density);
|
||||||
v.push_back({{cx, cy}, c, {}});
|
ImGui::GetStyle().FontScaleDpi = density;
|
||||||
v.push_back({{cx + std::cos(a0) * rx, cy + std::sin(a0) * ry}, c, {}});
|
|
||||||
v.push_back({{cx + std::cos(a1) * rx, cy + std::sin(a1) * ry}, c, {}});
|
SDL_Texture* logo = nullptr;
|
||||||
|
if (auto png = fs::read("branding/logo.png")) {
|
||||||
|
int w = 0, h = 0, n = 0;
|
||||||
|
if (stbi_uc* px = stbi_load_from_memory(png->data(), int(png->size()), &w, &h, &n, 4)) {
|
||||||
|
if ((logo = SDL_CreateTexture(ren, SDL_PIXELFORMAT_ABGR8888, SDL_TEXTUREACCESS_STATIC, w, h))) {
|
||||||
|
SDL_UpdateTexture(logo, nullptr, px, w * 4);
|
||||||
|
SDL_SetTextureBlendMode(logo, SDL_BLENDMODE_BLEND);
|
||||||
|
}
|
||||||
|
stbi_image_free(px);
|
||||||
}
|
}
|
||||||
SDL_RenderGeometry(ren, nullptr, v.data(), int(v.size()), nullptr, 0);
|
}
|
||||||
};
|
|
||||||
auto quad = [&](SDL_FPoint a, SDL_FPoint b, SDL_FPoint c, SDL_FPoint d, SDL_FColor col) {
|
using ui::Role;
|
||||||
const SDL_Vertex v[6] = {{a, col, {}}, {b, col, {}}, {c, col, {}}, {a, col, {}}, {c, col, {}}, {d, col, {}}};
|
auto col = [](Role r, float a = 1.0f) { return ImGui::ColorConvertU32ToFloat4(ui::color(r, a)); };
|
||||||
SDL_RenderGeometry(ren, nullptr, v, 6, nullptr, 0);
|
|
||||||
};
|
|
||||||
auto rgb = [](int r, int g, int b, float a = 1.0f) { return SDL_FColor{r / 255.0f, g / 255.0f, b / 255.0f, a}; };
|
|
||||||
// Word wrap for the 8-pixel font.
|
|
||||||
auto wrap = [](const std::string& s, size_t cols) {
|
|
||||||
std::vector<std::string> out;
|
|
||||||
std::string line;
|
|
||||||
std::stringstream ss(s);
|
|
||||||
for (std::string w; ss >> w;) {
|
|
||||||
if (!line.empty() && line.size() + 1 + w.size() > cols) out.push_back(line), line.clear();
|
|
||||||
line += (line.empty() ? "" : " ") + w;
|
|
||||||
}
|
|
||||||
if (!line.empty()) out.push_back(line);
|
|
||||||
return out;
|
|
||||||
};
|
|
||||||
const SDL_Color ink{236, 232, 226, 255}, dim{150, 146, 140, 255}, orange{255, 140, 60, 255};
|
|
||||||
int scroll = 0, result = 0;
|
|
||||||
std::string toast;
|
std::string toast;
|
||||||
double toastUntil = 0;
|
double toastUntil = 0;
|
||||||
|
int frame = 0, choice = -1;
|
||||||
for (bool running = true; running;) {
|
for (bool running = true; running;) {
|
||||||
int w = 0, h = 0;
|
|
||||||
SDL_GetRenderOutputSize(ren, &w, &h);
|
|
||||||
const float s = std::min(h / 620.0f, w / 1000.0f);
|
|
||||||
const double t = SDL_GetTicks() / 1000.0;
|
|
||||||
struct Button { const char* label; float x; } buttons[] = {{"OPEN IT AGAIN", 60 * s}, {"COPY REPORT", 290 * s}, {"REPORT ON GITHUB", 520 * s}, {"CLOSE", 750 * s}};
|
|
||||||
const float by = h - 90 * s, bw = 210 * s, bh = 50 * s;
|
|
||||||
float mx = 0, my = 0;
|
|
||||||
SDL_GetMouseState(&mx, &my);
|
|
||||||
float px = 1, py = 1; // window → pixels
|
|
||||||
{
|
|
||||||
int ww, wh;
|
|
||||||
SDL_GetWindowSize(win, &ww, &wh);
|
|
||||||
px = float(w) / ww, py = float(h) / wh;
|
|
||||||
}
|
|
||||||
mx *= px, my *= py;
|
|
||||||
int clicked = -1;
|
|
||||||
for (SDL_Event e; SDL_PollEvent(&e);) {
|
for (SDL_Event e; SDL_PollEvent(&e);) {
|
||||||
|
ImGui_ImplSDL3_ProcessEvent(&e);
|
||||||
if (e.type == SDL_EVENT_QUIT) running = false;
|
if (e.type == SDL_EVENT_QUIT) running = false;
|
||||||
if (e.type == SDL_EVENT_KEY_DOWN) {
|
|
||||||
if (e.key.key == SDLK_ESCAPE || e.key.key == SDLK_Q) running = false;
|
|
||||||
if (e.key.key == SDLK_R) clicked = 0;
|
|
||||||
if (e.key.key == SDLK_C) clicked = 1;
|
|
||||||
}
|
|
||||||
if (e.type == SDL_EVENT_MOUSE_WHEEL) scroll = std::max(0, scroll - int(e.wheel.y * 3));
|
|
||||||
if (e.type == SDL_EVENT_MOUSE_BUTTON_DOWN && e.button.button == SDL_BUTTON_LEFT)
|
|
||||||
for (int i = 0; i < 4; ++i)
|
|
||||||
if (mx >= buttons[i].x && mx < buttons[i].x + bw && my >= by && my < by + bh) clicked = i;
|
|
||||||
}
|
}
|
||||||
if (clicked == 0) { // opened again as it was started (it offers the autosave back)
|
ImGui_ImplSDLRenderer3_NewFrame();
|
||||||
std::vector<char*> a;
|
ImGui_ImplSDL3_NewFrame();
|
||||||
std::string exe = argv[0];
|
ui::beginFrame(1080.0f); // (sizes as they are: the window's density is in FontScaleDpi)
|
||||||
a.push_back(exe.data());
|
ImGui::NewFrame();
|
||||||
for (std::string& r : restart) a.push_back(r.data());
|
const double t = SDL_GetTicks() / 1000.0;
|
||||||
a.push_back(nullptr);
|
const float u = density; // (one point)
|
||||||
SDL_DestroyRenderer(ren);
|
|
||||||
SDL_DestroyWindow(win);
|
|
||||||
SDL_Quit();
|
|
||||||
#ifdef _WIN32
|
|
||||||
_spawnv(_P_NOWAIT, exe.c_str(), a.data());
|
|
||||||
return 0;
|
|
||||||
#else
|
|
||||||
execv(std::filesystem::read_symlink("/proc/self/exe").c_str(), a.data());
|
|
||||||
return 1;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
if (clicked == 1 || clicked == 2) {
|
|
||||||
SDL_SetClipboardText(report.c_str());
|
|
||||||
toast = clicked == 1 ? "Copied: paste it into a bug report." : "Copied: paste it into the new issue.";
|
|
||||||
toastUntil = t + 3.0;
|
|
||||||
if (clicked == 2) SDL_OpenURL("https://github.com/Nexoniarz/Fry3DX/issues/new");
|
|
||||||
}
|
|
||||||
if (clicked == 3) running = false;
|
|
||||||
|
|
||||||
SDL_SetRenderDrawColor(ren, 22, 22, 25, 255);
|
const ImGuiViewport* vp = ImGui::GetMainViewport();
|
||||||
SDL_RenderClear(ren);
|
ImGui::SetNextWindowPos(vp->Pos);
|
||||||
// The picture, top right: a pan, a cube on it burnt black, smoke going up.
|
ImGui::SetNextWindowSize(vp->Size);
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_WindowBg, col(Role::Background));
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(36 * u, 44 * u));
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0);
|
||||||
|
ImGui::Begin("crash", nullptr, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings);
|
||||||
|
ImDrawList* dl = ImGui::GetWindowDrawList();
|
||||||
|
|
||||||
|
// The logo, gone dark at the edges, with smoke curling up off it.
|
||||||
|
const float size = 92 * u;
|
||||||
|
const ImVec2 at = ImGui::GetCursorScreenPos();
|
||||||
|
if (logo) dl->AddImage(ImTextureRef(ImTextureID(logo)), at, ImVec2(at.x + size, at.y + size));
|
||||||
|
for (int i = 0; i < 7; ++i) {
|
||||||
|
const double life = std::fmod(t * 0.35 + i / 7.0, 1.0);
|
||||||
|
const float x = at.x + size * (0.45f + 0.18f * float(std::sin(life * 5.0 + i * 1.7))), y = at.y + size * 0.3f - float(life) * 52 * u;
|
||||||
|
dl->AddCircleFilled(ImVec2(x, y), (4 + float(life) * 12) * u, ui::color(Role::TextTertiary, 0.28f * float(1.0 - life)), 24);
|
||||||
|
}
|
||||||
|
ImGui::Dummy(ImVec2(size, size));
|
||||||
|
ImGui::SameLine(0, 24 * u);
|
||||||
|
ImGui::BeginGroup();
|
||||||
|
ImGui::Dummy(ImVec2(0, 4 * u));
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Text, col(Role::AccentText));
|
||||||
|
ImGui::PushFont(ui::font(ui::Font::Bold), 13.0f);
|
||||||
|
ImGui::TextUnformatted("FRY3DX");
|
||||||
|
ImGui::PopFont();
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
ImGui::PushFont(ui::font(ui::Font::Title), 34.0f);
|
||||||
|
ImGui::TextUnformatted("Something got burnt.");
|
||||||
|
ImGui::PopFont();
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Text, col(Role::TextSecondary));
|
||||||
|
ImGui::PushTextWrapPos(ImGui::GetContentRegionMax().x);
|
||||||
|
ImGui::TextUnformatted(headline.c_str());
|
||||||
|
ImGui::PopTextWrapPos();
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
ImGui::EndGroup();
|
||||||
|
ImGui::Dummy(ImVec2(0, 14 * u));
|
||||||
|
|
||||||
|
// The good news, on a card of its own.
|
||||||
{
|
{
|
||||||
const float cx = w - 230 * s, cy = 170 * s;
|
const ImVec2 a = ImGui::GetCursorScreenPos();
|
||||||
quad({cx + 80 * s, cy - 6 * s}, {cx + 200 * s, cy - 30 * s}, {cx + 204 * s, cy - 16 * s}, {cx + 84 * s, cy + 8 * s}, rgb(58, 58, 64)); // handle
|
const float w = ImGui::GetContentRegionAvail().x, h = ImGui::GetTextLineHeight() + 26 * u;
|
||||||
ellipse(cx, cy + 10 * s, 105 * s, 34 * s, rgb(34, 34, 38));
|
dl->AddRectFilled(a, ImVec2(a.x + w, a.y + h), ui::color(Role::Success, 0.14f), 8 * u);
|
||||||
ellipse(cx, cy, 105 * s, 34 * s, rgb(70, 70, 78));
|
dl->AddRect(a, ImVec2(a.x + w, a.y + h), ui::color(Role::Success, 0.4f), 8 * u);
|
||||||
ellipse(cx, cy + 2 * s, 88 * s, 26 * s, rgb(28, 28, 32));
|
dl->AddCircleFilled(ImVec2(a.x + 18 * u, a.y + h / 2), 4 * u, ui::color(Role::Success));
|
||||||
for (int i = 0; i < 3; ++i) { // flames under it
|
ImGui::SetCursorScreenPos(ImVec2(a.x + 32 * u, a.y + 13 * u));
|
||||||
const float f = float(std::sin(t * 7 + i * 2.1) * 0.5 + 0.5);
|
ImGui::PushStyleColor(ImGuiCol_Text, col(Role::SuccessText));
|
||||||
const float fx = cx + (i - 1) * 50 * s;
|
ImGui::TextUnformatted("Your work isn't lost: open it again and Fry3DX offers back the last autosave.");
|
||||||
ellipse(fx, cy + 46 * s, (14 + 4 * f) * s, (12 + 6 * f) * s, rgb(255, 74, 28, 0.85f));
|
ImGui::PopStyleColor();
|
||||||
ellipse(fx, cy + 48 * s, 7 * s, (6 + 4 * f) * s, rgb(255, 194, 74, 0.9f));
|
ImGui::SetCursorScreenPos(ImVec2(a.x, a.y + h + 18 * u));
|
||||||
}
|
|
||||||
const float k = 26 * s; // the cube (seen from a corner), charred
|
|
||||||
quad({cx, cy - 2 * k}, {cx + k, cy - 1.5f * k}, {cx, cy - k}, {cx - k, cy - 1.5f * k}, rgb(64, 52, 46));
|
|
||||||
quad({cx - k, cy - 1.5f * k}, {cx, cy - k}, {cx, cy}, {cx - k, cy - 0.5f * k}, rgb(40, 32, 28));
|
|
||||||
quad({cx, cy - k}, {cx + k, cy - 1.5f * k}, {cx + k, cy - 0.5f * k}, {cx, cy}, rgb(26, 21, 19));
|
|
||||||
for (int i = 0; i < 6; ++i) { // smoke, rising and fading
|
|
||||||
const double life = std::fmod(t * 0.45 + i / 6.0, 1.0);
|
|
||||||
const float sx = cx + float(std::sin(life * 6 + i) * 18 * s), sy = cy - 2.2f * k - float(life * 80 * s);
|
|
||||||
ellipse(sx, sy, float(10 + life * 22) * s, float(8 + life * 16) * s, rgb(150, 150, 158, float(0.35 * (1 - life))));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
text(60 * s, 60 * s, 4 * s, "Fry3DX", orange);
|
|
||||||
text(60 * s, 110 * s, 2.5f * s, "Something got burnt.", ink);
|
// The report: what was going on when it happened, for a bug report.
|
||||||
float y = 160 * s;
|
ImGui::PushStyleColor(ImGuiCol_Text, col(Role::TextTertiary));
|
||||||
for (const std::string& l : wrap(headline, size_t((w - 400 * s) / (16 * s))))
|
ImGui::PushTextWrapPos(0.0f);
|
||||||
text(60 * s, y, 2 * s, l, ink), y += 24 * s;
|
ImGui::Text("The report, saved to %s", argv[2]);
|
||||||
y += 6 * s;
|
ImGui::PopTextWrapPos();
|
||||||
for (const std::string& l : wrap("Your work isn't lost: Open it again and Fry3DX offers back the last autosave.", size_t((w - 400 * s) / (12 * s))))
|
ImGui::PopStyleColor();
|
||||||
text(60 * s, y, 1.5f * s, l, {130, 210, 120, 255}), y += 18 * s;
|
const float buttonsH = ImGui::GetFrameHeight() + 26 * u + ImGui::GetTextLineHeight();
|
||||||
text(60 * s, y + 4 * s, 1.25f * s, "Report saved to " + std::string(argv[2]), dim);
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(14 * u, 12 * u));
|
||||||
y += 30 * s;
|
ImGui::BeginChild("report", ImVec2(0, std::max(80 * u, ImGui::GetContentRegionAvail().y - buttonsH)), ImGuiChildFlags_FrameStyle | ImGuiChildFlags_AlwaysUseWindowPadding,
|
||||||
// The report's tail, in a panel, scrollable.
|
ImGuiWindowFlags_HorizontalScrollbar);
|
||||||
const SDL_FRect panel{50 * s, y - 8 * s, w - 100 * s, by - 20 * s - (y - 8 * s)};
|
ImGui::PushFont(ui::font(ui::Font::Mono), 13.0f);
|
||||||
SDL_SetRenderDrawColor(ren, 15, 15, 17, 255);
|
ImGui::PushStyleColor(ImGuiCol_Text, col(Role::TextSecondary));
|
||||||
SDL_RenderFillRect(ren, &panel);
|
ImGui::TextUnformatted(details.c_str());
|
||||||
SDL_SetRenderDrawColor(ren, 255, 120, 40, 255);
|
ImGui::PopStyleColor();
|
||||||
const SDL_FRect edge{panel.x, panel.y, 3 * s, panel.h};
|
ImGui::PopFont();
|
||||||
SDL_RenderFillRect(ren, &edge);
|
if (frame == 1) ImGui::SetScrollHereY(1.0f); // (the newest lines: the end)
|
||||||
const int visible = std::max(1, int((panel.h - 10 * s) / (13 * s)));
|
ImGui::EndChild();
|
||||||
const int start = std::clamp(int(lines.size()) - visible - scroll, 1, std::max(1, int(lines.size()) - 1));
|
ImGui::PopStyleVar();
|
||||||
for (int i = start; i < int(lines.size()) && y < by - 30 * s; ++i, y += 13 * s)
|
|
||||||
text(64 * s, y, 1.25f * s, lines[size_t(i)].substr(0, size_t((w - 140 * s) / (10 * s))), {165, 160, 152, 255});
|
ImGui::Dummy(ImVec2(0, 10 * u));
|
||||||
for (int i = 0; i < 4; ++i) {
|
const ImVec2 button(170 * u, ImGui::GetFrameHeight() + 10 * u);
|
||||||
const bool hover = mx >= buttons[i].x && mx < buttons[i].x + bw && my >= by && my < by + bh;
|
ImGui::PushStyleColor(ImGuiCol_Button, col(Role::Accent));
|
||||||
SDL_FRect r{buttons[i].x, by, bw, bh};
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, col(Role::AccentHover));
|
||||||
if (i == 0 || hover) SDL_SetRenderDrawColor(ren, hover ? 255 : 220, hover ? 140 : 100, hover ? 60 : 30, 255);
|
ImGui::PushStyleColor(ImGuiCol_ButtonActive, col(Role::AccentActive));
|
||||||
else SDL_SetRenderDrawColor(ren, 46, 46, 52, 255);
|
ImGui::PushStyleColor(ImGuiCol_Text, col(Role::OnAccent));
|
||||||
SDL_RenderFillRect(ren, &r);
|
if (ImGui::Button("Open it again", button)) choice = 0;
|
||||||
const float n = float(std::strlen(buttons[i].label)), ts = std::min(1.75f * s, (bw - 24 * s) / (n * 8)); // (fits its button)
|
ImGui::PopStyleColor(4);
|
||||||
text(buttons[i].x + (bw - n * 8 * ts) / 2, by + bh / 2 - 4 * ts, ts, buttons[i].label, ink);
|
ImGui::SameLine(0, 10 * u);
|
||||||
|
if (ImGui::Button("Copy the report", button)) choice = 1;
|
||||||
|
ImGui::SameLine(0, 10 * u);
|
||||||
|
if (ImGui::Button("Report it on GitHub", button)) choice = 2;
|
||||||
|
ImGui::SameLine(0, 10 * u);
|
||||||
|
if (ImGui::Button("Close", ImVec2(110 * u, button.y))) running = false;
|
||||||
|
if (!ImGui::GetIO().WantTextInput) {
|
||||||
|
if (ImGui::IsKeyPressed(ImGuiKey_R, false)) choice = 0;
|
||||||
|
if (ImGui::IsKeyPressed(ImGuiKey_C, false)) choice = 1;
|
||||||
|
if (ImGui::IsKeyPressed(ImGuiKey_Escape, false) || ImGui::IsKeyPressed(ImGuiKey_Q, false)) running = false;
|
||||||
}
|
}
|
||||||
text(60 * s, by + bh + 12 * s, 1.25f * s, "R open again C copy Esc close", dim);
|
ImGui::PushStyleColor(ImGuiCol_Text, col(t < toastUntil ? Role::AccentText : Role::TextTertiary));
|
||||||
if (t < toastUntil) text(60 * s, by - 16 * s, 1.5f * s, toast, orange);
|
ImGui::TextUnformatted(t < toastUntil ? toast.c_str() : "R open it again C copy Esc close");
|
||||||
if (const char* shot = std::getenv("KEEL_CRASHSHOT")) { // for checking the screen: one frame to a file, then quit
|
ImGui::PopStyleColor();
|
||||||
|
ImGui::End();
|
||||||
|
ImGui::PopStyleVar(2);
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
|
||||||
|
if (choice == 1 || choice == 2) {
|
||||||
|
SDL_SetClipboardText(report.c_str());
|
||||||
|
toast = choice == 1 ? "Copied: paste it into a bug report." : "Copied: paste it into the new issue.";
|
||||||
|
toastUntil = t + 3.0;
|
||||||
|
if (choice == 2) SDL_OpenURL("https://github.com/Nexoniarz/Fry3DX/issues/new");
|
||||||
|
choice = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::Render();
|
||||||
|
const ImVec4 bg = col(Role::Background);
|
||||||
|
SDL_SetRenderDrawColorFloat(ren, bg.x, bg.y, bg.z, 1.0f);
|
||||||
|
SDL_RenderClear(ren);
|
||||||
|
ImGui_ImplSDLRenderer3_RenderDrawData(ImGui::GetDrawData(), ren);
|
||||||
|
if (const char* shot = std::getenv("KEEL_CRASHSHOT"); shot && frame == 4) { // for checking the screen: one frame to a file, then quit
|
||||||
if (SDL_Surface* px = SDL_RenderReadPixels(ren, nullptr)) SDL_SaveBMP(px, shot), SDL_DestroySurface(px);
|
if (SDL_Surface* px = SDL_RenderReadPixels(ren, nullptr)) SDL_SaveBMP(px, shot), SDL_DestroySurface(px);
|
||||||
running = false;
|
running = false;
|
||||||
}
|
}
|
||||||
SDL_RenderPresent(ren);
|
SDL_RenderPresent(ren);
|
||||||
|
++frame;
|
||||||
|
if (choice == 0) break;
|
||||||
}
|
}
|
||||||
|
ImGui_ImplSDLRenderer3_Shutdown();
|
||||||
|
ImGui_ImplSDL3_Shutdown();
|
||||||
|
ImGui::DestroyContext();
|
||||||
|
if (logo) SDL_DestroyTexture(logo);
|
||||||
SDL_DestroyRenderer(ren);
|
SDL_DestroyRenderer(ren);
|
||||||
SDL_DestroyWindow(win);
|
SDL_DestroyWindow(win);
|
||||||
SDL_Quit();
|
SDL_Quit();
|
||||||
return result;
|
if (choice == 0) { // opened again as it was started (it offers the autosave back)
|
||||||
|
const std::string exe = sys::executablePath();
|
||||||
|
std::vector<char*> a;
|
||||||
|
std::string self = argv[0];
|
||||||
|
a.push_back(self.data());
|
||||||
|
for (std::string& r : restart) a.push_back(r.data());
|
||||||
|
a.push_back(nullptr);
|
||||||
|
#ifdef _WIN32
|
||||||
|
_spawnv(_P_NOWAIT, exe.c_str(), a.data());
|
||||||
|
return 0;
|
||||||
|
#else
|
||||||
|
execv(exe.c_str(), a.data());
|
||||||
|
return 1;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace keel::crash
|
} // namespace keel::crash
|
||||||
|
|
|
||||||
17
src/app.cpp
|
|
@ -541,6 +541,7 @@ public:
|
||||||
static keel::Cmd autosaveNow("fry_autosavenow", [](const keel::Args&) { self->autosave(true); }, "Fry3DX: autosave now");
|
static keel::Cmd autosaveNow("fry_autosavenow", [](const keel::Args&) { self->autosave(true); }, "Fry3DX: autosave now");
|
||||||
static keel::Cmd closeAsk("fry_close", [](const keel::Args&) { if (self->allowQuit()) keel::execute("quit"); }, "Fry3DX: as the window's close button");
|
static keel::Cmd closeAsk("fry_close", [](const keel::Args&) { if (self->allowQuit()) keel::execute("quit"); }, "Fry3DX: as the window's close button");
|
||||||
static keel::Cmd recover("fry_recover", [](const keel::Args&) { self->findAutosaves(), self->showRecover_ = !self->recover_.empty(); }, "Fry3DX: show the autosaves to recover");
|
static keel::Cmd recover("fry_recover", [](const keel::Args&) { self->findAutosaves(), self->showRecover_ = !self->recover_.empty(); }, "Fry3DX: show the autosaves to recover");
|
||||||
|
static keel::Cmd scriptPanel("fry_scriptpanel", [](const keel::Args& a) { self->showScript_ = a.size() < 2 || a[1] != "0"; }, "Fry3DX: show (1) or hide (0) the Script panel");
|
||||||
static keel::Cmd about("fry_about", [](const keel::Args&) { self->showAbout_ = true; }, "Fry3DX: show the About window");
|
static keel::Cmd about("fry_about", [](const keel::Args&) { self->showAbout_ = true; }, "Fry3DX: show the About window");
|
||||||
keel::execute("r_autoexposure 0; r_ssr 0; r_volumetric 0; r_shafts 0; r_dynres 0; r_renderscale 1; fps_max 144");
|
keel::execute("r_autoexposure 0; r_ssr 0; r_volumetric 0; r_shafts 0; r_dynres 0; r_renderscale 1; fps_max 144");
|
||||||
#if defined(__ANDROID__) // a phone: none of the heavy parts (and its GPU's driver is kept to well-trodden ground)
|
#if defined(__ANDROID__) // a phone: none of the heavy parts (and its GPU's driver is kept to well-trodden ground)
|
||||||
|
|
@ -5836,6 +5837,16 @@ private:
|
||||||
|
|
||||||
void paintProperties(float w) {
|
void paintProperties(float w) {
|
||||||
Object* o = selected_.empty() ? nullptr : find(active_);
|
Object* o = selected_.empty() ? nullptr : find(active_);
|
||||||
|
if (sculpting_) { // (its look, while you shape it: what it's made of shows how the light runs over it)
|
||||||
|
if (!o) return ImGui::TextWrapped("Brush over an object to sculpt it.\n\nA shape with few points is made finer first, keeping its shape. Tab goes back to painting colours.");
|
||||||
|
ImGui::TextColored(accentText(), "%s", o->name.c_str());
|
||||||
|
ImGui::TextDisabled("%zu points, %zu faces", o->mesh.verts.size(), o->mesh.faces.size());
|
||||||
|
ImGui::SeparatorText("Look");
|
||||||
|
materialPicker(*o);
|
||||||
|
undoable([&] { return ImGui::ColorEdit3("Colour", &o->color.x); });
|
||||||
|
if (ImGui::Checkbox("Smooth shading", &o->smooth)) pushUndo(), touched(o->id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!o) {
|
if (!o) {
|
||||||
ImGui::TextWrapped("Click an object to paint on it.\n\nIts surface gets laid out flat on a picture (the Picture panel), which you can paint on too, "
|
ImGui::TextWrapped("Click an object to paint on it.\n\nIts surface gets laid out flat on a picture (the Picture panel), which you can paint on too, "
|
||||||
"or put pictures onto: copy one in any app and press Ctrl+V.");
|
"or put pictures onto: copy one in any app and press Ctrl+V.");
|
||||||
|
|
@ -5955,7 +5966,8 @@ private:
|
||||||
if (o && ImGui::Button("Lay it out (and start its picture)")) readyToPaint(o->id);
|
if (o && ImGui::Button("Lay it out (and start its picture)")) readyToPaint(o->id);
|
||||||
return ImGui::End();
|
return ImGui::End();
|
||||||
}
|
}
|
||||||
ImGui::TextWrapped(o ? "Paint on it (or press Start painting it in Properties) and its picture shows here." : "Click an object to paint it: its picture shows here.");
|
ImGui::TextWrapped(sculpting_ ? "Sculpting changes its shape, not its picture. Tab goes back to painting colours."
|
||||||
|
: o ? "Paint on it (or press Start painting it in Properties) and its picture shows here." : "Click an object to paint it: its picture shows here.");
|
||||||
return ImGui::End();
|
return ImGui::End();
|
||||||
}
|
}
|
||||||
uploadPicture(*o, !paint_.active);
|
uploadPicture(*o, !paint_.active);
|
||||||
|
|
@ -6303,6 +6315,9 @@ private:
|
||||||
} else if (tab_ == Tab::Print && printTool_ != PrintTool::None) {
|
} else if (tab_ == Tab::Print && printTool_ != PrintTool::None) {
|
||||||
ImGui::TextDisabled(printTool_ == PrintTool::Measure ? "Measure: click two points on the model (corners catch the mouse); a third click starts again · M: done"
|
ImGui::TextDisabled(printTool_ == PrintTool::Measure ? "Measure: click two points on the model (corners catch the mouse); a third click starts again · M: done"
|
||||||
: "Lay flat: click the face it should stand on: it turns so that face is down, on the bed · L: done");
|
: "Lay flat: click the face it should stand on: it turns so that face is down, on the bed · L: done");
|
||||||
|
} else if (tab_ == Tab::Paint && sculpting_) {
|
||||||
|
for (const SculptToolInfo& t : kSculptTools)
|
||||||
|
if (t.brush == sculptBrush_) ImGui::TextDisabled("%s: %s · Shift: smooth X: both sides Tab: colour Right drag: orbit F1: all keys", t.name, t.hint);
|
||||||
} else if (tab_ == Tab::Paint) {
|
} else if (tab_ == Tab::Paint) {
|
||||||
for (const PaintToolInfo& t : kPaintTools)
|
for (const PaintToolInfo& t : kPaintTools)
|
||||||
if (t.tool == paintTool_) ImGui::TextDisabled("%s: %s · Right drag: orbit Wheel: zoom (in the Picture panel too) F1: all keys", t.name, t.hint);
|
if (t.tool == paintTool_) ImGui::TextDisabled("%s: %s · Right drag: orbit Wheel: zoom (in the Picture panel too) F1: all keys", t.name, t.hint);
|
||||||
|
|
|
||||||