Skip to content

Instantly share code, notes, and snippets.

@LouisGameDev
Created July 3, 2025 20:45
Show Gist options
  • Save LouisGameDev/61966ce089556a8bf5de2f87be37036a to your computer and use it in GitHub Desktop.
Save LouisGameDev/61966ce089556a8bf5de2f87be37036a to your computer and use it in GitHub Desktop.
--@module = true
local gui = require('gui')
local widgets = require('gui.widgets')
local INVISIBLE_FRAME = {
frame_pen=gui.CLEAR_PEN,
signature_pen=false,
}
-- MAIN BUG TRIGGERED HERE
-----------------------------------------
BugDemoPanel = defclass(BugDemoPanel, widgets.Panel)
BugDemoPanel.ATTRS {
--frame_style = INVISIBLE_FRAME
-- ^ This is the key part that triggers the bug.
-- if style is nil, it will be nil and panel.lua::Panel_end_drag will never be called
-- https://github.com/DFHack/dfhack/blob/e3165854e80411eec584c74949c73000f2aa0dcb/library/lua/gui/widgets/containers/panel.lua#L497C1-L498C1
-- even if user intends to finish drag
}
-----------------------------------------
function BugDemoPanel:onInput(keys)
local key_info = {}
for key, value in pairs(keys) do
table.insert(key_info, string.format("%s=%s", tostring(key), tostring(value)))
end
table.sort(key_info)
return BugDemoPanel.super.onInput(self, keys)
end
BugDemoWindow = defclass(BugDemoWindow, widgets.Window)
BugDemoWindow.ATTRS {
view_id = 'bugdemo_window',
frame_title = 'Frame Style Bug Demo',
resizable = true,
resize_min = {w = 54, h = 20},
frame_inset = {l = 0, r = 0, t = 0, b = 0}
}
BugDemoScreen = defclass(BugDemoScreen, gui.ZScreen)
BugDemoScreen.ATTRS {
view_id = 'bugdemo_screen',
focus_path = 'bugdemo',
pass_movement_keys = true,
defocusable = true
}
function BugDemoWindow:init()
self.resizing_panels = false
self:addviews{
BugDemoPanel {
view_id = 'category_panel',
frame = {l = 0, w = 22, t = 0, b = 1},
frame_inset = {l = 1, t = 0, b = 1, r = 1},
resizable = true,
resize_anchors = {l = false, t = false, r = true, b = true},
resize_min = {w = 24},
on_resize_begin = self:callback('onPanelResizeBegin'),
on_resize_end = self:callback('onPanelResizeEnd'),
subviews = {
widgets.Label {
frame = {t = 0, l = 0},
text = 'category_panel',
text_pen = COLOR_YELLOW
}
}
},
widgets.Divider {
view_id = 'divider1',
frame = {l = 30, t = 0, b = 2, w = 1},
interior_b = true,
frame_style_t = false
},
widgets.Panel {
view_id = 'item_panel',
frame = {t = 1, b = 3, l = 25, r = 0},
resize_min = {w = 30, h = 10},
frame_inset = {l = 1, r = 0},
subviews = {
widgets.Label {
view_id = 'item_header',
frame = {t = 0, l = 0, r = 0, h = 1},
text = 'item_panel',
text_pen = COLOR_YELLOW
}
}
},
widgets.HelpButton {
command = "gui/stockmonitor",
frame = {r = 0, t = 1}
},
widgets.Divider {
frame = {l = 0, r = 0, b = 2, h = 1},
frame_style_l = false,
frame_style_r = false,
interior_l = true
},
widgets.Panel {
frame = {l = 0, r = 0, b = 1, h = 1},
frame_inset = {l = 1, r = 1, t = 0, w = 100},
subviews = {
widgets.HotkeyLabel {
frame = {l = 0},
key = 'CUSTOM_CTRL_R',
label = 'Button A',
auto_width = true,
on_activate = function()
self:testResize()
end
},
widgets.HotkeyLabel {
frame = {l = 25},
key = 'CUSTOM_CTRL_O',
label = 'Button B',
auto_width = true,
on_activate = function()
end
}
}
}
}
end
function BugDemoWindow:testResize()
local category_panel = self.subviews.category_panel
if category_panel then
category_panel.frame.w = (category_panel.frame.w == 22) and 32 or 22
self:ensurePanelsRelSize()
self:updateLayout()
end
end
function BugDemoWindow:onPanelResizeBegin()
self.resizing_panels = true
end
function BugDemoWindow:onPanelResizeEnd()
self.resizing_panels = false
self:ensurePanelsRelSize()
self:updateLayout()
end
function BugDemoWindow:ensurePanelsRelSize()
local category_panel = self.subviews.category_panel
local item_panel = self.subviews.item_panel
local divider1 = self.subviews.divider1
if not category_panel or not item_panel or not divider1 then return end
local main_width = self.frame.w or 100
local min_item_width = 30
category_panel.frame.w =
math.min(math.max(category_panel.frame.w, 24),
main_width - min_item_width)
local cat_width = category_panel.frame.w
item_panel.frame.l = category_panel.visible and cat_width or 1
divider1.frame.l = item_panel.frame.l - 1
end
function BugDemoWindow:onRenderBody(dc)
if self.resizing_panels then
self:ensurePanelsRelSize()
self:updateLayout()
end
return BugDemoWindow.super.onRenderBody(self, dc)
end
function BugDemoWindow:preUpdateLayout()
self:ensurePanelsRelSize()
end
function BugDemoWindow:onInput(keys)
local key_info = {}
for key, value in pairs(keys) do
table.insert(key_info, string.format("%s=%s", tostring(key), tostring(value)))
end
table.sort(key_info)
return BugDemoWindow.super.onInput(self, keys)
end
function BugDemoScreen:init()
local optimal_size = self:calculateOptimalFrameSize()
self:addviews{BugDemoWindow {frame = optimal_size}}
end
function BugDemoScreen:calculateOptimalFrameSize()
local base_width = 100
local base_height = 35
local optimal_width = math.max(base_width, 90)
local optimal_height = math.max(base_height, 25)
return {w = optimal_width, h = optimal_height}
end
function BugDemoScreen:onDismiss()
bug_demo_screen = nil
end
bug_demo_screen = bug_demo_screen or nil
if dfhack_flags.module then
return
end
-- Main entry point
-- if not dfhack.isWorldLoaded() then
-- qerror("This script requires a loaded world")
-- end
-- Check for existing instance
if bug_demo_screen and not bug_demo_screen._native then
bug_demo_screen = nil
end
if bug_demo_screen then
local success, is_dismissed = pcall(function()
return bug_demo_screen:isDismissed()
end)
if not success or is_dismissed then
bug_demo_screen = nil
else
return
end
end
-- Create and show new instance
bug_demo_screen = BugDemoScreen{}:show()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment