|
|
|
|
@@ -1,4 +1,4 @@
|
|
|
|
|
-- sporel-module-map-editor v0.3.1
|
|
|
|
|
-- sporel-module-map-editor v0.4.0
|
|
|
|
|
-- Interactive map editor — Rev 4 UX architecture (see design paper
|
|
|
|
|
-- 2026-05-28-autotile-blob-styles-design.md §11 + plan stub
|
|
|
|
|
-- 2026-05-28-map-editor-blob-v2.md). 0.2.0b adds the bottom palette
|
|
|
|
|
@@ -22,7 +22,14 @@
|
|
|
|
|
-- active layer renders "S<slot>r<rot>" (and "f" when flip=1) via the
|
|
|
|
|
-- new lib-core.maps 0.5.5 cell_material_slot getter. Inline today;
|
|
|
|
|
-- extract to lib-sporel.debug-overlay when a 2nd consumer appears.
|
|
|
|
|
-- Decal/Entity sub-selectors + Material-Properties modal land in 0.2.0d.
|
|
|
|
|
-- 0.4.0 migrates the top-toolbar / right side-panel / bottom palette
|
|
|
|
|
-- to lib-core.panel v0.3.0 persistent widgets (z_tier="normal",
|
|
|
|
|
-- chromeless, input_block="self"). Canvas + status + modals stay
|
|
|
|
|
-- module-rendered. Canvas-paint is gated on
|
|
|
|
|
-- panel.point_in_any_panel(mx, my) so panel-widget clicks no longer
|
|
|
|
|
-- bleed through to drag-paint. Removed Window > Layers Panel + Palette
|
|
|
|
|
-- toggle items (panel-lib manages visibility; persistent widgets are
|
|
|
|
|
-- always on).
|
|
|
|
|
--
|
|
|
|
|
-- All editor logic in this single file: engine sandbox only allows
|
|
|
|
|
-- require() for declared lib-deps; multi-file modules either use
|
|
|
|
|
@@ -33,6 +40,7 @@
|
|
|
|
|
local maps = require("lib-core.maps")
|
|
|
|
|
local camera = require("lib-core.camera")
|
|
|
|
|
local input = require("lib-core.input")
|
|
|
|
|
local panel = require("lib-core.panel")
|
|
|
|
|
|
|
|
|
|
-- CI-mode auto-exit (matches vagrant-skeleton pattern). Manifest declares
|
|
|
|
|
-- ci_frames=30 but engine does not auto-enforce for kind:"module"; we count
|
|
|
|
|
@@ -70,8 +78,9 @@ local state = (function()
|
|
|
|
|
submenu_open = nil, -- 0.3.0: label of the open submenu item within menu_open
|
|
|
|
|
modal_open = nil, -- 0.3.0: nil | <modal_id> (single-stack)
|
|
|
|
|
modal_text_input = "", -- 0.3.0: scratch buffer for modal text inputs
|
|
|
|
|
show_layers_panel = true, -- 0.3.0: Window > Layers Panel toggle
|
|
|
|
|
show_palette = true, -- 0.3.0: Window > Palette toggle
|
|
|
|
|
-- 0.4.0: visibility of layers-panel + palette is now managed by
|
|
|
|
|
-- lib-core.panel (persistent widgets). The Window-menu items
|
|
|
|
|
-- flip panel.is_open("map-editor.{layers,palette}") directly.
|
|
|
|
|
mouse_cell = nil, -- {x, y} or nil if mouse off-map
|
|
|
|
|
snap_vertex = nil, -- 0.2.0b: {vx, vy} or nil (auto-tile mode snap target)
|
|
|
|
|
drag_paint = nil, -- 0.2.0c.1: nil | "paint" | "erase" (active drag-stroke mode)
|
|
|
|
|
@@ -179,11 +188,9 @@ local state = (function()
|
|
|
|
|
function M.get_submenu_open() return state.submenu_open end
|
|
|
|
|
function M.close_submenu() state.submenu_open = nil end
|
|
|
|
|
|
|
|
|
|
function M.is_layers_panel_shown() return state.show_layers_panel end
|
|
|
|
|
function M.toggle_layers_panel() state.show_layers_panel = not state.show_layers_panel end
|
|
|
|
|
|
|
|
|
|
function M.is_palette_shown() return state.show_palette end
|
|
|
|
|
function M.toggle_palette() state.show_palette = not state.show_palette end
|
|
|
|
|
-- 0.4.0: layers-panel + palette visibility moved to lib-core.panel
|
|
|
|
|
-- (persistent widgets managed via panel.is_open/toggle on the
|
|
|
|
|
-- "map-editor.layers" + "map-editor.palette" widget ids).
|
|
|
|
|
|
|
|
|
|
function M.set_mouse_cell(x, y)
|
|
|
|
|
if x == nil then
|
|
|
|
|
@@ -231,8 +238,6 @@ local state = (function()
|
|
|
|
|
state.submenu_open = nil
|
|
|
|
|
state.modal_open = nil
|
|
|
|
|
state.modal_text_input = ""
|
|
|
|
|
state.show_layers_panel = true
|
|
|
|
|
state.show_palette = true
|
|
|
|
|
state.debug_overlay = false
|
|
|
|
|
state.world_overlay = true
|
|
|
|
|
for name in pairs(state.layer_visible) do
|
|
|
|
|
@@ -586,13 +591,15 @@ local ui = (function()
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- 0.4.0: panel-lib manages widget visibility via panel.is_open/toggle.
|
|
|
|
|
-- The Window-menu items now flip the persistent widgets' open-state.
|
|
|
|
|
local WINDOW_ITEMS = {
|
|
|
|
|
{ type = "toggle", label = "Layers Panel",
|
|
|
|
|
is_on = function() return state.is_layers_panel_shown() end,
|
|
|
|
|
fire = function() state.toggle_layers_panel() end },
|
|
|
|
|
is_on = function() return panel.is_open("map-editor.layers") end,
|
|
|
|
|
fire = function() panel.toggle("map-editor.layers") end },
|
|
|
|
|
{ type = "toggle", label = "Palette",
|
|
|
|
|
is_on = function() return state.is_palette_shown() end,
|
|
|
|
|
fire = function() state.toggle_palette() end },
|
|
|
|
|
is_on = function() return panel.is_open("map-editor.palette") end,
|
|
|
|
|
fire = function() panel.toggle("map-editor.palette") end },
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
local HELP_ITEMS = {
|
|
|
|
|
@@ -1211,10 +1218,18 @@ local ui = (function()
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- =====================================================================
|
|
|
|
|
-- Public API
|
|
|
|
|
-- Public API (v0.4.0 split for panel-lib persistent widgets)
|
|
|
|
|
-- =====================================================================
|
|
|
|
|
--
|
|
|
|
|
-- The 3 persistent widgets (toolbar / layers / palette) get their own
|
|
|
|
|
-- render + handle_input pair. M.draw() now only paints the chrome that
|
|
|
|
|
-- is NOT a panel-widget: the bottom-left status chip and any open
|
|
|
|
|
-- modal overlay. Modals stay module-rendered + module-routed (they
|
|
|
|
|
-- are top-tier transient and need to fully absorb input via the
|
|
|
|
|
-- existing dispatch_modal_click path).
|
|
|
|
|
|
|
|
|
|
function M.draw()
|
|
|
|
|
-- Toolbar widget: menu-bar + mode-pill + open-dropdown render.
|
|
|
|
|
function M.draw_toolbar_widget(_ctx)
|
|
|
|
|
local mx, my = engine.input.get_mouse_pos()
|
|
|
|
|
draw_menu_bar(mx, my)
|
|
|
|
|
local open_name = state.get_menu_open()
|
|
|
|
|
@@ -1227,12 +1242,25 @@ local ui = (function()
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
draw_mode_pill()
|
|
|
|
|
if state.is_layers_panel_shown() then
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- Layers widget: right-side panel with per-layer rows + roof toggle.
|
|
|
|
|
function M.draw_layers_widget(_ctx)
|
|
|
|
|
local mx, my = engine.input.get_mouse_pos()
|
|
|
|
|
draw_side_panel(mx, my)
|
|
|
|
|
end
|
|
|
|
|
if state.is_palette_shown() then
|
|
|
|
|
|
|
|
|
|
-- Palette widget: bottom strip with tile-thumbnail swatches.
|
|
|
|
|
function M.draw_palette_widget(_ctx)
|
|
|
|
|
local mx, my = engine.input.get_mouse_pos()
|
|
|
|
|
draw_palette(mx, my)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- M.draw() draws non-widget chrome: status chip + modal overlay.
|
|
|
|
|
-- Called by init.lua's render() AFTER panel.render(), so modals
|
|
|
|
|
-- correctly appear on top of all widget panels.
|
|
|
|
|
function M.draw()
|
|
|
|
|
local mx, my = engine.input.get_mouse_pos()
|
|
|
|
|
draw_status(mx, my)
|
|
|
|
|
local mid = state.get_modal_open()
|
|
|
|
|
if mid and MODALS[mid] then
|
|
|
|
|
@@ -1334,14 +1362,22 @@ local ui = (function()
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- Hit-test mouse click. Returns true if the click was consumed by UI.
|
|
|
|
|
-- button: "left" | "right"
|
|
|
|
|
function M.handle_click(mx, my, button)
|
|
|
|
|
-- Modal absorbs all clicks while open.
|
|
|
|
|
if dispatch_modal_click(mx, my, button) then
|
|
|
|
|
return true
|
|
|
|
|
-- v0.4.0: hit-test split into per-widget handlers + a module-level
|
|
|
|
|
-- modal dispatcher. The 3 widget panels (toolbar / layers / palette)
|
|
|
|
|
-- get their own handle_input fns; the modal-click flow stays at
|
|
|
|
|
-- the M.update level since modals are NOT panel widgets.
|
|
|
|
|
|
|
|
|
|
--- M.handle_modal_click(mx, my, button) — returns true if modal
|
|
|
|
|
--- consumed the click. Called BEFORE panel.update() so a modal can
|
|
|
|
|
--- swallow clicks before they reach widget dispatch.
|
|
|
|
|
function M.handle_modal_click(mx, my, button)
|
|
|
|
|
return dispatch_modal_click(mx, my, button)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
--- M.handle_toolbar_click(mx, my, button) — handles menu-bar +
|
|
|
|
|
--- mode-pill + dropdown clicks. Wired into the toolbar widget's
|
|
|
|
|
--- handle_input via panel-lib. Returns true if consumed.
|
|
|
|
|
function M.handle_toolbar_click(mx, my, button)
|
|
|
|
|
-- Mode-pill
|
|
|
|
|
local pill = mode_pill_layout()
|
|
|
|
|
for _, seg in ipairs({ pill.auto, pill.cell, pill.direct }) do
|
|
|
|
|
@@ -1426,25 +1462,17 @@ local ui = (function()
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- Click outside the dropdown closes it (no return — fall through so
|
|
|
|
|
-- the click can also hit other UI like canvas).
|
|
|
|
|
-- Click outside the dropdown closes it (no return — fall
|
|
|
|
|
-- through so other UI can still hit-test the click).
|
|
|
|
|
state.close_menu()
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- Bottom palette swatches: left = select slot
|
|
|
|
|
if state.is_palette_shown() then
|
|
|
|
|
for _, sw in ipairs(palette_swatches_layout()) do
|
|
|
|
|
if in_rect(mx, my, sw.x, sw.y, sw.w, sw.h) then
|
|
|
|
|
if button == "left" then
|
|
|
|
|
actions.set_active_tile(sw.slot_id)
|
|
|
|
|
end
|
|
|
|
|
return true
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
return false
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- Side-panel layer rows: left = set active; right = toggle visibility
|
|
|
|
|
if state.is_layers_panel_shown() then
|
|
|
|
|
--- M.handle_layers_click(mx, my, button) — left = set active layer;
|
|
|
|
|
--- right = toggle visibility. Wired into the layers widget.
|
|
|
|
|
function M.handle_layers_click(mx, my, button)
|
|
|
|
|
for _, row in ipairs(side_panel_rows_layout()) do
|
|
|
|
|
if in_rect(mx, my, row.eye_x, row.eye_y, row.eye_w, row.eye_h) then
|
|
|
|
|
if button == "left" then
|
|
|
|
|
@@ -1461,16 +1489,30 @@ local ui = (function()
|
|
|
|
|
return true
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
return false
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- Status chip — no-op fallback to claim the click area
|
|
|
|
|
local s = status_layout()
|
|
|
|
|
if in_rect(mx, my, s.x, s.y, s.w, s.h) then
|
|
|
|
|
--- M.handle_palette_click(mx, my, button) — left = select slot.
|
|
|
|
|
--- Wired into the palette widget.
|
|
|
|
|
function M.handle_palette_click(mx, my, button)
|
|
|
|
|
for _, sw in ipairs(palette_swatches_layout()) do
|
|
|
|
|
if in_rect(mx, my, sw.x, sw.y, sw.w, sw.h) then
|
|
|
|
|
if button == "left" then
|
|
|
|
|
actions.set_active_tile(sw.slot_id)
|
|
|
|
|
end
|
|
|
|
|
return true
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
return false -- click falls through to map-area handler in init.lua
|
|
|
|
|
end
|
|
|
|
|
return false
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- Expose layout fns for the widget custom layout-fns registered in
|
|
|
|
|
-- the Setup section. Each widget needs its bounds to match the
|
|
|
|
|
-- absolute screen coords the existing draw_* helpers use, so we
|
|
|
|
|
-- expose the layout fns directly.
|
|
|
|
|
M._side_panel_layout = side_panel_layout
|
|
|
|
|
M._palette_layout = palette_layout
|
|
|
|
|
M._MENU_BAR_H_const = MENU_BAR_H
|
|
|
|
|
|
|
|
|
|
-- Expose internal tables needed by self_test (test-only; not part of
|
|
|
|
|
-- the public draw/click API).
|
|
|
|
|
@@ -1513,7 +1555,88 @@ input.bind("reset_transform", { "0" }) -- 0.2.0c
|
|
|
|
|
input.bind("toggle_debug_overlay", { "d" }) -- 0.2.0c.3
|
|
|
|
|
input.bind("toggle_world_overlay", { "g" }) -- 0.2.0c.4
|
|
|
|
|
|
|
|
|
|
engine.print("map-editor v0.3.1: ready. Tab=cycle mode (Auto/Cell/Direct), I=help, G=grid, D=debug, S=save, E=erase, R=rotate, H=flip, 0=reset, ESC=quit / close")
|
|
|
|
|
-- =====================================================================
|
|
|
|
|
-- 0.4.0: panel-lib persistent widget registration (W.2)
|
|
|
|
|
-- =====================================================================
|
|
|
|
|
-- Three persistent + chromeless + input_block="self" widgets cover the
|
|
|
|
|
-- top toolbar, right-side layers panel, and bottom palette strip. Each
|
|
|
|
|
-- widget's bounds match what the existing draw_* helpers expect — they
|
|
|
|
|
-- still draw at absolute screen coords via engine.window.size(). The
|
|
|
|
|
-- panel-lib's chrome (bg/border/title/X) is suppressed via
|
|
|
|
|
-- chromeless=true; the widgets draw their own visual style.
|
|
|
|
|
--
|
|
|
|
|
-- Map-canvas is NOT a widget. Module renders it directly inside
|
|
|
|
|
-- camera.begin/finish; module's drag-paint dispatch is gated on
|
|
|
|
|
-- panel.point_in_any_panel(mx, my) so widget clicks never bleed
|
|
|
|
|
-- through into canvas paint.
|
|
|
|
|
|
|
|
|
|
-- Toolbar bounds = top strip (menu-bar height + room for open
|
|
|
|
|
-- dropdowns). Use a custom layout fn that covers the union of
|
|
|
|
|
-- menu-bar + worst-case dropdown extent.
|
|
|
|
|
local TOOLBAR_MAX_H = 290 -- menu-bar (24) + max dropdown rows (12 * 22 + pad)
|
|
|
|
|
local toolbar_widget = {
|
|
|
|
|
title = "Toolbar",
|
|
|
|
|
pause_on_open = false,
|
|
|
|
|
render = function(ctx) ui.draw_toolbar_widget(ctx) end,
|
|
|
|
|
handle_input = function(_ctx, event)
|
|
|
|
|
if event.kind == "click" then
|
|
|
|
|
ui.handle_toolbar_click(event.x, event.y, event.button)
|
|
|
|
|
end
|
|
|
|
|
end,
|
|
|
|
|
}
|
|
|
|
|
panel.register("map-editor.toolbar", toolbar_widget, {
|
|
|
|
|
layout = function(sw, _sh)
|
|
|
|
|
return { x = 0, y = 0, w = sw, h = TOOLBAR_MAX_H }
|
|
|
|
|
end,
|
|
|
|
|
z_tier = "normal",
|
|
|
|
|
persistent = true,
|
|
|
|
|
input_block = "self",
|
|
|
|
|
chromeless = true,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
local layers_widget = {
|
|
|
|
|
title = "Layers",
|
|
|
|
|
pause_on_open = false,
|
|
|
|
|
render = function(ctx) ui.draw_layers_widget(ctx) end,
|
|
|
|
|
handle_input = function(_ctx, event)
|
|
|
|
|
if event.kind == "click" then
|
|
|
|
|
ui.handle_layers_click(event.x, event.y, event.button)
|
|
|
|
|
end
|
|
|
|
|
end,
|
|
|
|
|
}
|
|
|
|
|
panel.register("map-editor.layers", layers_widget, {
|
|
|
|
|
layout = function(_sw, _sh)
|
|
|
|
|
local p = ui._side_panel_layout()
|
|
|
|
|
return { x = p.x, y = p.y, w = p.w, h = p.h }
|
|
|
|
|
end,
|
|
|
|
|
z_tier = "normal",
|
|
|
|
|
persistent = true,
|
|
|
|
|
input_block = "self",
|
|
|
|
|
chromeless = true,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
local palette_widget = {
|
|
|
|
|
title = "Palette",
|
|
|
|
|
pause_on_open = false,
|
|
|
|
|
render = function(ctx) ui.draw_palette_widget(ctx) end,
|
|
|
|
|
handle_input = function(_ctx, event)
|
|
|
|
|
if event.kind == "click" then
|
|
|
|
|
ui.handle_palette_click(event.x, event.y, event.button)
|
|
|
|
|
end
|
|
|
|
|
end,
|
|
|
|
|
}
|
|
|
|
|
panel.register("map-editor.palette", palette_widget, {
|
|
|
|
|
layout = function(_sw, _sh)
|
|
|
|
|
local p = ui._palette_layout()
|
|
|
|
|
return { x = p.x, y = p.y, w = p.w, h = p.h }
|
|
|
|
|
end,
|
|
|
|
|
z_tier = "normal",
|
|
|
|
|
persistent = true,
|
|
|
|
|
input_block = "self",
|
|
|
|
|
chromeless = true,
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
engine.print("map-editor v0.4.0: ready. Tab=cycle mode (Auto/Cell/Direct), I=help, G=grid, D=debug, S=save, E=erase, R=rotate, H=flip, 0=reset, ESC=quit / close")
|
|
|
|
|
|
|
|
|
|
-- =====================================================================
|
|
|
|
|
-- 0.3.0: self-tests (Spec §7.1). Run once on the first CI frame.
|
|
|
|
|
@@ -1527,7 +1650,9 @@ local function self_test()
|
|
|
|
|
-- t1: opening a menu then clicking outside closes it.
|
|
|
|
|
state.set_menu_open("File")
|
|
|
|
|
engine.test.equals(state.get_menu_open(), "File", "t1: menu opened by setter")
|
|
|
|
|
ui.handle_click(2, MENU_BAR_H + 10, "left")
|
|
|
|
|
-- 0.4.0: handle_toolbar_click + handle_modal_click split out of
|
|
|
|
|
-- handle_click; call the toolbar-specific path here.
|
|
|
|
|
ui.handle_toolbar_click(2, MENU_BAR_H + 10, "left")
|
|
|
|
|
engine.test.equals(state.get_menu_open(), nil, "t1: outside click closes menu")
|
|
|
|
|
|
|
|
|
|
-- t2: toggle item flips bound state.
|
|
|
|
|
@@ -1549,7 +1674,12 @@ local function self_test()
|
|
|
|
|
"t3: cheatsheet modal id set")
|
|
|
|
|
engine.test.equals(state.get_menu_open(), nil,
|
|
|
|
|
"t3: menu closed when modal opens")
|
|
|
|
|
ui.handle_click(2, MENU_BAR_H + 10, "left")
|
|
|
|
|
-- 0.4.0: modal-priority is enforced by handle_modal_click (called
|
|
|
|
|
-- before panel.update() in M.update). Verify it returns true so
|
|
|
|
|
-- a click would be swallowed before reaching the toolbar widget.
|
|
|
|
|
local modal_consumed = ui.handle_modal_click(2, MENU_BAR_H + 10, "left")
|
|
|
|
|
engine.test.equals(modal_consumed, true,
|
|
|
|
|
"t3: modal swallows clicks while open")
|
|
|
|
|
engine.test.equals(state.get_modal_open(), "cheatsheet",
|
|
|
|
|
"t3: outside click does not close modal")
|
|
|
|
|
state.close_modal()
|
|
|
|
|
@@ -1772,24 +1902,48 @@ function update(ctx, dt)
|
|
|
|
|
actions.toggle_world_overlay()
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- Mouse clicks + drag: hit-test UI on press; if consumed we never
|
|
|
|
|
-- enter drag-mode for this stroke. Otherwise the press initiates a
|
|
|
|
|
-- drag-stroke whose paint/erase action repeats for every subsequent
|
|
|
|
|
-- snap_vertex (or cell) while the button stays down. set_vertex /
|
|
|
|
|
-- set_override are idempotent + no-op when state is unchanged so
|
|
|
|
|
-- repeating per-frame is cheap.
|
|
|
|
|
if engine.input.was_mouse_pressed(engine.input.MOUSE_LEFT) then
|
|
|
|
|
local consumed = ui.handle_click(mx, my, "left")
|
|
|
|
|
if not consumed then
|
|
|
|
|
-- 0.4.0: input dispatch is split between module-level + panel-lib:
|
|
|
|
|
-- 1. handle_modal_click: module-level, ALWAYS runs first (modals
|
|
|
|
|
-- are top-most overlays and not panel widgets).
|
|
|
|
|
-- 2. panel.update(dt): routes clicks to persistent widgets via
|
|
|
|
|
-- their input_block="self" hit-tests. Widget handle_input
|
|
|
|
|
-- delegates to ui.handle_toolbar_click / handle_layers_click /
|
|
|
|
|
-- handle_palette_click.
|
|
|
|
|
-- 3. Canvas drag-paint: gated on (no modal AND point not inside
|
|
|
|
|
-- any panel widget) to prevent widget-clicks bleeding through
|
|
|
|
|
-- to the map canvas underneath.
|
|
|
|
|
--
|
|
|
|
|
-- Modal precedence: when a modal is open, we still consume the
|
|
|
|
|
-- click here (so it doesn't reach panel widgets), but we DON'T
|
|
|
|
|
-- start a drag-paint. panel.update still runs but its dispatch
|
|
|
|
|
-- routes the press as normal (mostly a no-op for modals since the
|
|
|
|
|
-- modal isn't a widget).
|
|
|
|
|
local lmb_pressed = engine.input.was_mouse_pressed(engine.input.MOUSE_LEFT)
|
|
|
|
|
local rmb_pressed = engine.input.was_mouse_pressed(engine.input.MOUSE_RIGHT)
|
|
|
|
|
local modal_open = state.is_modal_open()
|
|
|
|
|
if lmb_pressed and modal_open then
|
|
|
|
|
ui.handle_modal_click(mx, my, "left")
|
|
|
|
|
end
|
|
|
|
|
if rmb_pressed and modal_open then
|
|
|
|
|
ui.handle_modal_click(mx, my, "right")
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- Panel-lib dispatch: reads engine.input mouse state directly,
|
|
|
|
|
-- emits click + wheel events to widget handle_input fns. Widgets
|
|
|
|
|
-- routed by tier (top → normal → hud) with input_block="self".
|
|
|
|
|
panel.update(dt)
|
|
|
|
|
|
|
|
|
|
-- Canvas drag-paint trigger. Only initiate when no modal open AND
|
|
|
|
|
-- the click did not land within any panel widget area. Drag-paint
|
|
|
|
|
-- still continues per-frame as long as the mouse button stays
|
|
|
|
|
-- down (no per-frame panel-bounds check — the originating press
|
|
|
|
|
-- already established the click as a canvas-stroke).
|
|
|
|
|
if lmb_pressed and not modal_open and not panel.point_in_any_panel(mx, my) then
|
|
|
|
|
state.set_drag_paint("paint")
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
if engine.input.was_mouse_pressed(engine.input.MOUSE_RIGHT) then
|
|
|
|
|
local consumed = ui.handle_click(mx, my, "right")
|
|
|
|
|
if not consumed then
|
|
|
|
|
if rmb_pressed and not modal_open and not panel.point_in_any_panel(mx, my) then
|
|
|
|
|
state.set_drag_paint("erase")
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- Per-frame drag-paint dispatch (Auto-Tile uses snap_vertex; Direct
|
|
|
|
|
-- uses mouse_cell). Stops as soon as the originating button releases.
|
|
|
|
|
@@ -1837,5 +1991,11 @@ function render(ctx)
|
|
|
|
|
ui.draw_world_overlay(m_size.w, m_size.h, t_size, state.get_snap_vertex())
|
|
|
|
|
ui.draw_debug_overlay(m_size.w, m_size.h, t_size)
|
|
|
|
|
camera.finish()
|
|
|
|
|
ui.draw() -- screen-space overlays after camera ends
|
|
|
|
|
-- 0.4.0: panel.render() draws the 3 persistent widgets (toolbar +
|
|
|
|
|
-- layers + palette) on top of the canvas. ui.draw() then draws the
|
|
|
|
|
-- remaining non-widget chrome (status chip + open modal overlay)
|
|
|
|
|
-- ON TOP of the widgets — modals are last so they appear above any
|
|
|
|
|
-- widget panel.
|
|
|
|
|
panel.render()
|
|
|
|
|
ui.draw()
|
|
|
|
|
end
|
|
|
|
|
|