Add a Painter class for lua viewscreens, and extract other utilities.

Painter clips to an arbitrary rectangle window, and
tracks current cursor and color state.
develop
Alexander Gavrilov 2012-08-21 11:35:39 +04:00
parent be7bce1541
commit 451e965936
6 changed files with 285 additions and 79 deletions

@ -194,16 +194,28 @@ static bool get_int_field(lua_State *L, T *pf, int idx, const char *name, int de
return !nil;
}
static bool get_char_field(lua_State *L, char *pf, int idx, const char *name, char defval)
{
lua_getfield(L, idx, name);
if (lua_type(L, -1) == LUA_TSTRING)
{
*pf = lua_tostring(L, -1)[0];
lua_pop(L, 1);
return true;
}
else
{
lua_pop(L, 1);
return get_int_field(L, pf, idx, name, defval);
}
}
static void decode_pen(lua_State *L, Pen &pen, int idx)
{
idx = lua_absindex(L, idx);
lua_getfield(L, idx, "ch");
if (lua_isstring(L, -1))
pen.ch = lua_tostring(L, -1)[0];
else
get_int_field(L, &pen.ch, idx, "ch", 0);
lua_pop(L, 1);
get_char_field(L, &pen.ch, idx, "ch", 0);
get_int_field(L, &pen.fg, idx, "fg", 7);
get_int_field(L, &pen.bg, idx, "bg", 0);
@ -1104,7 +1116,12 @@ static int screen_paintTile(lua_State *L)
int x = luaL_checkint(L, 2);
int y = luaL_checkint(L, 3);
if (lua_gettop(L) >= 4 && !lua_isnil(L, 4))
pen.ch = luaL_checkint(L, 4);
{
if (lua_type(L, 4) == LUA_TSTRING)
pen.ch = lua_tostring(L, 4)[0];
else
pen.ch = luaL_checkint(L, 4);
}
if (lua_gettop(L) >= 5 && !lua_isnil(L, 5))
pen.tile = luaL_checkint(L, 5);
lua_pushboolean(L, Screen::paintTile(pen, x, y));

@ -40,12 +40,153 @@ function simulateInput(screen,...)
dscreen._doSimulateInput(screen, keys)
end
Screen = defclass(Screen, dfhack.screen)
function mkdims_xy(x1,y1,x2,y2)
return { x1=x1, y1=y1, x2=x2, y2=y2, width=x2-x1+1, height=y2-y1+1 }
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,
rect.x2-(dx2 or dx1), rect.y2-(dy2 or dy1)
)
end
function to_pen(pen, default)
if pen == nil then
return default or {}
elseif type(pen) ~= 'table' then
return {fg=pen}
else
return pen
end
end
----------------------------
-- Clipped painter object --
----------------------------
Painter = defclass(Painter, nil)
function Painter.new(rect, pen)
rect = rect or {}
local sw, sh = dscreen.getWindowSize()
local self = mkdims_xy(
math.max(rect.x1 or 0, 0),
math.max(rect.y1 or 0, 0),
math.min(rect.x2 or sw-1, sw-1),
math.min(rect.y2 or sh-1, sh-1)
)
self.cur_pen = to_pen(pen or COLOR_GREY)
self.x = self.x1
self.y = self.y1
return mkinstance(Painter, self)
end
function Screen.new(attrs)
return mkinstance(Screen, attrs)
function Painter:setRect(x1,y1,x2,y2)
local rect = mkdims_xy(x1,y1,x2,y2)
self.x1 = rect.x1
self.y1 = rect.y1
self.x2 = rect.x2
self.y2 = rect.y2
self.width = rect.width
self.height = rect.height
end
function Painter:isValidPos()
return self.x >= self.x1 and self.x <= self.x2 and self.y >= self.y1 and self.y <= self.y2
end
function Painter:inset(dx1,dy1,dx2,dy1)
self:setRect(
self.x1+dx1, self.y1+dy1,
self.x2-(dx2 or dx1), self.y2-(dy2 or dy1)
)
return self
end
function Painter:seek(x,y)
if x then self.x = self.x1 + x end
if y then self.y = self.y1 + y end
return self
end
function Painter:advance(dx,dy)
if dx then self.x = self.x + dx end
if dy then self.y = self.y + dy end
return self
end
function Painter:newline(dx)
self.y = self.y + 1
self.x = self.x1 + (dx or 0)
return self
end
function Painter:pen(pen)
self.cur_pen = to_pen(pen, self.cur_pen)
return self
end
function Painter:clear()
dscreen.fillRect(CLEAR_PEN, self.x1, self.y1, self.x2, self.y2)
return self
end
function Painter:fill(x1,y1,x2,y2,pen)
if type(x1) == 'table' then
x1, y1, x2, y2, pen = x1.x1, x1.y1, x1.x2, x1.y2, x2
end
x1 = math.max(x1,self.x1)
y1 = math.max(y1,self.y1)
x2 = math.min(x2,self.x2)
y2 = math.min(y2,self.y2)
dscreen.fillRect(to_pen(pen, self.cur_pen),x1,y1,x2,y2)
return self
end
function Painter:char(char,pen)
if self:isValidPos() then
dscreen.paintTile(to_pen(pen, self.cur_pen), self.x, self.y, char)
end
return self:advance(1, nil)
end
function Painter:tile(char,tile,pen)
if self:isValidPos() then
dscreen.paintTile(to_pen(pen, self.cur_pen), self.x, self.y, char, tile)
end
return self:advance(1, nil)
end
function Painter:string(text,pen)
if self.y >= self.y1 and self.y <= self.y2 then
local dx = 0
if self.x < self.x1 then
dx = self.x1 - self.x
end
local len = #text
if self.x + len - 1 > self.x2 then
len = self.x2 - self.x + 1
end
if len > dx then
dscreen.paintString(
to_pen(pen, self.cur_pen),
self.x+dx, self.y,
string.sub(text,dx+1,len)
)
end
end
return self:advance(#text, nil)
end
------------------------
-- Base screen object --
------------------------
Screen = defclass(Screen, dfhack.screen)
function Screen:isShown()
return self._native ~= nil
end
@ -68,6 +209,22 @@ function Screen:inputToParent(...)
end
end
function Screen:show()
if self._native then
error("This screen is already on display")
end
self:onAboutToShow()
if dscreen.show(self) then
self:onShown()
end
end
function Screen:onAboutToShow()
end
function Screen:onShown()
end
function paint_border(x1,y1,x2,y2,color,title)
local pen = { ch = ' ', fg = COLOR_BLACK, bg = color }
dscreen.fillRect(pen,x1,y1,x2,y1)
@ -92,13 +249,6 @@ local function hint_coord(gap,hint)
end
end
function mkdims_xy(x1,y1,x2,y2)
return { x1=x1, y1=y1, x2=x2, y2=y2, width=x2-x1+1, height=y2-y1+1 }
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 Screen:renderFrame(color,title,width,height,xhint,yhint)
local sw, sh = dscreen.getWindowSize()
local iw, ih = sw-2, sh-2
@ -116,7 +266,8 @@ function Screen:renderFrame(color,title,width,height,xhint,yhint)
end
paint_border(x1,y1,x2,y2,color,title)
return x1+1,y1+1,width,height,x2-1,y2-1
return Painter.new(mkdims_wh(x1+1,y1+1,width,height))
end
return _ENV

@ -39,6 +39,14 @@ function getPanelLayout()
return rv
end
function getCursorPos()
return copyall(df.global.cursor)
end
function setCursorPos(cursor)
df.global.cursor = cursor
end
function getViewportPos()
return {
x = df.global.window_x,
@ -47,39 +55,59 @@ function getViewportPos()
}
end
function getCursorPos()
return copyall(df.global.cursor)
end
function setCursorPos(cursor)
df.global.cursor = cursor
function clipViewport(view, layout)
local map = df.global.world.map
layout = layout or getPanelLayout()
return {
x = math.max(0, math.min(view.x, map.x_count-layout.map.width)),
y = math.max(0, math.min(view.y, map.y_count-layout.map.height)),
z = math.max(0, math.min(view.z, map.z_count-1))
}
end
function setViewportPos(layout,view)
function setViewportPos(view, layout)
local map = df.global.world.map
df.global.window_x = math.max(0, math.min(view.x, map.x_count-layout.map.width))
df.global.window_y = math.max(0, math.min(view.y, map.y_count-layout.map.height))
df.global.window_z = math.max(0, math.min(view.z, map.z_count-1))
return getViewportPos()
layout = layout or getPanelLayout()
local vp = clipViewport(view, layout)
df.global.window_x = vp.x
df.global.window_y = vp.y
df.global.window_z = vp.z
return vp
end
function centerViewportOn(layout,target)
local mapsz = layout.map
function centerViewportOn(target, layout)
layout = layout or getPanelLayout()
local view = xyz2pos(
target.x-math.floor(layout.map.width/2),
target.y-math.floor(layout.map.height/2),
target.z
)
return setViewportPos(layout, view)
return setViewportPos(view, layout)
end
function isInViewport(layout,view,target,gap)
gap = gap or 0
return target.x-gap >= view.x and target.x+gap < view.x+layout.map.width
and target.y-gap >= view.y and target.y+gap < view.y+layout.map.height
local map = df.global.world.map
return math.max(target.x-gap,0) >= view.x
and math.min(target.x+gap,map.x_count-1) < view.x+layout.map.width
and math.max(target.y-gap,0) >= view.y
and math.min(target.y+gap,map.y_count-1) < view.y+layout.map.height
and target.z == view.z
end
function revealInViewport(target,gap,view,layout)
layout = layout or getPanelLayout()
if not isInViewport(layout, getViewportPos(), target, gap) then
if view and isInViewport(layout, view, target, gap) then
return setViewportPos(view, layout)
else
return centerViewportOn(target, layout)
end
end
end
DwarfOverlay = defclass(DwarfOverlay, gui.Screen)
function DwarfOverlay:updateLayout()
@ -106,14 +134,26 @@ function DwarfOverlay:propagateMoveKeys(keys)
end
function DwarfOverlay:onIdle()
-- Dwarfmode constantly needs repainting
dscreen.invalidate()
end
function DwarfOverlay:clearMenu()
function DwarfOverlay:onAboutToShow()
if not df.viewscreen_dwarfmodest:is_instance(dfhack.gui.getCurViewscreen()) then
error("This screen requires the main dwarfmode view")
end
end
MenuOverlay = defclass(MenuOverlay, DwarfOverlay)
function MenuOverlay:onRender()
self:renderParent()
self:updateLayout()
local menu = self.df_layout.menu
if not menu then return nil end
dscreen.fillRect(gui.CLEAR_PEN,menu.x1,menu.y1,menu.x2,menu.y2)
return menu.x1,menu.y1,menu.width,menu.height
if menu then
self:onRenderBody(gui.Painter.new(menu))
end
end
return _ENV

@ -361,6 +361,18 @@ function insert_or_update(vector,item,field,cmp)
return added,cur,pos
end
-- Calls a method with a string temporary
function call_with_string(obj,methodname,...)
return dfhack.with_temp_object(
df.new "string",
function(str,obj,methodname,...)
obj[methodname](obj,str,...)
return str.value
end,
obj,methodname,...
)
end
-- Ask a yes-no question
function prompt_yes_no(msg,default)
local prompt = msg

@ -2,11 +2,11 @@
local gui = require 'gui'
local screen = gui.Screen.new({
local screen = mkinstance(gui.Screen, {
onRender = function(self)
local text = 'Woohoo, lua viewscreen :)'
local x,y,w,h = self:renderFrame(COLOR_GREY,'Hello World',#text+6,3)
self.paintString({fg=COLOR_LIGHTGREEN},x+3,y+1,text)
local dc = self:renderFrame(COLOR_GREY,'Hello World',#text+6,3)
dc:seek(3,1):string(text, {fg=COLOR_LIGHTGREEN})
end,
onInput = function(self,keys)
if keys and (keys.LEAVESCREEN or keys.SELECT) then

@ -1,16 +1,11 @@
-- Shows mechanisms linked to the current building.
local utils = require 'utils'
local gui = require 'gui'
local guidm = require 'gui.dwarfmode'
function getBuildingName(building)
return dfhack.with_temp_object(
df.new "string",
function(str)
building:getName(str)
return str.value
end
)
return utils.call_with_string(building, 'getName')
end
function listMechanismLinks(building)
@ -45,53 +40,51 @@ function listMechanismLinks(building)
return lst
end
MechanismList = defclass(MechanismList, guidm.DwarfOverlay)
MechanismList = defclass(MechanismList, guidm.MenuOverlay)
MechanismList.focus_path = 'mechanisms'
function MechanismList.new(building)
local links = listMechanismLinks(building)
local self = {
old_cursor = guidm.getCursorPos(),
links = listMechanismLinks(building),
links = links,
selected = 1
}
self.links[1].viewport = guidm.getViewportPos()
self.links[1].cursor = guidm.getCursorPos()
links[1].viewport = guidm.getViewportPos()
links[1].cursor = guidm.getCursorPos()
if #links <= 1 then
links[1].mode = 'none'
end
return mkinstance(MechanismList, self)
end
local colors = {
self = COLOR_CYAN, trigger = COLOR_MAGENTA, target = COLOR_GREEN
self = COLOR_CYAN, none = COLOR_CYAN,
trigger = COLOR_GREEN, target = COLOR_GREEN
}
local icons = {
self = 128, trigger = 17, target = 16
self = 128, none = 63, trigger = 27, target = 26
}
function MechanismList:onRender()
self:renderParent()
self:updateLayout()
local x,y,w,h = self:clearMenu()
self.paintString({fg=COLOR_WHITE},x+1,y+1,"Mechanism Links")
function MechanismList:onRenderBody(dc)
dc:clear()
dc:seek(1,1):string("Mechanism Links", COLOR_WHITE):newline()
for i,v in ipairs(self.links) do
local pen = { fg=colors[v.mode], bold = (i == self.selected) }
self.paintTile(pen, x+2, y+2+i, icons[v.mode])
self.paintString(pen, x+4, y+2+i, v.name)
dc:newline(1):pen(pen):char(icons[v.mode])
dc:advance(1):string(v.name)
end
local nlinks = #self.links
local line = y+4+nlinks
if nlinks <= 1 then
self.paintString({fg=COLOR_LIGHTRED},x+1,line,"This building has no links")
line = line+2
dc:newline():newline(1):string("This building has no links", COLOR_LIGHTRED)
end
self.paintString({fg=COLOR_LIGHTGREEN},x+1,line,"Esc")
self.paintString({fg=COLOR_WHITE},x+4,line,": Back,")
self.paintString({fg=COLOR_LIGHTGREEN},x+12,line,"Enter")
self.paintString({fg=COLOR_WHITE},x+17,line,": Switch")
dc:newline():newline(1):pen(COLOR_WHITE)
dc:string("Esc", COLOR_LIGHTGREEN):string(": Back, ")
dc:string("Enter", COLOR_LIGHTGREEN):string(": Switch")
end
function MechanismList:zoomToLink(link)
@ -105,14 +98,7 @@ function MechanismList:zoomToLink(link)
end
guidm.setCursorPos(cursor)
if not guidm.isInViewport(self.df_layout, self.df_viewport, cursor, 5) then
local vp = link.viewport
if vp then
guidm.setViewportPos(self.df_layout,vp)
else
guidm.centerViewportOn(self.df_layout,cursor)
end
end
guidm.revealInViewport(cursor, 5, link.viewport, self.df_layout)
end
function MechanismList:changeSelected(delta)
@ -127,10 +113,10 @@ function MechanismList:onInput(keys)
elseif keys.STANDARDSCROLL_DOWN or keys.SECONDSCROLL_DOWN then
self:changeSelected(1)
elseif keys.LEAVESCREEN then
self:dismiss()
if self.selected ~= 1 then
self:zoomToLink(self.links[1])
end
self:dismiss()
elseif keys.SELECT then
self:dismiss()
end