diff --git a/library/lua/gui.lua b/library/lua/gui.lua index 35ae899c0..ee3bccb9f 100644 --- a/library/lua/gui.lua +++ b/library/lua/gui.lua @@ -53,11 +53,11 @@ function inset(rect,dx1,dy1,dx2,dy2) ) end -function to_pen(pen, default) +local function to_pen(default, pen, bg, bold) if pen == nil then return default or {} elseif type(pen) ~= 'table' then - return {fg=pen} + return {fg=pen,bg=bg,bold=bold} else return pen end @@ -70,40 +70,48 @@ end 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) + rect = rect or mkdims_wh(0,0,dscreen.getWindowSize()) + local self = { + x1 = rect.x1, clip_x1 = rect.x1, + y1 = rect.y1, clip_y1 = rect.y1, + x2 = rect.x2, clip_x2 = rect.x2, + y2 = rect.y2, clip_y2 = rect.y2, + width = rect.x2-rect.x1+1, + height = rect.y2-rect.y1+1, + cur_pen = to_pen(nil, pen or COLOR_GREY) + } + return mkinstance(Painter, self):seek(0,0) end -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 +function Painter:isValidPos() + return self.x >= self.clip_x1 and self.x <= self.clip_x2 + and self.y >= self.clip_y1 and self.y <= self.clip_y2 end -function Painter:isValidPos() - return self.x >= self.x1 and self.x <= self.x2 and self.y >= self.y1 and self.y <= self.y2 +function Painter:viewport(x,y,w,h) + local x1,y1 = self.x1+x, self.y1+y + local x2,y2 = x1+w-1, y1+h-1 + local vp = { + -- Logical viewport + x1 = x1, y1 = y1, x2 = x2, y2 = y2, + width = w, height = h, + -- Actual clipping rect + clip_x1 = math.max(self.clip_x1, x1), + clip_y1 = math.max(self.clip_y1, y1), + clip_x2 = math.min(self.clip_x2, x2), + clip_y2 = math.min(self.clip_y2, y2), + -- Pen + cur_pen = self.cur_pen + } + return mkinstance(Painter, vp):seek(0,0) 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 +function Painter:localX() + return self.x - self.x1 +end + +function Painter:localY() + return self.y - self.y1 end function Painter:seek(x,y) @@ -124,55 +132,63 @@ function Painter:newline(dx) return self end -function Painter:pen(pen) - self.cur_pen = to_pen(pen, self.cur_pen) +function Painter:pen(pen,...) + self.cur_pen = to_pen(self.cur_pen, pen, ...) + return self +end + +function Painter:color(fg,bold,bg) + self.cur_pen = copyall(self.cur_pen) + self.cur_pen.fg = fg + self.cur_pen.bold = bold + if bg then self.cur_pen.bg = bg end return self end function Painter:clear() - dscreen.fillRect(CLEAR_PEN, self.x1, self.y1, self.x2, self.y2) + dscreen.fillRect(CLEAR_PEN, self.clip_x1, self.clip_y1, self.clip_x2, self.clip_y2) return self end -function Painter:fill(x1,y1,x2,y2,pen) +function Painter:fill(x1,y1,x2,y2,pen,bg,bold) if type(x1) == 'table' then - x1, y1, x2, y2, pen = x1.x1, x1.y1, x1.x2, x1.y2, x2 + x1, y1, x2, y2, pen, bg, bold = x1.x1, x1.y1, x1.x2, x1.y2, y1, x2, y2 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) + x1 = math.max(x1,self.clip_x1) + y1 = math.max(y1,self.clip_y1) + x2 = math.min(x2,self.clip_x2) + y2 = math.min(y2,self.clip_y2) + dscreen.fillRect(to_pen(self.cur_pen,pen,bg,bold),x1,y1,x2,y2) return self end -function Painter:char(char,pen) +function Painter:char(char,pen,...) if self:isValidPos() then - dscreen.paintTile(to_pen(pen, self.cur_pen), self.x, self.y, char) + dscreen.paintTile(to_pen(self.cur_pen, pen, ...), self.x, self.y, char) end return self:advance(1, nil) end -function Painter:tile(char,tile,pen) +function Painter:tile(char,tile,pen,...) if self:isValidPos() then - dscreen.paintTile(to_pen(pen, self.cur_pen), self.x, self.y, char, tile) + dscreen.paintTile(to_pen(self.cur_pen, 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 +function Painter:string(text,pen,...) + if self.y >= self.clip_y1 and self.y <= self.clip_y2 then local dx = 0 - if self.x < self.x1 then - dx = self.x1 - self.x + if self.x < self.clip_x1 then + dx = self.clip_x1 - self.x end local len = #text - if self.x + len - 1 > self.x2 then - len = self.x2 - self.x + 1 + if self.x + len - 1 > self.clip_x2 then + len = self.clip_x2 - self.x + 1 end if len > dx then dscreen.paintString( - to_pen(pen, self.cur_pen), + to_pen(self.cur_pen, pen, ...), self.x+dx, self.y, string.sub(text,dx+1,len) ) @@ -209,12 +225,12 @@ function Screen:inputToParent(...) end end -function Screen:show() +function Screen:show(below) if self._native then error("This screen is already on display") end - self:onAboutToShow() - if dscreen.show(self) then + self:onAboutToShow(below) + if dscreen.show(self, below) then self:onShown() end end @@ -225,20 +241,40 @@ 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) - dscreen.fillRect(pen,x1,y2,x2,y2) - dscreen.fillRect(pen,x1,y1+1,x1,y2-1) - dscreen.fillRect(pen,x2,y1+1,x2,y2-1) +------------------------ +-- Framed screen object -- +------------------------ + +GREY_FRAME = { + frame_pen = { ch = ' ', fg = COLOR_BLACK, bg = COLOR_GREY }, + title_pen = { fg = COLOR_BLACK, bg = COLOR_WHITE }, +} + +function paint_frame(x1,y1,x2,y2,style,title) + local pen = style.frame_pen + dscreen.paintTile(style.lt_frame_pen or pen, x1, y1) + dscreen.paintTile(style.rt_frame_pen or pen, x2, y1) + dscreen.paintTile(style.lb_frame_pen or pen, x1, y2) + dscreen.paintTile(style.rb_frame_pen or pen, x2, y2) + dscreen.fillRect(style.t_frame_pen or style.h_frame_pen or pen,x1+1,y1,x2-1,y1) + dscreen.fillRect(style.b_frame_pen or style.h_frame_pen or pen,x1+1,y2,x2-1,y2) + dscreen.fillRect(style.l_frame_pen or style.v_frame_pen or pen,x1,y1+1,x1,y2-1) + dscreen.fillRect(style.r_frame_pen or style.v_frame_pen or pen,x2,y1+1,x2,y2-1) if title then - local x = math.floor((x2-x1-3-#title)/2 + x1) - pen.bg = bit32.bxor(pen.bg, 8) - dscreen.paintString(pen, x, y1, ' '..title..' ') + local x = math.max(0,math.floor((x2-x1-3-#title)/2)) + x1 + local tstr = ' '..title..' ' + if #tstr > x2-x1-1 then + tstr = string.sub(tstr,1,x2-x1-1) + end + dscreen.paintString(style.title_pen or pen, x, y1, tstr) end end +FramedScreen = defclass(FramedScreen, Screen) + +FramedScreen.frame_style = GREY_FRAME + local function hint_coord(gap,hint) if hint and hint > 0 then return math.min(hint,gap) @@ -249,25 +285,33 @@ local function hint_coord(gap,hint) end end -function Screen:renderFrame(color,title,width,height,xhint,yhint) +function FramedScreen:updateLayout() local sw, sh = dscreen.getWindowSize() local iw, ih = sw-2, sh-2 - width = math.min(width or iw, iw) - height = math.min(height or ih, ih) + local width = math.min(self.frame_width or iw, iw) + local height = math.min(self.frame_height or ih, ih) local gw, gh = iw-width, ih-height - local x1, y1 = hint_coord(gw,xhint), hint_coord(gh,yhint) - local x2, y2 = x1+width+1, y1+height+1 + local x1, y1 = hint_coord(gw,self.frame_xhint), hint_coord(gh,self.frame_yhint) + self.frame_rect = mkdims_wh(x1+1,y1+1,width,height) + self.frame_opaque = (gw == 0 and gh == 0) +end + +function FramedScreen:onRender() + self:updateLayout() + + local rect = self.frame_rect + local x1,y1,x2,y2 = rect.x1-1, rect.y1-1, rect.x2+1, rect.y2+1 - if gw == 0 and gh == 0 then + if self.frame_opaque then dscreen.clear() else self:renderParent() dscreen.fillRect(CLEAR_PEN,x1,y1,x2,y2) end - paint_border(x1,y1,x2,y2,color,title) + paint_frame(x1,y1,x2,y2,self.frame_style,self.frame_title) - return Painter.new(mkdims_wh(x1+1,y1+1,width,height)) + self:onRenderBody(Painter.new(rect)) end return _ENV diff --git a/library/lua/gui/dwarfmode.lua b/library/lua/gui/dwarfmode.lua index c10933f0f..8c761aefe 100644 --- a/library/lua/gui/dwarfmode.lua +++ b/library/lua/gui/dwarfmode.lua @@ -138,14 +138,25 @@ function DwarfOverlay:onIdle() dscreen.invalidate() end -function DwarfOverlay:onAboutToShow() - if not df.viewscreen_dwarfmodest:is_instance(dfhack.gui.getCurViewscreen()) then +function DwarfOverlay:onAboutToShow(below) + local screen = dfhack.gui.getCurViewscreen() + if below then screen = below.parent end + if not df.viewscreen_dwarfmodest:is_instance(screen) then error("This screen requires the main dwarfmode view") end end MenuOverlay = defclass(MenuOverlay, DwarfOverlay) +function MenuOverlay:onAboutToShow(below) + DwarfOverlay.onAboutToShow(self,below) + + self:updateLayout() + if not self.df_layout.menu then + error("The menu panel of dwarfmode is not visible") + end +end + function MenuOverlay:onRender() self:renderParent() self:updateLayout() diff --git a/scripts/devel/viewscreen.lua b/scripts/devel/viewscreen.lua deleted file mode 100644 index a07ea6f37..000000000 --- a/scripts/devel/viewscreen.lua +++ /dev/null @@ -1,18 +0,0 @@ --- Test lua viewscreens. - -local gui = require 'gui' - -local screen = mkinstance(gui.Screen, { - onRender = function(self) - local text = 'Woohoo, lua viewscreen :)' - 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 - self:dismiss() - end - end -}) - -screen:show() diff --git a/scripts/gui/hello-world.lua b/scripts/gui/hello-world.lua new file mode 100644 index 000000000..48f6675eb --- /dev/null +++ b/scripts/gui/hello-world.lua @@ -0,0 +1,21 @@ +-- Test lua viewscreens. + +local gui = require 'gui' + +local text = 'Woohoo, lua viewscreen :)' + +local screen = mkinstance(gui.FramedScreen, { + frame_title = 'Hello World', + frame_width = #text+6, + frame_height = 3, + onRenderBody = function(self, dc) + dc:seek(3,1):string(text, COLOR_LIGHTGREEN) + end, + onInput = function(self,keys) + if keys.LEAVESCREEN or keys.SELECT then + self:dismiss() + end + end +}) + +screen:show()