diff --git a/library/lua/gui.lua b/library/lua/gui.lua index 4f98e1bde..ea2a79da8 100644 --- a/library/lua/gui.lua +++ b/library/lua/gui.lua @@ -48,16 +48,74 @@ end function mkdims_wh(x1,y1,w,h) return { x1=x1, y1=y1, x2=x1+w-1, y2=y1+h-1, width=w, height=h } end -function inset(rect,dx1,dy1,dx2,dy2) - return mkdims_xy( - rect.x1+dx1, rect.y1+(dy1 or dx1), - rect.x2-(dx2 or dx1), rect.y2-(dy2 or dy1 or dx1) - ) -end function is_in_rect(rect,x,y) return x and y and x >= rect.x1 and x <= rect.x2 and y >= rect.y1 and y <= rect.y2 end +local function align_coord(gap,align,lv,rv) + if gap <= 0 then + return 0 + end + if not align then + if rv and not lv then + align = 1.0 + elseif lv and not rv then + align = 0.0 + else + align = 0.5 + end + end + return math.floor(gap*align) +end + +function compute_frame_rect(wavail,havail,spec,xgap,ygap) + if not spec then + return mkdims_wh(0,0,wavail,havail) + end + + local sw = wavail - (spec.l or 0) - (spec.r or 0) + local sh = havail - (spec.t or 0) - (spec.b or 0) + local rqw = math.min(sw, (spec.w or sw)+xgap) + local rqh = math.min(sh, (spec.h or sh)+ygap) + local ax = align_coord(sw - rqw, spec.xalign, spec.l, spec.r) + local ay = align_coord(sh - rqh, spec.yalign, spec.t, spec.b) + + local rect = mkdims_wh((spec.l or 0) + ax, (spec.t or 0) + ay, rqw, rqh) + rect.wgap = sw - rqw + rect.hgap = sh - rqh + return rect +end + +local function parse_inset(inset) + local l,r,t,b + if type(inset) == 'table' then + l,r = inset.l or inset.x, inset.r or inset.x + t,b = inset.t or inset.y, inset.b or inset.y + else + l = inset or 0 + t,r,b = l,l,l + end + return l,r,t,b +end + +function inset_frame(rect, inset, gap) + gap = gap or 0 + local l,t,r,b = parse_inset(inset) + return mkdims_xy(rect.x1+l+gap, rect.y1+t+gap, rect.x2-r-gap, rect.y2-b-gap) +end + +function compute_frame_body(wavail, havail, spec, inset, gap) + gap = gap or 0 + local l,t,r,b = parse_inset(inset) + local rect = compute_frame_rect(wavail, havail, spec, gap*2+l+r, gap*2+t+b) + local body = mkdims_xy(rect.x1+l+gap, rect.y1+t+gap, rect.x2-r-gap, rect.y2-b-gap) + return rect, body +end + +function blink_visible(delay) + return math.floor(dfhack.getTickCount()/delay) % 2 == 0 +end + local function to_pen(default, pen, bg, bold) if pen == nil then return default or {} @@ -296,13 +354,35 @@ View = defclass(View) View.ATTRS { active = true, - hidden = false, + visible = true, + view_id = DEFAULT_NIL, } function View:init(args) self.subviews = {} end +function View:addviews(list) + local sv = self.subviews + + for _,obj in ipairs(list) do + table.insert(sv, obj) + + local id = obj.view_id + if id and type(id) ~= 'number' and sv[id] == nil then + sv[id] = obj + end + end + + for _,dir in ipairs(list) do + for id,obj in pairs(dir) do + if id and type(id) ~= 'number' and sv[id] == nil then + sv[id] = obj + end + end + end +end + function View:getWindowSize() local rect = self.frame_body return rect.width, rect.height @@ -320,6 +400,12 @@ function View:computeFrame(parent_rect) return mkdims_wh(0,0,parent_rect.width,parent_rect.height) end +function View:updateSubviewLayout(frame_body) + for _,child in ipairs(self.subviews) do + child:updateLayout(frame_body) + end +end + function View:updateLayout(parent_rect) if not parent_rect then parent_rect = self.frame_parent_rect @@ -332,16 +418,14 @@ function View:updateLayout(parent_rect) self.frame_rect = frame_rect self.frame_body = parent_rect:viewport(body_rect or frame_rect) - for _,child in ipairs(self.subviews) do - child:updateLayout(frame_body) - end + self:updateSubviewLayout(self.frame_body) - self:invoke_after('postUpdateLayout',frame_body) + self:invoke_after('postUpdateLayout', self.frame_body) end function View:renderSubviews(dc) for _,child in ipairs(self.subviews) do - if not child.hidden then + if child.visible then child:render(dc) end end @@ -512,46 +596,28 @@ FramedScreen.ATTRS{ frame_width = DEFAULT_NIL, frame_height = DEFAULT_NIL, frame_inset = 0, + frame_background = CLEAR_PEN, } -local function hint_coord(gap,hint) - if hint and hint > 0 then - return math.min(hint,gap) - elseif hint and hint < 0 then - return math.max(0,gap-hint) - else - return math.floor(gap/2) - end -end - function FramedScreen:getWantedFrameSize() return self.frame_width, self.frame_height end function FramedScreen:computeFrame(parent_rect) local sw, sh = parent_rect.width, parent_rect.height - local ins = self.frame_inset - local thickness = 2+ins*2 - local iw, ih = sw-thickness, sh-thickness local fw, fh = self:getWantedFrameSize() - local width = math.min(fw or iw, iw) - local height = math.min(fh or ih, ih) - local gw, gh = iw-width, ih-height - local x1, y1 = hint_coord(gw,self.frame_xhint), hint_coord(gh,self.frame_yhint) - self.frame_opaque = (gw == 0 and gh == 0) - return mkdims_wh(x1,y1,width+thickness,height+thickness), - mkdims_wh(x1+1+ins,y1+1+ins,width,height) + return compute_frame_body(sw, sh, { w = fw, h = fh }, self.frame_inset, 1) end function FramedScreen:render(dc) local rect = self.frame_rect local x1,y1,x2,y2 = rect.x1, rect.y1, rect.x2, rect.y2 - if self.frame_opaque then + if rect.wgap <= 0 and rect.hgap <= 0 then dc:clear() else self:renderParent() - dc:fill(rect, CLEAR_PEN) + dc:fill(rect, self.frame_background) end paint_frame(x1,y1,x2,y2,self.frame_style,self.frame_title) diff --git a/library/lua/gui/dialogs.lua b/library/lua/gui/dialogs.lua index 75f7349d1..10a43c22e 100644 --- a/library/lua/gui/dialogs.lua +++ b/library/lua/gui/dialogs.lua @@ -3,6 +3,7 @@ local _ENV = mkmodule('gui.dialogs') local gui = require('gui') +local widgets = require('gui.widgets') local utils = require('utils') local dscreen = dfhack.screen @@ -101,8 +102,6 @@ InputBox = defclass(InputBox, MessageBox) InputBox.focus_path = 'InputBox' InputBox.ATTRS{ - input = '', - input_pen = DEFAULT_NIL, on_input = DEFAULT_NIL, } @@ -110,46 +109,36 @@ function InputBox:preinit(info) info.on_accept = nil end +function InputBox:init(info) + self:addviews{ + widgets.EditField{ + view_id = 'edit', + text = info.input, + text_pen = info.input_pen, + frame = { l = 1, r = 1, h = 1 }, + } + } +end + function InputBox:getWantedFrameSize() local mw, mh = InputBox.super.getWantedFrameSize(self) + self.subviews.edit.frame.t = mh return mw, mh+2 end -function InputBox:onRenderBody(dc) - InputBox.super.onRenderBody(self, dc) - - dc:newline(1) - dc:pen(self.input_pen or COLOR_LIGHTCYAN) - dc:fill(1,dc:cursorY(),dc.width-2,dc:cursorY()) - - local cursor = '_' - if math.floor(dfhack.getTickCount()/300) % 2 == 0 then - cursor = ' ' - end - local txt = self.input .. cursor - if #txt > dc.width-2 then - txt = string.char(27)..string.sub(txt, #txt-dc.width+4) - end - dc:string(txt) -end - function InputBox:onInput(keys) if keys.SELECT then self:dismiss() if self.on_input then - self.on_input(self.input) + self.on_input(self.subviews.edit.text) end elseif keys.LEAVESCREEN then self:dismiss() if self.on_cancel then self.on_cancel() end - elseif keys._STRING then - if keys._STRING == 0 then - self.input = string.sub(self.input, 1, #self.input-1) - else - self.input = self.input .. string.char(keys._STRING) - end + else + self:inputToSubviews(keys) end end diff --git a/library/lua/gui/dwarfmode.lua b/library/lua/gui/dwarfmode.lua index 4056fbfcc..0c1b0599c 100644 --- a/library/lua/gui/dwarfmode.lua +++ b/library/lua/gui/dwarfmode.lua @@ -354,11 +354,12 @@ end MenuOverlay = defclass(MenuOverlay, DwarfOverlay) MenuOverlay.ATTRS { - frame_inset = 0 + frame_inset = 0, + frame_background = CLEAR_PEN, } function MenuOverlay:computeFrame(parent_rect) - return self.df_layout.menu, gui.inset(self.df_layout.menu, self.frame_inset) + return self.df_layout.menu, gui.inset_frame(self.df_layout.menu, self.frame_inset) end function MenuOverlay:onAboutToShow(below) @@ -381,6 +382,10 @@ function MenuOverlay:render(dc) menu.x1+1, menu.y2+1, "DFHack" ) + if self.frame_background then + dc:fill(menu, self.frame_background) + end + MenuOverlay.super.render(self, dc) end end diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua new file mode 100644 index 000000000..010ea5510 --- /dev/null +++ b/library/lua/gui/widgets.lua @@ -0,0 +1,80 @@ +-- Simple widgets for screens + +local _ENV = mkmodule('gui.widgets') + +local gui = require('gui') +local utils = require('utils') + +local dscreen = dfhack.screen + +------------ +-- Widget -- +------------ + +Widget = defclass(Widget, gui.View) + +Widget.ATTRS { + frame = DEFAULT_NIL, + frame_inset = DEFAULT_NIL, + frame_background = DEFAULT_NIL, +} + +function Widget:computeFrame(parent_rect) + local sw, sh = parent_rect.width, parent_rect.height + return gui.compute_frame_body(sw, sh, self.frame, self.frame_inset) +end + +function Widget:render(dc) + if self.frame_background then + dc:fill(self.frame_rect, self.frame_background) + end + + Widget.super.render(self, dc) +end + +---------------- +-- Edit field -- +---------------- + +EditField = defclass(EditField, Widget) + +EditField.ATTRS{ + text = '', + text_pen = DEFAULT_NIL, + on_char = DEFAULT_NIL, + on_change = DEFAULT_NIL, +} + +function EditField:onRenderBody(dc) + dc:pen(self.text_pen or COLOR_LIGHTCYAN):fill(0,0,dc.width-1,0) + + local cursor = '_' + if not self.active or gui.blink_visible(300) then + cursor = ' ' + end + local txt = self.text .. cursor + if #txt > dc.width then + txt = string.char(27)..string.sub(txt, #txt-dc.width+2) + end + dc:string(txt) +end + +function EditField:onInput(keys) + if keys._STRING then + local old = self.text + if keys._STRING == 0 then + self.text = string.sub(old, 1, #old-1) + else + local cv = string.char(keys._STRING) + if not self.on_char or self.on_char(cv, old) then + self.text = old .. cv + end + end + if self.on_change and self.text ~= old then + self.on_change(self.text, old) + end + return true + end +end + +return _ENV diff --git a/scripts/gui/guide-path.lua b/scripts/gui/guide-path.lua index 4d32a99c2..1546150b7 100644 --- a/scripts/gui/guide-path.lua +++ b/scripts/gui/guide-path.lua @@ -69,17 +69,13 @@ local function get_path_point(gpath,i) return xyz2pos(gpath.x[i], gpath.y[i], gpath.z[i]) end -local function blink_visible(delay) - return math.floor(dfhack.getTickCount()/delay) % 2 == 0 -end - function GuidePathUI:renderPath(cursor) local gpath = self.order.guide_path local startp = self.stop.pos local endp = self.next_stop.pos local vp = self:getViewport() local dc = gui.Painter.new(self.df_layout.map) - local visible = blink_visible(500) + local visible = gui.blink_visible(500) if visible then paintMapTile(dc, vp, cursor, endp, '+', COLOR_LIGHTGREEN) @@ -123,7 +119,7 @@ function GuidePathUI:renderPath(cursor) end end - if blink_visible(120) then + if gui.blink_visible(120) then paintMapTile(dc, vp, cursor, startp, 240, COLOR_LIGHTGREEN, COLOR_GREEN) end diff --git a/scripts/gui/power-meter.lua b/scripts/gui/power-meter.lua index 81b56e29f..ac30c9273 100644 --- a/scripts/gui/power-meter.lua +++ b/scripts/gui/power-meter.lua @@ -12,6 +12,10 @@ PowerMeter = defclass(PowerMeter, guidm.MenuOverlay) PowerMeter.focus_path = 'power-meter' +PowerMeter.ATTRS { + frame_background = false +} + function PowerMeter:init() self:assign{ min_power = 0, max_power = -1, invert = false,