diff --git a/init.lua b/init.lua index e7a94e9..cb20dbb 100644 --- a/init.lua +++ b/init.lua @@ -384,10 +384,15 @@ end --- 1. Context-menu (if open) always wins. --- 2. Iterate reverse z_tier (top → normal → hud), within tier --- reverse-open-order. For each open window: +--- - If input_block="all" AND click is OUTSIDE its bounds: swallow +--- the event IMMEDIATELY (modal owns the screen — do not let +--- lower-tier widgets claim outside-clicks behind it). --- - input_block="none": skip hit-test entirely (HUD passes through). --- - else: hit-test bounds; on hit route to widget. +--- - chromeless=true suppresses the close-button hit-test (the +--- widget owns its full bounds, including the top-right area). --- 3. If no window claimed the event AND any open window has ---- input_block="all": swallow (modal block). +--- input_block="all": swallow (modal block). --- 4. Else drop (no game-routing — module handles its own input). local function _dispatch_event(event) -- Context-menu always wins if open (consumes the next click). @@ -407,6 +412,17 @@ local function _dispatch_event(event) if win and win.open and win.opts.z_tier == tier_name then if win.opts.input_block == "all" then saw_modal = true + -- Modal swallow: if the click is outside this modal's + -- bounds, swallow immediately so lower-tier widgets + -- behind it cannot claim it. Inside-bounds clicks fall + -- through to normal routing below. + if event.kind == "click" and event.x and event.y then + local mb = resolve_bounds(win.opts.layout, sw, sh) + if not (event.x >= mb.x and event.x < mb.x + mb.w + and event.y >= mb.y and event.y < mb.y + mb.h) then + return + end + end end if win.opts.input_block ~= "none" then local b = resolve_bounds(win.opts.layout, sw, sh) @@ -416,7 +432,10 @@ local function _dispatch_event(event) and event.y >= b.y and event.y < b.y + b.h then -- Close-button intercept (top-right X): left-click -- closes the window before forwarding to the widget. - if event.button == "left" then + -- Chromeless widgets have no chrome (no X), so we + -- must NOT hit-test the phantom close-zone — the + -- widget owns its full bounds. + if event.button == "left" and not win.opts.chromeless then local cb = win._close_button_rect if not cb then cb = close_button_rect(b.x, b.y, b.w, @@ -518,6 +537,12 @@ end --- no-op when it's focused (modder must close(id) explicit). --- input_block = "none" | "self" | "all" — defaults per tier: --- hud → "none", normal → "self", top → "all". +--- chromeless = false (default) | true — when true, suppresses the +--- panel-lib chrome (title-bar, border, X close-button). +--- Widget's render(ctx) draws into the full bounds +--- without panel-lib decoration. Useful for HUD-style +--- widgets that have their own visual design (status +--- bars, toolbars with their own theme). function M.register(widget_id, widget_def, opts) if type(widget_id) ~= "string" then error("panel.register: widget_id must be a string, got " .. type(widget_id))