diff options
| author | Alexander Gavrilov | 2012-09-18 20:30:25 +0400 |
|---|---|---|
| committer | Alexander Gavrilov | 2012-09-18 20:30:25 +0400 |
| commit | 57b72831ca700ab556566a85f2245e014ca96c30 (patch) | |
| tree | 50163cb2125460dc4207fda24666d1412c9c04dd /library/lua | |
| parent | a7998f71a2ee95d2d21f34468761118fd6b8585f (diff) | |
| download | dfhack-57b72831ca700ab556566a85f2245e014ca96c30.tar.gz dfhack-57b72831ca700ab556566a85f2245e014ca96c30.tar.bz2 dfhack-57b72831ca700ab556566a85f2245e014ca96c30.tar.xz | |
Overhaul the concept of lua 'class' initialization yet again.
Diffstat (limited to 'library/lua')
| -rw-r--r-- | library/lua/class.lua | 150 | ||||
| -rw-r--r-- | library/lua/dfhack.lua | 24 | ||||
| -rw-r--r-- | library/lua/gui.lua | 33 | ||||
| -rw-r--r-- | library/lua/gui/dialogs.lua | 99 | ||||
| -rw-r--r-- | library/lua/gui/dwarfmode.lua | 4 |
5 files changed, 227 insertions, 83 deletions
diff --git a/library/lua/class.lua b/library/lua/class.lua new file mode 100644 index 00000000..7b142e49 --- /dev/null +++ b/library/lua/class.lua @@ -0,0 +1,150 @@ +-- A trivial reloadable class system + +local _ENV = mkmodule('class') + +-- Metatable template for a class +class_obj = {} or class_obj + +-- Methods shared by all classes +common_methods = {} or common_methods + +-- Forbidden names for class fields and methods. +reserved_names = { super = true, ATTRS = true } + +-- Attribute table metatable +attrs_meta = {} or attrs_meta + +-- Create or updates a class; a class has metamethods and thus own metatable. +function defclass(class,parent) + class = class or {} + + local meta = getmetatable(class) + if not meta then + meta = {} + setmetatable(class, meta) + end + + for k,v in pairs(class_obj) do meta[k] = v end + + meta.__index = parent or common_methods + + local attrs = rawget(class, 'ATTRS') or {} + setmetatable(attrs, attrs_meta) + + rawset(class, 'super', parent) + rawset(class, 'ATTRS', attrs) + rawset(class, '__index', rawget(class, '__index') or class) + + return class +end + +-- An instance uses the class as metatable +function mkinstance(class,table) + table = table or {} + setmetatable(table, class) + return table +end + +-- Patch the stubs in the global environment +dfhack.BASE_G.defclass = _ENV.defclass +dfhack.BASE_G.mkinstance = _ENV.mkinstance + +-- Just verify the name, and then set. +function class_obj:__newindex(name,val) + if reserved_names[name] or common_methods[name] then + error('Method name '..name..' is reserved.') + end + rawset(self, name, val) +end + +function attrs_meta:__call(attrs) + for k,v in pairs(attrs) do + self[k] = v + end +end + +local function apply_attrs(obj, attrs, init_table) + for k,v in pairs(attrs) do + if v == DEFAULT_NIL then + v = nil + end + obj[k] = init_table[k] or v + end +end + +local function invoke_before_rec(self, class, method, ...) + local meta = getmetatable(class) + if meta then + local fun = rawget(class, method) + if fun then + fun(self, ...) + end + + invoke_before_rec(self, meta.__index, method, ...) + end +end + +local function invoke_after_rec(self, class, method, ...) + local meta = getmetatable(class) + if meta then + invoke_after_rec(self, meta.__index, method, ...) + + local fun = rawget(class, method) + if fun then + fun(self, ...) + end + end +end + +local function init_attrs_rec(obj, class, init_table) + local meta = getmetatable(class) + if meta then + init_attrs_rec(obj, meta.__index, init_table) + apply_attrs(obj, rawget(class, 'ATTRS'), init_table) + end +end + +-- Call metamethod constructs the object +function class_obj:__call(init_table) + -- The table is assumed to be a scratch temporary. + -- If it is not, copy it yourself before calling. + init_table = init_table or {} + + local obj = mkinstance(self) + + -- This initialization sequence is broadly based on how the + -- Common Lisp initialize-instance generic function works. + + -- preinit screens input arguments in subclass to superclass order + invoke_before_rec(obj, self, 'preinit', init_table) + -- initialize the instance table from init table + init_attrs_rec(obj, self, init_table) + -- init in superclass -> subclass + invoke_after_rec(obj, self, 'init', init_table) + -- postinit in superclass -> subclass + invoke_after_rec(obj, self, 'postinit', init_table) + + return obj +end + +-- Common methods for all instances: + +function common_methods:callback(method, ...) + return dfhack.curry(self[method], self, ...) +end + +function common_methods:assign(data) + for k,v in pairs(data) do + self[k] = v + end +end + +function common_methods:invoke_before(method, ...) + return invoke_before_rec(self, getmetatable(self), method, ...) +end + +function common_methods:invoke_after(method, ...) + return invoke_after_rec(self, getmetatable(self), method, ...) +end + +return _ENV diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index baf0d42e..ce3be5a8 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -113,26 +113,14 @@ function rawset_default(target,source) end end -function defclass(class,parent) - class = class or {} - rawset_default(class, { __index = class }) - if parent then - setmetatable(class, parent) - else - rawset_default(class, { - init_fields = rawset_default, - callback = function(self, name, ...) - return dfhack.curry(self[name], self, ...) - end - }) - end - return class +DEFAULT_NIL = DEFAULT_NIL or {} -- Unique token + +function defclass(...) + return require('class').defclass(...) end -function mkinstance(class,table) - table = table or {} - setmetatable(table, class) - return table +function mkinstance(...) + return require('class').mkinstance(...) end -- Misc functions diff --git a/library/lua/gui.lua b/library/lua/gui.lua index f9b6ab6d..6eaa9860 100644 --- a/library/lua/gui.lua +++ b/library/lua/gui.lua @@ -74,18 +74,23 @@ end Painter = defclass(Painter, nil) -function Painter.new(rect, pen) - 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, +function Painter:init(args) + local rect = args.rect or mkdims_wh(0,0,dscreen.getWindowSize()) + local crect = args.clip_rect or rect + self:assign{ + x = rect.x1, y = rect.y1, + x1 = rect.x1, clip_x1 = crect.x1, + y1 = rect.y1, clip_y1 = crect.y1, + x2 = rect.x2, clip_x2 = crect.x2, + y2 = rect.y2, clip_y2 = crect.y2, width = rect.x2-rect.x1+1, height = rect.y2-rect.y1+1, - cur_pen = to_pen(nil, pen or COLOR_GREY) + cur_pen = to_pen(nil, args.pen or COLOR_GREY) } - return mkinstance(Painter, self):seek(0,0) +end + +function Painter.new(rect, pen) + return Painter{ rect = rect, pen = pen } end function Painter:isValidPos() @@ -213,9 +218,8 @@ Screen = defclass(Screen) Screen.text_input_mode = false -function Screen:init() +function Screen:postinit() self:updateLayout() - return self end Screen.isDismissed = dscreen.isDismissed @@ -344,7 +348,12 @@ end FramedScreen = defclass(FramedScreen, Screen) -FramedScreen.frame_style = BOUNDARY_FRAME +FramedScreen.ATTRS{ + frame_style = BOUNDARY_FRAME, + frame_title = DEFAULT_NIL, + frame_width = DEFAULT_NIL, + frame_height = DEFAULT_NIL, +} local function hint_coord(gap,hint) if hint and hint > 0 then diff --git a/library/lua/gui/dialogs.lua b/library/lua/gui/dialogs.lua index eb883465..b1a96a55 100644 --- a/library/lua/gui/dialogs.lua +++ b/library/lua/gui/dialogs.lua @@ -10,24 +10,21 @@ local dscreen = dfhack.screen MessageBox = defclass(MessageBox, gui.FramedScreen) MessageBox.focus_path = 'MessageBox' -MessageBox.frame_style = gui.GREY_LINE_FRAME - -function MessageBox:init(info) - info = info or {} - self:init_fields{ - text = info.text or {}, - frame_title = info.title, - frame_width = info.frame_width, - on_accept = info.on_accept, - on_cancel = info.on_cancel, - on_close = info.on_close, - text_pen = info.text_pen - } - if type(self.text) == 'string' then - self.text = utils.split_string(self.text, "\n") + +MessageBox.ATTRS{ + frame_style = gui.GREY_LINE_FRAME, + -- new attrs + text = {}, + on_accept = DEFAULT_NIL, + on_cancel = DEFAULT_NIL, + on_close = DEFAULT_NIL, + text_pen = DEFAULT_NIL, +} + +function MessageBox:preinit(info) + if type(info.text) == 'string' then + info.text = utils.split_string(info.text, "\n") end - gui.FramedScreen.init(self, info) - return self end function MessageBox:getWantedFrameSize() @@ -82,9 +79,8 @@ function MessageBox:onInput(keys) end function showMessage(title, text, tcolor, on_close) - mkinstance(MessageBox):init{ - text = text, - title = title, + MessageBox{ + frame_title = title, text = text, text_pen = tcolor, on_close = on_close @@ -92,8 +88,8 @@ function showMessage(title, text, tcolor, on_close) end function showYesNoPrompt(title, text, tcolor, on_accept, on_cancel) - mkinstance(MessageBox):init{ - title = title, + MessageBox{ + frame_title = title, text = text, text_pen = tcolor, on_accept = on_accept, @@ -105,25 +101,23 @@ InputBox = defclass(InputBox, MessageBox) InputBox.focus_path = 'InputBox' -function InputBox:init(info) - info = info or {} - self:init_fields{ - input = info.input or '', - input_pen = info.input_pen, - on_input = info.on_input, - } - MessageBox.init(self, info) - self.on_accept = nil - return self +InputBox.ATTRS{ + input = '', + input_pen = DEFAULT_NIL, + on_input = DEFAULT_NIL, +} + +function InputBox:preinit(info) + info.on_accept = nil end function InputBox:getWantedFrameSize() - local mw, mh = MessageBox.getWantedFrameSize(self) + local mw, mh = InputBox.super.getWantedFrameSize(self) return mw, mh+2 end function InputBox:onRenderBody(dc) - MessageBox.onRenderBody(self, dc) + InputBox.super.onRenderBody(self, dc) dc:newline(1) dc:pen(self.input_pen or COLOR_LIGHTCYAN) @@ -161,8 +155,8 @@ function InputBox:onInput(keys) end function showInputPrompt(title, text, tcolor, input, on_input, on_cancel, min_width) - mkinstance(InputBox):init{ - title = title, + InputBox{ + frame_title = title, text = text, text_pen = tcolor, input = input, @@ -176,27 +170,28 @@ ListBox = defclass(ListBox, MessageBox) ListBox.focus_path = 'ListBox' +ListBox.ATTRS{ + selection = 0, + choices = {}, + select_pen = DEFAULT_NIL, + on_input = DEFAULT_NIL +} + +function InputBox:preinit(info) + info.on_accept = nil +end + function ListBox:init(info) - info = info or {} - self:init_fields{ - selection = info.selection or 0, - choices = info.choices or {}, - select_pen = info.select_pen, - on_input = info.on_input, - page_top = 0 - } - MessageBox.init(self, info) - self.on_accept = nil - return self + self.page_top = 0 end function ListBox:getWantedFrameSize() - local mw, mh = MessageBox.getWantedFrameSize(self) + local mw, mh = ListBox.super.getWantedFrameSize(self) return mw, mh+#self.choices end function ListBox:onRenderBody(dc) - MessageBox.onRenderBody(self, dc) + ListBox.super.onRenderBody(self, dc) dc:newline(1) @@ -220,6 +215,7 @@ function ListBox:onRenderBody(dc) end end end + function ListBox:moveCursor(delta) local newsel=self.selection+delta if #self.choices ~=0 then @@ -229,6 +225,7 @@ function ListBox:moveCursor(delta) end self.selection=newsel end + function ListBox:onInput(keys) if keys.SELECT then self:dismiss() @@ -257,8 +254,8 @@ function ListBox:onInput(keys) end function showListPrompt(title, text, tcolor, choices, on_input, on_cancel, min_width) - mkinstance(ListBox):init{ - title = title, + ListBox{ + frame_title = title, text = text, text_pen = tcolor, choices = choices, diff --git a/library/lua/gui/dwarfmode.lua b/library/lua/gui/dwarfmode.lua index 661e1559..ba3cfbe6 100644 --- a/library/lua/gui/dwarfmode.lua +++ b/library/lua/gui/dwarfmode.lua @@ -353,7 +353,7 @@ end MenuOverlay = defclass(MenuOverlay, DwarfOverlay) function MenuOverlay:updateLayout() - DwarfOverlay.updateLayout(self) + MenuOverlay.super.updateLayout(self) self.frame_rect = self.df_layout.menu end @@ -361,7 +361,7 @@ MenuOverlay.getWindowSize = gui.FramedScreen.getWindowSize MenuOverlay.getMousePos = gui.FramedScreen.getMousePos function MenuOverlay:onAboutToShow(below) - DwarfOverlay.onAboutToShow(self,below) + MenuOverlay.super.onAboutToShow(self,below) self:updateLayout() if not self.df_layout.menu then |
