From 9213d3f91b9c3ab5248ccb8410b0f4f1b29bfd4e Mon Sep 17 00:00:00 2001 From: Calic Date: Mon, 1 Jun 2026 19:58:30 +0200 Subject: [PATCH] =?UTF-8?q?feat(map-editor):=20submenu=20support=20?= =?UTF-8?q?=E2=80=94=20View=20>=20Layers,=20Tools=20>=20Mode?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit One level of submenu nesting. View > Layers exposes all eight terrain-stack layers as toggles, sharing state with the right-side Layers panel. Tools > Mode mirrors the Mode-pill's auto/direct toggle with a Tab hotkey shown on Direct. Submenu opens by clicking the parent item; clicking elsewhere closes everything. Co-Authored-By: Claude Opus 4.7 (1M context) --- init.lua | 115 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 113 insertions(+), 2 deletions(-) diff --git a/init.lua b/init.lua index 81ac52e..fa4a20c 100644 --- a/init.lua +++ b/init.lua @@ -67,6 +67,7 @@ local state = (function() dirty = false, picker_open = false, -- atlas+tile picker expanded? (legacy 0.1.0) menu_open = nil, -- 0.3.0: nil | (e.g. "File") + submenu_open = nil, -- 0.3.0: label of the open submenu item within menu_open modal_open = nil, -- 0.3.0: nil | (single-stack) show_layers_panel = true, -- 0.3.0: Window > Layers Panel toggle show_palette = true, -- 0.3.0: Window > Palette toggle @@ -149,7 +150,14 @@ local state = (function() function M.set_menu_open(name) state.menu_open = name end function M.get_menu_open() return state.menu_open end - function M.close_menu() state.menu_open = nil end + function M.close_menu() + state.menu_open = nil + state.submenu_open = nil + end + + function M.set_submenu_open(label) state.submenu_open = label end + 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 @@ -200,6 +208,7 @@ local state = (function() state.snap_vertex = nil state.drag_paint = nil state.menu_open = nil + state.submenu_open = nil state.modal_open = nil state.show_layers_panel = true state.show_palette = true @@ -511,6 +520,42 @@ local ui = (function() { type = "submenu", label = "Mode", items = nil }, -- populated in T10 } + do + local layer_items = {} + for _, name in ipairs(LAYER_PANEL_ROWS) do + local layer_name = name + layer_items[#layer_items + 1] = { + type = "toggle", + label = name, + is_on = function() return state.is_layer_visible(layer_name) end, + fire = function() state.toggle_layer_visible(layer_name) end, + } + end + for _, it in ipairs(VIEW_ITEMS) do + if it.type == "submenu" and it.label == "Layers" then + it.items = layer_items + break + end + end + end + + do + local mode_items = { + { type = "toggle", label = "Auto-Tile", + is_on = function() return mode_is("auto-tile") end, + fire = function() actions.set_mode("auto-tile") end }, + { type = "toggle", label = "Direct", hotkey = "Tab", + is_on = function() return mode_is("direct") end, + fire = function() actions.set_mode("direct") end }, + } + for _, it in ipairs(TOOLS_ITEMS) do + if it.type == "submenu" and it.label == "Mode" then + it.items = mode_items + break + end + end + end + local WINDOW_ITEMS = { { type = "toggle", label = "Layers Panel", is_on = function() return state.is_layers_panel_shown() end, @@ -768,6 +813,37 @@ local ui = (function() local hovered = in_rect(mx, my, row.x, row.y, row.w, row.h) draw_dropdown_item(row, hovered) end + + -- Submenu render (one level deep) + local sub_label = state.get_submenu_open() + if sub_label then + for _, row in ipairs(dd.rows) do + local it = row.item + if it.type == "submenu" and it.label == sub_label and it.items then + local sub_w = dropdown_width_for(it.items) + local sub_x = dd.x + dd.w + local sub_y = row.y + local sub_h = 0 + for _, si in ipairs(it.items) do + sub_h = sub_h + ((si.type == "separator") and 6 or DROPDOWN_ITEM_H) + end + sub_h = sub_h + DROPDOWN_PAD_Y * 2 + engine.render.draw_rect(sub_x, sub_y, sub_w, sub_h, + rgba(COL_MODAL_BG, 0xFA)) + engine.render.draw_rect_lines(sub_x, sub_y, sub_w, sub_h, + rgba(COL_MODAL_BORDER, 0xFF)) + local sy = sub_y + DROPDOWN_PAD_Y + for _, si in ipairs(it.items) do + local row_h = (si.type == "separator") and 6 or DROPDOWN_ITEM_H + local sub_row = { item = si, x = sub_x, y = sy, w = sub_w, h = row_h } + local hovered = in_rect(mx, my, sub_row.x, sub_row.y, sub_row.w, sub_row.h) + draw_dropdown_item(sub_row, hovered) + sy = sy + row_h + end + break + end + end + end end local function fire_item(it) @@ -1215,7 +1291,9 @@ local ui = (function() if button == "left" then for _, row in ipairs(dd.rows) do if in_rect(mx, my, row.x, row.y, row.w, row.h) then - if row.item.type ~= "separator" then + if row.item.type == "submenu" then + state.set_submenu_open(row.item.label) + elseif row.item.type ~= "separator" then if fire_item(row.item) then state.close_menu() end @@ -1229,6 +1307,39 @@ local ui = (function() break end end + + -- Submenu items (one level deep) + local sub_label = state.get_submenu_open() + if sub_label then + for _, cat in ipairs(menu_bar_categories_layout()) do + if cat.name == open_name then + local dd2 = dropdown_layout(cat) + for _, row in ipairs(dd2.rows) do + local it = row.item + if it.type == "submenu" and it.label == sub_label and it.items then + local sub_x = dd2.x + dd2.w + local sub_y = row.y + local sy = sub_y + DROPDOWN_PAD_Y + for _, si in ipairs(it.items) do + local row_h = (si.type == "separator") and 6 or DROPDOWN_ITEM_H + if in_rect(mx, my, sub_x, sy, dropdown_width_for(it.items), row_h) then + if si.type ~= "separator" then + if fire_item(si) then + state.close_menu() + end + end + return true + end + sy = sy + row_h + end + break + end + end + break + end + end + end + -- Click outside the dropdown closes it (no return — fall through so -- the click can also hit other UI like canvas). state.close_menu()