Start implementing common widgets for lua screens.

develop
Alexander Gavrilov 2012-10-15 20:03:18 +04:00
parent 33bd8103de
commit abfe2754fb
6 changed files with 209 additions and 69 deletions

@ -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)

@ -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

@ -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

@ -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

@ -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

@ -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,