Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- local sheets
- local __debug_err_map={
- }
- local __debug_err_pats={
- ["lua.arithmetic"]={{"attempt to perform arithmetic __(%w+) on (%w+) and (%w+)","failed to %1 ${1} (%2) and ${2} (%3)"}}
- }
- local __debug_line_tracker={
- {119,122,"main",1},
- {123,141,"/sheets/src/v0.0.4/sheets",1},
- {142,261,"constants",1},
- {262,318,"/sheets/src/v0.0.4/sheets",20},
- {319,543,".sheets.lib.class",1},
- {544,544,"/sheets/src/v0.0.4/sheets",77},
- {545,565,"lib.clipboard",1},
- {566,566,"/sheets/src/v0.0.4/sheets",78},
- {567,609,"lib.parameters",1},
- {610,611,"/sheets/src/v0.0.4/sheets",79},
- {612,1902,"surface2",1},
- {1903,1904,"/sheets/src/v0.0.4/sheets",81},
- {1905,1929,"enum.Easing",1},
- {1930,1931,"/sheets/src/v0.0.4/sheets",83},
- {1932,2035,"exceptions.Exception",1},
- {2036,2036,"/sheets/src/v0.0.4/sheets",85},
- {2037,2046,"exceptions.IncorrectParameterException",1},
- {2047,2047,"/sheets/src/v0.0.4/sheets",86},
- {2048,2057,"exceptions.IncorrectConstructorException",1},
- {2058,2058,"/sheets/src/v0.0.4/sheets",87},
- {2059,2068,"exceptions.ResourceLoadException",1},
- {2069,2069,"/sheets/src/v0.0.4/sheets",88},
- {2070,2079,"exceptions.ThreadRuntimeException",1},
- {2080,2081,"/sheets/src/v0.0.4/sheets",89},
- {2082,2145,"interfaces.ICollatedChildren",1},
- {2146,2146,"/sheets/src/v0.0.4/sheets",91},
- {2147,2156,"interfaces.IColoured",1},
- {2157,2157,"/sheets/src/v0.0.4/sheets",92},
- {2158,2292,"interfaces.IQueryable",1},
- {2293,2293,"/sheets/src/v0.0.4/sheets",93},
- {2294,2449,"interfaces.IChildContainer",1},
- {2450,2450,"/sheets/src/v0.0.4/sheets",94},
- {2451,2535,"interfaces.ITagged",1},
- {2536,2536,"/sheets/src/v0.0.4/sheets",95},
- {2537,2548,"interfaces.ISize",1},
- {2549,2549,"/sheets/src/v0.0.4/sheets",96},
- {2550,2623,"interfaces.ITimer",1},
- {2624,2625,"/sheets/src/v0.0.4/sheets",97},
- {2626,2640,"events.Event",1},
- {2641,2641,"/sheets/src/v0.0.4/sheets",99},
- {2642,2687,"events.KeyboardEvent",1},
- {2688,2688,"/sheets/src/v0.0.4/sheets",100},
- {2689,2700,"events.MiscEvent",1},
- {2701,2701,"/sheets/src/v0.0.4/sheets",101},
- {2702,2747,"events.MouseEvent",1},
- {2748,2748,"/sheets/src/v0.0.4/sheets",102},
- {2749,2760,"events.TextEvent",1},
- {2761,2762,"/sheets/src/v0.0.4/sheets",103},
- {2763,3785,"dynamic.Codegen",1},
- {3786,3786,"/sheets/src/v0.0.4/sheets",105},
- {3787,4156,"dynamic.DynamicValueParser",1},
- {4157,4157,"/sheets/src/v0.0.4/sheets",106},
- {4158,4302,"dynamic.QueryTracker",1},
- {4303,4303,"/sheets/src/v0.0.4/sheets",107},
- {4304,4519,"dynamic.Stream",1},
- {4520,4520,"/sheets/src/v0.0.4/sheets",108},
- {4521,4550,"dynamic.Transition",1},
- {4551,4551,"/sheets/src/v0.0.4/sheets",109},
- {4552,4661,"dynamic.Type",1},
- {4662,4662,"/sheets/src/v0.0.4/sheets",110},
- {4663,4929,"dynamic.Typechecking",1},
- {4930,4930,"/sheets/src/v0.0.4/sheets",111},
- {4931,5187,"dynamic.ValueHandler",1},
- {5188,5189,"/sheets/src/v0.0.4/sheets",112},
- {5190,5621,"core.Application",1},
- {5622,5622,"/sheets/src/v0.0.4/sheets",114},
- {5623,5797,"core.Screen",1},
- {5798,5798,"/sheets/src/v0.0.4/sheets",115},
- {5799,5929,"core.Sheet",1},
- {5930,5932,"/sheets/src/v0.0.4/sheets",116},
- {5933,5986,"core.Thread",1},
- {5987,5989,"/sheets/src/v0.0.4/sheets",119},
- {5990,6090,"elements.Container",1},
- {6091,6093,"/sheets/src/v0.0.4/sheets",122},
- {6094,6190,"interfaces.IHasText",1},
- {6191,6191,"/sheets/src/v0.0.4/sheets",125},
- {6192,6250,"elements.Button",1},
- {6251,6256,"/sheets/src/v0.0.4/sheets",126},
- {6257,6307,"elements.ClippedContainer",1},
- {6308,6317,"/sheets/src/v0.0.4/sheets",132},
- {6318,6447,"elements.KeyHandler",1},
- {6448,6450,"/sheets/src/v0.0.4/sheets",142},
- {6451,6466,"elements.Panel",1},
- {6467,6477,"/sheets/src/v0.0.4/sheets",145},
- {6479,6482,"main",5}
- }
- local function __get_src_and_line( line )
- for i = 1, #__debug_line_tracker do
- local t = __debug_line_tracker[i]
- if line >= t[1] and line <= t[2] then
- return t[3], t[4] + line - t[1]
- end
- end
- return "unknown source", 0
- end
- local function __get_err_msg( src, line, err )
- if __debug_err_map[src] and __debug_err_map[src][line] then
- local name = __debug_err_map[src][line][1]
- for i = 1, #__debug_err_pats[name] do
- local data, r = err:gsub( __debug_err_pats[name][i][1], __debug_err_pats[name][i][2]:gsub( "%$%{(%d+)%}", function( n )
- return __debug_err_map[src][line][tonumber( n ) + 1]
- end ), 1 )
- if r > 0 then
- return data
- end
- end
- end
- return err
- end
- local ok, err = pcall( function()
- do sheets={}local KeyHandler,ICollatedChildren,KeyboardEvent,Exception,Event,ITimer,class,QueryTracker,Stream,Transition,Application,IQueryable,ThreadRuntimeException,Button,MouseEvent,ClippedContainer,ISize,TableType,Easing,MiscEvent,IColoured,Thread,Container,TextEvent,parameters,Panel,ResourceLoadException,Codegen,Typechecking,UnionType,ValueHandler,IncorrectParameterException,DynamicValueParser,Sheet,IHasText,Screen,Type,IChildContainer,IncorrectConstructorException,ListType,clipboard,ITagged
- event={
- mouse_down=0;
- mouse_up=1;
- mouse_click=2;
- mouse_hold=3;
- mouse_drag=4;
- mouse_scroll=5;
- mouse_ping=6;
- key_down=7;
- key_up=8;
- text=9;
- voice=10;
- paste=11;
- }
- alignment={
- left=0;
- centre=1;
- center=1;
- right=2;
- top=3;
- bottom=4;
- }
- colour={
- transparent=0;
- white=1;
- orange=2;
- magenta=4;
- light_blue=8;
- yellow=16;
- lime=32;
- pink=64;
- grey=128;
- light_grey=256;
- cyan=512;
- purple=1024;
- blue=2048;
- brown=4096;
- green=8192;
- red=16384;
- black=32768;
- }
- token={
- eof="eof";
- string="string";
- float="float";
- int=TOKEN_INT;
- ident=TOKEN_IDENT;
- newline="newline";
- symbol="symbol";
- operator=TOKEN_OPERATOR;
- }
- class={}
- local classobj=setmetatable({},{__index=class})
- local supported_meta_methods={__add=true,__sub=true,__mul=true,__div=true,__mod=true,__pow=true,__unm=true,__len=true,__eq=true,__lt=true,__lte=true,__tostring=true,__concat=true}
- local function _tostring(self)
- return"[Class] "..self:type()
- end
- local function _concat(a,b)
- return tostring(a)..tostring(b)
- end
- local function _instance_tostring(self)
- return"[Instance] "..self:type()
- end
- local function new_super(object,super)
- local super_proxy={}
- if super.super then
- super_proxy.super=new_super(object,super.super)
- end
- setmetatable(super_proxy,{__index=function(t,k)
- if type(super[k])=="function"then
- return function(self,...)
- if self==super_proxy then
- self=object
- end
- object.super=super_proxy.super
- local v={super[k](self,...)}
- object.super=super_proxy
- return unpack(v)
- end
- else
- return super[k]
- end
- end,__newindex=super,__tostring=function(self)
- return"[Super] "..tostring(super).." of "..tostring(object)
- end})
- return super_proxy
- end
- function classobj:new(...)
- local mt={__index=self,__INSTANCE=true}
- local instance=setmetatable({class=self,meta=mt},mt)
- if self.super then
- instance.super=new_super(instance,self.super)
- end
- for k,v in pairs(self.meta)do
- if supported_meta_methods[k]then
- mt[k]=v
- end
- end
- if mt.__tostring==_tostring then
- function mt:__tostring()
- return self:tostring()
- end
- end
- function instance:type()
- return self.class:type()
- end
- function instance:type_of(class)
- return self.class:type_of(class)
- end
- function instance:implements(interface)
- return self.class:implements(interface)
- end
- if not self.tostring then
- instance.tostring=_instance_tostring
- end
- local ob=self
- while ob do
- if ob[ob.meta.__type]then
- ob[ob.meta.__type](instance,...)
- break
- end
- ob=ob.super
- end
- return instance
- end
- function classobj:type()
- return tostring(self.meta.__type)
- end
- function classobj:type_of(super)
- return super==self or(self.super and self.super:type_of(super))or false
- end
- function classobj:implements(interface)
- return self.__interface_list[interface]==true
- end
- function class.new(name,super,...)
- local implements={...}
- local len=#implements
- local implements_lookup={}
- if type(name)~="string"then
- return error("expected string class name, got "..type(name))
- end
- local mt={__index=classobj,__CLASS=true,__tostring=_tostring,__concat=_concat,__call=classobj.new,__type=name}
- local obj=setmetatable({meta=mt,__interface_list=implements_lookup},mt)
- if super then
- obj.super=super
- obj.meta.__index=super
- for interface in pairs(super.__interface_list)do
- implements_lookup[interface]=true
- end
- end
- for i=1,len do
- implements_lookup[implements[i]]=true
- for k,v in pairs(implements[i])do
- if k~="__interface_list"then
- obj[k]=v
- end
- end
- for interface in pairs(implements[i].__interface_list)do
- implements_lookup[interface]=true
- end
- end
- return function(t)
- for k,v in pairs(t)do
- obj[k]=v
- end
- return obj
- end
- end
- function class.type(object)
- local _type=type(object)
- if _type=="table"then
- pcall(function()
- local mt=getmetatable(object)
- _type=((mt.__CLASS or mt.__INSTANCE)and object:type())or _type
- end)
- end
- return _type
- end
- function class.type_of(object,class)
- if type(object)=="table"then
- local ok,v=pcall(function()
- return getmetatable(object).__CLASS or getmetatable(object).__INSTANCE or error()
- end)
- return ok and object:type_of(class)
- end
- return false
- end
- function class.is_class(object)
- return pcall(function()if not getmetatable(object).__CLASS then error()end end),nil
- end
- function class.is_instance(object)
- return pcall(function()if not getmetatable(object).__INSTANCE then error()end end),nil
- end
- setmetatable(class,{
- __call=class.new;
- })
- function class.new_interface(name,...)
- local implements={...}
- local implements_lookup={}
- local obj={__interface_list=implements_lookup}
- local len=#implements
- for i=1,len do
- implements_lookup[implements[i]]=true
- for k,v in pairs(implements[i])do
- if k~="__interface_list"then
- obj[k]=v
- end
- end
- for interface in pairs(implements[i].__interface_list)do
- implements_lookup[interface]=true
- end
- end
- return function(t)
- for k,v in pairs(t)do
- obj[k]=v
- end
- return obj
- end
- end
- function class.new_enum(name)
- return function(t)
- return setmetatable({},{__index=t,__newindex=function(t,k,v)
- return error("attempt to set enum index '"..tostring(k).."'" )
- end } )
- end
- end
- local c = {}
- clipboard = {}
- function clipboard.put( modes )
- parameters.check( 1, "modes", "table", modes )
- c = modes
- end
- function clipboard.get( mode )
- parameters.check( 1, "mode", "string", mode )
- return c[mode]
- end
- function clipboard.clear()
- c = {}
- end
- parameters = {}
- function parameters.check_constructor( _class, argc, ... )
- local args = { ... }
- for i = 1, argc * 3, 3 do
- local name = args[i]
- local expected_type = args[i + 1]
- local value = args[i + 2]
- if type( expected_type ) == "string" then
- if type( value ) ~= expected_type then
- Exception.throw( IncorrectConstructorException, _class:type() .. " expects " .. expected_type .. " " .. name .. " when created, got " .. class.type( value ), 4 )
- end
- else
- if not class.type_of( value, expected_type ) then
- Exception.throw( IncorrectConstructorException, _class:type() .. " expects " .. expected_type:type() .. " " .. name .. " when created, got " .. class.type( value ), 4 )
- end
- end
- end
- end
- function parameters.check( argc, ... )
- local args = { ... }
- for i = 1, argc * 3, 3 do
- local name = args[i]
- local expected_type = args[i + 1]
- local value = args[i + 2]
- if type( expected_type ) == "string" then
- if type( value ) ~= expected_type then
- Exception.throw( IncorrectParameterException, "expected " .. expected_type .. " " .. name .. ", got " .. class.type( value ), 3 )
- end
- else
- if not class.type_of( value, expected_type ) then
- Exception.throw( IncorrectParameterException, "expected " .. expected_type:type() .. " " .. name .. ", got " .. class.type( value ), 3 )
- end
- end
- end
- end
- surface = { } do
- --[[
- Surface version 2.0.0
- The MIT License (MIT)
- Copyright (c) 2017 CrazedProgrammer
- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
- associated documentation files (the "Software"), to deal in the Software without restriction,
- table: 19526e4 without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
- and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do
- so, subject to the following conditions:
- The above copyright notice and this permission notice shall be included in all copies or
- substantial portions of the Software.
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
- INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
- PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
- COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
- AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- ]]
- local surf = { }
- surface.surf = surf
- local table_concat, math_floor, math_atan2 = table.concat, math.floor, math.atan2
- local _cc_color_to_hex, _cc_hex_to_color = { }, { }
- for i = 0, 15 do
- _cc_color_to_hex[2 ^ i] = string.format("%01x", i)
- _cc_hex_to_color[string.format("%01x", i)] = 2 ^ i
- end
- local _chars = { }
- for i = 0, 255 do
- _chars[i] = string.char(i)
- end
- local _eprc, _esin, _ecos = 20, { }, { }
- for i = 0, _eprc - 1 do
- _esin[i + 1] = (1 - math.sin(i / _eprc * math.pi * 2)) / 2
- _ecos[i + 1] = (1 + math.cos(i / _eprc * math.pi * 2)) / 2
- end
- local _steps, _palette, _rgbpal, _palr, _palg, _palb = 16
- local function calcStack(stack, width, height)
- local ox, oy, cx, cy, cwidth, cheight = 0, 0, 0, 0, width, height
- for i = 1, #stack do
- ox = ox + stack[i].ox
- oy = oy + stack[i].oy
- cx = cx + stack[i].x
- cy = cy + stack[i].y
- cwidth = stack[i].width
- cheight = stack[i].height
- end
- return ox, oy, cx, cy, cwidth, cheight
- end
- local function clipRect(x, y, width, height, cx, cy, cwidth, cheight)
- if x < cx then
- width = width + x - cx
- x = cx
- end
- if y < cy then
- height = height + y - cy
- y = cy
- end
- if x + width > cx + cwidth then
- width = cwidth + cx - x
- end
- if y + height > cy + cheight then
- height = cheight + cy - y
- end
- return x, y, width, height
- end
- function surface.create(width, height, b, t, c)
- local surface = setmetatable({ }, {__index = surface.surf})
- surface.width = width
- surface.height = height
- surface.buffer = { }
- surface.overwrite = false
- surface.stack = { }
- surface.ox, surface.oy, surface.cx, surface.cy, surface.cwidth, surface.cheight = calcStack(surface.stack, width, height)
- -- force array indeces instead of hashed indices
- local buffer = surface.buffer
- for i = 1, width * height * 3, 3 do
- buffer[i] = b or false
- buffer[i + 1] = t or false
- buffer[i + 2] = c or false
- end
- buffer[width * height * 3 + 1] = false
- if not b then
- for i = 1, width * height * 3, 3 do
- buffer[i] = b
- end
- end
- if not t then
- for i = 2, width * height * 3, 3 do
- buffer[i] = t
- end
- end
- if not c then
- for i = 3, width * height * 3, 3 do
- buffer[i] = c
- end
- end
- return surface
- end
- function surf:output(output, x, y, sx, sy, swidth, sheight)
- output = output or (term or gpu)
- if love then output = output or love.graphics end
- x = x or 0
- y = y or 0
- sx = sx or 0
- sy = sy or 0
- swidth = swidth or self.width
- sheight = sheight or self.height
- sx, sy, swidth, sheight = clipRect(sx, sy, swidth, sheight, 0, 0, self.width, self.height)
- local buffer = self.buffer
- local bwidth = self.width
- local xoffset, yoffset, idx
- if output.blit and output.setCursorPos then
- -- CC
- local cmd, str, text, back = { }, { }, { }, { }
- for j = 0, sheight - 1 do
- yoffset = (j + sy) * bwidth + sx
- for i = 0, swidth - 1 do
- xoffset = (yoffset + i) * 3
- idx = i + 1
- str[idx] = buffer[xoffset + 3] or " "
- text[idx] = _cc_color_to_hex[buffer[xoffset + 2] or 1]
- back[idx] = _cc_color_to_hex[buffer[xoffset + 1] or 32768]
- end
- output.setCursorPos(x + 1, y + j + 1)
- output.blit(table_concat(str), table_concat(text), table_concat(back))
- end
- elseif output.write and output.setCursorPos and output.setTextColor and output.setBackgroundColor then
- -- CC pre-1.76
- local str, b, t, pb, pt = { }
- for j = 0, sheight - 1 do
- output.setCursorPos(x + 1, y + j + 1)
- yoffset = (j + sy) * bwidth + sx
- for i = 0, swidth - 1 do
- xoffset = (yoffset + i) * 3
- pb = buffer[xoffset + 1] or 32768
- pt = buffer[xoffset + 2] or 1
- if pb ~= b then
- if #str ~= 0 then
- output.write(table_concat(str))
- str = { }
- end
- b = pb
- output.setBackgroundColor(b)
- end
- if pt ~= t then
- if #str ~= 0 then
- output.write(table_concat(str))
- str = { }
- end
- t = pt
- output.setTextColor(t)
- end
- str[#str + 1] = buffer[xoffset + 3] or " "
- end
- output.write(table_concat(str))
- str = { }
- end
- elseif output.blitPixels then
- -- Riko 4
- local pixels = { }
- for j = 0, sheight - 1 do
- yoffset = (j + sy) * bwidth + sx
- for i = 0, swidth - 1 do
- pixels[j * swidth + i + 1] = buffer[(yoffset + i) * 3 + 1] or 0
- end
- end
- output.blitPixels(x, y, swidth, sheight, pixels)
- elseif output.points and output.setColor then
- -- Love2D
- local pos, r, g, b, pr, pg, pb = { }
- for j = 0, sheight - 1 do
- yoffset = (j + sy) * bwidth + sx
- for i = 0, swidth - 1 do
- xoffset = (yoffset + i) * 3
- pr = buffer[xoffset + 1]
- pg = buffer[xoffset + 2]
- pb = buffer[xoffset + 3]
- if pr ~= r or pg ~= g or pb ~= b then
- if #pos ~= 0 then
- output.setColor((r or 0) * 255, (g or 0) * 255, (b or 0) * 255, (r or g or b) and 255 or 0)
- output.points(pos)
- end
- r, g, b = pr, pg, pb
- pos = { }
- end
- pos[#pos + 1] = i + x
- pos[#pos + 1] = j + y
- end
- end
- elseif output.drawPixel then
- -- Redirection arcade (gpu)
- -- todo: add image:write support for extra performance
- local px = output.drawPixel
- for j = 0, sheight - 1 do
- for i = 0, swidth - 1 do
- px(x + i, y + j, buffer[((j + sy) * bwidth + (i + sx)) * 3 + 1] or 0)
- end
- end
- else
- error("unsupported output object")
- end
- end
- function surf:push(x, y, width, height, nooffset)
- x, y = x + self.ox, y + self.oy
- local ox, oy = nooffset and self.ox or x, nooffset and self.oy or y
- x, y, width, height = clipRect(x, y, width, height, self.cx, self.cy, self.cwidth, self.cheight)
- self.stack[#self.stack + 1] = {ox = ox - self.ox, oy = oy - self.oy, x = x - self.cx, y = y - self.cy, width = width, height = height}
- self.ox, self.oy, self.cx, self.cy, self.cwidth, self.cheight = calcStack(self.stack, self.width, self.height)
- end
- function surf:pop()
- if #self.stack == 0 then
- error("no stencil to pop")
- end
- self.stack[#self.stack] = nil
- self.ox, self.oy, self.cx, self.cy, self.cwidth, self.cheight = calcStack(self.stack, self.width, self.height)
- end
- function surf:copy()
- local surface = setmetatable({ }, {__index = surface.surf})
- for k, v in pairs(self) do
- surface[k] = v
- end
- surface.buffer = { }
- for i = 1, self.width * self.height * 3 + 1 do
- surface.buffer[i] = false
- end
- for i = 1, self.width * self.height * 3 do
- surface.buffer[i] = self.buffer[i]
- end
- surface.stack = { }
- for i = 1, #self.stack do
- surface.stack[i] = self.stack[i]
- end
- return surface
- end
- function surf:clear(b, t, c)
- local xoffset, yoffset
- for j = 0, self.cheight - 1 do
- yoffset = (j + self.cy) * self.width + self.cx
- for i = 0, self.cwidth - 1 do
- xoffset = (yoffset + i) * 3
- self.buffer[xoffset + 1] = b
- self.buffer[xoffset + 2] = t
- self.buffer[xoffset + 3] = c
- end
- end
- end
- function surf:drawPixel(x, y, b, t, c)
- x, y = x + self.ox, y + self.oy
- local idx
- if x >= self.cx and x < self.cx + self.cwidth and y >= self.cy and y < self.cy + self.cheight then
- idx = (y * self.width + x) * 3
- if b or self.overwrite then
- self.buffer[idx + 1] = b
- end
- if t or self.overwrite then
- self.buffer[idx + 2] = t
- end
- if c or self.overwrite then
- self.buffer[idx + 3] = c
- end
- end
- end
- function surf:drawString(x, y, str, b, t)
- x, y = x + self.ox, y + self.oy
- local sx = x
- local insidey = y >= self.cy and y < self.cy + self.cheight
- local idx
- local lowerxlim = self.cx
- local upperxlim = self.cx + self.cwidth
- local writeb = b or self.overwrite
- local writet = t or self.overwrite
- for i = 1, #str do
- local c = str:sub(i, i)
- if c == "\n" then
- x = sx
- y = y + 1
- if insidey then
- if y >= self.cy + self.cheight then
- return
- end
- else
- insidey = y >= self.cy
- end
- else
- idx = (y * self.width + x) * 3
- if x >= lowerxlim and x < upperxlim and insidey then
- if writeb then
- self.buffer[idx + 1] = b
- end
- if writet then
- self.buffer[idx + 2] = t
- end
- self.buffer[idx + 3] = c
- end
- x = x + 1
- end
- end
- end
- -- You can remove any of these components
- function surface.load(strpath, isstr)
- local data = strpath
- if not isstr then
- local handle = io.open(strpath, "rb")
- if not handle then return end
- chars = { }
- local byte = handle:read(1)
- while byte do
- chars[#chars + 1] = _chars[byte]
- byte = handle:read(1)
- end
- handle:close()
- data = table_concat(chars)
- end
- if data:sub(1, 3) == "RIF" then
- -- Riko 4 image format
- local width, height = data:byte(4) * 256 + data:byte(5), data:byte(6) * 256 + data:byte(7)
- local surf = surface.create(width, height)
- local buffer = surf.buffer
- local upper, byte = 8, false
- local byte = data:byte(index)
- for j = 0, height - 1 do
- for i = 0, height - 1 do
- if not upper then
- buffer[(j * width + i) * 3 + 1] = math_floor(byte / 16)
- else
- buffer[(j * width + i) * 3 + 1] = byte % 16
- index = index + 1
- data = data:byte(index)
- end
- upper = not upper
- end
- end
- return surf
- elseif data:sub(1, 2) == "BM" then
- -- BMP format
- local width = data:byte(0x13) + data:byte(0x14) * 256
- local height = data:byte(0x17) + data:byte(0x18) * 256
- if data:byte(0xF) ~= 0x28 or data:byte(0x1B) ~= 1 or data:byte(0x1D) ~= 0x18 then
- error("unsupported bmp format, only uncompressed 24-bit rgb is supported.")
- end
- local offset, linesize = 0x36, math.ceil((width * 3) / 4) * 4
- local surf = surface.create(width, height)
- local buffer = surf.buffer
- for j = 0, height - 1 do
- for i = 0, width - 1 do
- buffer[(j * width + i) * 3 + 1] = data:byte((height - j - 1) * linesize + i * 3 + offset + 3) / 255
- buffer[(j * width + i) * 3 + 2] = data:byte((height - j - 1) * linesize + i * 3 + offset + 2) / 255
- buffer[(j * width + i) * 3 + 3] = data:byte((height - j - 1) * linesize + i * 3 + offset + 1) / 255
- end
- end
- return surf
- elseif data:find("\30") then
- -- NFT format
- local width, height, lwidth = 0, 1, 0
- for i = 1, #data do
- if data:byte(i) == 10 then -- newline
- height = height + 1
- if lwidth > width then
- width = lwidth
- end
- lwidth = 0
- elseif data:byte(i) == 30 or data:byte(i) == 31 then -- color control
- lwidth = lwidth - 1
- elseif data:byte(i) ~= 13 then -- not carriage return
- lwidth = lwidth + 1
- end
- end
- if data:byte(#data) == 10 then
- height = height - 1
- end
- local surf = surface.create(width, height)
- local buffer = surf.buffer
- local index, x, y, b, t = 1, 0, 0
- while index <= #data do
- if data:byte(index) == 10 then
- x, y = 0, y + 1
- elseif data:byte(index) == 30 then
- index = index + 1
- b = _cc_hex_to_color[data:sub(index, index)]
- elseif data:byte(index) == 31 then
- index = index + 1
- t = _cc_hex_to_color[data:sub(index, index)]
- elseif data:byte(index) ~= 13 then
- buffer[(y * width + x) * 3 + 1] = b
- buffer[(y * width + x) * 3 + 2] = t
- if b or t then
- buffer[(y * width + x) * 3 + 3] = data:sub(index, index)
- elseif data:sub(index, index) ~= " " then
- buffer[(y * width + x) * 3 + 3] = data:sub(index, index)
- end
- x = x + 1
- end
- index = index + 1
- end
- return surf
- else
- -- NFP format
- local width, height, lwidth = 0, 1, 0
- for i = 1, #data do
- if data:byte(i) == 10 then -- newline
- height = height + 1
- if lwidth > width then
- width = lwidth
- end
- lwidth = 0
- elseif data:byte(i) ~= 13 then -- not carriage return
- lwidth = lwidth + 1
- end
- end
- if data:byte(#data) == 10 then
- height = height - 1
- end
- local surf = surface.create(width, height)
- local buffer = surf.buffer
- local x, y = 0, 0
- for i = 1, #data do
- if data:byte(i) == 10 then
- x, y = 0, y + 1
- elseif data:byte(i) ~= 13 then
- buffer[(y * width + x) * 3 + 1] = _cc_hex_to_color[data:sub(i, i)]
- x = x + 1
- end
- end
- return surf
- end
- end
- function surf:save(file, format)
- format = format or "nfp"
- local data = { }
- if format == "nfp" then
- for j = 0, self.height - 1 do
- for i = 0, self.width - 1 do
- data[#data + 1] = _cc_color_to_hex[self.buffer[(j * self.width + i) * 3 + 1]] or " "
- end
- data[#data + 1] = "\n"
- end
- elseif format == "nft" then
- for j = 0, self.height - 1 do
- local b, t, pb, pt
- for i = 0, self.width - 1 do
- pb = self.buffer[(j * self.width + i) * 3 + 1]
- pt = self.buffer[(j * self.width + i) * 3 + 2]
- if pb ~= b then
- data[#data + 1] = "\30"..(_cc_color_to_hex[pb] or " ")
- b = pb
- end
- if pt ~= t then
- data[#data + 1] = "\31"..(_cc_color_to_hex[pt] or " ")
- t = pt
- end
- data[#data + 1] = self.buffer[(j * self.width + i) * 3 + 3] or " "
- end
- data[#data + 1] = "\n"
- end
- elseif format == "rif" then
- data[1] = "RIF"
- data[2] = string.char(math_floor(self.width / 256), self.width % 256)
- data[3] = string.char(math_floor(self.height / 256), self.height % 256)
- local byte, upper, c = 0, false
- for j = 0, self.width - 1 do
- for i = 0, self.height - 1 do
- c = self.buffer[(j * self.width + i) * 3 + 1] or 0
- if not upper then
- byte = c * 16
- else
- byte = byte + c
- data[#data + 1] = string.char(byte)
- end
- upper = not upper
- end
- end
- if upper then
- data[#data + 1] = string.char(byte)
- end
- elseif format == "bmp" then
- data[1] = "BM"
- data[2] = string.char(0, 0, 0, 0) -- file size, change later
- data[3] = string.char(0, 0, 0, 0, 0x36, 0, 0, 0, 0x28, 0, 0, 0)
- data[4] = string.char(self.width % 256, math_floor(self.width / 256), 0, 0)
- data[5] = string.char(self.height % 256, math_floor(self.height / 256), 0, 0)
- data[6] = string.char(1, 0, 0x18, 0, 0, 0, 0, 0)
- data[7] = string.char(0, 0, 0, 0) -- pixel data size, change later
- data[8] = string.char(0x13, 0x0B, 0, 0, 0x13, 0x0B, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
- local padchars = math.ceil((self.width * 3) / 4) * 4 - self.width * 3
- for j = 0, self.height - 1 do
- for i = 0, self.width - 1 do
- data[#data + 1] = string.char((self.buffer[(j * self.width + i) * 3 + 1] or 0) * 255)
- data[#data + 1] = string.char((self.buffer[(j * self.width + i) * 3 + 2] or 0) * 255)
- data[#data + 1] = string.char((self.buffer[(j * self.width + i) * 3 + 3] or 0) * 255)
- end
- data[#data + 1] = ("\0"):rep(padchars)
- end
- local size = #table_concat(data)
- data[2] = string.char(size % 256, math_floor(size / 256) % 256, math_floor(size / 65536), 0)
- size = size - 54
- data[7] = string.char(size % 256, math_floor(size / 256) % 256, math_floor(size / 65536), 0)
- else
- error("format not supported")
- end
- data = table_concat(data)
- if file then
- local handle = io.open(file, "wb")
- for i = 1, #data do
- handle:write(data:byte(i))
- end
- handle:close()
- end
- return data
- end
- function surf:drawLine(x1, y1, x2, y2, b, t, c)
- if x1 == x2 then
- x1, y1, x2, y2 = x1 + self.ox, y1 + self.oy, x2 + self.ox, y2 + self.oy
- if x1 < self.cx or x1 >= self.cx + self.cwidth then return end
- if y2 < y1 then
- local temp = y1
- y1 = y2
- y2 = temp
- end
- if y1 < self.cy then y1 = self.cy end
- if y2 >= self.cy + self.cheight then y2 = self.cy + self.cheight - 1 end
- if b or self.overwrite then
- for j = y1, y2 do
- self.buffer[(j * self.width + x1) * 3 + 1] = b
- end
- end
- if t or self.overwrite then
- for j = y1, y2 do
- self.buffer[(j * self.width + x1) * 3 + 2] = t
- end
- end
- if c or self.overwrite then
- for j = y1, y2 do
- self.buffer[(j * self.width + x1) * 3 + 3] = c
- end
- end
- elseif y1 == y2 then
- x1, y1, x2, y2 = x1 + self.ox, y1 + self.oy, x2 + self.ox, y2 + self.oy
- if y1 < self.cy or y1 >= self.cy + self.cheight then return end
- if x2 < x1 then
- local temp = x1
- x1 = x2
- x2 = temp
- end
- if x1 < self.cx then x1 = self.cx end
- if x2 >= self.cx + self.cwidth then x2 = self.cx + self.cwidth - 1 end
- if b or self.overwrite then
- for i = x1, x2 do
- self.buffer[(y1 * self.width + i) * 3 + 1] = b
- end
- end
- if t or self.overwrite then
- for i = x1, x2 do
- self.buffer[(y1 * self.width + i) * 3 + 2] = t
- end
- end
- if c or self.overwrite then
- for i = x1, x2 do
- self.buffer[(y1 * self.width + i) * 3 + 3] = c
- end
- end
- else
- local delta_x = x2 - x1
- local ix = delta_x > 0 and 1 or -1
- delta_x = 2 * math.abs(delta_x)
- local delta_y = y2 - y1
- local iy = delta_y > 0 and 1 or -1
- delta_y = 2 * math.abs(delta_y)
- self:drawPixel(x1, y1, b, t, c)
- if delta_x >= delta_y then
- local error = delta_y - delta_x / 2
- while x1 ~= x2 do
- if (error >= 0) and ((error ~= 0) or (ix > 0)) then
- error = error - delta_x
- y1 = y1 + iy
- end
- error = error + delta_y
- x1 = x1 + ix
- self:drawPixel(x1, y1, b, t, c)
- end
- else
- local error = delta_x - delta_y / 2
- while y1 ~= y2 do
- if (error >= 0) and ((error ~= 0) or (iy > 0)) then
- error = error - delta_y
- x1 = x1 + ix
- end
- error = error + delta_x
- y1 = y1 + iy
- self:drawPixel(x1, y1, b, t, c)
- end
- end
- end
- end
- function surf:drawRect(x, y, width, height, b, t, c)
- self:drawLine(x, y, x + width - 1, y, b, t, c)
- self:drawLine(x, y, x, y + height - 1, b, t, c)
- self:drawLine(x + width - 1, y, x + width - 1, y + height - 1, b, t, c)
- self:drawLine(x, y + height - 1, x + width - 1, y + height - 1, b, t, c)
- end
- function surf:fillRect(x, y, width, height, b, t, c)
- x, y, width, height = clipRect(x + self.ox, y + self.oy, width, height, self.cx, self.cy, self.cwidth, self.cheight)
- if b or self.overwrite then
- for j = 0, height - 1 do
- for i = 0, width - 1 do
- self.buffer[((j + y) * self.width + i + x) * 3 + 1] = b
- end
- end
- end
- if t or self.overwrite then
- for j = 0, height - 1 do
- for i = 0, width - 1 do
- self.buffer[((j + y) * self.width + i + x) * 3 + 2] = t
- end
- end
- end
- if c or self.overwrite then
- for j = 0, height - 1 do
- for i = 0, width - 1 do
- self.buffer[((j + y) * self.width + i + x) * 3 + 3] = c
- end
- end
- end
- end
- function surf:drawTriangle(x1, y1, x2, y2, x3, y3, b, t, c)
- self:drawLine(x1, y1, x2, y2, b, t, c)
- self:drawLine(x2, y2, x3, y3, b, t, c)
- self:drawLine(x3, y3, x1, y1, b, t, c)
- end
- function surf:fillTriangle(x1, y1, x2, y2, x3, y3, b, t, c)
- if y1 > y2 then
- local tempx, tempy = x1, y1
- x1, y1 = x2, y2
- x2, y2 = tempx, tempy
- end
- if y1 > y3 then
- local tempx, tempy = x1, y1
- x1, y1 = x3, y3
- x3, y3 = tempx, tempy
- end
- if y2 > y3 then
- local tempx, tempy = x2, y2
- x2, y2 = x3, y3
- x3, y3 = tempx, tempy
- end
- if y1 == y2 and x1 > x2 then
- local temp = x1
- x1 = x2
- x2 = temp
- end
- if y2 == y3 and x2 > x3 then
- local temp = x2
- x2 = x3
- x3 = temp
- end
- local x4, y4
- if x1 <= x2 then
- x4 = x1 + (y2 - y1) / (y3 - y1) * (x3 - x1)
- y4 = y2
- local tempx, tempy = x2, y2
- x2, y2 = x4, y4
- x4, y4 = tempx, tempy
- else
- x4 = x1 + (y2 - y1) / (y3 - y1) * (x3 - x1)
- y4 = y2
- end
- local finvslope1 = (x2 - x1) / (y2 - y1)
- local finvslope2 = (x4 - x1) / (y4 - y1)
- local linvslope1 = (x3 - x2) / (y3 - y2)
- local linvslope2 = (x3 - x4) / (y3 - y4)
- local xstart, xend, dxstart, dxend
- for y = math.ceil(y1 + 0.5) - 0.5, math.floor(y3 - 0.5) + 0.5, 1 do
- if y <= y2 then -- first half
- xstart = x1 + finvslope1 * (y - y1)
- xend = x1 + finvslope2 * (y - y1)
- else -- second half
- xstart = x3 - linvslope1 * (y3 - y)
- xend = x3 - linvslope2 * (y3 - y)
- end
- dxstart, dxend = math.ceil(xstart - 0.5), math.floor(xend - 0.5)
- if dxstart <= dxend then
- self:drawLine(dxstart, y - 0.5, dxend, y - 0.5, b, t, c)
- end
- end
- end
- function surf:drawEllipse(x, y, width, height, b, t, c)
- for i = 0, _eprc - 1 do
- self:drawLine(math_floor(x + _ecos[i + 1] * (width - 1) + 0.5), math_floor(y + _esin[i + 1] * (height - 1) + 0.5), math_floor(x + _ecos[(i + 1) % _eprc + 1] * (width - 1) + 0.5), math_floor(y + _esin[(i + 1) % _eprc + 1] * (height - 1) + 0.5), b, t, c)
- end
- end
- function surf:fillEllipse(x, y, width, height, b, t, c)
- x, y = x + self.ox, y + self.oy
- for j = 0, height - 1 do
- for i = 0, width - 1 do
- if ((i + 0.5) / width * 2 - 1) ^ 2 + ((j + 0.5) / height * 2 - 1) ^ 2 <= 1 then
- if b or self.overwrite then
- self.buffer[((j + y) * self.width + i + x) * 3 + 1] = b
- end
- if t or self.overwrite then
- self.buffer[((j + y) * self.width + i + x) * 3 + 2] = t
- end
- if c or self.overwrite then
- self.buffer[((j + y) * self.width + i + x) * 3 + 3] = c
- end
- end
- end
- end
- end
- function surf:drawArc(x, y, width, height, fromangle, toangle, b, t, c)
- if fromangle > toangle then
- local temp = fromangle
- fromangle = toangle
- temp = toangle
- end
- fromangle = math_floor(fromangle / math.pi / 2 * _eprc + 0.5)
- toangle = math_floor(toangle / math.pi / 2 * _eprc + 0.5) - 1
- for j = fromangle, toangle do
- local i = j % _eprc
- self:drawLine(math_floor(x + _ecos[i + 1] * (width - 1) + 0.5), math_floor(y + _esin[i + 1] * (height - 1) + 0.5), math_floor(x + _ecos[(i + 1) % _eprc + 1] * (width - 1) + 0.5), math_floor(y + _esin[(i + 1) % _eprc + 1] * (height - 1) + 0.5), b, t, c)
- end
- end
- function surf:fillArc(x, y, width, height, fromangle, toangle, b, t, c)
- x, y = x + self.ox, y + self.oy
- if fromangle > toangle then
- local temp = fromangle
- fromangle = toangle
- temp = toangle
- end
- local diff = toangle - fromangle
- fromangle = fromangle % (math.pi * 2)
- local fx, fy, dir
- for j = 0, height - 1 do
- for i = 0, width - 1 do
- fx, fy = (i + 0.5) / width * 2 - 1, (j + 0.5) / height * 2 - 1
- dir = math_atan2(-fy, fx) % (math.pi * 2)
- if fx ^ 2 + fy ^ 2 <= 1 and ((dir >= fromangle and dir - fromangle <= diff) or (dir <= (fromangle + diff) % (math.pi * 2))) then
- if b or self.overwrite then
- self.buffer[((j + y) * self.width + i + x) * 3 + 1] = b
- end
- if t or self.overwrite then
- self.buffer[((j + y) * self.width + i + x) * 3 + 2] = t
- end
- if c or self.overwrite then
- self.buffer[((j + y) * self.width + i + x) * 3 + 3] = c
- end
- end
- end
- end
- end
- function surf:drawSurface(surf2, x, y, width, height, sx, sy, swidth, sheight)
- x, y, width, height, sx, sy, swidth, sheight = x + self.ox, y + self.oy, width or surf2.width, height or surf2.height, sx or 0, sy or 0, swidth or surf2.width, sheight or surf2.height
- if width == swidth and height == sheight then
- local nx, ny
- nx, ny, width, height = clipRect(x, y, width, height, self.cx, self.cy, self.cwidth, self.cheight)
- swidth, sheight = width, height
- if nx > x then
- sx = sx + nx - x
- x = nx
- end
- if ny > y then
- sy = sy + ny - y
- y = ny
- end
- nx, ny, swidth, sheight = clipRect(sx, sy, swidth, sheight, 0, 0, surf2.width, surf2.height)
- width, height = swidth, sheight
- if nx > sx then
- x = x + nx - sx
- sx = nx
- end
- if ny > sy then
- y = y + ny - sy
- sy = ny
- end
- local b, t, c
- for j = 0, height - 1 do
- for i = 0, width - 1 do
- b = surf2.buffer[((j + sy) * surf2.width + i + sx) * 3 + 1]
- t = surf2.buffer[((j + sy) * surf2.width + i + sx) * 3 + 2]
- c = surf2.buffer[((j + sy) * surf2.width + i + sx) * 3 + 3]
- if b or self.overwrite then
- self.buffer[((j + y) * self.width + i + x) * 3 + 1] = b
- end
- if t or self.overwrite then
- self.buffer[((j + y) * self.width + i + x) * 3 + 2] = t
- end
- if c or self.overwrite then
- self.buffer[((j + y) * self.width + i + x) * 3 + 3] = c
- end
- end
- end
- else
- local hmirror, vmirror = false, false
- if width < 0 then
- hmirror = true
- x = x + width
- end
- if height < 0 then
- vmirror = true
- y = y + height
- end
- if swidth < 0 then
- hmirror = not hmirror
- sx = sx + swidth
- end
- if sheight < 0 then
- vmirror = not vmirror
- sy = sy + sheight
- end
- width, height, swidth, sheight = math.abs(width), math.abs(height), math.abs(swidth), math.abs(sheight)
- local xscale, yscale, px, py, ssx, ssy, b, t, c = swidth / width, sheight / height
- for j = 0, height - 1 do
- for i = 0, width - 1 do
- px, py = math_floor((i + 0.5) * xscale), math_floor((j + 0.5) * yscale)
- if hmirror then
- ssx = x + width - i - 1
- else
- ssx = i + x
- end
- if vmirror then
- ssy = y + height - j - 1
- else
- ssy = j + y
- end
- if ssx >= self.cx and ssx < self.cx + self.cwidth and ssy >= self.cy and ssy < self.cy + self.cheight and px >= 0 and px < surf2.width and py >= 0 and py < surf2.height then
- b = surf2.buffer[(py * surf2.width + px) * 3 + 1]
- t = surf2.buffer[(py * surf2.width + px) * 3 + 2]
- c = surf2.buffer[(py * surf2.width + px) * 3 + 3]
- if b or self.overwrite then
- self.buffer[(ssy * self.width + ssx) * 3 + 1] = b
- end
- if t or self.overwrite then
- self.buffer[(ssy * self.width + ssx) * 3 + 2] = t
- end
- if c or self.overwrite then
- self.buffer[(ssy * self.width + ssx) * 3 + 3] = c
- end
- end
- end
- end
- end
- end
- function surf:drawSurfaceRotated(surf2, x, y, ox, oy, angle)
- local sin, cos, sx, sy, px, py = math.sin(angle), math.cos(angle)
- for j = math.floor(-surf2.height * 0.75), math.ceil(surf2.height * 0.75) do
- for i = math.floor(-surf2.width * 0.75), math.ceil(surf2.width * 0.75) do
- sx, sy, px, py = x + i, y + j, math_floor(cos * (i + 0.5) - sin * (j + 0.5) + ox), math_floor(sin * (i + 0.5) + cos * (j + 0.5) + oy)
- if sx >= self.cx and sx < self.cx + self.cwidth and sy >= self.cy and sy < self.cy + self.cheight and px >= 0 and px < surf2.width and py >= 0 and py < surf2.height then
- b = surf2.buffer[(py * surf2.width + px) * 3 + 1]
- t = surf2.buffer[(py * surf2.width + px) * 3 + 2]
- c = surf2.buffer[(py * surf2.width + px) * 3 + 3]
- if b or self.overwrite then
- self.buffer[(sy * self.width + sx) * 3 + 1] = b
- end
- if t or self.overwrite then
- self.buffer[(sy * self.width + sx) * 3 + 2] = t
- end
- if c or self.overwrite then
- self.buffer[(sy * self.width + sx) * 3 + 3] = c
- end
- end
- end
- end
- end
- function surf:drawSurfacesInterlaced(surfs, x, y, step)
- x, y, step = x + self.ox, y + self.oy, step or 0
- local width, height = surfs[1].width, surfs[1].height
- for i = 2, #surfs do
- if surfs[i].width ~= width or surfs[i].height ~= height then
- error("surfaces should be the same size")
- end
- end
- local sx, sy, swidth, sheight, index, b, t, c = clipRect(x, y, width, height, self.cx, self.cy, self.cwidth, self.cheight)
- for j = sy, sy + sheight - 1 do
- for i = sx, sx + swidth - 1 do
- index = (i + j + step) % #surfs + 1
- b = surfs[index].buffer[((j - sy) * surfs[index].width + i - sx) * 3 + 1]
- t = surfs[index].buffer[((j - sy) * surfs[index].width + i - sx) * 3 + 2]
- c = surfs[index].buffer[((j - sy) * surfs[index].width + i - sx) * 3 + 3]
- if b or self.overwrite then
- self.buffer[(j * self.width + i) * 3 + 1] = b
- end
- if t or self.overwrite then
- self.buffer[(j * self.width + i) * 3 + 2] = t
- end
- if c or self.overwrite then
- self.buffer[(j * self.width + i) * 3 + 3] = c
- end
- end
- end
- end
- function surf:drawSurfaceSmall(surf2, x, y)
- x, y = x + self.ox, y + self.oy
- if surf2.width % 2 ~= 0 or surf2.height % 3 ~= 0 then
- error("surface width must be a multiple of 2 and surface height a multiple of 3")
- end
- local sub, char, c1, c2, c3, c4, c5, c6 = 32768
- for j = 0, surf2.height / 3 - 1 do
- for i = 0, surf2.width / 2 - 1 do
- if i + x >= self.cx and i + x < self.cx + self.cwidth and j + y >= self.cy and j + y < self.cy + self.cheight then
- char, c1, c2, c3, c4, c5, c6 = 0,
- surf2.buffer[((j * 3) * surf2.width + i * 2) * 3 + 1],
- surf2.buffer[((j * 3) * surf2.width + i * 2 + 1) * 3 + 1],
- surf2.buffer[((j * 3 + 1) * surf2.width + i * 2) * 3 + 1],
- surf2.buffer[((j * 3 + 1) * surf2.width + i * 2 + 1) * 3 + 1],
- surf2.buffer[((j * 3 + 2) * surf2.width + i * 2) * 3 + 1],
- surf2.buffer[((j * 3 + 2) * surf2.width + i * 2 + 1) * 3 + 1]
- if c1 ~= c6 then
- sub = c1
- char = 1
- end
- if c2 ~= c6 then
- sub = c2
- char = char + 2
- end
- if c3 ~= c6 then
- sub = c3
- char = char + 4
- end
- if c4 ~= c6 then
- sub = c4
- char = char + 8
- end
- if c5 ~= c6 then
- sub = c5
- char = char + 16
- end
- self.buffer[((j + y) * self.width + i + x) * 3 + 1] = c6
- self.buffer[((j + y) * self.width + i + x) * 3 + 2] = sub
- self.buffer[((j + y) * self.width + i + x) * 3 + 3] = _chars[128 + char]
- end
- end
- end
- end
- function surf:flip(horizontal, vertical)
- local ox, oy, nx, ny, tb, tt, tc
- if horizontal then
- for i = 0, math.ceil(self.cwidth / 2) - 1 do
- for j = 0, self.cheight - 1 do
- ox, oy, nx, ny = i + self.cx, j + self.cy, self.cx + self.cwidth - i - 1, j + self.cy
- tb = self.buffer[(oy * self.width + ox) * 3 + 1]
- tt = self.buffer[(oy * self.width + ox) * 3 + 2]
- tc = self.buffer[(oy * self.width + ox) * 3 + 3]
- self.buffer[(oy * self.width + ox) * 3 + 1] = self.buffer[(ny * self.width + nx) * 3 + 1]
- self.buffer[(oy * self.width + ox) * 3 + 2] = self.buffer[(ny * self.width + nx) * 3 + 2]
- self.buffer[(oy * self.width + ox) * 3 + 3] = self.buffer[(ny * self.width + nx) * 3 + 3]
- self.buffer[(ny * self.width + nx) * 3 + 1] = tb
- self.buffer[(ny * self.width + nx) * 3 + 2] = tt
- self.buffer[(ny * self.width + nx) * 3 + 3] = tc
- end
- end
- end
- if vertical then
- for j = 0, math.ceil(self.cheight / 2) - 1 do
- for i = 0, self.cwidth - 1 do
- ox, oy, nx, ny = i + self.cx, j + self.cy, i + self.cx, self.cy + self.cheight - j - 1
- tb = self.buffer[(oy * self.width + ox) * 3 + 1]
- tt = self.buffer[(oy * self.width + ox) * 3 + 2]
- tc = self.buffer[(oy * self.width + ox) * 3 + 3]
- self.buffer[(oy * self.width + ox) * 3 + 1] = self.buffer[(ny * self.width + nx) * 3 + 1]
- self.buffer[(oy * self.width + ox) * 3 + 2] = self.buffer[(ny * self.width + nx) * 3 + 2]
- self.buffer[(oy * self.width + ox) * 3 + 3] = self.buffer[(ny * self.width + nx) * 3 + 3]
- self.buffer[(ny * self.width + nx) * 3 + 1] = tb
- self.buffer[(ny * self.width + nx) * 3 + 2] = tt
- self.buffer[(ny * self.width + nx) * 3 + 3] = tc
- end
- end
- end
- end
- function surf:shift(x, y, b, t, c)
- local hdir, vdir = x < 0, y < 0
- local xstart, xend = self.cx, self.cx + self.cwidth - 1
- local ystart, yend = self.cy, self.cy + self.cheight - 1
- local nx, ny
- for j = vdir and ystart or yend, vdir and yend or ystart, vdir and 1 or -1 do
- for i = hdir and xstart or xend, hdir and xend or xstart, hdir and 1 or -1 do
- nx, ny = i - x, j - y
- if nx >= 0 and nx < self.width and ny >= 0 and ny < self.height then
- self.buffer[(j * self.width + i) * 3 + 1] = self.buffer[(ny * self.width + nx) * 3 + 1]
- self.buffer[(j * self.width + i) * 3 + 2] = self.buffer[(ny * self.width + nx) * 3 + 2]
- self.buffer[(j * self.width + i) * 3 + 3] = self.buffer[(ny * self.width + nx) * 3 + 3]
- else
- self.buffer[(j * self.width + i) * 3 + 1] = b
- self.buffer[(j * self.width + i) * 3 + 2] = t
- self.buffer[(j * self.width + i) * 3 + 3] = c
- end
- end
- end
- end
- function surf:map(colors)
- local c
- for j = self.cy, self.cy + self.cheight - 1 do
- for i = self.cx, self.cx + self.cwidth - 1 do
- c = colors[self.buffer[(j * self.width + i) * 3 + 1]]
- if c or self.overwrite then
- self.buffer[(j * self.width + i) * 3 + 1] = c
- end
- end
- end
- end
- surface.palette = { }
- surface.palette.cc = {[1]="F0F0F0",[2]="F2B233",[4]="E57FD8",[8]="99B2F2",[16]="DEDE6C",[32]="7FCC19",[64]="F2B2CC",[128]="4C4C4C",[256]="999999",[512]="4C99B2",[1024]="B266E5",[2048]="3366CC",[4096]="7F664C",[8192]="57A64E",[16384]="CC4C4C",[32768]="191919"}
- surface.palette.riko4 = {"181818","1D2B52","7E2553","008651","AB5136","5F564F","7D7F82","FF004C","FFA300","FFF023","00E755","29ADFF","82769C","FF77A9","FECCA9","ECECEC"}
- local function setPalette(palette)
- if palette == _palette then return end
- _palette = palette
- _rgbpal, _palr, _palg, _palb = { }, { }, { }, { }
- local indices = { }
- for k, v in pairs(_palette) do
- if type(v) == "string" then
- _palr[k] = tonumber(v:sub(1, 2), 16) / 255
- _palg[k] = tonumber(v:sub(3, 4), 16) / 255
- _palb[k] = tonumber(v:sub(5, 6), 16) / 255
- elseif type(v) == "number" then
- _palr[k] = math.floor(v / 65536) / 255
- _palg[k] = (math.floor(v / 256) % 256) / 255
- _palb[k] = (v % 256) / 255
- end
- indices[#indices + 1] = k
- end
- local pr, pg, pb, dist, d, id
- for i = 0, _steps - 1 do
- for j = 0, _steps - 1 do
- for k = 0, _steps - 1 do
- pr = (i + 0.5) / _steps
- pg = (j + 0.5) / _steps
- pb = (k + 0.5) / _steps
- dist = 1e10
- for l = 1, #indices do
- d = (pr - _palr[indices[l]]) ^ 2 + (pg - _palg[indices[l]]) ^ 2 + (pb - _palb[indices[l]]) ^ 2
- if d < dist then
- dist = d
- id = l
- end
- end
- _rgbpal[i * _steps * _steps + j * _steps + k + 1] = indices[id]
- end
- end
- end
- end
- function surf:toRGB(palette)
- setPalette(palette)
- local c
- for j = 0, self.height - 1 do
- for i = 0, self.width - 1 do
- c = self.buffer[(j * self.width + i) * 3 + 1]
- self.buffer[(j * self.width + i) * 3 + 1] = _palr[c]
- self.buffer[(j * self.width + i) * 3 + 2] = _palg[c]
- self.buffer[(j * self.width + i) * 3 + 3] = _palb[c]
- end
- end
- end
- function surf:toPalette(palette, dither)
- setPalette(palette)
- local scale, r, g, b, nr, ng, nb, c, dr, dg, db = _steps - 1
- for j = 0, self.height - 1 do
- for i = 0, self.width - 1 do
- r = self.buffer[(j * self.width + i) * 3 + 1]
- g = self.buffer[(j * self.width + i) * 3 + 2]
- b = self.buffer[(j * self.width + i) * 3 + 3]
- r = (r > 1) and 1 or r
- r = (r < 0) and 0 or r
- g = (g > 1) and 1 or g
- g = (g < 0) and 0 or g
- b = (b > 1) and 1 or b
- b = (b < 0) and 0 or b
- nr = (r == 1) and scale or math_floor(r * _steps)
- ng = (g == 1) and scale or math_floor(g * _steps)
- nb = (b == 1) and scale or math_floor(b * _steps)
- c = _rgbpal[nr * _steps * _steps + ng * _steps + nb + 1]
- if dither then
- dr = (r - _palr[c]) / 16
- dg = (g - _palg[c]) / 16
- db = (b - _palb[c]) / 16
- if i < self.width - 1 then
- self.buffer[(j * self.width + i + 1) * 3 + 1] = self.buffer[(j * self.width + i + 1) * 3 + 1] + dr * 7
- self.buffer[(j * self.width + i + 1) * 3 + 2] = self.buffer[(j * self.width + i + 1) * 3 + 2] + dg * 7
- self.buffer[(j * self.width + i + 1) * 3 + 3] = self.buffer[(j * self.width + i + 1) * 3 + 3] + db * 7
- end
- if j < self.height - 1 then
- if i > 0 then
- self.buffer[((j + 1) * self.width + i - 1) * 3 + 1] = self.buffer[((j + 1) * self.width + i - 1) * 3 + 1] + dr * 3
- self.buffer[((j + 1) * self.width + i - 1) * 3 + 2] = self.buffer[((j + 1) * self.width + i - 1) * 3 + 2] + dg * 3
- self.buffer[((j + 1) * self.width + i - 1) * 3 + 3] = self.buffer[((j + 1) * self.width + i - 1) * 3 + 3] + db * 3
- end
- self.buffer[((j + 1) * self.width + i) * 3 + 1] = self.buffer[((j + 1) * self.width + i) * 3 + 1] + dr * 5
- self.buffer[((j + 1) * self.width + i) * 3 + 2] = self.buffer[((j + 1) * self.width + i) * 3 + 2] + dg * 5
- self.buffer[((j + 1) * self.width + i) * 3 + 3] = self.buffer[((j + 1) * self.width + i) * 3 + 3] + db * 5
- if i < self.width - 1 then
- self.buffer[((j + 1) * self.width + i + 1) * 3 + 1] = self.buffer[((j + 1) * self.width + i + 1) * 3 + 1] + dr * 1
- self.buffer[((j + 1) * self.width + i + 1) * 3 + 2] = self.buffer[((j + 1) * self.width + i + 1) * 3 + 2] + dg * 1
- self.buffer[((j + 1) * self.width + i + 1) * 3 + 3] = self.buffer[((j + 1) * self.width + i + 1) * 3 + 3] + db * 1
- end
- end
- end
- self.buffer[(j * self.width + i) * 3 + 1] = c
- self.buffer[(j * self.width + i) * 3 + 2] = nil
- self.buffer[(j * self.width + i) * 3 + 3] = nil
- end
- end
- end
- function surface.loadFont(surf)
- local font = {width = surf.width, height = surf.height - 1}
- font.buffer = { }
- font.indices = {0}
- font.widths = { }
- local startc, hitc, curc = surf.buffer[((surf.height - 1) * surf.width) * 3 + 1]
- for i = 0, surf.width - 1 do
- curc = surf.buffer[((surf.height - 1) * surf.width + i) * 3 + 1]
- if curc ~= startc then
- hitc = curc
- break
- end
- end
- for j = 0, surf.height - 2 do
- for i = 0, surf.width - 1 do
- font.buffer[j * font.width + i + 1] = surf.buffer[(j * surf.width + i) * 3 + 1] == hitc
- end
- end
- local curchar = 1
- for i = 0, surf.width - 1 do
- if surf.buffer[((surf.height - 1) * surf.width + i) * 3 + 1] == hitc then
- font.widths[curchar] = i - font.indices[curchar]
- curchar = curchar + 1
- font.indices[curchar] = i + 1
- end
- end
- font.widths[curchar] = font.width - font.indices[curchar] + 1
- return font
- end
- function surface.getTextSize(str, font)
- local cx, cy, maxx = 0, 0, 0
- local ox, char = cx
- for i = 1, #str do
- char = str:byte(i) - 31
- if char + 31 == 10 then -- newline
- cx = ox
- cy = cy + font.height + 1
- elseif font.indices[char] then
- cx = cx + font.widths[char] + 1
- else
- cx = cx + font.widths[1]
- end
- if cx > maxx then
- maxx = cx
- end
- end
- return maxx - 1, cy + font.height
- end
- function surf:drawText(str, x, y, font, b, t, c)
- local cx, cy = x + self.ox, y + self.oy
- local ox, char, idx = cx
- for i = 1, #str do
- char = str:byte(i) - 31
- if char + 31 == 10 then -- newline
- cx = ox
- cy = cy + font.height + 1
- elseif font.indices[char] then
- for i = 0, font.widths[char] - 1 do
- for j = 0, font.height - 1 do
- x, y = cx + i, cy + j
- if font.buffer[j * font.width + i + font.indices[char] + 1] then
- if x >= self.cx and x < self.cx + self.cwidth and y >= self.cy and y < self.cy + self.cheight then
- idx = (y * self.width + x) * 3
- if b or self.overwrite then
- self.buffer[idx + 1] = b
- end
- if t or self.overwrite then
- self.buffer[idx + 2] = t
- end
- if c or self.overwrite then
- self.buffer[idx + 3] = c
- end
- end
- end
- end
- end
- cx = cx + font.widths[char] + 1
- else
- cx = cx + font.widths[1]
- end
- end
- end
- end
- local sin, cos = math.sin, math.cos
- local halfpi = math.pi / 2
- Easing = class.new_enum "Easing" {
- linear = function( u, d, t )
- return u + d * t
- end;
- smooth = function( u, d, t )
- return u + d * ( 3 * t * t - 2 * t * t * t )
- end;
- exit = function( u, d, t )
- return -d * cos(t * halfpi) + d + u
- end;
- entrance = function( u, d, t )
- return u + d * sin(t * halfpi)
- end;
- -- TODO: probably should add in all the default ones but why are they required?
- }
- local thrown
- local function handler( t )
- for i = 1, #t do
- if t[i].catch == thrown.name or t[i].default or t[i].catch == thrown.class then
- return t[i].handler( thrown )
- end
- end
- return Exception.throw( thrown )
- end
- Exception = class.new( "Exception", nil, nil ) {
- name = "undefined";
- data = "undefined";
- trace = {};
- }
- function Exception:Exception( name, data, level )
- self.name = name
- self.data = data
- self.trace = {}
- level = ( level or 1 ) + 2
- if level > 2 then
- for i = 1, 5 do
- local src = select( 2, pcall( error, "", level + i ) ):gsub( ": $", "" )
- if src == "pcall" or src == "" then
- break
- else
- self.trace[i] = src
- end
- end
- end
- end
- function Exception:get_traceback( initial, delimiter )
- initial = initial or ""
- delimiter = delimiter or "\n"
- parameters.check( 2, "initial", "string", initial, "delimiter", "string", delimiter )
- if #self.trace == 0 then return "" end
- return initial .. table.concat( self.trace, delimiter )
- end
- function Exception:get_data()
- if type( self.data ) == "string" or class.is_class( self.data ) or class.is_instance( self.data ) then
- return tostring( self.data )
- else
- return textutils.serialize( self.data )
- end
- end
- function Exception:get_data_and_traceback( indent )
- parameters.check( 1, "indent", "number", indent or 1 )
- return self:get_data() .. self:get_traceback( "\n" .. (" "):rep( indent or 1 ) .. "in ", "\n" .. (" "):rep( indent or 1 ) .. "in " )
- end
- function Exception:tostring()
- return tostring( self.name ) .. " exception:\n " .. self:get_data_and_traceback( 4 )
- end
- function Exception.thrown()
- return thrown
- end
- function Exception.throw( e, data, level )
- if class.is_class( e ) then
- e = e( data, ( level or 1 ) + 1 )
- elseif type( e ) == "string" then
- e = Exception( e, data, ( level or 1 ) + 1 )
- elseif not class.type_of( e, Exception ) then
- return Exception.throw( "IncorrectParameterException", "expected class, string, or Exception e, got " .. class.type( e ) )
- end
- thrown = e
- error( "SHEETS_EXCEPTION\nPut code in a try block to catch the exception.", 0 )
- end
- function Exception.try( func )
- local ok, err = pcall( func )
- if not ok and err == "SHEETS_EXCEPTION\nPut code in a try block to catch the exception." then
- return handler
- end
- return error( err, 0 )
- end
- function Exception.catch( etype )
- return function( handler )
- return { catch = etype, handler = handler }
- end
- end
- function Exception.default( handler )
- return { default = true, handler = handler }
- end
- IncorrectParameterException = class.new( "IncorrectParameterException", Exception, nil ) {
- }
- function IncorrectParameterException:IncorrectParameterException( data, level )
- return self:Exception( "IncorrectParameterException", data, level )
- end
- IncorrectConstructorException = class.new( "IncorrectConstructorException", Exception, nil ) {
- }
- function IncorrectConstructorException:IncorrectConstructorException( data, level )
- return self:Exception( "IncorrectConstructorException", data, level )
- end
- ResourceLoadException = class.new( "ResourceLoadException", Exception, nil ) {
- }
- function ResourceLoadException:ResourceLoadException( data, level )
- return self:Exception( "ResourceLoadException", data, level )
- end
- ThreadRuntimeException = class.new( "ThreadRuntimeException", Exception, nil ) {
- }
- function ThreadRuntimeException:ThreadRuntimeException( data, level )
- return self:Exception( "ThreadRuntimeException", data, level )
- end
- ICollatedChildren = class.new_interface( "ICollatedChildren", nil ) {
- collated_children = {}
- }
- function ICollatedChildren:ICollatedChildren()
- self.collated_children = {}
- end
- function ICollatedChildren:update_collated( mode, child, data )
- local collated = self.collated_children
- if mode == "child-added" then
- if data == self then
- if child:implements( ICollatedChildren ) then
- for i = 1, #child.collated_children do
- collated[#collated + 1] = child.collated_children[i]
- end
- end
- collated[#collated + 1] = child
- else
- for i = #collated, 1, -1 do
- if collated[i] == data then
- if child:implements( ICollatedChildren ) then
- i = i - 1 -- so that i + n starts with just i
- for n = 1, #child.collated_children do
- table.insert( collated, i + n, child.collated_children[n] )
- end
- table.insert( collated, i + #child.collated_children + 1, child )
- else
- table.insert( collated, i, child )
- end
- end
- end
- end
- if self.parent then
- self.parent:update_collated( "child-added", child, data )
- end
- elseif mode == "child-removed" then
- local open, close = child:implements( ICollatedChildren ) and child.collated_children[1] or child, child
- local removing = false
- for i = #collated, 1, -1 do
- if collated[i] == close then removing = true end
- local brk = collated[i] == open
- if removing then table.remove( collated, i ) end
- if brk then break end
- end
- if self.parent then
- self.parent:update_collated( "child-removed", child )
- end
- end
- if self.query_tracker then
- self.query_tracker:update( mode, child )
- end
- end
- IColoured = class.new_interface( "IColoured", nil ) {
- colour = nil;
- }
- function IColoured:IColoured()
- self.values:add( "colour", 1 )
- end
- local setf, addtag, remtag, query_raw
- IQueryable = class.new_interface( "IQueryable", ICollatedChildren ) {
- query_tracker = nil;
- }
- function IQueryable:IQueryable()
- self.query_tracker = QueryTracker( self )
- end
- function IQueryable:iquery( query )
- local results = query_raw( self, query, nil, false, false )
- local i = 0
- return function()
- i = i + 1
- return results[i], i
- end
- end
- function IQueryable:query( query )
- return query_raw( self, query, nil, false, false )
- end
- function IQueryable:query_tracked( query )
- return query_raw( self, query, nil, true, false )
- end
- function IQueryable:preparsed_query( query, lifetime )
- return query_raw( self, query, lifetime, false, true )
- end
- function IQueryable:preparsed_query_tracked( query, lifetime )
- return query_raw( self, query, lifetime, true, true )
- end
- function setf( self, properties )
- local prop_setters = {}
- for k, v in pairs( properties ) do
- prop_setters[#prop_setters + 1] = { k, "set_" .. k, v }
- end
- for i = 1, #self do
- local vals = self[i].values
- for n = 1, #prop_setters do
- if vals:has( prop_setters[n][1] ) then
- self[i][prop_setters[n][2]]( self[i], prop_setters[n][3] )
- end
- end
- end
- end
- function addtag( self, tag )
- for i = 1, #self do
- self[i]:add_tag( tag )
- end
- end
- function remtag( self, tag )
- for i = 1, #self do
- self[i]:remove_tag( tag )
- end
- end
- function query_raw( self, query, lifetime, track, parsed )
- if not parsed then
- lifetime = {}
- parameters.check( 1, "query", "string", query )
- local parser = DynamicValueParser( Stream( query ) )
- parser.enable_queries = true
- query = parser:parse_query()
- end
- local query_f, init_f
- local nodes = self.collated_children
- local matches = { set = setf, add_tag = addtag, remove_tag = remtag }
- local n, ID = 0
- local function updater() -- this can definitely be optimised
- local n = 1
- for i = 1, #nodes do
- if query_f( nodes[i] ) then
- if matches[n] ~= nodes[i] then
- table.insert( matches, n, nodes[i] )
- self.query_tracker:invoke_child_change( ID, nodes[i], "child-added" )
- end
- n = n + 1
- elseif matches[n] == nodes[i] then
- table.remove( matches, n )
- self.query_tracker:invoke_child_change( ID, nodes[i], "child-removed" )
- end
- end
- end
- query_f, init_f = Codegen.node_query( query, lifetime, updater )
- init_f( self )
- for i = 1, #nodes do
- if query_f( nodes[i] ) then
- n = n + 1
- matches[n] = nodes[i]
- end
- end
- if track then
- ID = self.query_tracker:track( query_f, matches )
- self.query_tracker.lifetimes[ID] = lifetime
- return matches, ID
- else
- if not parsed then
- for i = #lifetime, 1, -1 do
- local l = lifetime[i]
- lifetime[i] = nil
- if l[1] == "value" then
- l[2].values:unsubscribe( l[3], l[4] )
- elseif l[1] == "query" then
- l[2]:unsubscribe( l[3], l[4] )
- elseif l[1] == "tag" then
- l[2]:unsubscribe_from_tag( l[3], l[4] )
- end
- end
- end
- return matches
- end
- end
- IChildContainer = class.new_interface( "IChildContainer", ICollatedChildren, IQueryable ) {
- children = {};
- application = nil;
- }
- function IChildContainer:IChildContainer()
- self.children = {}
- self.meta.__add = self.add_child
- function self.meta:__concat( child )
- self:add_child( child )
- return self
- end
- end
- function IChildContainer:child_value_changed( child )
- self.query_tracker:update( "child-changed", child )
- if self.parent then
- return self.parent:child_value_changed( child )
- end
- end
- function IChildContainer:add_child( child )
- parameters.check( 1, "child", Sheet, child )
- local children = self.children
- local collated = self.collated_children
- if child.parent then
- child.parent:remove_child( child, true )
- end
- local index = #children + 1
- for i = 1, #children do
- if children[i].z > child.z then
- index = i
- break
- end
- end
- local c, l = children[index], index <= #children
- table.insert( children, index, child )
- self:update_collated( "child-added", child, l and (c:implements( ICollatedChildren ) and c.collated_children[1] or c) or self )
- self:set_changed()
- if child:implements( ICollatedChildren ) then
- for i = 1, #child.collated_children do
- child.collated_children[i].application = self.application
- child.collated_children[i].values:trigger "application"
- end
- end
- child.parent = self
- child.application = self.application
- child.values:trigger "parent"
- child.values:trigger "application"
- child.values:child_inserted()
- return child
- end
- function IChildContainer:remove_child( child, reinsert )
- for i = 1, #self.children do
- if self.children[i] == child then
- child.parent = nil
- child.application = nil
- table.remove( self.children, i )
- self:set_changed()
- self:update_collated( "child-removed", child )
- if child:implements( ICollatedChildren ) then
- for i = 1, #child.collated_children do
- child.collated_children[i].application = nil
- child.collated_children[i].values:trigger "application"
- end
- end
- child.values:trigger "parent"
- child.values:trigger "application"
- if not reinsert then
- child.values:child_removed()
- end
- return child
- end
- end
- end
- function IChildContainer:get_children()
- local c = {}
- local children = self.children
- for i = 1, #children do
- c[i] = children[i]
- end
- return c
- end
- function IChildContainer:get_children_at( x, y )
- parameters.check( 2, "x", "number", x, "y", "number", y )
- local c = self:get_children()
- local elements = {}
- for i = #c, 1, -1 do
- c[i]:handle( MouseEvent( 6, x - c[i].x, y - c[i].y, elements, true ) )
- end
- return elements
- end
- function IChildContainer:is_child_visible( child )
- parameters.check( 1, "child", Sheet, child )
- return child.x + child.width > 0 and child.y + child.height > 0 and child.x < self.width and child.y < self.height
- end
- function IChildContainer:reposition_child_z_index( child )
- local children = self.children
- for i = 1, #children do
- if children[i] == child then
- local moved = false
- while children[i-1] and children[i-1].z > child.z do
- children[i-1], children[i] = child, children[i-1]
- moved = true
- i = i - 1
- end
- while children[i+1] and children[i+1].z < child.z do
- children[i+1], children[i] = child, children[i+1]
- moved = true
- i = i + 1
- end
- if moved then
- self:update_collated( "child-removed", child )
- self:update_collated( "child-added", child, i + 1 > #children and self or children[i + 1]:implements( ICollatedChildren ) and children[i + 1].collated_children[1] or children[i + 1] )
- self:set_changed()
- end
- break
- end
- end
- end
- ITagged = class.new_interface( "ITagged", nil ) {
- tags = {};
- subscriptions = {};
- id = "ID";
- }
- function ITagged:ITagged()
- self.tags = {}
- self.subscriptions = {}
- end
- function ITagged:add_tag( tag )
- self.tags[tag] = true
- if self.parent then
- self.parent:child_value_changed( self )
- end
- return self:trigger( tag )
- end
- function ITagged:remove_tag( tag )
- self.tags[tag] = ni
- if self.parent then
- self.parent:child_value_changed( self )
- end
- return self:trigger( tag )
- end
- function ITagged:has_tag( tag )
- return self.tags[tag] or false
- end
- function ITagged:toggle_tag( tag )
- self.tags[tag] = not self.tags[tag] or nil
- if self.parent then
- self.parent:child_value_changed( self )
- end
- return self:trigger( tag )
- end
- function ITagged:set_ID( id ) -- TODO: make this a dynamic property
- self.id = tostring( id )
- if self.parent then
- self.parent:child_value_changed( self )
- end
- return self
- end
- function ITagged:subscribe_to_tag( tag, lifetime, callback )
- self.subscriptions[tag] = self.subscriptions[tag] or {}
- self.subscriptions[tag][#self.subscriptions[tag] + 1] = callback
- lifetime[#lifetime + 1] = { "tag", self, tag, callback }
- return callback
- end
- function ITagged:unsubscribe_from_tag( tag, f )
- if self.subscriptions[tag] then
- for i = #self.subscriptions[tag], 1, -1 do
- if self.subscriptions[tag][i] == f then
- return table.remove( self.subscriptions[tag], i )
- end
- end
- end
- end
- function ITagged:trigger( tag )
- if self.subscriptions[tag] then
- for i = #self.subscriptions[tag], 1, -1 do
- self.subscriptions[tag][i]()
- end
- end
- return self
- end
- ISize = class.new_interface( "ISize", nil ) {
- width = 0;
- height = 0;
- }
- function ISize:ISize()
- self.values:add( "width", 0, { update_surface_size = true } )
- self.values:add( "height", 0, { update_surface_size = true } )
- end
- ITimer = class.new_interface( "ITimer", nil ) {
- timerID = 0;
- time = nil;
- lt = nil;
- timers = {};
- }
- function ITimer:ITimer()
- self.time = os.clock()
- self.timers = {}
- self:step_timer()
- end
- function ITimer:new_timer( n )
- parameters.check( 1, "n", "number", n )
- local finish, ID = self.time + n, nil -- avoids duplicating timer events
- for i = 1, #self.timers do
- if self.timers[i].time == finish then
- ID = self.timers[i].ID
- break
- end
- end
- return ID or os.startTimer( n )
- end
- function ITimer:queue( response, n )
- parameters.check( 2, "response", "function", response, "n", "number", n )
- local timer_id = self:new_timer( n )
- local finish = self.time + n
- self.timers[#self.timers + 1] = { time = finish, response = response, ID = timer_id }
- return timer_id
- end
- function ITimer:cancel_timer( ID )
- parameters.check( 1, "ID", "number", ID )
- for i = #self.timers, 1, -1 do
- if self.timers[i].ID == ID then
- table.remove( self.timers, i )
- break
- end
- end
- return self
- end
- function ITimer:step_timer()
- self.lt = self.time
- self.time = os.clock()
- return self
- end
- function ITimer:get_timer_delta()
- return self.time - self.lt
- end
- function ITimer:update_timer( timer_id )
- local updated = false
- for i = #self.timers, 1, -1 do
- if self.timers[i].ID == timer_id then
- table.remove( self.timers, i ).response()
- updated = true
- end
- end
- return updated
- end
- Event = class.new( "Event", nil, nil ) {
- event = "Event";
- }
- function Event:is( event )
- return self.event == event
- end
- function Event:handle( handler )
- self.handled = true
- self.handler = handler
- end
- KeyboardEvent = class.new( "KeyboardEvent", Event, nil ) {
- event = "KeyboardEvent";
- key = 0;
- held = {};
- }
- function KeyboardEvent:KeyboardEvent( event, key, held )
- self.event = event
- self.key = key
- self.held = held
- end
- function KeyboardEvent:matches( hotkey )
- local t, segment2
- for segment in hotkey:gmatch "(.-)%-" do
- if segment == "ctrl" or segment == "shift" or segment == "alt" then
- segment = segment:sub( 1, 1 ):upper() .. segment:sub( 2 )
- segment2 = "right" .. segment
- segment = "left" .. segment
- if self.held[segment2] then
- if self.held[segment] then
- segment = self.held[segment] < self.held[segment2] and (not t or self.held[segment] > t) and segment or segment2
- else
- segment = segment2
- end
- end
- end
- if not self.held[segment] or ( t and self.held[segment] < t ) then
- return false
- end
- t = self.held[segment]
- end
- return self.key == keys[hotkey:gsub( ".+%-", "" )]
- end
- function KeyboardEvent:is_held( key )
- return self.key == keys[key] or self.held[key]
- end
- MiscEvent = class.new( "MiscEvent", Event, nil ) {
- event = "MiscEvent";
- parameters = {};
- }
- function MiscEvent:MiscEvent( event, ... )
- self.event = event
- self.parameters = { ... }
- end
- MouseEvent = class.new( "MouseEvent", Event, nil ) {
- event = "MouseEvent";
- x = 0;
- y = 0;
- button = 0;
- within = true;
- }
- function MouseEvent:MouseEvent( event, x, y, button, within )
- self.event = event
- self.x = x
- self.y = y
- self.button = button
- self.within = within
- end
- function MouseEvent:is_within_area( x, y, width, height )
- parameters.check( 4,
- "x", "number", x,
- "y", "number", y,
- "width", "number", width,
- "height", "number", height
- )
- return self.x >= x and self.y >= y and self.x < x + width and self.y < y + height
- end
- function MouseEvent:clone( x, y, within )
- parameters.check( 2,
- "x", "number", x,
- "y", "number", y
- )
- local sub = MouseEvent( self.event, self.x - x, self.y - y, self.button, self.within and within or false )
- sub.handled = self.handled
- function sub.handle()
- sub.handled = true
- self:handle()
- end
- return sub
- end
- TextEvent = class.new( "TextEvent", Event, nil ) {
- event = "TextEvent";
- text = "";
- }
- function TextEvent:TextEvent( event, text )
- self.event = event
- self.text = text
- end
- local property_cache = {}
- local CHANGECODE_NO_TRANSITION, CHANGECODE_TRANSITION, SELF_INDEX_UPDATER,
- ARBITRARY_DOTINDEX_UPDATER, ARBITRARY_INDEX_UPDATER, DYNAMIC_QUERY_UPDATER,
- QUERY_UPDATER, GENERIC_SETTER, STRING_CASTING, RAW_STRING_CASTING,
- INTEGER_CASTING, RAW_INTEGER_CASTING, NUMBER_CASTING, RAW_NUMBER_CASTING,
- COLOUR_CASTING, RAW_COLOUR_CASTING, ALIGNMENT_CASTING,
- RAW_ALIGNMENT_CASTING, ERR_CASTING
- local node_query_internal, dynamic_value_internal
- Codegen = class.new( "Codegen", nil, nil ) {
- }
- function Codegen.node_query( parsed_query, lifetime, updater )
- local names = {}
- local named_values = {}
- local val_names = {}
- local init_localised = {}
- local initialise_code = {}
- local tracked = {}
- local query_str = node_query_internal( parsed_query, "n", tracked )
- local tl = #tracked
- for i = 1, tl do
- names[i] = "n" .. i
- named_values[i] = tracked[i].value
- init_localised[i] = "f" .. i
- init_localised[tl + i] = "i" .. i
- val_names[i] = "v" .. i
- initialise_code[i] = "f" .. i .. ", i" .. i .. " = Codegen.dynamic_value( n" .. i .. ", lifetime, env, n, function()\n"
- .. "\tv" .. i .. " = f" .. i .. "()\n"
- .. "\treturn updater()\n"
- .. "end )"
- end
- for i = 1, tl do
- initialise_code[i + tl] = "i" .. i .. "()"
- end
- for i = 1, tl do
- initialise_code[i + tl + tl] = "v" .. i .. " = f" .. i .. "()"
- end
- local code = "local lifetime, updater" .. (#names == 0 and "" or ", " .. table.concat( names, ", " )) .. " = ...\n"
- .. (#val_names == 0 and "" or "local " .. table.concat( val_names, ", " ) .. "\n")
- .. "return function( n )\n"
- .. "\treturn " .. query_str
- .. "\nend, function( n )\n"
- .. "\tlocal env = {}\n"
- .. (#init_localised == 0 and "" or "\tlocal " .. table.concat( init_localised, ", " ) .. "\n")
- .. table.concat( initialise_code, "\n" )
- .. "\nend"
- local f, err = assert( (load or loadstring)( code, "query", nil, _ENV ) )
- if setfenv then
- setfenv( f, getfenv() )
- end
- local getter, initialiser = f( lifetime, updater, unpack( named_values ) )
- return getter, initialiser
- end
- function Codegen.dynamic_value( parsed_value, lifetime, env, obj, updater )
- local names = {}
- local functions = {}
- local inputs = {}
- local state = {
- environment = env;
- object = obj;
- names = names;
- functions = functions;
- inputs = inputs;
- }
- local return_value = dynamic_value_internal( parsed_value, state )
- local roots = {}
- local roots_tocheck = { return_value }
- local i = 1
- local func_compiled = {}
- local initialisers = {}
- local initialise_function
- local input_names = {}
- for i = 1, #inputs do
- input_names[i] = "i" .. i
- end
- while i <= #roots_tocheck do
- local t = roots_tocheck[i]
- if #t.dependencies == 0 then
- roots[#roots + 1] = t
- else
- local added = false
- for n = 1, #t.dependencies do
- if t.dependencies[n].update or t.dependencies[n].initialise then
- roots_tocheck[#roots_tocheck + 1] = t.dependencies[n]
- added = true
- end
- end
- if not added then
- roots[#roots + 1] = t
- end
- end
- i = i + 1
- end
- for i = 1, #functions do
- local dependants = {}
- local tocheck = { functions[i].node }
- local index = 1
- local update_root = false
- while index <= #tocheck do
- if index ~= 1 then
- dependants[#dependants + 1] = tocheck[index].update
- end
- if index == 1 or not tocheck[index].complex then
- update_root = update_root or tocheck[index] == return_value
- local idx = #tocheck
- for n = 1, #tocheck[index].dependants do
- tocheck[idx + n] = tocheck[index].dependants[n]
- end
- end
- index = index + 1
- end
- if update_root then
- dependants[#dependants + 1] = "updater()"
- end
- if dependants[1] then
- dependants[#dependants] = "return " .. dependants[#dependants]
- end
- func_compiled[i] = functions[i].code:gsub( "DEPENDENCIES", table.concat( dependants, "\n" ) )
- end
- local i = 1
- while i <= #roots do
- initialisers[#initialisers + 1] = roots[i].initialise or roots[i].update
- if not roots[i].complex then
- for n = 1, #roots[i].dependants do
- roots[#roots + 1] = roots[i].dependants[n]
- end
- end
- i = i + 1
- end
- local s = initialisers[#initialisers]
- if s and s:find "^f%d+%(%)" then
- if #initialisers == 1 then
- initialise_function = s:match "^f%d+"
- else
- initialisers[#initialisers] = "return " .. s
- end
- end
- local code
- = "local self, lifetime, updater"
- .. (#inputs > 0 and ", " .. table.concat( input_names, ", ") or "")
- .. " = ...\n"
- .. (state.tostringed and "local tostring = tostring\n" or "")
- .. (state.floored and "local floor = math.floor\n" or "")
- .. (#names > 0 and "local " .. table.concat( names, ", " ) .. "\n" or "")
- .. table.concat( func_compiled, "\n" ) .. "\n"
- .. "return function() return " .. return_value.value .. " end, "
- .. (initialise_function or "function()\n"
- .. table.concat( initialisers, "\n" )
- .. (#initialisers == 0 and "" or "\n") .. "end")
- if parsed_value.type == "binary operator expression" and parsed_value.lvalue.lvalue and parsed_value.lvalue.lvalue.type == "tag" then
- local h = fs.open( "demo/log.txt", "w" )
- h.write( code )
- h.close()
- end
- local f, err = assert( (load or loadstring)( code, "dynamic value", nil, _ENV ) )
- if setfenv then
- setfenv( f, getfenv() )
- end
- local getter, initialiser = f( obj, lifetime, updater, unpack( inputs ) )
- return getter, initialiser
- end
- function Codegen.dynamic_property_setter( property, options )
- property_cache[property] = property_cache[property] or {}
- options = options or {}
- local self_changed = ValueHandler.properties[property].change == "self"
- local parent_changed = ValueHandler.properties[property].change == "parent"
- local ptype = ValueHandler.properties[property].type
- local t1 = {}
- local t2 = {}
- local t3 = {}
- local t4 = {}
- local t5 = {}
- if options.update_surface_size then
- t4[#t4 + 1] = "if self.surface then self.surface = surface.create( self.width, self.height ) end"
- self_changed = true
- end
- if self_changed then
- t4[#t4 + 1] = "if not self.changed then self:set_changed() end"
- elseif parent_changed then
- t4[#t4 + 1] = "if self.parent then self.parent:set_changed() end"
- end
- if self_changed or parent_changed then
- t4[#t4 + 1] = "if self.parent then self.parent:child_value_changed( self ) end"
- end
- if ptype == Type.primitive.string then
- t1[#t1 + 1] = "if value:sub( 1, 1 ) == '!' then value = value:sub( 2 ) else value = ('%q'):format( value ) end"
- end
- if ptype == Type.sheets.colour then
- for k, v in pairs( colour ) do
- t2[#t2 + 1] = "environment." .. k .. " = { type = rtype, value = " .. v .. " }"
- end
- t5[#t5 + 1] = "if value == 0 then value = nil end"
- end
- if ptype == Type.sheets.alignment then
- for k, v in pairs( alignment ) do
- t2[#t2 + 1] = "environment." .. k .. " = { type = rtype, value = " .. v .. " }"
- end
- end
- t4[#t4 + 1] = options.custom_update_code
- local s5 = table.concat( t5, "\n" ) -- code to update the value before assignment
- local s4 = table.concat( t4, "\n" ) -- code to run on value update
- local s3 = table.concat( t3, "\n" ) -- code to update the AST
- local s2 = table.concat( t2, "\n" ) -- code to change the environment
- local s1 = table.concat( t1, "\n" ) -- code to update the string value
- for i = 1, #property_cache[property] do
- local c = property_cache[property][i]
- if c[1] == s1 and c[2] == s2 and c[3] == s3 and c[4] == s4 and c[5] == s5 then
- return c.f
- end
- end
- local change_code
- if ValueHandler.properties[property].transitionable then
- change_code = CHANGECODE_TRANSITION
- if s4 ~= "" then
- change_code = change_code
- :gsub( "CUSTOM_UPDATE", ", function( self )\n" .. s4 .. "\nend" )
- :gsub( "PROPERTY_TRANSITION_QUOTED", ("%q"):format( property .. "_transition" ) )
- :gsub( "PROCESS_VALUE", s5 )
- end
- else
- change_code = CHANGECODE_NO_TRANSITION
- :gsub( "ONCHANGE", s4 )
- :gsub( "PROCESS_VALUE", s5 )
- end
- local prop_quoted = ("%q"):format( property )
- local caster = ptype == Type.primitive.string and STRING_CASTING
- or ptype == Type.primitive.integer and INTEGER_CASTING
- or ptype == Type.primitive.number and NUMBER_CASTING
- or ptype == Type.sheets.colour and COLOUR_CASTING
- or ptype == Type.sheets.alignment and ALIGNMENT_CASTING
- or ERR_CASTING
- local rawcaster = ptype == Type.primitive.string and RAW_STRING_CASTING
- or ptype == Type.primitive.integer and RAW_INTEGER_CASTING
- or ptype == Type.primitive.number and RAW_NUMBER_CASTING
- or ptype == Type.sheets.colour and RAW_COLOUR_CASTING
- or ptype == Type.sheets.alignment and RAW_ALIGNMENT_CASTING
- or ERR_CASTING
- local str = GENERIC_SETTER
- :gsub( "CHANGECODE", change_code )
- :gsub( "PROPERTY_QUOTED", ("%q"):format( property ) )
- :gsub( "RAW_PROPERTY", ("%q"):format( "raw_" .. property ) )
- :gsub( "VALUE_MODIFICATION", function() return s1 end )
- :gsub( "ENV_MODIFICATION", function() return s2 end )
- :gsub( "AST_MODIFICATION", function() return s3 end )
- :gsub( "CASTING_RAW", function() return rawcaster end )
- :gsub( "CASTING", function() return caster end )
- local f = assert( (load or loadstring)( str, "property setter '" .. property .. "'", nil, _ENV ) )
- if setfenv then
- setfenv( f, getfenv() )
- end
- local fr = f( ptype )
- property_cache[property][#property_cache[property] + 1] = { s1, s2, s3, s4, s5, f = fr }
- return fr
- end
- CHANGECODE_NO_TRANSITION = [[
- PROCESS_VALUE
- self[PROPERTY_QUOTED] = value
- ONCHANGE
- self.values:trigger PROPERTY_QUOTED]]
- CHANGECODE_TRANSITION = [[
- PROCESS_VALUE
- self.values:transition( PROPERTY_QUOTED, value, self[PROPERTY_TRANSITION_QUOTED]CUSTOM_UPDATE )]]
- STRING_CASTING = [[
- if value_type == Type.primitive.integer or value_type == Type.primitive.number or value_type == Type.primitive.boolean then
- value_parsed = {
- type = "tostring";
- value = value_parsed;
- }
- else
- error "TODO: fix this error"
- end
- ]]
- RAW_STRING_CASTING = [[
- if value_type == Type.primitive.integer or value_type == Type.primitive.number or value_type == Type.primitive.boolean then
- value = tostring( value )
- else
- error "TODO: fix this error"
- end
- ]]
- INTEGER_CASTING = [[
- if value_type == Type.primitive.number then
- value_parsed = {
- type = "floor";
- value = value_parsed;
- }
- else
- error "TODO: fix this error"
- end
- ]]
- RAW_INTEGER_CASTING = [[
- if value_type == Type.primitive.number then
- value = math.floor( value )
- else
- error "TODO: fix this error"
- end
- ]]
- NUMBER_CASTING = [[
- if not (value_type == Type.primitive.integer) then
- error "TODO: fix this error"
- end
- ]]
- RAW_NUMBER_CASTING = NUMBER_CASTING
- COLOUR_CASTING = [[
- error "TODO: fix this error"
- ]]
- RAW_COLOUR_CASTING = [[
- if value_type == Type.primitive.integer then
- if value ~= 0 and (math.log( value ) / math.log( 2 ) % 1 ~= 0 or value < 1 or value > 2 ^ 15) then
- error "TODO: fix this error"
- end
- else
- error "TODO: fix this error"
- end
- ]]
- ALIGNMENT_CASTING = [[
- error "TODO: fix this error"
- ]]
- RAW_ALIGNMENT_CASTING = [[
- if value_type == Type.primitive.integer then
- if value ~= 0 and value ~= 2 and value ~= 3 and value ~= 4 and value ~= 1 then
- error "TODO: fix this error"
- end
- else
- error "TODO: fix this error"
- end
- ]]
- ERR_CASTING = [[
- error "TODO: fix this error"
- ]]
- SELF_INDEX_UPDATER = [[function FUNC()
- NAME = self.INDEX
- DEPENDENCIES
- end]]
- ARBITRARY_DOTINDEX_UPDATER = [[do
- local function f0()
- DEPENDENCIES
- end
- function FUNC()
- local obj = LVALUE
- if NAME then
- NAME.values:unsubscribe( "INDEX", f0 )
- end
- if obj then
- obj.values:subscribe( "INDEX", lifetime, f0 )
- end
- NAME = obj
- return f0()
- end
- end]]
- TAG_CHECK_UPDATER = [[do
- local function f0()
- VALUE = NAME and NAME:has_tag TAG
- DEPENDENCIES
- end
- function FUNC()
- local obj = LVALUE
- if NAME then
- NAME:unsubscribe_from_tag( TAG, f0 )
- end
- if obj then
- obj:subscribe_to_tag( TAG, lifetime, f0 )
- end
- NAME = obj
- return f0()
- end
- end]]
- ARBITRARY_INDEX_UPDATER = [[do
- local function f0()
- NAME = OLDVALUE and OLDINDEX and OLDVALUE[OLDINDEX]
- DEPENDENCIES
- end
- function FUNC()
- local obj = LVALUE
- local idx = INDEX
- if OLDVALUE and type( OLDINDEX ) == "string" then
- OLDVALUE.values:unsubscribe( OLDINDEX, f0 )
- end
- if obj and type( idx ) == "string" then
- obj.values:subscribe( idx, lifetime, f0 )
- end
- OLDVALUE = obj
- OLDINDEX = idx
- return f0()
- end
- end]]
- DYNAMIC_QUERY_UPDATER = [[do
- local elems, ID
- local function f0()
- NAME = elems and elems[1]
- DEPENDENCIES
- end
- function FUNC()
- local object = SOURCE
- if PREVSOURCE then
- PREVSOURCE.query_tracker:unsubscribe( ID, f0 )
- end
- if object then
- elems, ID = object:preparsed_query_tracked( QDATA, lifetime )
- object.query_tracker:subscribe( ID, lifetime, f0 )
- end
- PREVSOURCE = object
- return f0()
- end
- end]]
- QUERY_UPDATER = [[function FUNC()
- local object = SOURCE
- if object then
- local elems = object:preparsed_query( QDATA, lifetime )
- NAME = elems[1]
- if NAME then
- FUNC = function()end
- DEPENDENCIES
- end
- end
- end]]
- GENERIC_SETTER = [[
- local rtype = ...
- return function( self, value )
- self.values:respawn PROPERTY_QUOTED
- self[RAW_PROPERTY] = value
- if type( value ) ~= "string" then
- local value_type = Typechecking.resolve_type( value )
- if not (value_type == rtype) then
- print( value_type, rtype )
- CASTING_RAW
- end
- CHANGECODE
- return self
- end
- VALUE_MODIFICATION
- local parser = DynamicValueParser( Stream( value ) )
- local environment = {}
- parser.flags.enable_queries = true
- ENV_MODIFICATION
- local value_parsed = parser:parse_expression()
- or "TODO: fix this error"
- AST_MODIFICATION
- local value_parsed, value_type = Typechecking.check_type( value_parsed, {
- object = self;
- environment = environment;
- } )
- local lifetime = self.values.lifetimes[PROPERTY_QUOTED]
- local default = self.values .defaults[PROPERTY_QUOTED]
- local setter_f, initialiser_f
- if not (value_type == rtype) then
- CASTING
- end
- local function update()
- local value = setter_f( self ) or default
- if value ~= self[PROPERTY_QUOTED] then
- CHANGECODE
- end
- end
- if not parser.stream:is_EOF() then
- error "TODO: fix this error"
- end
- setter_f, initialiser_f = Codegen.dynamic_value( value_parsed, lifetime, environment, self, update )
- initialiser_f()
- update()
- return self
- end]]
- function node_query_internal( query, name, tracked )
- if query.type == "id" then
- return ("%s.id=='%s'"):format( name, query.value )
- elseif query.type == "tag" then
- return ("%s:has_tag'%s'"):format( name, query.value )
- elseif query.type == "any" then
- return "true"
- elseif query.type == "class" then
- return ("%s:type():lower()=='%s'"):format( name, query.value:lower() )
- elseif query.type == "negate" then
- local i = node_query_internal( query.value, name, tracked )
- return i == "true" and "false" or i == "false" and "true" or "not (" .. i .. ")"
- elseif query.type == "attributes" then
- local t = {}
- local idx = #tracked + 1
- for i = 1, #query.attributes do
- local attr = query.attributes[i]
- local op = attr.comparison
- if op == "=" then
- op = "=="
- end
- tracked[idx] = { value = attr.value }
- t[i] = "v" .. idx .. " and " .. name .. "." .. attr.name .. " " .. op .. " v" .. idx
- idx = idx + 1
- end
- return table.concat( t, " and " )
- elseif query.type == "operator" then
- if query.operator == "&" then
- local lvalue = node_query_internal( query.lvalue, name, tracked )
- local rvalue = node_query_internal( query.rvalue, name, tracked )
- if lvalue == "true" then return rvalue end
- if rvalue == "true" then return lvalue end
- if lvalue == "false" then return lvalue end
- if rvalue == "false" then return rvalue end
- return lvalue .. " and " .. rvalue
- elseif query.operator == "|" then
- local lvalue = node_query_internal( query.lvalue, name, tracked )
- local rvalue = node_query_internal( query.rvalue, name, tracked )
- if lvalue == "true" then return lvalue end
- if rvalue == "true" then return rvalue end
- if lvalue == "false" then return rvalue end
- if rvalue == "false" then return lvalue end
- return "(" .. lvalue .. " or " .. rvalue .. ")"
- elseif query.operator == ">" then
- local lvalue = node_query_internal( query.lvalue, name .. ".parent", tracked )
- local rvalue = node_query_internal( query.rvalue, name, tracked )
- if lvalue == "true" then return name .. ".parent and " .. rvalue end
- if rvalue == "true" then return name .. ".parent and " .. lvalue end
- if lvalue == "false" then return lvalue end
- if rvalue == "false" then return rvalue end
- return rvalue .. " and " .. name .. ".parent and " .. lvalue
- end
- end
- end
- function dynamic_value_internal( value, state )
- if not value then return error "here" end
- if value.type == "integer"
- or value.type == "float"
- or value.type == "boolean" then
- return {
- value = tostring( value.value );
- complex = false;
- update = nil;
- initialise = nil;
- dependants = {};
- dependencies = {};
- }
- elseif value.type == "string" then
- return {
- value = ("%q"):format( value.value );
- complex = false;
- update = nil;
- initialise = nil;
- dependants = {};
- dependencies = {};
- }
- elseif value.type == "self" then
- return {
- value = "self";
- complex = false;
- update = nil;
- initialise = nil;
- dependants = {};
- dependencies = {};
- }
- elseif value.type == "parent" then
- if state.object:type_of( Application ) then
- error "TODO: fix this error"
- else
- local nr = #state.names + 1
- local nu = #state.names + 2
- local t = {
- value = "n" .. nr;
- complex = true;
- update = "f" .. nu .. "()";
- initialise = "self.values:subscribe( 'parent', lifetime, f" .. nu .. " )\nf" .. nu .. "()";
- dependants = {};
- dependencies = {};
- }
- state.names[nr] = "n" .. nr
- state.names[nu] = "f" .. nu
- state.functions[#state.functions + 1] = {
- code = SELF_INDEX_UPDATER:gsub( "NAME", "n" .. nr ):gsub( "INDEX", "parent" ):gsub( "FUNC", "f" .. nu );
- node = t;
- }
- return t
- end
- elseif value.type == "application" then
- if state.object:type_of( Application ) then
- return {
- value = "self";
- complex = false;
- update = nil;
- initialise = nil;
- dependants = {};
- dependencies = {};
- }
- else
- local nr = #state.names + 1
- local nu = #state.names + 2
- local t = {
- value = "n" .. nr;
- complex = true;
- update = "f" .. nu .. "()";
- initialise = "self.values:subscribe( 'application', lifetime, f" .. nu .. " )\nf" .. nu .. "()";
- dependants = {};
- dependencies = {};
- }
- state.names[nr] = "n" .. nr
- state.names[nu] = "f" .. nu
- state.functions[#state.functions + 1] = {
- code = SELF_INDEX_UPDATER:gsub( "NAME", "n" .. nr ):gsub( "INDEX", "application" ):gsub( "FUNC", "f" .. nu );
- node = t;
- }
- return t
- end
- elseif value.type == "identifier" then
- if state.environment[value.value] ~= nil then
- state.inputs[#state.inputs + 1] = state.environment[value.value].value;
- return {
- value = "i" .. #state.inputs;
- complex = false;
- update = nil;
- initialise = nil;
- dependants = {};
- dependencies = {};
- }
- else
- error "TODO: fix this error"
- end
- elseif value.type == "percentage" then
- error "NYI"
- elseif value.type == "unary operator expression" then
- local val = dynamic_value_internal( value.value, state )
- local n = #state.names + 1
- local t = {
- value = "n" .. n;
- complex = false;
- update = "n" .. n .. " = " .. val.value .. " ~= nil and " .. value.operator .. " " .. val.value .. " or nil";
- initialise = nil;
- dependants = {};
- dependencies = { val };
- }
- state.names[n] = "n" .. n
- val.dependants[#val.dependants + 1] = t
- return t
- elseif value.type == "call" then
- local val = dynamic_value_internal( value.value, state )
- local params = {}
- local params_strval = {}
- local n = #state.names + 1
- for i = 1, #value.parameters do
- params[i] = dynamic_value_internal( value.parameters[i], state )
- params_strval[i] = params[i].value
- end
- local t = {
- value = "n" .. n;
- complex = false;
- update = "n" .. n .. " = " .. val.value .. " ~= nil and " .. table.concat( params_strval, " ~= nil and " ) .. " ~= nil and " .. val.value .. "(" .. table.concat( params_strval, ", " ) .. ") or nil";
- initialise = nil;
- dependants = {};
- dependencies = { val, unpack( params ) };
- }
- state.names[n] = "n" .. n
- val.dependants[#val.dependants + 1] = t
- for i = 1, #params do
- params[i].dependants[#params[i].dependants + 1] = t
- end
- return t
- elseif value.type == "index" then
- local val = dynamic_value_internal( value.value, state )
- local idx = dynamic_value_internal( value.index, state )
- local nval = #state.names + 1 -- copy of the value
- local nidx = #state.names + 2 -- copy of the index
- local nret = #state.names + 3 -- return value
- local npdt = #state.names + 4 -- updater function
- local t = {
- value = "n" .. nret;
- complex = true;
- update = "f" .. npdt .. "()";
- initialise = nil;
- dependants = {};
- dependencies = { val, idx };
- }
- val.dependants[#val.dependants + 1] = t;
- idx.dependants[#idx.dependants + 1] = t;
- state.names[nval] = "n" .. nval
- state.names[nidx] = "n" .. nidx
- state.names[nret] = "n" .. nret
- state.names[npdt] = "f" .. npdt
- state.functions[#state.functions + 1] = {
- code = ARBITRARY_INDEX_UPDATER
- :gsub( "NAME", "n" .. nret )
- :gsub( "FUNC", "f" .. npdt )
- :gsub( "OLDVALUE", "n" .. nval )
- :gsub( "OLDINDEX", "n" .. nidx )
- :gsub( "LVALUE", val.value )
- :gsub( "INDEX", idx.value );
- node = t;
- }
- return t
- elseif value.type == "binary operator expression" then
- local lvalue = dynamic_value_internal( value.lvalue, state )
- local rvalue = dynamic_value_internal( value.rvalue, state )
- local n = #state.names + 1
- local t = {
- value = "n" .. n;
- complex = false;
- update = "n" .. n .. " = " .. (
- (value.operator == "or" and lvalue.value .. " or " .. rvalue.value)
- or (value.operator == "and" and lvalue.value .. " and " .. rvalue.value .. " or nil")
- -- or value.operator == "==" and "" -- potentially $abc == $def == true if both are undefined
- -- or value.operator == "~=" and "" -- potentially $abc != $def == true if one is undefined and false if both are undefined
- or (lvalue.value .. " ~= nil and " .. rvalue.value .. " ~= nil and " .. lvalue.value .. " " .. value.operator .. " " .. rvalue.value .. " or nil")
- );
- initialise = nil;
- dependants = {};
- dependencies = { lvalue, rvalue };
- }
- state.names[n] = "n" .. n
- lvalue.dependants[#lvalue.dependants + 1] = t
- rvalue.dependants[#rvalue.dependants + 1] = t
- return t
- elseif value.type == "dotindex" then
- local val = dynamic_value_internal( value.value, state )
- local nr = #state.names + 1
- local nu = #state.names + 2
- local t = {
- value = "(n" .. nr .. " and n" .. nr .. "." .. value.index .. ")";
- complex = true;
- update = "f" .. nu .. "()";
- initialise = nil;
- dependants = {};
- dependencies = { val };
- }
- state.names[nr] = "n" .. nr
- state.names[nu] = "f" .. nu
- state.functions[#state.functions + 1] = {
- code = ARBITRARY_DOTINDEX_UPDATER
- :gsub( "NAME", "n" .. nr )
- :gsub( "INDEX", value.index )
- :gsub( "FUNC", "f" .. nu )
- :gsub( "LVALUE", val.value );
- node = t;
- }
- val.dependants[#val.dependants + 1] = t
- return t
- elseif value.type == "query" then
- local val = dynamic_value_internal( value.source, state )
- local nret = #state.names + 1
- local npdt = #state.names + 2
- local t = {
- value = "n" .. nret;
- complex = true;
- update = "f" .. npdt .. "()";
- initialise = nil;
- dependants = {};
- dependencies = { val };
- }
- state.inputs[#state.inputs + 1] = value.query
- state.names[nret] = "n" .. nret
- state.names[npdt] = "f" .. npdt
- state.functions[#state.functions + 1] = {
- code = QUERY_UPDATER
- :gsub( "NAME", "n" .. nret )
- :gsub( "SOURCE", val.value )
- :gsub( "QDATA", "i" .. #state.inputs )
- :gsub( "FUNC", "f" .. npdt );
- node = t;
- }
- val.dependants[#val.dependants + 1] = t
- return t
- elseif value.type == "dynamic query" then
- local val = dynamic_value_internal( value.source, state )
- local nret = #state.names + 1
- local nsrc = #state.names + 2
- local npdt = #state.names + 3
- local t = {
- value = "n" .. nret;
- complex = true;
- update = "f" .. npdt .. "()";
- initialise = nil;
- dependants = {};
- dependencies = { val };
- }
- state.inputs[#state.inputs + 1] = value.query
- state.names[nret] = "n" .. nret
- state.names[nsrc] = "n" .. nsrc
- state.names[npdt] = "f" .. npdt
- state.functions[#state.functions + 1] = {
- code = DYNAMIC_QUERY_UPDATER
- :gsub( "NAME", "n" .. nret )
- :gsub( "PREVSOURCE", "n" .. nsrc )
- :gsub( "SOURCE", val.value )
- :gsub( "QDATA", "i" .. #state.inputs )
- :gsub( "FUNC", "f" .. npdt );
- node = t;
- }
- val.dependants[#val.dependants + 1] = t
- return t
- elseif value.type == "floor" then
- local val = dynamic_value_internal( value.value, state )
- local n = #state.names + 1
- local t = {
- value = "n" .. n;
- complex = false;
- update = "n" .. n .. " = " .. val.value .. " ~= nil and floor( " .. val.value .. " ) or nil";
- initialise = nil;
- dependants = {};
- dependencies = { val };
- }
- state.names[n] = "n" .. n
- val.dependants[#val.dependants + 1] = t
- state.floored = true
- return t
- elseif value.type == "tostring" then
- local val = dynamic_value_internal( value.value, state )
- local n = #state.names + 1
- local t = {
- value = "n" .. n;
- complex = false;
- update = "n" .. n .. " = " .. val.value .. " ~= nil and tostring( " .. val.value .. " ) or nil";
- initialise = nil;
- dependants = {};
- dependencies = { val };
- }
- state.names[n] = "n" .. n
- val.dependants[#val.dependants + 1] = t
- state.tostringed = true
- return t
- elseif value.type == "tag" then
- local val = dynamic_value_internal( value.value, state )
- local nidx = #state.names + 1
- local nval = #state.names + 2
- local npdt = #state.names + 3
- local t = {
- value = "n" .. nval;
- complex = true;
- update = "f" .. npdt .. "()";
- initialise = nil;
- dependants = {};
- dependencies = { val };
- }
- state.names[nidx] = "n" .. nidx
- state.names[nval] = "n" .. nval
- state.names[npdt] = "f" .. npdt
- state.functions[#state.functions + 1] = {
- code = TAG_CHECK_UPDATER
- :gsub( "NAME", "n" .. nidx )
- :gsub( "TAG", ("%q"):format( value.tag ) )
- :gsub( "FUNC", "f" .. npdt )
- :gsub( "LVALUE", val.value )
- :gsub( "VALUE", "n" .. nval );
- node = t;
- }
- val.dependants[#val.dependants + 1] = t
- return t
- else
- -- TODO: every other type of node
- error "TODO: fix this error"
- end
- end
- local is_operator = {
- ["+"] = true;
- ["-"] = true;
- ["*"] = true;
- ["/"] = true;
- ["%"] = true;
- ["^"] = true;
- ["&"] = true;
- ["|"] = true;
- [">"] = true;
- ["<"] = true;
- [">="] = true;
- ["<="] = true;
- ["!="] = true;
- ["=="] = true;
- }
- local op_precedences = {
- ["|"] = 0;
- ["&"] = 1;
- ["!="] = 2;
- ["=="] = 2;
- [">"] = 3;
- ["<"] = 3;
- [">="] = 3;
- ["<="] = 3;
- ["+"] = 4;
- ["-"] = 4;
- ["*"] = 5;
- ["/"] = 5;
- ["%"] = 5;
- ["^"] = 6;
- }
- local lua_operators = {
- ["|"] = "or";
- ["&"] = "and";
- ["!="] = "~=";
- }
- local function parse_name( stream )
- return stream:skip_value( "identifier" )
- end
- DynamicValueParser = class.new( "DynamicValueParser", nil, nil ) {
- stream = nil;
- flags = {};
- }
- function DynamicValueParser:DynamicValueParser( stream )
- self.stream = stream
- self.flags = {}
- end
- function DynamicValueParser:parse_primary_expression()
- if self.stream:skip( "float", "self" ) then
- return { type = "self" }
- elseif self.stream:skip( "float", "application" ) then
- return { type = "application" }
- elseif self.stream:skip( "float", "parent" ) then
- return { type = "parent" }
- elseif self.stream:test( "identifier" ) then
- return { type = "identifier", value = parse_name( self.stream ) }
- elseif self.stream:test( "int" ) then
- return { type = "integer", value = self.stream:next().value }
- elseif self.stream:test( "float" ) then
- return { type = "float", value = self.stream:next().value }
- elseif self.stream:test( "float" ) then
- return { type = "boolean", value = self.stream:next().value }
- elseif self.stream:test( "string" ) then
- return { type = "string", value = self.stream:next().value }
- elseif self.stream:test( "symbol", "$" ) then
- if self.flags.enable_queries then
- self.stream:next()
- else
- error "TODO: fix this error"
- end
- local dynamic = not self.stream:skip( "symbol", "$" )
- local query = self:parse_query_term( true )
- return { type = dynamic and "dynamic query" or "query", query = query, source = { type = "application" } }
- elseif self.stream:skip( "symbol", "(" ) then
- local expr = self:parse_expression() or error "TODO: fix this error"
- return self.stream:skip( "symbol", ")" ) and expr or error "TODO: fix this error"
- end
- return nil
- end
- function DynamicValueParser:parse_term()
- local operators = {}
- while self.stream:test( "symbol", "#" )
- or self.stream:test( "symbol", "!" )
- or self.stream:test( "symbol", "-" )
- or self.stream:test( "symbol", "+" ) do
- operators[#operators + 1] = self.stream:next().value
- end
- local term = self:parse_primary_expression()
- while term do
- if self.stream:skip( "symbol", "." ) then
- local index = parse_name( self.stream )
- or self.stream:skip_value( "float", "parent" )
- or self.stream:skip_value( "float", "application" )
- or error "TODO: fix this error"
- term = { type = "dotindex", value = term, index = index }
- elseif self.stream:skip( "symbol", "#" ) then
- local tag = parse_name( self.stream )
- or self.stream:skip_value( "float" )
- or error "TODO: fix this error"
- term = { type = "tag", value = term, tag = tag }
- elseif self.stream:skip( "symbol", "(" ) then
- local parameters = {}
- while self.stream:skip( "whitespace" ) do end
- if not self.stream:skip( "symbol", ")" ) then
- repeat
- while self.stream:skip( "whitespace" ) do end
- parameters[#parameters + 1] = self:parse_expression() or error "TODO: fix this error"
- while self.stream:skip( "whitespace" ) do end
- until not self.stream:skip( "symbol", "," )
- if not self.stream:skip( "symbol", ")" ) then
- error "TODO: fix this error"
- end
- end
- term = { type = "call", value = term, parameters = parameters }
- elseif self.stream:skip( "symbol", "[" ) then
- while self.stream:skip( "whitespace" ) do end
- local index = self:parse_expression() or error "TODO: fix this error"
- while self.stream:skip( "whitespace" ) do end
- if not self.stream:skip( "symbol", "]" ) then
- error "TODO: fix this error"
- end
- term = { type = "index", value = term, index = index }
- elseif self.stream:test( "symbol", "$" ) then
- if self.flags.enable_queries then
- self.stream:next()
- else
- error "TODO: fix this error"
- end
- local dynamic = not self.stream:skip( "symbol", "$" )
- local query = self:parse_query_term( true )
- term = { type = dynamic and "dynamic query" or "query", query = query, source = term }
- elseif self.stream:test( "symbol", "%" ) then
- if self.flags.enable_percentages then
- self.stream:next()
- else
- error "TODO: fix this error"
- end
- term = { type = "percentage", value = term }
- else
- break
- end
- end
- for i = #operators, 1, -1 do
- term = term and { type = "unary operator expression", value = term, operator = operators[i] }
- end
- return term
- end
- function DynamicValueParser:parse_expression()
- local operand_stack = { self:parse_term() }
- local operator_stack = {}
- local precedences = {}
- if #operand_stack == 0 then
- return nil
- end
- while self.stream:skip( "whitespace" ) do end
- while self.stream:test( "symbol" ) and is_operator[self.stream:peek().value] do
- local op = self.stream:next().value
- local prec = op_precedences[op]
- while precedences[1] and precedences[#precedences] >= prec do
- local rvalue = table.remove( operand_stack, #operand_stack )
- table.remove( precedences, #precedences )
- operand_stack[#operand_stack] = {
- type = "binary operator expression";
- operator = table.remove( operator_stack, #operator_stack );
- lvalue = operand_stack[#operand_stack];
- rvalue = rvalue;
- }
- end
- while self.stream:skip( "whitespace" ) do end
- operand_stack[#operand_stack + 1] = self:parse_term() or error "TODO: fix this"
- operator_stack[#operator_stack + 1] = lua_operators[op] or op
- precedences[#precedences + 1] = prec
- while self.stream:skip( "whitespace" ) do end
- end
- while precedences[1] do
- local rvalue = table.remove( operand_stack, #operand_stack )
- table.remove( precedences, #precedences )
- operand_stack[#operand_stack] = {
- type = "binary operator expression";
- operator = table.remove( operator_stack, #operator_stack );
- lvalue = operand_stack[#operand_stack];
- rvalue = rvalue;
- }
- end
- return operand_stack[1]
- end
- function DynamicValueParser:parse_query_term( in_dynamic_value )
- local negation_count, obj = 0
- while self.stream:skip( "symbol", "!" ) do
- negation_count = negation_count + 1
- end
- if self.stream:test( "identifier" ) or self.stream:skip( "symbol", "#" ) then -- ID
- obj = { type = "id", value = parse_name( self.stream ) or error "TODO: fix this error" }
- ID_parsed = true
- elseif self.stream:skip( "symbol", "*" ) then
- obj = { type = "any" }
- elseif self.stream:skip( "symbol", "?" ) then
- obj = { type = "class", value = parse_name( self.stream ) or error "TODO: fix this error" }
- elseif self.stream:skip( "symbol", "(" ) then
- print( self.stream:peek().value )
- obj = self:parse_query()
- if not self.stream:skip( "symbol", ")" ) then
- error "TODO: fix this error"
- end
- end
- local tags = {}
- while (not in_dynamic_value or not obj) and self.stream:skip( "symbol", "." ) do -- tags
- local tag = { type = "tag", value = parse_name( self.stream ) or error "TODO: fix this error" }
- if obj then
- obj = { type = "operator", operator = "&", lvalue = obj, rvalue = tag }
- else
- obj = tag
- end
- end
- if self.stream:skip( "symbol", "[" ) then
- local attributes = {}
- repeat
- local name = parse_name( self.stream ) or error "TODO: fix this error"
- while self.stream:skip( "whitespace" ) do end
- local comparison
- = self.stream:skip_value( "symbol", "=" )
- or self.stream:skip_value( "symbol", ">" )
- or self.stream:skip_value( "symbol", "<" )
- or self.stream:skip_value( "symbol", ">=" )
- or self.stream:skip_value( "symbol", "<=" )
- or self.stream:skip_value( "symbol", "!=" )
- or error "TODO: fix this"
- while self.stream:skip( "whitespace" ) do end
- local value = self:parse_expression() or error "TODO: fix this error"
- attributes[#attributes + 1] = {
- name = name;
- comparison = comparison;
- value = value;
- }
- until not self.stream:skip( "symbol", "," )
- if not self.stream:skip( "symbol", "]" ) then
- error "TODO: fix this error"
- end
- obj = obj and {
- type = "operator";
- rvalue = obj;
- lvalue = { type = "attributes", attributes = attributes };
- operator = "&";
- } or { type = "attributes", attributes = attributes }
- end
- if not obj then
- error "TODO: fix this error"
- end
- if negation_count % 2 == 1 then
- obj = { type = "negate", value = obj }
- end
- return obj
- end
- function DynamicValueParser:parse_query( in_dynamic_value )
- local operands = { self:parse_query_term( in_dynamic_value ) }
- local operators = {}
- while self.stream:skip( "whitespace" ) do end
- while self.stream:test( "symbol" ) do
- local prec = operator_list[self.stream:peek().value]
- if prec then
- while operators[1] and operator_list[operators[#operators]] >= prec do -- assumming left associativity for all operators
- operands[#operands - 1] = {
- type = "operator";
- lvalue = operands[#operands - 1];
- rvalue = table.remove( operands, #operands );
- operator = table.remove( operators, #operators );
- }
- end
- operators[#operators + 1] = self.stream:next().value
- while self.stream:skip( "whitespace" ) do end
- operands[#operands + 1] = self:parse_query_term( in_dynamic_value )
- while self.stream:skip( "whitespace" ) do end
- else
- break
- end
- end
- while operators[1] do
- operands[#operands - 1] = {
- type = "operator";
- lvalue = operands[#operands - 1];
- rvalue = table.remove( operands, #operands );
- operator = table.remove( operators, #operators );
- }
- end
- return operands[1]
- end
- QueryTracker=class.new("QueryTracker",nil,nil){
- parent=nil;
- queries={};
- lifetimes={};
- subscriptions={};
- ID=0;
- }
- function QueryTracker:QueryTracker(parent)
- self.parent=parent
- self.queries={}
- self.lifetimes={}
- self.subscriptions={}
- end
- function QueryTracker:track(query,nodes)
- local ID=self.ID
- self.queries[#self.queries+1]={query,nodes,ID}
- self.ID=ID+1
- self.lifetimes[ID]={}
- return ID
- end
- function QueryTracker:is_tracking(ID)
- for i=1,#self.queries do
- if self.queries[i][3]==ID then
- return true
- end
- end
- return false
- end
- function QueryTracker:get_query(ID)
- for i=1,#self.queries do
- if self.queries[i][3]==ID then
- return self.queries[i]
- end
- end
- end
- function QueryTracker:untrack(ID)
- for i=#self.queries,1,-1 do
- if self.queries[i][3]==ID then
- local t=self.lifetimes[ID]
- for i=#t,1,-1 do
- local l=t[i]
- t[i]=nil
- if l[1]=="value"then
- l[2].values:unsubscribe(l[3],l[4])
- elseif l[1]=="query"then
- l[2]:unsubscribe(l[3],l[4])
- elseif l[1]=="tag"then
- l[2]:unsubscribe_from_tag(l[3],l[4])
- end
- end
- self.lifetimes[ID]=nil
- self.subscriptions[ID]=nil
- return table.remove(self.queries,i)
- end
- end
- end
- function QueryTracker:subscribe(ID,lifetime,callback)
- local t=self.subscriptions[ID]or{}
- lifetime[#lifetime+1]={"query",self,ID,callback}
- self.subscriptions[ID]=t
- t[#t+1]=callback
- end
- function QueryTracker:unsubscribe(ID,callback)
- if self.subscriptions[ID]then
- for i=#self.subscriptions[ID],1,-1 do
- if self.subscriptions[ID][i]==callback then
- if#self.subscriptions[ID]==1 then
- self:untrack(ID)
- else
- table.remove(self.subscriptions[ID],i)
- end
- return callback
- end
- end
- end
- end
- function QueryTracker:update(mode,child)
- for i=1,#self.queries do
- local add,remove=(mode=="child-added"or mode=="child-changed")and self.queries[i][1](child),mode=="child-removed"
- if mode=="child-changed"and not add then
- remove=true
- end
- if add then
- local nodes=self.queries[i][2]
- local collated=self.parent.collated_children
- local n=1
- for i=1,#collated do
- if collated[i]==child then
- break
- elseif collated[i]==nodes[n]then
- n=n+1
- end
- end
- if nodes[n]~=child then--if it's not already in the query
- table.insert(nodes,n,child)
- self:invoke_child_change(self.queries[i][3],child,"child-added")
- else
- self:invoke_child_change(self.queries[i][3],child,"child-changed")
- end
- elseif remove then
- local t=self.queries[i][2]
- for n=1,#t do
- if t[n]==child then
- table.remove(t,n)
- self:invoke_child_change(self.queries[i][3],child,"child-removed")
- break
- end
- end
- end
- end
- end
- function QueryTracker:invoke_child_change(query_ID,child,mode)
- local callbacks=self.subscriptions[query_ID]or{}
- for i=1,#callbacks do
- callbacks[i](mode,child)
- end
- end
- local escape_chars={
- ["n"]="\n";
- ["r"]="\r";
- ["t"]="\t";
- }
- local symbols={
- ["("]=true;[")"]=true;
- ["["]=true;["]"]=true;
- ["{"]=true;["}"]=true;
- ["."]=true;[":"]=true;
- [","]=true;[";"]=true;
- ["="]=true;
- ["$"]=true;
- ["+"]=true;["-"]=true;
- ["*"]=true;["/"]=true;
- ["%"]=true;["^"]=true;
- ["#"]=true;
- ["!"]=true;
- ["&"]=true;["|"]=true;
- ["?"]=true;
- [">"]=true;["<"]=true;
- [">="]=true;["<="]=true;
- ["!="]=true;["=="]=true;
- }
- local keywords={
- ["self"]=true;
- ["application"]=true;
- ["parent"]=true;
- }
- Stream=class.new("Stream",nil,nil){
- position=1;
- line=1;
- character=1;
- text="";
- }
- function Stream:Stream(text)
- self.text=text
- end
- function Stream:consume_string()
- local text=self.text
- local close=text:sub(self.position,self.position)
- local escaped=false
- local sub=string.sub
- local str={}
- for i=self.position+1,#text do
- local char=sub(text,i,i)
- if char=="\n"then
- self.line=self.line+1
- self.character=0
- end
- if escaped then
- str[#str+1]=escape_chars[char]or"\\"..char
- elseif char=="\\"then
- escaped=true
- elseif char==close then
- self.position=i+1
- return{type="string",value=table.concat(str),position={
- character=char,line=line;
- }}
- else
- str[#str+1]=char
- end
- self.character=self.character+1
- end
- error("TODO:fix this error")
- end
- function Stream:consume_identifier()
- local word=self.text:match("[%w_]+",self.position)
- local char=self.character
- local type=keywords[word]and"float"
- or(word=="true"or word=="false"and"float")
- or"identifier"
- self.position=self.position+#word
- self.character=self.character+#word
- return{type=type,value=word,position={
- character=char,line=self.line;
- }}
- end
- function Stream:consume_number()
- local char=self.character
- local num=self.text:match("%d*%.?%d+e[%+%-]?%d+",self.position)
- or self.text:match("%d*%.?%d+",self.position)
- local type=(num:find"%."or num:find"e%-")
- and"float"or"int"
- self.position=self.position+#num
- self.character=self.character+#num
- return{type=type,value=num,position={
- character=char,line=line;
- }}
- end
- function Stream:consume_whitespace()
- local line,char=self.line,self.character
- local type="whitespace"
- local value="\n"
- if self.text:sub(1,1)=="\n"then
- self.line=self.line+1
- self.position=self.position+1
- self.character=1
- type="newline"
- else
- local n=#self.text:match("^[^%S\n]+",self.position)
- value=self.text:sub(self.position,self.position+n-1)
- self.position=self.position+n
- self.character=self.character+n
- end
- return{type=type,value=value,position={
- character=char,line=line;
- }}
- end
- function Stream:consume_symbol()
- local text=self.text
- local sub=string.sub
- local pos=self.position
- local s3=sub(text,pos,pos+2)
- local s2=sub(text,pos,pos+1)
- local s1=sub(text,pos,pos+0)
- local value=s1
- local char=self.character
- if symbols[s3]then
- value=s3
- elseif symbols[s2]then
- value=s2
- elseif not symbols[s1]then
- print(s1,s2,s3)
- error("TODO:fix this error")
- end
- self.character=self.character+#value
- self.position=self.position+#value
- return{type="symbol",value=value,position={
- character=char,line=self.line;
- }}
- end
- function Stream:consume()
- if self.position>#self.text then
- return{type="eof",value="",position={
- character=self.character,line=self.line;
- }}
- end
- local char=self.text:sub(self.position,self.position)
- if char=="\""or char=="'" then
- return self:consume_string()
- elseif char == "" or char == "\t" or char == "\n" then
- return self:consume_whitespace()
- elseif self.text:find( "^%.?%d", self.position ) then
- return self:consume_number()
- elseif char:find "%w" or char == "_" then
- return self:consume_identifier()
- else
- return self:consume_symbol()
- end
- end
- function Stream:is_EOF()
- return self:peek().type == "eof"
- end
- function Stream:peek()
- if self.buffer then
- return self.buffer
- end
- local token = self:consume()
- self.buffer = token
- return token
- end
- function Stream:next()
- local token = self:peek()
- self.buffer = nil
- return token
- end
- function Stream:test( type, value )
- local token = self:peek()
- return token.type == type and (value == nil or token.value == value) and token or nil
- end
- function Stream:skip( type, value )
- local token = self:peek()
- return token.type == type and (value == nil or token.value == value) and self:next() or nil
- end
- function Stream:skip_value( type, value )
- local token = self:peek()
- return token.type == type and (value == nil or token.value == value) and self:next().value or nil
- end
- Transition = class.new( "Transition", nil, nil ) {
- duration = 0.4;
- easing_function = nil;
- }
- function Transition:Transition( easing, duration )
- self.easing_function = easing
- self.duration = duration or self.duration
- end
- Transition.none = nil
- Transition.linear = Transition( Easing.linear, 0.4 )
- Transition.linear_slow = Transition( Easing.linear, 0.8 )
- Transition.linear_fast = Transition( Easing.linear, 0.2 )
- Transition.smooth = Transition( Easing.smooth, 0.4 )
- Transition.smooth_slow = Transition( Easing.smooth, 0.8 )
- Transition.smooth_fast = Transition( Easing.smooth, 0.2 )
- Transition.entrance = Transition( Easing.entrance, 0.4 )
- Transition.entrance_slow = Transition( Easing.entrance, 0.8 )
- Transition.entrance_fast = Transition( Easing.entrance, 0.2 )
- Transition.exit = Transition( Easing.exit, 0.4 )
- Transition.exit_slow = Transition( Easing.exit, 0.8 )
- Transition.exit_fast = Transition( Easing.exit, 0.2 )
- Type = class.new( "Type", nil, nil ) {
- name = "";
- }
- UnionType = class.new( "UnionType", Type, nil ) {
- lvalue = nil;
- rvalue = nil;
- }
- ListType = class.new( "ListType", Type, nil ) {
- value = nil;
- }
- TableType = class.new( "TableType", Type, nil ) {
- index = nil;
- value = nil;
- }
- function Type:Type( name )
- self.name = name
- self.meta.__div = self.either
- self.meta.__eq = self.matches
- end
- function UnionType:UnionType( lvalue, rvalue )
- self.lvalue = lvalue
- self.rvalue = rvalue
- return self:Type "Union"
- end
- function ListType:ListType( value )
- self.value = value
- return self:Type "List"
- end
- function TableType:TableType( index, value )
- self.index = index
- self.value = value
- return self:Type "Table"
- end
- function Type:tostring()
- return self.name
- end
- function UnionType:tostring()
- return self.lvalue:tostring() .. " | " .. self.rvalue:tostring()
- end
- function ListType:tostring()
- return self.value:tostring() .. "[]"
- end
- function TableType:tostring()
- return self.value:tostring() .. "{" .. self.index:tostring() .. "}"
- end
- function Type:either( other )
- return UnionType( self, other )
- end
- function Type:matches( type )
- if self:type_of( UnionType ) then
- return self.lvalue:matches( type ) and self.rvalue:matches( type )
- end
- if type:type_of( UnionType ) then
- return self:matches( type.lvalue ) or self:matches( type.rvalue )
- elseif type:type_of( ListType ) then
- return self.name == "List" and self.value:matches( type.value )
- elseif type:type_of( TableType ) then
- return self.name == "Table" and self.value:matches( type.value ) and self.index:matches( type.index )
- elseif type.name == "Any" then
- return true
- else
- return self.name == type.name
- end
- end
- function Type:casts_to( type )
- end
- Type.primitive = {}
- Type.primitive.null = Type "Null"
- Type.primitive.integer = Type "Integer"
- Type.primitive.number = Type "Number"
- Type.primitive.string = Type "String"
- Type.primitive.boolean = Type "Boolean"
- Type.primitive.optional_integer = Type.primitive.integer / Type.primitive.null
- Type.primitive.optional_number = Type.primitive.number / Type.primitive.null
- Type.primitive.optional_string = Type.primitive.string / Type.primitive.null
- Type.primitive.optional_boolean = Type.primitive.boolean / Type.primitive.null
- Type.any = Type "Any"
- Type.sheets = {}
- Type.sheets.colour = Type "colour"
- Type.sheets.alignment = Type "alignment"
- Type.sheets.Sheet = Type "Sheet"
- Type.sheets.optional_Sheet = Type "Sheet" / Type.primitive.null
- Type.sheets.Screen = Type "Screen"
- Type.sheets.Application = Type "Application"
- Type.sheets.Sheet_or_Screen = Type.sheets.Sheet / Type.sheets.Screen
- Typechecking = class.new( "Typechecking", nil, nil ) {
- }
- function Typechecking.check_type( ast, state )
- if ast.type == "self" then
- return ast, state.object:type_of( Sheet ) and Type.sheets.Sheet
- or state.object:type_of( Screen ) and Type.sheets.Screen
- or state.object:type_of( Application ) and Type.sheets.Application
- or error "this really should never happen but just incase here's an error message"
- elseif ast.type == "parent" then
- return ast, state.object:type_of( Sheet ) and Type.sheets.Sheet_or_Screen
- or state.object:type_of( Screen ) and Type.sheets.Application
- or state.object:type_of( Application ) and Type.primitive.null
- or error "this really should never happen but just incase here's another error message"
- elseif ast.type == "call" then
- return ast, error "TODO: implement calls, idk how but you can do this!"
- elseif ast.type == "query" or ast.type == "dynamic query" then
- local src, srctype = Typechecking.check_type( ast.source, state )
- if not (srctype == Type.sheets.Sheet_or_Screen or srctype == Type.sheets.Application) then
- error "TODO: fix this error"
- end
- ast.source = src
- return ast, Type.sheets.Sheet_or_Screen
- elseif ast.type == "string" then
- return ast, Type.primitive.string
- elseif ast.type == "integer" then
- return ast, Type.primitive.integer
- elseif ast.type == "float" then
- return ast, Type.primitive.number
- elseif ast.type == "boolean" then
- return ast, Type.primitive.boolean
- elseif ast.type == "index" then
- return ast, error "TODO: implement indexes, idk how but you can do this!"
- elseif ast.type == "application" then
- return ast, Type.sheets.Application
- elseif ast.type == "identifier" then
- if state.environment[ast.value] then
- return ast, state.environment[ast.value].type
- elseif state.object.values:has( ast.value ) then
- return {
- type = "dotindex";
- value = {
- type = "self";
- };
- index = ast.value;
- }, ValueHandler.properties[ast.value].type
- else
- error "TODO: fix this error"
- end
- elseif ast.type == "unary operator expression" then
- local _ast, type = Typechecking.check_type( ast.value, state )
- ast.value = _ast
- if ast.operator == "#" then
- if not (type == ListType( Type.any ) or type == Type.primitive.string) then
- error "TODO: fix this error"
- end
- type = Type.primitive.integer
- elseif ast.operator == "!" then
- -- any type is fine
- elseif ast.operator == "-" or ast.operator == "+" then
- if not (type == Type.primitive.integer or type == Type.primitive.number) then
- error "TODO: fix this error"
- end
- end
- return ast, type
- elseif ast.type == "dotindex" then
- local _ast, vtype = Typechecking.check_type( ast.value, state )
- ast.value = _ast
- if ValueHandler.properties[ast.index] then
- if vtype == (Type.sheets.Sheet_or_Screen / Type.sheets.Application / Type.primitive.null) then
- return ast, ValueHandler.properties[ast.index].type -- do a check for the index
- else
- error "TODO: fix this error"
- end
- else
- error "TODO: fix this error"
- end
- return ast
- elseif ast.type == "percentage" then
- -- See issue #37
- elseif ast.type == "binary operator expression" then
- local lvalue_ast, lvalue_type = Typechecking.check_type( ast.lvalue, state )
- local rvalue_ast, rvalue_type = Typechecking.check_type( ast.rvalue, state )
- ast.lvalue = lvalue_ast
- ast.rvalue = rvalue_ast
- if ast.operator == "+" then
- if lvalue_type == Type.primitive.string then
- if not (rvalue_type == Type.primitive.string or rvalue_type == Type.primitive.integer or rvalue_type == Type.primitive.number) then
- -- tostring it
- error "TODO: implement this"
- end
- ast.operator = ".."
- return ast, Type.primitive.string
- elseif rvalue_type == Type.primitive.string then
- if not (lvalue_type == Type.primitive.string or lvalue_type == Type.primitive.integer or lvalue_type == Type.primitive.number) then
- -- tostring it
- error "TODO: implement this"
- end
- ast.operator = ".."
- elseif lvalue_type == Type.primitive.integer then
- if rvalue_type == Type.primitive.integer then
- return ast, Type.primitive.integer
- elseif rvalue_type == Type.primitive.number then
- return ast, Type.primitive.number
- else
- error "TODO: fix this error"
- end
- elseif lvalue_type == Type.primitive.number then
- if rvalue_type == Type.primitive.integer or rvalue_type == Type.primitive.number then
- return ast, Type.primitive.number
- else
- error "TODO: fix this error"
- end
- else
- error "TODO: fix this error"
- end
- elseif ast.operator == "-" or ast.operator == "*" or ast.operator == "^" then
- if lvalue_type == Type.primitive.integer then
- if rvalue_type == Type.primitive.integer then
- return ast, Type.primitive.integer
- elseif rvalue_type == Type.primitive.number then
- return ast, Type.primitive.number
- else
- error "TODO: fix this error"
- end
- elseif lvalue_type == Type.primitive.number then
- if rvalue_type == Type.primitive.integer or rvalue_type == Type.primitive.number then
- return ast, Type.primitive.number
- else
- error "TODO: fix this error"
- end
- else
- error "TODO: fix this error"
- end
- elseif ast.operator == "/" then
- if lvalue_type == Type.primitive.integer / Type.primitive.number
- and rvalue_type == Type.primitive.integer / Type.primitive.number then
- return ast, Type.primitive.number
- else
- error "TODO: fix this error"
- end
- elseif ast.operator == "%" then
- if lvalue_type == Type.primitive.integer and rvalue_type == Type.primitive.integer then
- return ast, Type.primitive.number
- else
- error "TODO: fix this error"
- end
- elseif ast.operator == "and" then
- return ast, rvalue_type / Type.primitive.null
- elseif ast.operator == "or" then
- local tr = { lvalue_type }
- local idx = 1
- while tr[idx] do
- local t = tr[idx]
- if t:type_of( UnionType ) then
- if not (t.lvalue == Type.primitive.null) then
- tr[idx] = t.lvalue
- if not (t.rvalue == Type.primitive.null) then
- table.insert( tr, idx, t.rvalue )
- end
- elseif not (t.rvalue == Type.primitive.null) then
- tr[idx] = t.rvalue
- else
- table.remove( tr, idx )
- end
- else
- idx = idx + 1
- end
- end
- local t = tr[1]
- if t then
- for i = 2, #tr do
- t = UnionType( t, tr[i] )
- end
- return ast, t / rvalue_type
- else
- return rvalue_ast, rvalue_type
- end
- elseif ast.operator == ">" or ast.operator == "<" or ast.operator == ">=" or ast.operator == "<=" then
- if lvalue_type == Type.primitive.integer / Type.primitive.number
- and rvalue_type == Type.primitive.integer / Type.primitive.number then
- return Type.primitive.boolean
- else
- error "TODO: fix this error"
- end
- elseif ast.operator == "~=" or ast.operator == "==" then
- return type.primitive.boolean
- end
- elseif ast.type == "tag" then
- local obj, objtype = Typechecking.check_type( ast.value, state )
- ast.value = obj
- if objtype == Type.sheets.Sheet_or_Screen / Type.primitive.null then
- return ast, Type.primitive.boolean
- else
- error "TODO: fix this error"
- end
- end
- end
- function Typechecking.resolve_type( value )
- local t = type( value )
- if t == "number" then
- return value % 1 == 0 and Type.primitive.integer or Type.primitive.number
- elseif t == "boolean" or t == "string" then
- return Type.primitive[t]
- elseif t == "nil" then
- return Type.primitive.null
- -- potentially add tables here
- else
- return Type.any
- end
- end
- local floor = math.floor
- local get_transition_function
- local TRANSITION_FUNCTION_CODE
- local tfcache = {}
- local setf
- ValueHandler = class.new( "ValueHandler", nil, nil ) {
- object = nil;
- lifetimes = {};
- values = {};
- subscriptions = {};
- defaults = {};
- removed_lifetimes = {};
- transitions = {};
- transitions_lookup = {};
- }
- ValueHandler.properties = {}
- function ValueHandler:ValueHandler( object )
- self.object = object
- self.lifetimes = {}
- self.values = {}
- self.subscriptions = {}
- self.defaults = {}
- self.removed_lifetimes = {}
- self.transitions = {}
- self.transitions_lookup = {}
- object.set = setf
- end
- function ValueHandler:add( name, default, options )
- if not ValueHandler.properties[name] then
- error "TODO: fix this error"
- end
- self.object["set_" .. name] = type( options ) == "function" and options or Codegen.dynamic_property_setter( name, options )
- self.object["raw_" .. name] = default
- self.object[name] = default
- self.values[#self.values + 1] = name
- self.defaults[name] = default
- self.lifetimes[name] = {}
- if ValueHandler.properties[name].transitionable then
- self.object["set_" .. name .. "_transition"] = get_transition_function( name )
- self.object[name .. "_transition"] = Transition.none
- end
- end
- function ValueHandler:remove( name )
- self.lifetimes[name] = nil
- self.values[name] = nil
- self.object[name] = nil
- self.object["raw_" .. name] = nil
- self.object["set_" .. name] = nil
- end
- function ValueHandler:has( name )
- return self.lifetimes[name] ~= nil
- end
- function ValueHandler:trigger( name )
- if self.subscriptions[name] then
- for i = #self.subscriptions[name], 1, -1 do
- self.subscriptions[name][i]()
- end
- end
- end
- function ValueHandler:respawn( name )
- local t = self.lifetimes[name]
- for i = #t, 1, -1 do
- local l = t[i]
- t[i] = nil
- if l[1] == "value" then
- l[2].values:unsubscribe( l[3], l[4] )
- elseif l[1] == "query" then
- l[2]:unsubscribe( l[3], l[4] )
- elseif l[1] == "tag" then
- l[2]:unsubscribe_from_tag( l[3], l[4] )
- end
- end
- self.lifetimes[name] = {}
- end
- function ValueHandler:subscribe( name, lifetime, callback )
- self.subscriptions[name] = self.subscriptions[name] or {}
- lifetime[#lifetime + 1] = { "value", self.object, name, callback }
- local t = self.subscriptions[name]
- t[#t + 1] = callback
- return callback
- end
- function ValueHandler:unsubscribe( name, callback )
- if self.subscriptions[name] then
- for i = #self.subscriptions[name], 1, -1 do
- if self.subscriptions[name][i] == callback then
- table.remove( self.subscriptions[name], i )
- return callback
- end
- end
- end
- end
- function ValueHandler:child_removed()
- for k, v in pairs( self.lifetimes ) do
- self.removed_lifetimes[k] = v
- self:respawn( k )
- end
- end
- function ValueHandler:child_inserted()
- for k, v in pairs( self.removed_lifetimes ) do
- local lifetime = self.lifetimes[k]
- for i = 1, #v do
- if v[i][1] == "value" then
- v[i][2].values:subscribe( v[i][3], lifetime, v[i][4] )
- elseif v[i][1] == "query" then
- v[i][2]:subscribe( v[i][3], lifetime, v[i][4] )
- end
- v[i][4]()
- end
- end
- self.removed_lifetimes = {}
- end
- function ValueHandler:transition( property, final, transition, custom_update )
- local index = self.transitions_lookup[property] or #self.transitions + 1
- local floored = false -- TODO: make this respect the property
- local ptype = ValueHandler.properties[property].type
- if ptype == Type.primitive.integer then
- floored = true
- elseif ptype ~= Type.primitive.number then
- Exception.throw( Exception( "PropertyTransitionException", "Cannot animate non-number property '" .. property .. "'" ) ) -- TODO: make custom exception for this
- end
- final = floored and floor( final + 0.5 ) or final
- if transition ~= Transition.none and self.object.application then
- self.transitions_lookup[property] = index
- self.transitions[index] = {
- property = property;
- initial = self.object[property];
- final = final;
- diff = final - self.object[property];
- duration = transition.duration;
- clock = 0;
- easing = transition.easing_function;
- floored = floored;
- change = ValueHandler.properties[property].change;
- custom_update = custom_update;
- }
- else
- if self.object[property] ~= final then
- self.object[property] = final
- if ValueHandler.properties[property].change == "self" then
- self.object:set_changed()
- elseif ValueHandler.properties[property].change == "parent" and self.object.parent then
- self.object.parent:set_changed()
- end
- if custom_update then
- custom_update( self.object )
- end
- self:trigger( property )
- end
- end
- end
- function ValueHandler:update( dt )
- for i = #self.transitions, 1, -1 do
- local trans = self.transitions[i]
- trans.clock = trans.clock + dt
- if trans.clock >= trans.duration then
- self.object[trans.property] = trans.final
- table.remove( self.transitions, i )
- self.transitions_lookup[trans.property] = nil
- else
- local eased = trans.easing( trans.initial, trans.diff, trans.clock / trans.duration )
- self.object[trans.property] = trans.floored and floor( eased + 0.5 ) or eased
- end
- if trans.change == "self" then
- self.object:set_changed()
- elseif trans.change == "parent" and self.object.parent then
- self.object.parent:set_changed()
- end
- if trans.custom_update then
- trans.custom_update( self.object )
- end
- self:trigger( trans.property )
- end
- end
- ValueHandler.properties.x = { type = Type.primitive.integer, change = "parent", transitionable = true }
- ValueHandler.properties.y = { type = Type.primitive.integer, change = "parent", transitionable = true }
- ValueHandler.properties.z = { type = Type.primitive.integer, change = "parent", transitionable = true }
- ValueHandler.properties.x_offset = { type = Type.primitive.integer, change = "self", transitionable = true }
- ValueHandler.properties.y_offset = { type = Type.primitive.integer, change = "self", transitionable = true }
- ValueHandler.properties.width = { type = Type.primitive.integer, change = "self", transitionable = true }
- ValueHandler.properties.height = { type = Type.primitive.integer, change = "self", transitionable = true }
- ValueHandler.properties.text = { type = Type.primitive.string, change = "self", transitionable = false }
- ValueHandler.properties.horizontal_alignment = { type = Type.sheets.alignment, change = "self", transitionable = false }
- ValueHandler.properties.vertical_alignment = { type = Type.sheets.alignment, change = "self", transitionable = false }
- ValueHandler.properties.colour = { type = Type.sheets.colour, change = "self", transitionable = false }
- ValueHandler.properties.text_colour = { type = Type.sheets.colour, change = "self", transitionable = false }
- ValueHandler.properties.active_colour = { type = Type.sheets.colour, change = "self", transitionable = false }
- ValueHandler.properties.parent = { type = Type.sheets.optional_Sheet, change = "parent", transitionable = false }
- function get_transition_function( name )
- if not tfcache[name] then
- tfcache[name] = (load or loadstring)( TRANSITION_FUNCTION_CODE:gsub( "PROPERTY", name ) )()
- end
- return tfcache[name]
- end
- function setf( self, t )
- for k, v in pairs( t ) do
- if self["set_" .. k] then
- self["set_" .. k]( self, v )
- else
- -- TODO: error or just ignore?
- end
- end
- return self
- end
- TRANSITION_FUNCTION_CODE = [[
- return function( self, value )
- self.PROPERTY_transition = value
- return self
- end]]
- local function exception_handler( e )
- return error( tostring( e ), 0 )
- end
- local handle_event
- Application = class.new( "Application", nil, IQueryable, ITimer ) {
- name = "UnNamed Application";
- path = "";
- terminateable = true;
- running = true;
- screen = nil;
- -- internal
- screens = {};
- resource_loaders = {};
- extensions = {};
- mouse = nil;
- keys = {};
- changed = false;
- }
- function Application:Application( name, path )
- self.name = name
- self.path = path or name
- self.screens = {}
- self.resource_loaders = {}
- self.extensions = {}
- self.keys = {}
- self:ICollatedChildren()
- self:IQueryable()
- self:ITimer()
- self.screens[1] = Screen( self, term.getSize() ):add_terminal( term )
- self.screen = self.screens[1]
- end
- function Application:register_resource_loader( type, loader )
- parameters.check( 2, "type", "string", type, "loader", "function", loader )
- self.resource_loaders[type] = loader
- end
- function Application:unregister_resource_loader( type )
- parameters.check( 1, "type", "string", type )
- self.resource_loaders[type] = nil
- end
- function Application:register_file_extension( extension, type )
- parameters.check( 2, "extension", "string", extension, "type", "string", type )
- self.extensions[extension] = type
- end
- function Application:unregister_file_extension( extension )
- parameters.check( 1, "extension", "string", extension )
- self.extensions[extension] = nil
- end
- function Application:load_resource( resource, type, ... )
- parameters.check( 2, "resource", "string", resource, "type", "string", type or "" )
- if not type then
- type = self.extensions[resource:match( "%.(%w+)$" ) or "txt"] or "text.plain"
- end
- if self.resource_loaders[type] then
- local h = fs.open( fs.combine( self.path, resource ), "r" ) or fs.open( resource, "r" )
- if h then
- local content = h.readAll()
- h.close()
- return self.resource_loaders[type]( self, resource, content, ... )
- else
- Exception.throw( ResourceLoadException, "Failed to open file'" .. resource .. "':not found under'/''or'" .. self.path .. "'", 2 )
- end
- else
- Exception.throw( ResourceLoadException, "No loader for resource type'" .. type .. "'", 2 )
- end
- end
- function Application:is_key_pressed( key )
- parameters.check( 1, "key", "string", key )
- return self.keys[key] ~= nil
- end
- function Application:stop()
- self.running = false
- return self
- end
- function Application:add_screen()
- local screen = Screen( self, term.get_size() )
- self.screens[#self.screens + 1] = screen
- return screen
- end
- function Application:remove_screen( screen )
- parameters.check( 1, "screen", Screen, screen )
- for i = #self.screens, 1, -1 do
- if self.screens[i] == screen then
- return table.remove( self.screens, i )
- end
- end
- end
- function Application:child_value_changed( child )
- return self.query_tracker:update( "child-changed", child )
- end
- function Application:update_collated( mode, child, data )
- local collated = self.collated_children
- if mode == "child-added" then
- if data:type_of( Screen ) then
- local v = data
- data = nil
- for i = 1, #self.screens do
- if self.screens[i] == v then
- repeat
- i = i + 1
- until not self.screens[i] or #self.screens[i].collated_children > 0
- if self.screens[i] then
- data = self.screens[i].collated_children[1]
- end
- break
- end
- end
- end
- if data then
- for i = #collated, 1, -1 do
- if collated[i] == data then
- if child:implements( ICollatedChildren ) then
- i = i - 1
- for n = 1, #child.collated_children do
- table.insert( collated, i + n, child.collated_children[n] )
- end
- table.insert( collated, i + #child.collated_children + 1, child )
- else
- table.insert( collated, i, child )
- end
- end
- end
- else
- if child:implements( ICollatedChildren ) then
- for i = 1, #child.collated_children do
- collated[#collated + 1] = child.collated_children[i]
- end
- end
- collated[#collated + 1] = child
- end
- elseif mode == "child-removed" then
- local open, close = child:implements( ICollatedChildren ) and child.collated_children[1] or child, child
- local removing = false
- for i = #collated, 1, -1 do
- if collated[i] == close then removing = true end
- local brk = collated[i] == open
- if removing then table.remove( collated, i ) end
- if brk then break end
- end
- end
- self.query_tracker:update( mode, child )
- end
- function Application:event( event, ... )
- local params = { ... }
- if event == "timer" and self:update_timer( ... ) then
- return
- end
- return handle_event( self, event, params, ... )
- end
- function Application:draw()
- if self.changed then
- for i = 1, #self.screens do
- self.screens[i]:draw()
- end
- self.changed = false
- end
- end
- function Application:update()
- local dt = self:step_timer():get_timer_delta()
- for i = 1, #self.screens do
- self.screens[i]:update( dt )
- end
- if self.on_update then
- self:on_update( dt )
- end
- end
- function Application:load()
- self.changed = true
- if self.on_load then
- return self:on_load()
- end
- end
- function Application:run()
- Exception.try (function()
- self:load()
- local t = os.startTimer( 0 ) -- updating timer
- while self.running do
- local event = { coroutine.yield() }
- if event[1] == "timer" and event[2] == t then
- t = os.startTimer( .05 )
- elseif event[1] == "terminate" and self.terminateable then
- self:stop()
- else
- self:event( unpack( event ) )
- end
- self:update()
- self:draw()
- end
- end) {
- Exception.default (exception_handler);
- }
- end
- function handle_event( self, event, params, ... )
- local screens = {}
- for i = 1, #self.screens do
- screens[i] = self.screens[i]
- end
- if event == "mouse_click" then
- self.mouse = {
- x = params[2] - 1, y = params[3] - 1;
- down = true, button = params[1];
- timer = os.startTimer( 1 ), time = os.clock(), moved = false;
- }
- local e = MouseEvent( 0, params[2] - 1, params[3] - 1, params[1], true )
- for i = #screens, 1, -1 do
- if screens[i]:gets_term_events() then
- screens[i]:handle( e )
- end
- end
- elseif event == "mouse_up" then
- local e = MouseEvent( 1, params[2] - 1, params[3] - 1, params[1], true )
- for i = #screens, 1, -1 do
- if screens[i]:gets_term_events() then
- screens[i]:handle( e )
- end
- end
- self.mouse.down = false
- os.cancelTimer( self.mouse.timer )
- if not self.mouse.moved and os.clock() - self.mouse.time < 1 and params[1] == self.mouse.button then
- local e = MouseEvent( 2, params[2] - 1, params[3] - 1, params[1], true )
- for i = #screens, 1, -1 do
- if screens[i]:gets_term_events() then
- screens[i]:handle( e )
- end
- end
- end
- elseif event == "mouse_drag" then
- local e = MouseEvent( 4, params[2] - 1, params[3] - 1, params[1], true )
- for i = #screens, 1, -1 do
- if screens[i]:gets_term_events() then
- screens[i]:handle( e )
- end
- end
- self.mouse.moved = true
- os.cancelTimer( self.mouse.timer )
- elseif event == "mouse_scroll" then
- local e = MouseEvent( 5, params[2] - 1, params[3] - 1, params[1], true )
- for i = #screens, 1, -1 do
- if screens[i]:gets_term_events() then
- screens[i]:handle( e )
- end
- end
- elseif event == "monitor_touch" then
- local events = {
- MouseEvent( 0, params[2] - 1, params[3] - 1, 1 );
- MouseEvent( 1, params[2] - 1, params[3] - 1, 1 );
- MouseEvent( 2, params[2] - 1, params[3] - 1, 1 );
- }
- for i = 1, #screens do
- if screens[i]:uses_monitor( params[1] ) then
- for n = 1, #events do
- screens[i]:handle( events[n] )
- end
- end
- end
- elseif event == "chatbox_something" then
- -- TODO: implement this
- -- handle( TextEvent( 10, params[1] ) )
- elseif event == "char" then
- local e = TextEvent( 9, params[1] )
- for i = #screens, 1, -1 do
- screens[i]:handle( e )
- end
- elseif event == "paste" then
- local e
- if self.keys.leftShift or self.keys.rightShift then -- TODO: why the left_ctrl/right_ctrl?
- e = KeyboardEvent( 7, keys.v, { left_ctrl = true, right_ctrl = true } )
- else
- e = TextEvent( 11, params[1] )
- end
- for i = #screens, 1, -1 do
- screens[i]:handle( e )
- end
- elseif event == "key" then
- self.keys[keys.getName( params[1] ) or params[1]] = os.clock()
- local e = KeyboardEvent( 7, params[1], self.keys )
- for i = #screens, 1, -1 do
- screens[i]:handle( e )
- end
- elseif event == "key_up" then
- self.keys[keys.getName( params[1] ) or params[1]] = nil
- local e = KeyboardEvent( 8, params[1], self.keys )
- for i = #screens, 1, -1 do
- screens[i]:handle( e )
- end
- elseif event == "term_resize" then
- local width, height = term.getSize()
- for i = 1, #screens do
- if screens[i].terminals[1] == term then
- screens[i]:set_width( width )
- screens[i]:set_height( height )
- end
- end
- elseif event == "timer" and self.mouse and params[1] == self.mouse.timer then
- local e = MouseEvent( 3, self.mouse.x, self.mouse.y, self.mouse.button, true )
- for i = #screens, 1, -1 do
- if screens[i]:gets_term_events() then
- screens[i]:handle( e )
- end
- end
- else
- local ev = MiscEvent( event, ... )
- for i = #screens, 1, -1 do
- screens[i]:handle( ev )
- end
- if not ev.handled then
- end
- end
- end
- Screen = class.new( "Screen", nil, IChildContainer, ITagged, ISize ) {
- parent = nil;
- -- internal
- terminals = {};
- monitors = {};
- surface = nil;
- changed = true;
- values = nil;
- }
- function Screen:Screen( application, width, height )
- self.parent = application
- self.terminals = {}
- self.monitors = {}
- self.surface = surface.create( 0, 0 )
- self.application = application
- self.values = ValueHandler( self )
- self:ICollatedChildren()
- self:IQueryable()
- self:IChildContainer()
- self:ITagged()
- self:ISize()
- self:set_width( width )
- self:set_height( height )
- end
- function Screen:gets_term_events()
- for i = 1, #self.terminals do
- if self.terminals[i] == term then
- return true
- end
- end
- return false
- end
- function Screen:set_changed( state )
- self.changed = state ~= false
- if state ~= false then -- must have a parent Application
- self.parent.changed = true
- end
- return self
- end
- function Screen:add_monitor( side )
- parameters.check( 1, "side", "string", side )
- if peripheral.getType( side ) ~= "monitor" then
- throw( IncorrectParameterException, "expected monitor on side'" .. side .. "',got" .. peripheral.getType( side ), 2 )
- end
- local mon = peripheral.wrap( side )
- self.monitors[side] = mon
- return self:add_terminal( mon )
- end
- function Screen:remove_monitor( side )
- parameters.check( 1, "side", "string", side )
- local mon = self.monitors[side]
- if mon then
- self.monitors[side] = nil
- self:remove_terminal( mon )
- end
- return self
- end
- function Screen:uses_monitor( side )
- return self.monitors[side] ~= nil
- end
- function Screen:add_terminal( t )
- parameters.check( 1, "terminal", "table", t )
- self.terminals[#self.terminals + 1] = t
- self.surface:clear()
- return self:set_changed()
- end
- function Screen:remove_terminal( t )
- parameters.check( 1, "terminal", "table", t )
- for i = #self.terminals, 1, -1 do
- if self.terminals[i] == t then
- self:set_changed()
- return table.remove( self.terminals, i )
- end
- end
- return self
- end
- function Screen:draw()
- if self.changed then
- local surface = self.surface
- local children = {}
- local cx, cy, cc
- surface:clear( 1 )
- for i = 1, #self.children do
- children[i] = self.children[i]
- end
- for i = 1, #children do
- local child = children[i]
- if child:is_visible() then
- child:draw( self.surface, child.x, child.y )
- if child.cursor_active then
- cx, cy, cc = child.x + child.cursor_x, child.y + child.cursor_y, child.cursor_colour
- end
- end
- end
- for i = 1, #self.terminals do
- surface:output( self.terminals[i] )
- end
- self.changed = false
- for i = 1, #self.terminals do
- if cx then
- self.terminals[i].setCursorPos( cx + 1, cy + 1 )
- self.terminals[i].setTextColour( cc )
- self.terminals[i].setCursorBlink( true )
- else
- self.terminals[i].setCursorBlink( false )
- end
- end
- end
- end
- function Screen:handle( event )
- local c = {}
- local children = self.children
- for i = 1, #children do
- c[i] = children[i]
- end
- if event:type_of( MouseEvent ) then
- local within = event:is_within_area( 0, 0, self.width, self.height )
- for i = #c, 1, -1 do
- c[i]:handle( event:clone( c[i].x, c[i].y, within ) )
- end
- else
- for i = #c, 1, -1 do
- c[i]:handle( event )
- end
- end
- end
- function Screen:update( dt )
- local children = {}
- self.values:update( dt )
- for i = 1, #self.children do
- children[i] = self.children[i]
- end
- for i = 1, #children do
- children[i]:update( dt )
- end
- end
- Sheet = class.new( "Sheet", nil, ITagged, ISize ) {
- x = 0;
- y = 0;
- z = 0;
- style = nil;
- parent = nil;
- -- internal
- changed = true;
- cursor_x = 0;
- cursor_y = 0;
- cursor_colour = 0;
- cursor_active = false;
- handles_keyboard = false;
- handles_text = false;
- values = nil;
- }
- function Sheet:Sheet( x, y, width, height )
- if x ~= nil then self:set_x( x ) end
- if y ~= nil then self:set_y( y ) end
- if width ~= nil then self:set_width( width ) end
- if height ~= nil then self:set_height( height ) end
- end
- function Sheet:initialise()
- self.values = ValueHandler( self )
- self:ITagged()
- self:ISize()
- self.values:add( "x", 0 )
- self.values:add( "y", 0 )
- self.values:add( "z", 0, { custom_update_code = "if self.parent then self.parent:reposition_child_z_index(self)end" } )
- self.values:add( "parent", nil, function( self, parent )
- if parent and not class.type_of( parent, Sheet ) and not class.type_of( parent, Screen ) then
- Exception.throw( IncorrectParameterException( "expected Sheet or Screen parent,got" .. class.type( parent ), 2 ) )
- end
- if parent then
- return parent:add_child( self )
- else
- return self:remove()
- end
- end )
- end
- function Sheet:remove()
- if self.parent then
- return self.parent:remove_child( self )
- end
- end
- function Sheet:is_visible()
- return self.parent and self.parent:is_child_visible( self )
- end
- function Sheet:bring_to_front()
- if self.parent then
- return self:set_parent( self.parent ) -- TODO: improve this
- end
- return self
- end
- function Sheet:set_changed( state )
- self.changed = state ~= false
- if state ~= false and self.parent and not self.parent.changed then -- TODO: why not self.parent.changed?
- self.parent:set_changed()
- end
- return self
- end
- function Sheet:set_cursor_blink( x, y, colour )
- colour = colour or 128
- parameters.check( 3, "x", "number", x, "y", "number", y, "colour", "number", colour )
- self.cursor_active = true
- self.cursor_x = x
- self.cursor_y = y
- self.cursor_colour = colour
- return self
- end
- function Sheet:reset_cursor_blink()
- self.cursor_active = false
- return self
- end
- function Sheet:tostring()
- return "[Instance]" .. self.class:type() .. "" .. tostring( self.id )
- end
- function Sheet:update( dt )
- self.values:update( dt )
- if self.on_update then
- self:on_update( dt )
- end
- end
- function Sheet:draw( surface, x, y )
- self.changed = false
- end
- function Sheet:handle( event )
- if event:type_of( MouseEvent ) then
- if event:is( 6 ) and event:is_within_area( 0, 0, self.width, self.height ) and event.within then
- event.button[#event.button + 1] = self
- end
- self:on_mouse_event( event )
- elseif event:type_of( KeyboardEvent ) and self.handles_keyboard and self.on_keyboard_event then
- self:on_keyboard_event( event )
- elseif event:type_of( TextEvent ) and self.handles_text and self.on_text_event then
- self:on_text_event( event )
- end
- end
- function Sheet:on_mouse_event( event )
- if not event.handled and event:is_within_area( 0, 0, self.width, self.height ) and event.within then
- if event:is( 0 ) then
- return event:handle( self )
- end
- end
- end
- Thread = class.new( "Thread", nil, nil ) {
- running = true;
- f = nil;
- co = nil;
- filter = nil;
- }
- function Thread:Thread( f, ... )
- if type( f ) == "string" then
- f = load( f )
- elseif type( f ) ~= "function" then
- parameters.check( 1, "f", "function/string", f )
- end
- self.f = f
- self.co = coroutine.create( f )
- self:resume( ... )
- end
- function Thread:stop()
- self.running = false
- end
- function Thread:restart()
- self.running = true
- self.co = coroutine.create( self.f )
- end
- function Thread:resume( event, ... )
- if not self.running or (self.filter ~= nil and event ~= self.filter) then
- return
- end
- local ok, data = coroutine.resume( self.co, event, ... )
- if ok then
- if coroutine.status( self.co ) == "dead" then
- self.running = false
- end
- self.filter = data
- else
- if data == "SHEETS_EXCEPTION\nPut code in a try block to catch the exception." then
- return Exception.throw( Exception.thrown() )
- end
- return Exception.throw( ThreadRuntimeException, data, 0 )
- end
- end
- -- needs to update to new exception system
- Container = class.new( "Container", Sheet, IChildContainer, IColoured ) {
- colour = nil;
- x_offset = 0;
- y_offset = 0;
- on_pre_draw = nil;
- on_post_draw = nil;
- }
- function Container:Container( x, y, w, h )
- self:initialise()
- self:ICollatedChildren()
- self:IQueryable()
- self:IChildContainer()
- self:IColoured()
- self.values:add( "x_offset", 0 )
- self.values:add( "y_offset", 0 )
- return self:Sheet( x, y, w, h )
- end
- function Container:update( dt )
- local children = self:get_children()
- self.values:update( dt )
- if self.on_update then
- self:on_update( dt )
- end
- for i = #children, 1, -1 do
- children[i]:update( dt )
- end
- end
- function Container:draw( surface, x, y )
- local children = self.children
- local cx, cy, cc
- local x_offset, y_offset = self.x_offset, self.y_offset
- self:reset_cursor_blink()
- surface:fillRect( x, y, self.width, self.height, self.colour )
- if self.on_pre_draw then
- self:on_pre_draw()
- end
- for i = 1, #children do
- local child = children[i]
- if child:is_visible() then
- child:draw( surface, x + child.x + x_offset, y + child.y + y_offset )
- if child.cursor_active then
- cx, cy, cc = child.x + child.cursor_x, child.y + child.cursor_y, child.cursor_colour
- end
- end
- end
- if cx then
- self:set_cursor_blink( cx, cy, cc )
- end
- if self.on_post_draw then
- self:on_post_draw()
- end
- self.changed = false
- end
- function Container:handle( event )
- local children = self:get_children()
- local x_offset, y_offset = self.x_offset, self.y_offset
- if event:type_of( MouseEvent ) then
- local within = event:is_within_area( 0, 0, self.width, self.height )
- for i = #children, 1, -1 do
- children[i]:handle( event:clone( children[i].x + x_offset, children[i].y + y_offset, within ) )
- end
- else
- for i = #children, 1, -1 do
- children[i]:handle( event )
- end
- end
- if event:type_of( MouseEvent ) then
- if event:is( 6 ) and event:is_within_area( 0, 0, self.width, self.height ) and event.within then
- event.button[#event.button + 1] = self
- end
- self:on_mouse_event( event )
- elseif event:type_of( KeyboardEvent ) and self.handles_keyboard and self.on_keyboard_event then
- self:on_keyboard_event( event )
- elseif event:type_of( TextEvent ) and self.handles_text and self.on_text_event then
- self:on_text_event( event )
- end
- end
- local wrapline, wrap
- IHasText = class.new_interface( "IHasText", nil ) {
- text = "";
- text_lines = nil;
- horizontal_alignment = 0;
- vertical_alignment = 3;
- text_colour = 1;
- }
- function IHasText:IHasText()
- local function wrap()
- self:wrap_text()
- self:set_changed()
- end
- self.values:add( "text", "" )
- self.values:add( "text_colour", 1 )
- self.values:add( "horizontal_alignment", 0 )
- self.values:add( "vertical_alignment", 3 )
- self.values:subscribe( "width", {}, wrap )
- self.values:subscribe( "text", {}, wrap )
- end
- function IHasText:auto_height()
- if not self.text_lines then
- self:wrap_text( true )
- end
- return self:set_height( #self.text_lines )
- end
- function IHasText:wrap_text( ignore_height )
- self.text_lines = wrap( self.text, self.width, not ignore_height and self.height )
- end
- function IHasText:draw_text( surface, x, y )
- local offset, lines = 0, self.text_lines
- local horizontal_alignment = self.horizontal_alignment
- local vertical_alignment = self.vertical_alignment
- if not lines then
- self:wrap_text()
- lines = self.text_lines
- end
- if vertical_alignment == 1 then
- offset = math.floor( self.height / 2 - #lines / 2 + .5 )
- elseif vertical_alignment == 4 then
- offset = self.height - #lines
- end
- for i = 1, #lines do
- local x_offset = 0
- if horizontal_alignment == 1 then
- x_offset = math.floor( self.width / 2 - #lines[i] / 2 + .5 )
- elseif horizontal_alignment == 2 then
- x_offset = self.width - #lines[i]
- end
- surface:drawString( x + x_offset, y + offset + i - 1, lines[i], nil, self.text_colour )
- end
- end
- function IHasText:on_pre_draw()
- self:draw_text "default"
- end
- function wrapline( text, width )
- if text:sub( 1, width ):find "\n" then
- return text:match "^(.-)\n[^%S\n]*(.*)$"
- end
- if #text <= width then
- return text
- end
- for i = width + 1, 1, -1 do
- if text:sub( i, i ):find "%s" then
- return text:sub( 1, i - 1 ):gsub( "[^%S\n]+$", "" ), text:sub( i + 1 ):gsub( "^[^%S\n]+", "" )
- end
- end
- return text:sub( 1, width ), text:sub( width + 1 )
- end
- function wrap( text, width, height )
- local lines, line = {}
- while text and ( not height or #lines < height ) do
- line, text = wrapline( text, width )
- lines[#lines + 1] = line
- end
- return lines
- end
- Button = class.new( "Button", Sheet, IHasText, IColoured ) {
- down = false;
- colour = nil;
- active_colour = nil;
- horizontal_alignment = 1;
- vertical_alignment = 1;
- }
- function Button:Button( x, y, width, height, text )
- self:initialise()
- self:IHasText()
- self:IColoured()
- self.values:add( "active_colour", 8 )
- self:Sheet( x, y, width, height )
- self:set_colour( 512 )
- self:set_horizontal_alignment( 1 )
- self:set_vertical_alignment( 1 )
- if text then
- self:set_text( text )
- end
- end
- function Button:draw( surface, x, y )
- surface:fillRect( x, y, self.width, self.height, self.down and self.active_colour or self.colour, 1, "" )
- self:draw_text( surface, x, y )
- self.changed = false
- end
- function Button:on_mouse_event( event )
- if event:is( 1 ) and self.down then
- self.down = false
- self:set_changed()
- end
- if event.handled or not event:is_within_area( 0, 0, self.width, self.height ) or not event.within then
- return
- end
- if event:is( 0 ) and not self.down then
- self.down = true
- self:set_changed()
- event:handle()
- elseif event:is( 2 ) then
- if self.on_click then
- self:on_click( event.button, event.x, event.y )
- end
- event:handle()
- elseif event:is( 3 ) then
- if self.on_hold then
- self:on_hold( event.button, event.x, event.y )
- end
- event:handle()
- end
- end
- -- @/require elements.Checkbox
- ClippedContainer = class.new( "ClippedContainer", Container, nil ) {
- surface = nil;
- colour = nil;
- }
- function ClippedContainer:ClippedContainer( ... )
- self.surface = surface.create( 0, 0 )
- return self:Container( ... )
- end
- function ClippedContainer:draw( surface, x, y )
- if self.changed then
- local children = self.children
- local cx, cy, cc
- local x_offset, y_offset = self.x_offset, self.y_offset
- self:reset_cursor_blink()
- self.surface:clear( self.colour )
- if self.on_pre_draw then
- self:on_pre_draw()
- end
- for i = 1, #children do
- local child = children[i]
- if child:is_visible() then
- child:draw( self.surface, child.x + x_offset, child.y + y_offset )
- if child.cursor_active then
- cx, cy, cc = child.x + child.cursor_x, child.y + child.cursor_y, child.cursor_colour
- end
- end
- end
- if cx then
- self:set_cursor_blink( cx, cy, cc )
- end
- if self.on_post_draw then
- self:on_post_draw()
- end
- self.changed = false
- end
- surface:drawSurface( self.surface, x, y )
- end
- -- @/require interfaces.IHasText
- -- @/require elements.Draggable
- -- @/require elements.Image
- KeyHandler = class.new( "KeyHandler", Sheet, nil ) {
- actions = {};
- shortcuts = {};
- handles_keyboard = true;
- }
- function KeyHandler:KeyHandler()
- self.actions = {}
- self.shortcuts = {}
- self:initialise()
- return self:Sheet( 0, 0, 0, 0 )
- end
- function KeyHandler:add_action( name, callback, ... )
- for i = 1, #self.actions do
- if self.actions[i].name == name then
- Exception.throw( Exception( "KeyHandlerAction", "cannot create new action'" .. name .. "':action already exists" ) ) -- TODO: create custom exception for this
- end
- end
- self.actions[#self.actions + 1] = {
- name = name;
- callback = callback;
- parameters = { ... };
- keybindings = {};
- }
- end
- function KeyHandler:remove_action( name )
- for i = 1, #self.actions do
- if self.actions[i].name == name then
- for j = 1, #self.actions[i].keybindings do
- self:unbind_key( self.actions[i].keybindings[j] )
- end
- return table.remove( self.actions, i ).callback
- end
- end
- end
- function KeyHandler:set_callback( action, callback )
- for i = 1, #self.actions do
- if self.actions[i].name == action then
- self.actions[i].callback = callback
- return
- end
- end
- end
- function KeyHandler:set_parameters( action, parameters )
- for i = 1, #self.actions do
- if self.actions[i].name == action then
- self.actions[i].parameters = parameters
- return
- end
- end
- end
- function KeyHandler:bind_key( key, action )
- if self.shortcuts[key] then
- self:unbind_key( key )
- end
- for i = 1, #self.actions do
- if self.actions[i].name == action then
- self.actions[i].keybindings[#self.actions[i].keybindings + 1] = key
- self.shortcuts[key] = action
- return
- end
- end
- Exception.throw( Exception( "KeyHandlerBindingException", "cannot bind key'" .. key .. "'to action'" .. action .. "':action doesn't exist" ) )
- end
- function KeyHandler:unbind_key( key )
- local action = self.shortcuts[key]
- if not action then
- Exception.throw( Exception( "KeyHandlerBindingException", "cannot unbind key'" .. key .. "':key not bound" ) )
- end
- for i = 1, #self.actions do
- if self.actions[i].name == action then
- for j = 1, #self.actions[i].keybindings do
- if self.actions[i].keybindings[j] == key then
- table.remove( self.actions[i].keybindings, j )
- break
- end
- end
- self.shortcuts[key] = nil
- return
- end
- end
- end
- function KeyHandler:on_keyboard_event( event )
- if not event.handled and event:is( 7 ) then
- local longest_match, longest_match_action
- local actions = self.actions
- local shortcuts = self.shortcuts
- local k, v = next( shortcuts )
- while k do
- if event:matches( k ) then
- if not longest_match or #k > #longest_match then
- longest_match = k
- longest_match_action = v
- end
- end
- k, v = next( shortcuts, k )
- end
- if longest_match then
- event:handle( self )
- for i = 1, #actions do
- if actions[i].name == longest_match_action then
- return actions[i].callback( unpack( actions[i].parameters ) )
- end
- end
- end
- end
- end
- function KeyHandler:draw() end
- Panel = class.new( "Panel", Sheet, IColoured ) {
- colour = nil;
- }
- function Panel:Panel( x, y, w, h )
- self:initialise()
- self:IColoured()
- return self:Sheet( x, y, w, h )
- end
- function Panel:draw( canvas, x, y )
- canvas:fillRect( x, y, self.width, self.height, self.colour, 1, " " )
- end
- -- @/require elements.ScrollContainer
- -- @/require interfaces.IHasText
- -- @/require elements.Text
- -- @/require elements.TextInput
- sheets.KeyHandler = KeyHandler;sheets.ICollatedChildren = ICollatedChildren;sheets.KeyboardEvent = KeyboardEvent;sheets.Exception = Exception;sheets.Event = Event;sheets.ITimer = ITimer;sheets.class = class;sheets.QueryTracker = QueryTracker;sheets.Stream = Stream;sheets.Transition = Transition;sheets.Application = Application;sheets.IQueryable = IQueryable;sheets.ThreadRuntimeException = ThreadRuntimeException;sheets.Button = Button;sheets.MouseEvent = MouseEvent;sheets.ClippedContainer = ClippedContainer;sheets.ISize = ISize;sheets.TableType = TableType;sheets.Easing = Easing;sheets.MiscEvent = MiscEvent;sheets.IColoured = IColoured;sheets.Thread = Thread;sheets.Container = Container;sheets.TextEvent = TextEvent;sheets.parameters = parameters;sheets.Panel = Panel;sheets.ResourceLoadException = ResourceLoadException;sheets.Codegen = Codegen;sheets.Typechecking = Typechecking;sheets.UnionType = UnionType;sheets.ValueHandler = ValueHandler;sheets.IncorrectParameterException = IncorrectParameterException;sheets.DynamicValueParser = DynamicValueParser;sheets.Sheet = Sheet;sheets.IHasText = IHasText;sheets.Screen = Screen;sheets.Type = Type;sheets.IChildContainer = IChildContainer;sheets.IncorrectConstructorException = IncorrectConstructorException;sheets.ListType = ListType;sheets.clipboard = clipboard;sheets.ITagged = ITagged end
- local app = sheets.Application()
- app:run()
- end )
- if not ok then
- local e = select( 2, pcall( error, "@", 2 ) )
- local src = e:match "^(.*):%d+: @$"
- local line, msg = err:match( src .. ":(%d+): (.*)" )
- if line then
- local src, line = __get_src_and_line( tonumber( line ) )
- error( src .. "[" .. line .. "]: " .. __get_err_msg( src, line, msg ), 0 )
- else
- error( err, 0 )
- end
- end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement