aboutsummaryrefslogtreecommitdiff
path: root/src/ui.cpp
diff options
context:
space:
mode:
authorrtk0c <[email protected]>2025-05-17 22:47:59 -0700
committerrtk0c <[email protected]>2025-05-17 22:47:59 -0700
commit82d363cc2c4c1ce2d54802b59fc0e20dbdb438a4 (patch)
tree0af3658f4d025855edde304c8a5457a766b3fc64 /src/ui.cpp
parent81e957ef919709c0cf9f0a82e818227ea545e405 (diff)
Painting sand
Diffstat (limited to 'src/ui.cpp')
-rw-r--r--src/ui.cpp123
1 files changed, 104 insertions, 19 deletions
diff --git a/src/ui.cpp b/src/ui.cpp
index 8e4e275..f870a24 100644
--- a/src/ui.cpp
+++ b/src/ui.cpp
@@ -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();