loadmodule "luaiup" loadmodule "luahandle" loadmodule "lualibs" load "iupe-dbuffer.lua" load "iupe-tview.lua" if not dalosp then dalosp = {} end if not dalos then dalos = {} end dalos.objectstypes = {} dalos.objectstypes_by_name = {} dalosp.NORTH = 1 dalosp.SOUTH = 2 dalosp.WEST = 3 dalosp.EAST = 4 dalosp.cross = { } function dalos:register_obj(name, constructor) if self.objectstypes_by_name[name] then error("An object of that name already exists: " .. name) end table.insert(self.objectstypes, { name = name, constructor = constructor, counter = 1 }) self.objectstypes_by_name[name] = #self.objectstypes if self.activemenu then self.activemenu:update_objects() end end dalosp.canvas = { DARK_WHITE = cd.EncodeColor(224, 224, 224), BEZIER_CTRL_LEN = 40, drawlink = function (self, src, dst, si, di) local cv = self.cvdb local iy = function (y) return cv:InvertYAxis(y) end local ctrl_len = dalosp.canvas.BEZIER_CTRL_LEN cv:Foreground(cd.BLACK) local x1, y1, x2, y2 x1 = self.origin.x + src.x + src.w x2 = self.origin.x + dst.x y1 = self.origin.y + src.y + si * src.h / (src.obj.noutputs + 1) y2 = self.origin.y + dst.y + di * src.h / (dst.obj.ninputs + 1) x1, x2, y1, y2 = math.floor(x1 + 0.5), math.floor(x2 + 0.5), math.floor(y1 + 0.5), math.floor(y2 + 0.5) cv:Begin(cd.BEZIER) cv:Vertex(x1, iy(y1)) cv:Vertex(x1 + ctrl_len, iy(y1)) cv:Vertex(x2 - ctrl_len, iy(y2)) cv:Vertex(x2, iy(y2)) cv:End() end, XMENUWIDTH = 100, drawxmenu = function (self, x, y) local cv = self.cvdb local width = dalosp.canvas.XMENUWIDTH local x1, x2, y1, y2 = x - width / 2, x + width / 2, y - width / 2, y + width / 2 x1, x2, y1, y2 = math.floor(x1 + 0.5), math.floor(x2 + 0.5), math.floor(y1 + 0.5), math.floor(y2 + 0.5) local iy = function (y) return cv:InvertYAxis(y) end local cx1, cx2, cy1, cy2 = x - 5, x + 5, y - 5, y + 5 cv:Foreground(dalosp.canvas.DARK_WHITE) cv:Box(x1, x2, iy(y2), iy(y1)) cv:Foreground(cd.GRAY) cv:Line(x1, iy(y1), x2, iy(y2)) cv:Line(x1, iy(y2), x2, iy(y1)) cv:Line(x1 + 1, iy(y1), x2, iy(y2 - 1)) cv:Line(x1, iy(y2 - 1), x2 - 1, iy(y1)) cv:Foreground(cd.WHITE) cv:Line(x1, iy(y1 + 1), x2 - 1, iy(y2)) cv:Line(x1 + 1, iy(y2), x2, iy(y1 + 1)) cv:Line(x1, iy(y1 + 2), x2 - 2, iy(y2)) cv:Line(x1 + 2, iy(y2), x2, iy(y1 + 2)) cv:Foreground(dalosp.canvas.DARK_WHITE) cv:Sector(x, iy(y), 10, 10, 0, 360) cv:Foreground(cd.GRAY) cv:Arc(x, iy(y), 10, 10, 0, 360) cv:Foreground(cd.WHITE) cv:Arc(x + 1, iy(y + 1), 10, 10, 0, 360) cv:Foreground(cd.GRAY) cv:Line(x1, iy(y2), x2, iy(y2)) cv:Line(x1 + 1, iy(y2 - 1), x2 - 1, iy(y2 - 1)) cv:Line(x1 + 2, iy(y2 - 2), x2 - 2, iy(y2 - 2)) cv:Line(x2, iy(y1), x2, iy(y2)) cv:Line(x2 - 1, iy(y1 + 1), x2 - 1, iy(y2 - 1)) cv:Line(x2 - 2, iy(y1 + 2), x2 - 2, iy(y2 - 2)) cv:Foreground(cd.WHITE) cv:Line(x1, iy(y1), x1, iy(y2)) cv:Line(x1 + 1, iy(y1 + 1), x1 + 1, iy(y2 - 1)) cv:Line(x1 + 2, iy(y1 + 2), x1 + 2, iy(y2 - 2)) cv:Line(x1, iy(y1), x2, iy(y1)) cv:Line(x1 + 1, iy(y1 + 1), x2 - 1, iy(y1 + 1)) cv:Line(x1 + 2, iy(y1 + 2), x2 - 2, iy(y1 + 2)) local f, st, si = cv:GetFont() cv:Font("Helvetica", cd.PLAIN, 10) cv:Foreground(cd.BLACK) cv:TextOrientation(0) if dalosp.cross.north then cv:TextAlignment(cd.NORTH) cv:Text(x, iy(y1 + 3), dalosp.cross.north.name) end cv:TextAlignment(cd.SOUTH) if dalosp.cross.south then cv:Text(x, iy(y2 - 3), dalosp.cross.south.name) end if dalosp.cross.west then cv:TextOrientation(270) cv:Text(x1 + 3, iy(y), dalosp.cross.west.name) end if dalosp.cross.east then cv:TextOrientation(90) cv:Text(x2 - 3, iy(y), dalosp.cross.east.name) end cv:Font(f, st, si) end, draw = function (self) local cvdb = self.cvdb if not cvdb then return iup.DEFAULT end cvdb:Background(dalosp.canvas.DARK_WHITE) cvdb:Clear() for k, v in ipairs(self.objects) do v.obj:draw(cvdb, self.origin.x + v.x, self.origin.y + v.y, v.w, v.h) for oi = 1, v.obj.noutputs do local dest = v.obj.outputs[oi] if dest and dest.obj then self:drawlink(v, dest.obj, oi, dest.ind) end end end if self.stateful.linking then cvdb:Foreground(cd.BLACK) cvdb:Line(self.bx, cvdb:InvertYAxis(self.by), self.ox, cvdb:InvertYAxis(self.oy)) end if self.menu.x then self:drawxmenu(self.menu.x, self.menu.y) end cvdb:Flush() return iup.DEFAULT end, addobj = function (self, obj, x, y, w, h) table.insert(self.objects, { obj = obj, x = x, y = y, w = w, h = h }) self:draw() end, findobj = function (self, x, y) local obj, ind x = x - self.origin.x y = y - self.origin.y for k, v in ipairs(self.objects) do if v.x <= x and (v.x + v.w) >= x and v.y <= y and (v.y + v.h) >= y then obj, ind = v, k end end return obj, ind end, moveobj = function (self, obj, dx, dy) obj.x, obj.y = obj.x + dx, obj.y + dy self:draw() end, setobjplace = function (self, obj, x, y) obj.x, obj.y = x, y self:draw() end, panning = function (self, dx, dy) self.origin.x = self.origin.x + dx self.origin.y = self.origin.y + dy self:draw() end, focus_cb = function (self, focus) if focus == 0 and not self.stateful.rghtbutton and not self.stateful.leftbutton then self:button_cb() end end, stypes = { INFO = 0, WARNING = 1, ERROR = 2, }, setstatus = function (self, stype, msg) -- todo: add a status line end, motion_cb = function (self, x, y, status) if self.stateful.panning then local ox, oy = self.ox, self.oy local dx, dy = x - ox, y - oy self:panning(dx, dy) self.ox, self.oy = x, y elseif self.stateful.leftbutton then local linking = self.stateful.linking self.stateful.dragging = true local got_error = false if self.stateful.leftclk and not self.stateful.linking then linking = self.stateful.leftclk if linking.obj.noutputs >= 1 then self.stateful.linking = linking else self:setstatus(dalosp.canvas.stypes.ERROR, "Can't link: origin object doesn't have any output") self.stateful.leftclk = nil linking = nil got_error = true end else linking = self.stateful.linking end if linking then self.ox, self.oy = x, y self:draw() elseif not got_error then -- todo: start selection square ? end elseif self.stateful.rghtbutton then if self.stateful.rghtclk and not self.stateful.moving then self.stateful.moving = self.stateful.rghtclk end self.stateful.dragging = true local moving = self.stateful.moving if moving then local ox, oy = self.ox, self.oy local dx, dy = x - ox, y - oy self:moveobj(moving, dx, dy) self.ox, self.oy = x, y elseif not self.menu.x then self.menu.x, self.menu.y = x, y self:draw() end end end, createlink = function (self, src, dst) local oldsrc = src.obj.outputs[src.obj.curoutput] local olddst = dst.obj.inputs[dst.obj.curinput] if oldsrc then oldsrc.obj.obj.inputs[oldsrc.ind] = nil oldsrc.obj.obj:input_change(oldsrc.ind) end if olddst then olddst.obj.obj.outputs[olddst.ind] = nil olddst.obj.obj:output_change(olddst.ind) end src.obj.outputs[src.obj.curoutput] = { obj = dst, ind = dst.obj.curinput } dst.obj.inputs[dst.obj.curinput] = { obj = src, ind = src.obj.curoutput } src.obj:output_change(src.obj.curoutput) dst.obj:input_change(dst.obj.curinput) end, destroylink = function (self, src) local oldsrc = src.obj.outputs[src.obj.curoutput] if oldsrc then src.obj.outputs[src.obj.curoutput] = nil src.obj:output_change(src.obj.curoutput) oldsrc.obj.obj.inputs[oldsrc.ind] = nil oldsrc.obj.obj:input_change(oldsrc.ind) end end, button_cb = function (self, button, pressed, x, y, status) if not pressed then pressed = 0 end if not x then x = self.ox end if not y then y = self.oy end if button == iup.BUTTON1 or not button then if pressed == 1 then self.stateful.leftbutton = true if self.stateful.rghtbutton then self.stateful.panning = true else local obj, ind = self:findobj(x, y) if obj then self.stateful.leftclk = obj table.remove(self.objects, ind) table.insert(self.objects, obj) end self.ox, self.oy = x, y self.bx, self.by = x, y self:draw() end else if not self.stateful.linking and self.stateful.leftclk and not self.stateful.dragging then self.stateful.leftclk.obj:activate() elseif self.stateful.linking then local dest = self:findobj(x,y) if not dest then self:destroylink(self.stateful.linking) elseif dest == self.stateful.linking then self:setstatus(dalosp.canvas.stypes.ERROR, "Can't link: origin is the same as destination") elseif dest.obj.ninputs <= 0 then self:setstatus(dalosp.canvas.stypes.ERROR, "Can't link: destination has no input") else self:createlink(self.stateful.linking, dest) end self.stateful.linking = nil self:draw() end self.stateful.panning = nil self.stateful.linking = nil self.stateful.leftclk = nil self.stateful.leftbutton = nil if not self.stateful.rghtbutton then self.stateful.dragging = nil end end end if button == iup.BUTTON3 or not button then if pressed == 1 then self.stateful.rghtbutton = true if self.stateful.leftbutton then self.stateful.panning = true else local obj, ind = self:findobj(x, y) if obj then self.stateful.rghtclk = obj table.remove(self.objects, ind) table.insert(self.objects, obj) end self.ox, self.oy = x, y self.bx, self.by = x, y self:draw() end else if not self.stateful.moving and self.stateful.rghtclk and not self.stateful.dragging then self.stateful.rghtclk.obj:configure() self:draw() elseif self.menu.x then local dx, dy = x - self.menu.x, y - self.menu.y local obj if math.abs(dx) > math.abs(dy) then if dx < 0 then obj = dalosp.cross.west else obj = dalosp.cross.east end else if dy < 0 then obj = dalosp.cross.north else obj = dalosp.cross.south end end if obj then dalosp.menu.add_object(self, obj, self.menu.x, self.menu.y) end self.menu.x = nil self.menu.y = nil self:draw() elseif self.stateful.moving then -- check for the trash can end self.stateful.panning = nil self.stateful.moving = nil self.stateful.rghtclk = nil self.stateful.rghtbutton = nil if not self.stateful.leftbutton then self.stateful.dragging = nil end end end end, wheel_cb = function (self, delta, x, y, status) local obj = self:findobj(x, y) if obj then if iup.isshift(status) then obj.obj:change_curoutput(delta) elseif iup.isalt(status) then obj.obj:change_quicksetting(delta) else obj.obj:change_curinput(delta) end end self:draw() end, create = function (tab) tab.border = "No" tab.expand = "Yes" tab.shrink = "Yes" tab.minsize = "1x1" tab.rastersize = "1x1" local r = iup.canvas(tab) r.action = iupep.dbuffer.action r.draw = dalosp.canvas.draw r.resize_cb = iupep.dbuffer.resize_cb r.map_cb = iupep.dbuffer.map_cb r.unmap_cb = iupep.dbuffer.unmap_cb r.focus_cb = dalosp.canvas.focus_cb r.motion_cb = dalosp.canvas.motion_cb r.button_cb = dalosp.canvas.button_cb r.wheel_cb = dalosp.canvas.wheel_cb r.addobj = dalosp.canvas.addobj r.findobj = dalosp.canvas.findobj r.moveobj = dalosp.canvas.moveobj r.setobjplace = dalosp.canvas.setobjplace r.panning = dalosp.canvas.panning r.setstatus = dalosp.canvas.setstatus r.createlink = dalosp.canvas.createlink r.destroylink = dalosp.canvas.destroylink r.drawlink = dalosp.canvas.drawlink r.drawxmenu = dalosp.canvas.drawxmenu r.origin = { x = 0, y = 0 } r.menu = { } r.stateful = {} r.objects = {} dalos.active_canvas = r return r end, } dalosp.menu = { action_load = function (self) end, action_save = function(self) local s_obj = { } if not dalos.active_canvas then return end local d = dalos.active_canvas for i, v in ipairs(d.objects) do s_obj[i] = { x = v.x, y = v.y, name = v.obj.name, ntype = v.obj.ntype, settings = v.obj:get_settings(), lookup = v } end local s_links = { } for iobj, obj in ipairs(d.objects) do for iout, out in pairs(obj.obj.outputs) do for ilookup, lobj in ipairs(s_obj) do if lobj.lookup == out.obj then table.insert(s_links, { src = iobj, dst = ilookup, isrc = iout, idst = out.ind }) end end end end for i, v in ipairs(s_obj) do v.lookup = nil end local save = { objects = s_obj, links = s_links } local dlg = iup.filedlg { dialogtype = "Save", } iup.Popup(dlg) if dlg.status ~= -1 then print(dlg.value) local s, v = pcall(Output, dlg.value) if s then v:write "local " dumpvars(v, save, "save") v:write "return save" end end end, action_exit = function (self) return iup.CLOSE end, action_about = function (self) local dlg = iup.messagedlg { DialogType = "Information", ButtonDefault = "1", Buttons = "OK", Title = "About", Value = 'DALOS (c) 2009-2010 Nicolas "Pixel" Noble.\nThis is free software with ABSOLUTELY NO WARRANTY.\nPlease look at the COPYRIGHT file for details.', } dlg:popup() return iup.DEFAULT end, update_objects = function (self) if dalos.dlg then local newmenu = dalos.menu {} -- copy anything from self to newmenu ? *shrug* dalos.dlg.menu = newmenu end end, add_object = function (canvas, object, x, y) object.constructor(canvas, { counter = object.counter, x = (x or 25) + 0, y = (y or 25) + 0}) object.counter = object.counter + 1 end, set_cross = function (canvas, object, dir) if dir == dalosp.NORTH then dalosp.cross.north = object elseif dir == dalosp.SOUTH then dalosp.cross.south = object elseif dir == dalosp.EAST then dalosp.cross.east = object elseif dir == dalosp.WEST then dalosp.cross.west = object end end, create = function (canvas, tab) local item_load = iup.item { title = "Load" } item_load.action = dalosp.menu.action_load local item_save = iup.item { title = "Save" } item_save.action = dalosp.menu.action_save local item_exit = iup.item { title = "Exit" } item_exit.action = dalosp.menu.action_exit local item_about = iup.item { title = "About" } item_about.action = dalosp.menu.action_about local add_menu = { } local north_menu = { radio = "1" } local east_menu = { radio = "1" } local west_menu = { radio = "1" } local south_menu = { radio = "1" } local item for k, v in ipairs(dalos.objectstypes) do item = iup.item { title = v.name } item.action = function (self) dalosp.menu.add_object(canvas, v) end table.insert(add_menu, item) item = iup.item { title = v.name } item.action = function (self) dalosp.menu.set_cross(canvas, v, dalosp.NORTH) end table.insert(north_menu, item) item = iup.item { title = v.name } item.action = function (self) dalosp.menu.set_cross(canvas, v, dalosp.EAST) end table.insert(east_menu, item) item = iup.item { title = v.name } item.action = function (self) dalosp.menu.set_cross(canvas, v, dalosp.WEST) end table.insert(west_menu, item) item = iup.item { title = v.name } item.action = function (self) dalosp.menu.set_cross(canvas, v, dalosp.SOUTH) end table.insert(south_menu, item) end local menu_file = iup.submenu { iup.menu { item_load, item_save, iup.separator {}, item_exit }, title = "File" } local menu_add = iup.submenu { iup.menu(add_menu), title = "Add" } local menu_cross = iup.submenu { iup.menu { iup.submenu { iup.menu(north_menu), title = "North" }, iup.submenu { iup.menu(east_menu), title = "East" }, iup.submenu { iup.menu(west_menu), title = "West" }, iup.submenu { iup.menu(south_menu), title = "South" }, }, title = "Cross", } local menu_help = iup.submenu { iup.menu { item_about }, title = "Help" } local r = iup.menu { menu_file, menu_add, menu_cross, menu_help } r.update_objects = dalosp.menu.update_objects r.add_menu = add_menu dalos.active_menu = r return r end, } dalosp.object = { default_draw = function (self, cv, x, y, w, h ) local x1, x2, y1, y2 = x, x + w, y, y + h y1, y2 = cv:InvertYAxis(y2), cv:InvertYAxis(y1) local cx, cy = (x1 + x2) / 2, (y1 + y2) / 2 cv:Foreground(self.color) cv:Box(x1, x2, y1, y2) cv:Foreground(cd.BLACK) cv:Rect(x1, x2, y1, y2) cv:TextAlignment(cd.SOUTH) cv:Text(cx, y2, self.name) cv:TextAlignment(cd.NORTH) if self.ninputs >= 1 then cv:Text(x1, y1, self.curinput .. "/" .. self.ninputs) end if self.noutputs >= 1 then cv:Text(x2, y1, self.curoutput .. "/" .. self.noutputs) end if self.quicksetting then cv:Text(cx, y1, self.quicksetting) end end, get_linked_input = function (self, ind) local ni = self.inputs[ind] if ni then local ii = ni.ind local io = ni.obj return io.obj:get_output(ii) else return nil end end, default_activate = function (self) print "default activate" end, default_configure = function (self) print "default configure" end, change_curinput = function (self, delta) if self.ninputs <= 1 then return end self.curinput = self.curinput + delta if self.curinput > self.ninputs then self.curinput = self.ninputs end if self.curinput <= 0 then self.curinput = 1 end end, change_curoutput = function (self, delta) if self.noutputs <= 1 then return end self.curoutput = self.curoutput + delta if self.curoutput > self.noutputs then self.curoutput = self.noutputs end if self.curoutput <= 0 then self.curoutput = 1 end end, default_change_quicksetting = function (self, delta) end, default_getoutput = function (self, ind) return self.houtputs[ind] end, default_inputchange = function (self, ind) end, default_outputchange = function (self, ind) end, set_houtput = function (self, h, ind) if not ind then ind = 1 end self.houtputs[ind] = h local obj = self.outputs[ind] if obj then obj.obj.obj:input_change(obj.ind) end end, default_get_settings = function (self) return {} end, create = function (dcanvas, tab, extra) if not tab then tab = {} end if not tab.name then tab.name = tab.default_name if tab.counter then tab.name = tab.name .. " " .. tab.counter end end local obj = { draw = tab.draw or dalosp.object.default_draw, name = tab.name or "NoName", color = tab.color or cd.GRAY, ninputs = tab.ninputs or 0, noutputs = tab.noutputs or 0, inputs = {}, outputs = {}, curinput = 1, curoutput = 1, ntype = tab.ntype, quicksetting = tab.quicksetting, otype = tab.otype or dalos.objtype.DUMMY, activate = tab.activate or dalosp.object.default_activate, configure = tab.configure or dalosp.object.default_configure, change_curinput = dalosp.object.change_curinput, change_curoutput = dalosp.object.change_curoutput, change_quicksetting = tab.change_quicksetting or dalosp.object.default_change_quicksetting, extra = extra, get_output = tab.getoutput or dalosp.object.default_getoutput, input_change = tab.input_change or dalosp.object.default_inputchange, output_change = tab.output_change or dalosp.object.default_outputchange, set_houtput = dalosp.object.set_houtput, get_settings = tab.get_settings or dalosp.object.default_get_settings, houtputs = {}, get_linked_input = dalosp.object.get_linked_input, dcanvas = dcanvas, } if tab.otype and not tab.c then if tab.otype == dalos.objtype.DUMMY then obj.color = cd.GRAY elseif tab.otype == dalos.objtype.HANDLE then obj.color = cd.CYAN elseif tab.otype == dalos.objtype.LUA_VIEWER then obj.color = cd.MAGENTA elseif tab.otype == dalos.objtype.LUA_FILTER then obj.color = cd.YELLOW else obj.color = cd.DARK_RED end end dcanvas:addobj(obj, tab.x or 0, tab.y or 0, tab.w or 50, tab.h or 50) return obj end, } dalos.canvas = dalosp.canvas.create dalos.menu = dalosp.menu.create dalos.object = dalosp.object.create dalos.objtype = { UNKNOWN = 0, DUMMY = 1, HANDLE = 2, LUA_FILTER = 3, LUA_VIEWER = 4, } ---------------- load "dalos-luahandle.lua" load "dalos-hexview.lua" load "dalos-binaryops.lua" load "dalos-limiter.lua" load "dalos-textbuffer.lua" load "dalos-input.lua" ---------------- function dalos:main() d = self.canvas {} m = self.menu(d) dlg = iup.dialog { d, title = "Dalos", menu = m } self.dialog = dlg dlg:show() iup.MainLoop() dlg:hide() end dalos:main()