diff options
Diffstat (limited to 'src/ui.cpp')
-rw-r--r-- | src/ui.cpp | 123 |
1 files changed, 104 insertions, 19 deletions
@@ -5,17 +5,11 @@ #include <imgui.h> #include <memory> -static void AddSand(Sandbox& sb) { - for (int y = 80; y <= 90; ++y) { - for (int x = 10; x <= 13; ++x) { - sb.set_sand(x, y, Tile{ .so = Tile::SAND }); - } - } -} - +// NOTE: also exists in imgui_internal.h +// intentionally name it the same (including the fields), so if somebody else comes along and includes imgui_internal.h, they get a compile error and they can remove this struct ImRect { - ImVec2 top_left; - ImVec2 bottom_right; + ImVec2 Min; + ImVec2 Max; }; /// Helpers for displaying and coordinate space tracking of the sandbox image display. @@ -25,6 +19,8 @@ struct SandboxDisplay { int sandbox_height = 100; ImVec2 size_screenspace; + + /* AVAILABLE AFTER CALLING show_image() */ ImVec2 origin; SandboxDisplay(float tile_size, const Sandbox& sb) @@ -45,15 +41,62 @@ struct SandboxDisplay { }; } + // `pos` is +X right, +Y down from top left of the window's content region + Pt tr_screen2sandbox(ImVec2 pos) const { + // +X right, +Y up from `origin` + ImVec2 translated(pos.x - origin.x, origin.y - pos.y); + return Pt(translated.x / tile_size, translated.y / tile_size); + } + + bool is_out_of_bounds(Pt pt) const { + return pt.x < 0 || pt.x >= sandbox_width || pt.y < 0 || pt.y >= sandbox_height; + } + void show_image(ImTextureID texid) { auto img_topleft = ImGui::GetCursorScreenPos(); - ImGui::Image(texid, size_screenspace, ImVec2(0, 1), ImVec2(1, 0)); + ImGui::InvisibleButton("sandbox", size_screenspace); + const auto rect_min = ImGui::GetItemRectMin(); + const auto rect_max = ImGui::GetItemRectMax(); + ImGui::GetWindowDrawList()->AddImage(texid, rect_min, rect_max, ImVec2(0, 1), ImVec2(1, 0)); + // bottom-left corner of the sandbox, in screen space - origin = img_topleft; + origin = rect_min; origin.y += size_screenspace.y; } }; +typedef int BrushType; +enum BrushType_ { + BrushType_Box, + BrushType_Circle, +}; + +static void paint_sand(Sandbox& sb, const Tile& palette, Pt pt, BrushType type, int size) { + switch (type) { + case BrushType_Box: { + // size: side length + int y0 = pt.y - size / 2; + int x0 = pt.x - size / 2; + for (int y = y0; y < y0 + size; ++y) { + for (int x = x0; x < x0 + size; ++x) { + sb.set_sand(x, y, palette); + } + } + } break; + + case BrushType_Circle: { + // size: radius + // TODO + } break; + + default: { + ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 0, 0, 255)); + ImGui::Text("invalid brush type %d", type); + ImGui::PopStyleVar(); + } break; + } +} + void ShowEverything() { ImGui::Begin("Options"); @@ -92,22 +135,27 @@ void ShowEverything() { ImGui::Checkbox("Run", &running); static bool show_dirty_rect = true; ImGui::Checkbox("Show dirty rects", &show_dirty_rect); + static bool hide_mouse = true; + ImGui::Checkbox("Hide mouse", &hide_mouse); ImGui::Text("ncycle = %d", sandbox->ncycle); ImGui::SameLine(); bool step = ImGui::Button("Step"); - if (step || running) { - sandbox->simulate_step(); - } + static Tile palette{ .so = Tile::SAND }; + static int brush_size = 1; + static BrushType brush_type = BrushType_Box; - if (ImGui::Button("Add sand")) { - AddSand(*sandbox); - } + ImGui::SliderInt("Brush radius", &brush_size, 1, 20); + ImGui::Combo("Brush type", + &brush_type, + "Box\0" + "Circle"); ImGui::End(); ImGui::Begin("Sandbox"); + // Update texture to the current state of the the sandbox. // Do this every frame, because it's easier than trying to track when did the sandbox change, which may be caused by: // - Initialization @@ -118,11 +166,48 @@ void ShowEverything() { SandboxDisplay dis(tile_size, *sandbox); dis.show_image(gl.as_imgui()); + auto dl = ImGui::GetWindowDrawList(); + if (show_dirty_rect) { - auto dl = ImGui::GetWindowDrawList(); auto [top_left, bottom_right] = dis.tr_sandbox2screen(sandbox->dirty_writeto); dl->AddRect(top_left, bottom_right, IM_COL32(255, 0, 0, 255)); } + + // Unfortunate technical limitation: update HAS to come after render + // We don't get the screen pos of the ImGui::Image after having called it + + if (step || running) { + sandbox->simulate_step(); + } + + // HERE BE DRAGON: poor man's inner function + do { + // This checks for popup windows, drag&drop state, overlapping widgets, etc. + // So it's necessary even if tr_screen2sandbox() can already detect if the mouse is out of bounds + if (!ImGui::IsItemHovered()) + break; + + if (hide_mouse) { + ImGui::SetMouseCursor(ImGuiMouseCursor_None); + } + + auto mouse_pt = dis.tr_screen2sandbox(ImGui::GetMousePos()); + // This does trigger, if mouse is at very top of the image + // There must be some discrepency between the math above, and ImGui's hover rect calculation. I'm not sure what's happening. + if (dis.is_out_of_bounds(mouse_pt)) + break; + + // Show the tile under mouse + auto [top_left, bottom_right] = dis.tr_sandbox2screen(Rect(mouse_pt, mouse_pt)); + dl->AddRect(top_left, bottom_right, IM_COL32_BLACK); + + if (ImGui::IsItemActive()) { + paint_sand(*sandbox, palette, mouse_pt, brush_type, brush_size); + } + + break; + } while (false); + ImGui::End(); ImGui::ShowDemoWindow(); |