├── .gitignore
├── .gitmodules
├── README.md
├── conf.lua
├── docs
├── index.html
├── lasagna
│ ├── fakelove.html
│ └── projector.html
├── main.html
└── src
│ └── classes
│ ├── Console.html
│ ├── Editor.html
│ ├── Hierarchy.html
│ ├── Inspector.html
│ ├── Template.html
│ └── UIHelper.html
├── imgui.dll
├── lasagna
├── fakelove.lua
└── projector.lua
├── main.lua
├── project
├── nogame
│ └── main.lua
└── pong
│ ├── main.lua
│ └── middleclass.lua
├── src
└── classes
│ ├── Console.lua
│ ├── Editor.lua
│ ├── Hierarchy.lua
│ ├── Inspector.lua
│ ├── Template.lua
│ └── UIHelper.lua
└── tools
└── generate_docs.sh
/.gitignore:
--------------------------------------------------------------------------------
1 | binary/*
2 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "libs/middleclass"]
2 | path = libs/middleclass
3 | url = https://github.com/kikito/middleclass
4 | [submodule "libs/lume"]
5 | path = libs/lume
6 | url = https://github.com/rxi/lume
7 | [submodule "tools/rtfm"]
8 | path = tools/rtfm
9 | url = https://github.com/airstruck/rtfm
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # sandsmas
2 | A visual editor for LÖVE, that runs in LÖVE.
3 |
4 | 
5 |
6 | # Structure
7 | The basic structure is that there is, from least complex to most complex:
8 | * **Underlying Libraries** specifically chosen because they do what they do the best.
9 | * **Lasagna** composed of the underlying libraries, in several layers, with just enough glue to combine them meaningfully without permanently binding to a particular library for a given purpose.
10 | * **Layer 1** low-level serialization and entity-component relationships that will make networking & physics a lot easier.
11 | * **Layer 2** mid-level features that integrate with the structure provided from layer 1, such as graphic optimizations and complex rendering.
12 | * **Layer 3** high-level features, such as logging, profiling, and libraries that editor tools can take advantage of.
13 | * **A Project Structure** a specially formatted love project that will enable the editor to edit it while still being permissive about its configuration.
14 | * **A Bootstrap** maintained separate from the editor, allowing for running of project files as a standalone and within the editor.
15 | * **The Editor** that is capable of producing project files and allowing for easy composition of a game.
16 |
17 | ## Lasagna
18 | Below are some example layer categories and some candidate libraries that would be nice to have in each.
19 | * **Serialization & Net:** [tiny-ecs](https://github.com/bakpakin/tiny-ecs), [binser](https://github.com/bakpakin/binser), [trickle](https://github.com/bjornbytes/trickle), [hero](https://github.com/airstruck/hero), [sock.lua](https://github.com/camchenry/sock.lua)
20 | * **Configuration & Time:** [tick](https://github.com/bjornbytes/tick), [modrun](https://github.com/Asmageddon/modrun), [tactile](https://github.com/tesselode/tactile)
21 | * **Rendering:** love.physics-visualizer, [chiro](https://github.com/bjornbytes/chiro), [anim8](https://github.com/kikito/anim8), [anim9](https://github.com/excessive/anim9), [Simple Tiled Implementation](https://github.com/karai17/Simple-Tiled-Implementation), [love-imgui](https://github.com/slages/love-imgui), scene.lua, anchor.lua
22 | * **Assets:** [Autobatch](https://github.com/rxi/autobatch), [cargo](https://github.com/bjornbytes/cargo)
23 | * **Profiling:** [PROBE](https://github.com/jorio/PROBE), [inspect.lua](https://github.com/kikito/inspect.lua), [lurker](https://github.com/rxi/lurker), [Lovebird](https://github.com/rxi/lovebird), [Lovecat](https://github.com/CoffeeKitty/lovecat), love-console, perfhud
24 | * **Logging:** ansicolors, [log.lua](https://github.com/rxi/log.lua), log-lua, [i18n](https://github.com/excessive/i18n), argparse
25 | * **Wiring:** talkback, middleclass, stateful, router.lua
26 | * **Advanced Items:** [lume](https://github.com/rxi/lume/), pool.lua, memoize, LuaFun, lua-stdlib, [knife](https://github.com/airstruck/knife), microlight, [CPML](https://github.com/excessive/cpml), LuaDate, dkjson, LuaFFT, Worp
27 | * **Upkeep:** Busted, [RTFM](https://github.com/airstruck/rtfm), [LDoc](https://github.com/stevedonovan/LDoc), [lustache](https://github.com/Olivine-Labs/lustache), [semver](https://github.com/kikito/semver.lua)
28 | * **High Level:** lua-imgur, gyfcat, gifload, magick, sfxr, pegasus, turbo, lua-websockets, lua-sqlite3
29 |
30 | # Good Reads
31 | ## Design Goals
32 | * [how to a framework that is not in the way](http://weierophinney.github.io/2015-10-22-ZF3/#/)
33 | * [reading and writing network packets](http://gafferongames.com/building-a-game-network-protocol/reading-and-writing-packets/)
34 | * [middleclass class standards, for internal classes](https://github.com/kikito/middleclass/wiki)
35 | * [regions for humans](http://magcius.github.io/xplain/article/regions.html)
36 |
37 | ## LuaJIT
38 | * [luajit and C calls](http://stackoverflow.com/questions/34405678/using-lua-ffi-with-complex-types)
39 | * [type conversion](http://luajit.org/ext_ffi_semantics.html#convert)
40 | * [idioms](http://luajit.org/ext_ffi_tutorial.html#idioms)
41 |
42 |
43 | ## Technical Reference
44 | * [setfenv outside lua51](http://leafo.net/guides/setfenv-in-lua52-and-above.html)
45 | * [non-recursive deepcopy](https://gist.github.com/Deco/3985043)
46 | * [love console and zerobrane](https://github.com/EntranceJew/love-notes/blob/master/love2d-and-zerobrane.md)
47 |
--------------------------------------------------------------------------------
/conf.lua:
--------------------------------------------------------------------------------
1 | function love.conf(t)
2 | t.identity = "sandsmas" -- The name of the save directory (string)
3 | t.version = "0.10.1" -- The LÖVE version this game was made for (string)
4 | t.console = false -- Attach a console (boolean, Windows only)
5 | t.accelerometerjoystick = true -- Enable the accelerometer on iOS and Android by exposing it as a Joystick (boolean)
6 | t.externalstorage = false -- True to save files (and read from the save directory) in external storage on Android (boolean)
7 | t.gammacorrect = false -- Enable gamma-correct rendering, when supported by the system (boolean)
8 |
9 | t.window.title = "sandsmas" -- The window title (string)
10 | t.window.icon = nil -- Filepath to an image to use as the window's icon (string)
11 | t.window.width = 800 -- The window width (number)
12 | t.window.height = 600 -- The window height (number)
13 | t.window.borderless = false -- Remove all border visuals from the window (boolean)
14 | t.window.resizable = true -- Let the window be user-resizable (boolean)
15 | t.window.minwidth = 1 -- Minimum window width if the window is resizable (number)
16 | t.window.minheight = 1 -- Minimum window height if the window is resizable (number)
17 | t.window.fullscreen = false -- Enable fullscreen (boolean)
18 | t.window.fullscreentype = "desktop" -- Choose between "desktop" fullscreen or "exclusive" fullscreen mode (string)
19 | t.window.vsync = true -- Enable vertical sync (boolean)
20 | t.window.msaa = 0 -- The number of samples to use with multi-sampled antialiasing (number)
21 | t.window.display = 1 -- Index of the monitor to show the window in (number)
22 | t.window.highdpi = false -- Enable high-dpi mode for the window on a Retina display (boolean)
23 | t.window.x = nil -- The x-coordinate of the window's position in the specified display (number)
24 | t.window.y = nil -- The y-coordinate of the window's position in the specified display (number)
25 |
26 | t.modules.audio = true -- Enable the audio module (boolean)
27 | t.modules.event = true -- Enable the event module (boolean)
28 | t.modules.graphics = true -- Enable the graphics module (boolean)
29 | t.modules.image = true -- Enable the image module (boolean)
30 | t.modules.joystick = true -- Enable the joystick module (boolean)
31 | t.modules.keyboard = true -- Enable the keyboard module (boolean)
32 | t.modules.math = true -- Enable the math module (boolean)
33 | t.modules.mouse = true -- Enable the mouse module (boolean)
34 | t.modules.physics = true -- Enable the physics module (boolean)
35 | t.modules.sound = true -- Enable the sound module (boolean)
36 | t.modules.system = true -- Enable the system module (boolean)
37 | t.modules.timer = true -- Enable the timer module (boolean), Disabling it will result 0 delta time in love.update
38 | t.modules.touch = true -- Enable the touch module (boolean)
39 | t.modules.video = true -- Enable the video module (boolean)
40 | t.modules.window = true -- Enable the window module (boolean)
41 | t.modules.thread = true -- Enable the thread module (boolean)
42 | end
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/docs/lasagna/fakelove.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | API Docs
4 |
48 |
API Docs
49 |
Table of Contents
50 |
51 | fakelove
52 | Pretend to be LÖVE.
53 |
54 | fakelove
55 | A fake instance of LÖVE.
56 |
62 |
63 |
64 |
65 |
fakelove Pretend to be LÖVE.
66 | Classes fakelove A fake instance of LÖVE.
67 | Functions fakelove.new (Self., The)Launch the generator from the command line.
68 | Arguments table Self.
69 | table The real instance of love to duplicate.
70 | close = function()
71 |
72 | end,
73 | fromPixels = function()
74 |
75 | end,
76 | getDisplayCount = function()
77 |
78 | end,
79 | getDesktopDimensions = function()
80 |
81 | end,
82 | getDisplayName = function()
83 |
84 | end,
85 | getFullscreen = function()
86 |
87 | end,
88 | getFullscreenModes = function()
89 |
90 | end,
91 | getIcon = function()
92 |
93 | end,
94 | getPixelScale = function()
95 |
96 | end,
97 | isDisplaySleepEnabled = function()
98 |
99 | end,
100 |
101 | WARNING: UNDOCUMENTED end,
102 | isVisible = function()
103 |
104 | end,
105 | maximize = function()
106 |
107 | end,
108 | minimize = function()
109 |
110 | end,
111 | requestAttention = function()
112 |
113 | end,
114 | setDisplaySleepEnabled = function()
115 |
116 | end,
117 | setFullscreen = function()
118 |
119 | end,
120 | setIcon = function()
121 |
122 | end,
123 | showMessageBox = function()
124 |
125 | end,
126 | toPixels = function()
127 |
128 | end,
129 |
130 |
131 |
132 |
133 | Documentation generated by
134 | RTFM .
135 |
136 |
137 |
138 |
--------------------------------------------------------------------------------
/docs/lasagna/projector.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | API Docs
4 |
48 |
API Docs
49 |
Table of Contents
50 |
51 |
52 |
53 | TODO: * because we pass through a clone of love, things like love.keyboard.isDown
54 | read from the editor's LOVE despite sandboxed love.keypressed.* events
55 | not being run.
56 | * makes u think
57 | LÖVE Callback Emulation Goes Here
58 | special checks for when the mouse is given focus
59 | before we know where it is
60 |
61 | Documentation generated by
62 | RTFM .
63 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/docs/main.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | API Docs
4 |
48 |
API Docs
49 |
Table of Contents
50 |
51 |
52 |
53 | Documentation generated by
54 | RTFM .
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/docs/src/classes/Console.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | API Docs
4 |
48 |
API Docs
49 |
Table of Contents
50 |
65 |
66 |
67 | Classes Console Puts colored text in a scrollable window.
68 | Methods Console:Log (status, text)Add text to the console.
69 | Arguments string status
70 | string text
71 |
72 | Console:Render ()Draws the console's contents.
73 |
74 |
75 | Documentation generated by
76 | RTFM .
77 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/docs/src/classes/Editor.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | API Docs
4 |
48 |
API Docs
49 |
Table of Contents
50 |
55 |
56 |
57 | Classes Editor An editor instance, contains references to other classes as internal components.
58 |
59 | Documentation generated by
60 | RTFM .
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/docs/src/classes/Hierarchy.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | API Docs
4 |
48 |
API Docs
49 |
Table of Contents
50 |
51 |
52 |
53 | Documentation generated by
54 | RTFM .
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/docs/src/classes/Inspector.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | API Docs
4 |
48 |
API Docs
49 |
Table of Contents
50 |
51 |
52 |
53 | Documentation generated by
54 | RTFM .
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/docs/src/classes/Template.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | API Docs
4 |
48 |
API Docs
49 |
Table of Contents
50 |
51 | Template
52 | Example class.
53 |
59 |
60 |
61 |
62 | Classes Template Example class.
63 | Methods Template:ExampleMethod (a_table, a_string, a_number)A method that exemplifies an instance method.
64 | Arguments table a_table A table that stuff happens to.
65 | string a_string A string that things happen to.
66 | number a_number A number that things happen to.
67 |
68 | Returns bool The status of the operation.
69 |
70 |
71 |
72 | Documentation generated by
73 | RTFM .
74 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/docs/src/classes/UIHelper.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | API Docs
4 |
48 |
API Docs
49 |
Table of Contents
50 |
51 |
52 |
53 | Documentation generated by
54 | RTFM .
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/imgui.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EntranceJew/sandsmas/77de71cd3b5133820f735f294e7a70d56b587614/imgui.dll
--------------------------------------------------------------------------------
/lasagna/fakelove.lua:
--------------------------------------------------------------------------------
1 | --[[
2 | Pretend to be LÖVE, what a great prank
3 | another great job, EntranceJew "gee thanks"
4 | MIT license probably.
5 | ]]
6 | --- @module fakelove Pretend to be LÖVE.
7 | local lume = require("libs.lume.lume")
8 |
9 | --- @class fakelove A fake instance of LÖVE.
10 | local fakelove = {}
11 | fakelove.__index = fakelove
12 |
13 | local function scale(valueIn, baseMin, baseMax, limitMin, limitMax)
14 | return ((limitMax - limitMin) * (valueIn - baseMin) / (baseMax - baseMin)) + limitMin
15 | end
16 |
17 | local function deepcopy(orig)
18 | local orig_type = type(orig)
19 | local copy
20 | if orig_type == 'table' then
21 | copy = {}
22 | for orig_key, orig_value in next, orig, nil do
23 | copy[deepcopy(orig_key)] = deepcopy(orig_value)
24 | end
25 | setmetatable(copy, deepcopy(getmetatable(orig)))
26 | else -- number, string, boolean, etc
27 | copy = orig
28 | end
29 | return copy
30 | end
31 |
32 | --- @function fakelove.new Launch the generator from the command line.
33 | --- @param table Self.
34 | --- @param table The real instance of love to duplicate.
35 | local function new(self, reallove)
36 | local zt = deepcopy(reallove)
37 | local t = setmetatable(zt, fakelove)
38 | t:initialize(reallove)
39 | return t
40 | end
41 |
42 | function fakelove:initialize(reallove)
43 | local x, y, display = reallove.window.getPosition()
44 | local width, height, flags = reallove.window.getMode()
45 | self.store = {
46 | audio = {},
47 | event = {},
48 | filesystem = {},
49 | font = {},
50 | graphics = {},
51 | image = {},
52 | joystick = {},
53 | keyboard = {},
54 | math = {},
55 | mouse = {
56 | x = 0,
57 | y = 0,
58 | buttons = {},
59 | cursor = reallove.mouse.getCursor(),
60 | grabbed = reallove.mouse.isGrabbed(),
61 | visible = reallove.mouse.isVisible(),
62 | relativeMode = reallove.mouse.getRelativeMode(),
63 | },
64 | physics = {},
65 | sound = {},
66 | system = {},
67 | thread = {},
68 | timer = {},
69 | touch = {},
70 | video = {},
71 | window = {
72 | title = reallove.window.getTitle(),
73 | x = x,
74 | y = y,
75 | display = display,
76 | width = width,
77 | height = height,
78 | flags = flags,
79 |
80 | focus = reallove.window.hasFocus(),
81 | mouseFocus = false,
82 | },
83 | }
84 | self.reallove = reallove
85 |
86 | local mergelove = {
87 | mouse = {
88 | getPosition = function()
89 | return self.store.mouse.x, self.store.mouse.y
90 | end,
91 | getX = function()
92 | return self.store.mouse.x
93 | end,
94 | getY = function()
95 | return self.store.mouse.y
96 | end,
97 | },
98 | window = {
99 | --[[
100 | close = function()
101 |
102 | end,
103 | fromPixels = function()
104 |
105 | end,
106 | getDisplayCount = function()
107 |
108 | end,
109 | getDesktopDimensions = function()
110 |
111 | end,
112 | getDisplayName = function()
113 |
114 | end,
115 | getFullscreen = function()
116 |
117 | end,
118 | getFullscreenModes = function()
119 |
120 | end,
121 | getIcon = function()
122 |
123 | end,
124 | ]]
125 | getMode = function()
126 | return self.store.window.width, self.store.window.height, self.store.window.flags
127 | end,
128 | --[[
129 | getPixelScale = function()
130 |
131 | end,
132 | ]]
133 | getPosition = function()
134 | return self.store.window.x, self.store.window.y, self.store.window.display
135 | end,
136 | getTitle = function()
137 | return self.store.window.title
138 | end,
139 | hasFocus = function()
140 | return self.store.window.focus
141 | end,
142 | hasMouseFocus = function()
143 | return self.store.window.mouseFocus
144 | end,
145 | --[[
146 | isDisplaySleepEnabled = function()
147 |
148 | end,
149 | isOpen = function() -- @WARNING: UNDOCUMENTED
150 |
151 | end,
152 | isVisible = function()
153 |
154 | end,
155 | maximize = function()
156 |
157 | end,
158 | minimize = function()
159 |
160 | end,
161 | requestAttention = function()
162 |
163 | end,
164 | setDisplaySleepEnabled = function()
165 |
166 | end,
167 | setFullscreen = function()
168 |
169 | end,
170 | setIcon = function()
171 |
172 | end,
173 | ]]
174 | setMode = function(width, height, flags)
175 | self.store.window.width = width
176 | self.store.window.height = height
177 | self.store.window.flags = flags or self.store.window.flags
178 | return true
179 | end,
180 | setPosition = function(x, y, display)
181 | self.store.window.x = x
182 | self.store.window.y = y
183 | self.store.window.display = display or self.store.window.display
184 | end,
185 | setTitle = function(title)
186 | self.store.window.title = title
187 | end
188 | --[[
189 | showMessageBox = function()
190 |
191 | end,
192 | toPixels = function()
193 |
194 | end,
195 | ]]
196 | }
197 | }
198 |
199 | -- does this work? I don't know.
200 | for k, v in pairs(mergelove) do
201 | self[k] = lume.merge(self[k], v)
202 | end
203 | end
204 |
205 | return setmetatable({new=new}, {__call=function(_,...) return new(...) end})
--------------------------------------------------------------------------------
/lasagna/projector.lua:
--------------------------------------------------------------------------------
1 | --[[
2 | Projector For Picture-in-Picture
3 | very good thanks EntranceJew
4 | MIT license probably.
5 | ]]
6 | --[[
7 | things to think about:
8 | * l.graphics does not thread nicely so we can't utilize this for sandboxing a render
9 | * stuff like l.k.isDown bounce reads right off SDL and don't matter to us
10 | ]]
11 | --[[
12 | @TODO:
13 | * because we pass through a clone of love, things like love.keyboard.isDown
14 | read from the editor's LOVE despite sandboxed love.keypressed.* events
15 | not being run.
16 | * makes u think
17 | ]]
18 | local lume = require("libs.lume.lume")
19 | local fakelove = require("lasagna.fakelove")
20 |
21 | local projector = {}
22 | projector.__index = projector
23 |
24 | local function nop() end
25 |
26 | -- this comes from lume don't beat me up
27 | local function clone(t)
28 | local rtn = {}
29 | for k, v in pairs(t) do rtn[k] = v end
30 | return rtn
31 | end
32 |
33 | local function new(self, entry, exposed, x, y, sx, sy)
34 | local t = setmetatable({}, projector)
35 | t:initialize(entry, exposed, x, y, sx, sy)
36 | return t
37 | end
38 |
39 | function projector:load_core(entry, exposed)
40 | -- create new environment
41 | local env = {
42 | love = fakelove:new(love)
43 | }
44 |
45 | -- merge an external environment
46 | if exposed and type(exposed) == "table" then
47 | env = lume.merge(env, exposed)
48 | end
49 |
50 | local metaenv = { __index = _G }
51 | setmetatable(env, metaenv)
52 |
53 | -- put it on us
54 | self.env = env
55 |
56 | -- wipe all callbacks
57 | self:loveEncapsulate()
58 |
59 | -- load the chunk
60 | local ok, chunk = pcall( love.filesystem.load, entry )
61 | assert(ok, "The entry point '" .. entry .. "' appears invalid.")
62 |
63 | -- apply the environment to the chunk
64 | setfenv(chunk, env)
65 |
66 | -- invoke the chunk
67 | local result
68 | ok, result = pcall( chunk )
69 | assert(ok, "Chunk execution failed for entry point '" .. entry .. "', error: " .. tostring(result) )
70 |
71 | -- we have now captured 'entry' inside 'env'
72 | -- we have not entered the loaded environment
73 | return env, metaenv
74 | end
75 |
76 | function projector:loadstringInEnv(str)
77 | local ok, chunk = pcall( loadstring, str )
78 | assert(ok, "The chunk was messed up: " .. tostring(chunk) )
79 |
80 | setfenv( chunk, self.env )
81 |
82 | local result
83 | ok, result = pcall( chunk )
84 | assert(ok, "Chunk execution failed: " .. tostring(result) )
85 |
86 | return result
87 | end
88 |
89 | -- search and destroy: https://love2d.org/wiki/Category:Callbacks
90 | function projector:loveEncapsulate()
91 | -- maybe_scopes: nogame, conf
92 |
93 | local bomb = [[
94 | -- keep error handlers because they prevent silent crashes
95 | local scopes = {
96 | --'errhand',
97 | --'threaderror',
98 | 'draw',
99 | 'load',
100 | 'run',
101 | 'update',
102 | }
103 | for k,v in pairs(love.handlers) do
104 | love[k] = nil
105 | end
106 | for k,v in ipairs(scopes) do
107 | love[v] = nil
108 | end
109 | ]]
110 | return self:loadstringInEnv(bomb)
111 | end
112 |
113 | function projector:getBaseFromEntryPoint(entry)
114 | local ret = {string.match(entry, "(.-)([^\\/]-%.?([^%.\\/]*))$")}
115 | return ret[1]
116 | end
117 |
118 | function projector:initialize(entry, exposed, x, y, w, h)
119 | -- set the entry point for our emulation
120 | self.entry = entry or 'main.lua'
121 |
122 | -- emulate path level imports and hope to god we don't get a collision
123 | self.base_path = self:getBaseFromEntryPoint(self.entry)
124 |
125 | if self.base_path ~= '' then
126 | self.added_path = ';' .. self.base_path .. '?.lua;' .. self.base_path .. '?/init.lua'
127 | love.filesystem.setRequirePath(love.filesystem.getRequirePath() .. self.added_path)
128 | else
129 | assert(false, "Base path was wrong or broken.")
130 | end
131 |
132 | -- timers for play
133 | self.dt = 0
134 | self.gt = 0
135 | self.tt = 0
136 |
137 | -- preserve reality as it was before we messed with things
138 | -- the "absolutely safe capsule" for package, etc.
139 | self.asc = getfenv()
140 |
141 | -- load our core
142 | self.env = self:load_core(self.entry, exposed)
143 |
144 | if self.env.love.load then
145 | self:doInEnv( self.env.love.load )
146 | end
147 | end
148 |
149 | function projector:renderAbsolutelySafeCapsule()
150 | self.asc = {
151 | package = {
152 | cpath = package.cpath,
153 | loaded = package.loaded,
154 | loadlib = package.loadlib,
155 | path = package.path,
156 | preload = package.preload,
157 | searchers = package.searchers,
158 | searchpath = package.searchpath
159 | },
160 | }
161 | end
162 |
163 | function projector:doInEnv(func, ...)
164 | -- have some variables set in our scope before entering env
165 | local ok, result
166 | local pcall = pcall
167 |
168 | -- enter the env, execute, immediately leave
169 | setfenv(1, self.env)
170 | ok, result = pcall( func, ... )
171 | setfenv(1, self.asc)
172 |
173 | -- we're done here
174 | return ok, result
175 | end
176 |
177 | --[[
178 | LÖVE Callback Emulation Goes Here
179 | ]]
180 | function projector:draw()
181 | love.graphics.push("all")
182 |
183 | local x, y = self.env.love.window.getPosition()
184 | local w, h = self.env.love.window.getMode()
185 | love.graphics.translate(x, y)
186 | love.graphics.scale(w/love.graphics.getWidth(), h/love.graphics.getHeight())
187 | love.graphics.setScissor(x, y, w, h)
188 | -- @TODO: right here, we should reload the graphics state
189 | self:doInEnv(self.env.love.draw)
190 |
191 | love.graphics.pop()
192 | end
193 |
194 | function projector:update(dt)
195 | if self.env.love.window.hasFocus() then
196 | self.gt = self.gt + dt
197 | self.dt = dt
198 | self:doInEnv(self.env.love.update, dt)
199 | end
200 | self.tt = self.tt + dt
201 | end
202 |
203 | -- do not use this from inside whatever is wrapping the projection's mousefocus
204 | -- unless the projection is fullscreened
205 | function projector:mousefocus(focus)
206 | --[[
207 | @TODO: special checks for when the mouse is given focus
208 | before we know where it is
209 | ]]
210 |
211 | self:doInEnv(self.env.love.mousefocus, focus)
212 | end
213 |
214 | -- x, y is from global mouse position
215 | function projector:_is_mousepos_in_bounds(x, y)
216 | return (x >= self.env.love.store.window.x
217 | and y >= self.env.love.store.window.y
218 | and x <= self.env.love.store.window.x + self.env.love.store.window.width
219 | and y <= self.env.love.store.window.y + self.env.love.store.window.height
220 | )
221 | end
222 |
223 | function projector:mousemoved(x, y, dx, dy, istouch)
224 | if self:_is_mousepos_in_bounds(x, y) then
225 | if not self.env.love.window.hasMouseFocus() then
226 | self.env.love.store.window.mouseFocus = true
227 | self:mousefocus(true)
228 | end
229 |
230 | self.env.love.store.mouse.x = x - self.env.love.store.window.x
231 | self.env.love.store.mouse.y = y - self.env.love.store.window.y
232 |
233 | -- pass to the *emulated game*
234 | self:doInEnv(self.env.love.mousemoved,
235 | self.env.love.store.mouse.x,
236 | self.env.love.store.mouse.y,
237 | dx, dy, istouch
238 | )
239 | else
240 | -- the mouse moved away frome our fake window,
241 | -- therefore we only care about the last reported position
242 | if self.env.love.window.hasMouseFocus() then
243 | self.env.love.store.window.mouseFocus = false
244 | self:mousefocus(false)
245 | end
246 | end
247 | end
248 |
249 | function projector:mousepressed(x, y, button, istouch)
250 | if self:_is_mousepos_in_bounds(x, y) then
251 | -- @TODO: flash focus? issue a mousemoved?
252 | self.env.love.store.mouse.buttons[button] = true
253 |
254 | x = x - self.env.love.store.window.x
255 | y = y - self.env.love.store.window.y
256 |
257 | self:doInEnv(self.env.love.mousepressed, x, y, button, istouch)
258 | end
259 | end
260 |
261 | function projector:mousepressed(x, y, button, istouch)
262 | if self:_is_mousepos_in_bounds(x, y) then
263 | -- @TODO: flash focus? issue a mousemoved?
264 | self.env.love.store.mouse.buttons[button] = nil
265 |
266 | x = x - self.env.love.store.window.x
267 | y = y - self.env.love.store.window.y
268 |
269 | self:doInEnv(self.env.love.mousepressed, x, y, button, istouch)
270 | end
271 | end
272 |
273 | return setmetatable({new=new}, {__call=function(_,...) return new(...) end})
--------------------------------------------------------------------------------
/main.lua:
--------------------------------------------------------------------------------
1 | io.stdout:setvbuf("no")
2 | local imgui = require("imgui")
3 | local UIHelper = require("src.classes.UIHelper"):new()
4 | local editor = require("src.classes.Editor"):new('yay')
5 | local projector = require("lasagna.projector")
6 | local nogame, pong
7 |
8 | --
9 | -- LOVE callbacks
10 | --
11 |
12 | -- this gets merged into the sandboxes
13 | -- therefore, we are exposing the gift of sandsmas here
14 | local sandsmas = {
15 | sandsmas = {
16 | editor = editor,
17 | imgui = imgui,
18 | }
19 | }
20 |
21 | function love.load(arg)
22 | nogame = projector:new("project/nogame/main.lua", sandsmas)
23 | pong = projector:new("project/pong/main.lua", sandsmas)
24 |
25 | local testfunc = function() end
26 | UIHelper:AddMenu("File/New", testfunc)
27 | UIHelper:AddMenu("File/Open", testfunc)
28 | UIHelper:AddMenu("File/Save", testfunc)
29 | UIHelper:AddMenu("File/Save As...", testfunc)
30 | UIHelper:AddMenu("Edit/This", testfunc)
31 | UIHelper:AddMenu("Edit/Thing", testfunc)
32 | UIHelper:AddMenu("Help/About", testfunc)
33 | end
34 |
35 | function love.update(dt)
36 | imgui.NewFrame()
37 | nogame:update(dt)
38 | pong:update(dt)
39 |
40 | -- reverse the hierarchy stack so things are roughly in hierarchial view
41 | editor.inspector:ClearSelection()
42 | for _, uid in ipairs(editor.hierarchy.chrono_selection) do
43 | editor.inspector:AddSelection(editor.hierarchy.objects[uid])
44 | end
45 | end
46 |
47 | function love.draw()
48 | local wx, wy, x, y
49 | UIHelper:RenderMenu()
50 |
51 | imgui.SetNextWindowPos(0, 0)
52 | imgui.SetNextWindowSize(love.graphics.getWidth(), love.graphics.getHeight())
53 |
54 | if UIHelper:Begin("DockArea", nil, { "NoResize", "NoMove", "NoBringToFrontOnFocus" }) then
55 | imgui.BeginDockspace()
56 | imgui.SetNextDock("Right")
57 |
58 | if imgui.BeginDock("Inspector") then
59 | editor.inspector:Render()
60 | end
61 | imgui.EndDock()
62 |
63 | imgui.SetNextDock("Left")
64 |
65 | if imgui.BeginDock("Project") then
66 | imgui.Text("Project")
67 | end
68 | imgui.EndDock()
69 | if imgui.BeginDock("Console") then
70 | editor.console:Render()
71 | end
72 | imgui.EndDock()
73 |
74 | imgui.SetNextDock("Top")
75 |
76 | if imgui.BeginDock("Hierarchy") then
77 | editor.hierarchy:Render()
78 | end
79 | imgui.EndDock()
80 |
81 | imgui.SetNextDock("Right")
82 |
83 | if imgui.BeginDock("Scene") then
84 | nogame.env.love.store.window.focus = imgui.IsWindowFocused()
85 | local x, y = imgui.GetWindowPos()
86 | local w, h = imgui.GetWindowSize()
87 |
88 | nogame.env.love.window.setPosition(x, y)
89 | nogame.env.love.window.setMode(w, h)
90 | UIHelper:PushPostRender(function()
91 | nogame:draw()
92 | end)
93 | end
94 | imgui.EndDock()
95 |
96 | if imgui.BeginDock("Game") then
97 | -- pong game goes here when active
98 | end
99 | imgui.EndDock()
100 |
101 | imgui.EndDockspace()
102 | end
103 | UIHelper:End()
104 |
105 | love.graphics.clear(100, 100, 100, 255)
106 |
107 | local mx, my = love.mouse.getPosition()
108 | if imgui.Begin(pong.env.love.window.getTitle() .. " | " .. mx .. "\\" .. my .. "###GameWindow") then
109 | pong.env.love.store.window.focus = imgui.IsWindowFocused()
110 | local x, y = imgui.GetWindowPos()
111 | local w, h = imgui.GetWindowSize()
112 | local titleHeight = 19
113 | y = y + titleHeight
114 | h = h - titleHeight
115 |
116 | pong.env.love.window.setPosition(x, y)
117 | pong.env.love.window.setMode(w, h)
118 | UIHelper:PushPostRender(function()
119 | pong:draw()
120 | end)
121 | end
122 | imgui.End()
123 |
124 | imgui.Render()
125 |
126 | UIHelper:PostRender()
127 | end
128 |
129 | function love.quit()
130 | imgui.ShutDown()
131 | end
132 |
133 | --
134 | -- User inputs
135 | --
136 | function love.textinput(t)
137 | imgui.TextInput(t)
138 | if not imgui.GetWantCaptureKeyboard() then
139 |
140 | end
141 | end
142 |
143 | function love.keypressed(key, scan, isRepeat)
144 | imgui.KeyPressed(key)
145 | if not imgui.GetWantCaptureKeyboard() then
146 | if key == "escape" then
147 | love.event.quit()
148 | elseif key == "y" then
149 | editor.console:Log('error', "too many dannies")
150 | else
151 | pong.env.love.keypressed(key, scan, isRepeat)
152 | end
153 | end
154 | end
155 |
156 | function love.keyreleased(key)
157 | imgui.KeyReleased(key)
158 | if not imgui.GetWantCaptureKeyboard() then
159 |
160 | end
161 | end
162 |
163 | function love.mousemoved(x, y, dx, dy, istouch)
164 | imgui.MouseMoved(x, y)
165 | pong:mousemoved(x, y, dx, dy, istouch)
166 | if not imgui.GetWantCaptureMouse() then
167 |
168 | end
169 | end
170 |
171 | function love.mousepressed(x, y, button)
172 | imgui.MousePressed(button)
173 | if not imgui.GetWantCaptureMouse() then
174 |
175 | end
176 | end
177 |
178 | function love.mousereleased(x, y, button)
179 | imgui.MouseReleased(button)
180 | if not imgui.GetWantCaptureMouse() then
181 |
182 | end
183 | end
184 |
185 | function love.mousefocus( focus )
186 | -- don't send mousefocus to the game because
187 | -- they don't actually receive focus when we do
188 | end
189 |
190 | function love.wheelmoved(x, y)
191 | imgui.WheelMoved(y)
192 | if not imgui.GetWantCaptureMouse() then
193 |
194 | end
195 | end
--------------------------------------------------------------------------------
/project/nogame/main.lua:
--------------------------------------------------------------------------------
1 | --[[
2 | Copyright (c) 2006-2016 LOVE Development Team
3 | Modified by EntranceJew
4 |
5 | This software is provided 'as-is', without any express or implied
6 | warranty. In no event will the authors be held liable for any damages
7 | arising from the use of this software.
8 |
9 | Permission is granted to anyone to use this software for any purpose,
10 | including commercial applications, and to alter it and redistribute it
11 | freely, subject to the following restrictions:
12 |
13 | 1. The origin of this software must not be misrepresented; you must not
14 | claim that you wrote the original software. If you use this software
15 | in a product, an acknowledgment in the product documentation would be
16 | appreciated but is not required.
17 | 2. Altered source versions must be plainly marked as such, and must not be
18 | misrepresented as being the original software.
19 | 3. This notice may not be removed or altered from any source distribution.
20 | --]]
21 |
22 | --love = require("love")
23 |
24 | function love.conf(t)
25 | t.title = "L\195\150VE " .. love._version .. " (" .. love._version_codename .. ")"
26 | t.gammacorrect = true
27 | t.modules.audio = false
28 | t.modules.sound = false
29 | t.modules.physics = false
30 | t.modules.joystick = false
31 | t.window.resizable = true
32 | t.window.highdpi = true
33 |
34 | if love._os == "iOS" then
35 | t.window.borderless = true
36 | end
37 | end
38 |
39 | local math = math
40 |
41 | -- 30log.lua begins
42 | local function require_30log()
43 | local assert, ipairs, pairs, type, tostring, setmetatable = assert, ipairs, pairs, type, tostring, setmetatable
44 | local baseMt, _instances, _classes, _class = {}, setmetatable({},{__mode='k'}), setmetatable({},{__mode='k'})
45 | local function assert_class(class, method) assert(_classes[class], ('Wrong method call. Expected class:%s.'):format(method)) end
46 | local function deep_copy(t, dest, aType) t = t or {}; local r = dest or {}
47 | for k,v in pairs(t) do
48 | if aType and type(v)==aType then r[k] = v elseif not aType then
49 | if type(v) == 'table' and k ~= "__index" then r[k] = deep_copy(v) else r[k] = v end
50 | end
51 | end; return r
52 | end
53 | local function instantiate(self,...)
54 | assert_class(self, 'new(...) or class(...)'); local instance = {class = self}; _instances[instance] = tostring(instance); setmetatable(instance,self)
55 | if self.init then if type(self.init) == 'table' then deep_copy(self.init, instance) else self.init(instance, ...) end; end; return instance
56 | end
57 | local function extend(self, name, extra_params)
58 | assert_class(self, 'extend(...)'); local heir = {}; _classes[heir] = tostring(heir); deep_copy(extra_params, deep_copy(self, heir));
59 | heir.name, heir.__index, heir.super = extra_params and extra_params.name or name, heir, self; return setmetatable(heir,self)
60 | end
61 | baseMt = { __call = function (self,...) return self:new(...) end, __tostring = function(self,...)
62 | if _instances[self] then return ("instance of '%s' (%s)"):format(rawget(self.class,'name') or '?', _instances[self]) end
63 | return _classes[self] and ("class '%s' (%s)"):format(rawget(self,'name') or '?',_classes[self]) or self
64 | end}; _classes[baseMt] = tostring(baseMt); setmetatable(baseMt, {__tostring = baseMt.__tostring})
65 | local class = {isClass = function(class, ofsuper) local isclass = not not _classes[class]; if ofsuper then return isclass and (class.super == ofsuper) end; return isclass end, isInstance = function(instance, ofclass)
66 | local isinstance = not not _instances[instance]; if ofclass then return isinstance and (instance.class == ofclass) end; return isinstance end}; _class = function(name, attr)
67 | local c = deep_copy(attr); c.mixins=setmetatable({},{__mode='k'}); _classes[c] = tostring(c); c.name, c.__tostring, c.__call = name or c.name, baseMt.__tostring, baseMt.__call
68 | c.include = function(self,mixin) assert_class(self, 'include(mixin)'); self.mixins[mixin] = true; return deep_copy(mixin, self, 'function') end
69 | c.new, c.extend, c.__index, c.includes = instantiate, extend, c, function(self,mixin) assert_class(self,'includes(mixin)') return not not (self.mixins[mixin] or (self.super and self.super:includes(mixin))) end
70 | c.extends = function(self, class) assert_class(self, 'extends(class)') local super = self; repeat super = super.super until (super == class or super == nil); return class and (super == class) end
71 | return setmetatable(c, baseMt) end; class._DESCRIPTION = '30 lines library for object orientation in Lua'; class._VERSION = '30log v1.0.0'; class._URL = 'http://github.com/Yonaba/30log'; class._LICENSE = 'MIT LICENSE '
72 | return setmetatable(class,{__call = function(_,...) return _class(...) end })
73 | end
74 | -- 30log.lua ends
75 |
76 | local mosaic_png =
77 | "iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAAGXRFWHRTb2Z0d2FyZQBBZG9i\
78 | ZSBJbWFnZVJlYWR5ccllPAAAA2ZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tl\
79 | dCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1l\
80 | dGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUu\
81 | My1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpS\
82 | REYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgt\
83 | bnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6\
84 | Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRv\
85 | YmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9u\
86 | cy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRp\
87 | ZDo2NDY3ODU2OTk0NkJFNTExQTg3RkQ3MTNCOTc2N0UzNyIgeG1wTU06RG9jdW1lbnRJRD0i\
88 | eG1wLmRpZDo5ODkwN0RFRTg3RUExMUU1QTRBMTk2NDJEQUYyRkUwNyIgeG1wTU06SW5zdGFu\
89 | Y2VJRD0ieG1wLmlpZDo5ODkwN0RFRDg3RUExMUU1QTRBMTk2NDJEQUYyRkUwNyIgeG1wOkNy\
90 | ZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M2IChXaW5kb3dzKSI+IDx4bXBNTTpEZXJp\
91 | dmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjBGQTIzQUI1RTc4N0U1MTFBNDUy\
92 | ODc2NjFDRjM3OTIwIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjY0Njc4NTY5OTQ2QkU1\
93 | MTFBODdGRDcxM0I5NzY3RTM3Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwv\
94 | eDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+GCWHRwAAGTRJREFUeNrsXQecFdXVv+wu\
95 | TXqVIrAKwQKKQRAkqIANBZUYRRPDF8EaItiCyaeCSOwJfERRhBhUYqxEMSoCSijSQSCIICqw\
96 | KKDAwtLLLji5f995YZi95dwpDz955/c7v4U3c8vcOXPv6aeM53kiC0cv5GSXIEsAWTiKIc9w\
97 | 7QmJP5NYSWI1Sz97JJZILPb9e7fh73Xf4zUpK7G3xAsl1pS4WuLzEmc69oO290m8WmIDiZsl\
98 | jpI4WOJBh7l0lthRYiuJzSXWD7yP7RLXS/xC4nyJ0yXOlvgtawTwABpc6iUDewxjHmmsLHGO\
99 | Zt4DHPqpL3GVpp9HGO2rSnxW4r6Qa/y1xD9IrGcby3RxXUIEsCHEi8GCDpG4UOJ+iWsknqC4\
100 | r63E2yX2ktg8xDiDDfM+ILExs5+xhn62ScwztG0gsSCmtd5JhJsThgD2JEQAnzi+lP4S9yr6\
101 | mRG4r73E4sA9IJgLHMaaaZn7z5n9bLT009LQ9m8JrPm7Eo9RjadjAitIrBj47cuYztgih3sH\
102 | SfwzzScIZ0vs5vv/UDoz/XCGxMkSBzLHq2G5fjCmNfix4Vr3BPiaSyS+reL5cgwMjB8elthE\
103 | 4j0ZJIBWRAAm6Ed/T5PYwXDfEIkXM8asZLn+FXPun1iutzRcq54Qc9tF4r1cAggu5ib6uyaD\
104 | BNBfYq7lngsk1pX4U0Z/dzPuqWC5/jVz7h9Zrp90hCSc39N6acXAXNoug1vm47TdnpVBAric\
105 | qcfoSSKbDTpkkAAWW643jbB++2mH2UJiNUTC0xm7V/r5+kh8VCUG1pQ4xUseHmAyUlyYRRw6\
106 | B2xjFhvaFjowk60t89hkaLvX0na4ok0uMbufMtZgqo4JnEznRNKwlXnfBuZ9HRhHBWAjU/Gi\
107 | g28cntF2bx3DtR2WtpU1zOn7Es9hrO8pOh7gLtIoJQ3cI2B6zOMujdjeZW0ORBjHdsw0NFwD\
108 | r/a6pX1dHQFMJ+oY/T0hgFdiHnduxPZ7He491qaAjbB7NJKYTyrhfJIowJtdQbzapS6ibJ5i\
109 | +7mZqPD+hAigkHnfOxJXSjwxpnGnRGxfxuHejhGOCNvR1yKiNLaOIwZuS3AH4BIAjBmPxTTm\
110 | ZqYxZ7vhWj3mWFjTvpZ71sQgaYSFGRwCqJbgBDY63Ps3hlKFA68ytXimL7O54JnPB1kUPYAF\
111 | MSibwsJIDgGUSWjwEgaXG2Smboth3LHM+z6zaOguskgQQ5lH5z+PEAHg5c/hEMCOhCZQGKIN\
112 | zu6XI579C5j3LrJcH0Wilh9gM+lFUsadjDHA10w1XP8yobXHR9BPdV7pzt8kYFPIdreFJB7A\
113 | EId7JzM4cEhLa+nvApK7sbhc9e7vLFJA3DsAmL5fEZY6BvMiKmsyRQBg4m6U+KZju5eCTI8F\
114 | sD3CA+gEy32NCV0BPM1blnvAgO8TdrW0TdSeQjqB8SLlqaUEHQEUfM8IQNCDPCPxFub9YOju\
115 | cBwDXyYsn88m8OzY9m9yYJSbWNZxjo+n2ko7x2riYz637DK+J1bro6tJ3JWAHeChiC5bFSQu\
116 | YowDnf45IceAXv3DmJ/7HzR37hyWWPqbEJcbXJ5BHsYWB4eK40nrBO1WVRIR24qU/n0X/b8a\
117 | U0SKurNga4Sj6kJR2mfBz7/0ctz6g5qya+iMbxpxvvhC4Rj6J0e+arvleqW4tiWTVzC2lfc1\
118 | 156ns+p2+j/+wnwLu3wVOr8qEcGk/4KAPohhzmtonImitNdSCb28NyKOsZ64/dck/iRkHyCg\
119 | /iKcDcJGAGUzQQAmqEiccBqq0KS3JaxF9GuzLqIXXdsnul4m4jMiQSXbic7tgQ6aQHw0wyVO\
120 | iDD2PGHW6S+NayHDBobUDegKKtH2nEn4UOKpEl8kpucsEb8FEYqop+k47EFjrfKJUweI6RpP\
121 | MjaOywsjvnzAkyLl56/TpTx2pHeA4wI7QJMEFRg2Tr9XBsYpIfHtrQw9Fz6u9iIVRHIVHZ87\
122 | Jb4nUn59a440ATxH21QayhkoNgvhYAvtKv2SHKRMNjr46IZscGiWAH7QAGeS0dnXfPQRQA7p\
123 | JhZH4HP+Pz1rXtjn5DSC0gFWMChfzhUpv8GG9Ptu4v5hRoVi5l8ipUCCeHSkmAuIYmNIhgd8\
124 | 9AN50RVpRztTpEK9oAcJGoxg9Pm3SIWHv0Gisvk9GPTE5Sng8h0HnfcBuv88CrUuk8HQbox1\
125 | k8KG0TamvttT9PBkipwuof6/pWDQaRSSfVrMz9WAoo0PhLBBfCHxxjDRwQ0ljoxoAIFB5VxH\
126 | I0hOyEXCfCcp5lBChBx28ctKvEXiSsdnf0lipxg+gPMlbo3BGDVXYlMuAZxCYdVxwaMU3895\
127 | 4Mm0g9xNX1weo82vKOZeBYsiLH57ZqSNCZZJ7BmSEDpZIpVcoVC1GwYHBZUsT8AMPFFiM8ZD\
128 | 9wi02y3xA4mDaEH8uwmyX4y3jPtMyJd/fcgtVwczdV+gBhGm900C72G9iQCQluTNBGMCcSQc\
129 | xzgCllvs/DMpPq6QMeaNIV5+n4Sef7vErsw5DE3wPZTVEUDfDASGvqDLVOHDa2Mcr7Xjyz+d\
130 | UtAkBSDgiy1zwLFXlOAcyvnHS6uCa5Auv2YGxBmEc79uEU0/FdGdMWCdrEqGHC7MFvGEwJsA\
131 | Rp02Qu+C3pohuqLtMLqvkETEfJHKmgLfydqadhCPr1eJgVd5mYPljF3glpg4X5evv6PjWYpc\
132 | R/OJUXRl1qYY5nE5o/39Bsa6rsS3A/fvkDiQ3N2UPMBYL7PQyfIyylE2sSjwlCMBPMnoExJK\
133 | C0XbihIvlPga6QU40MEg+nHhcxpzIDHQTX0Sx/F03HSg+Rn1ACszTACcl3N7xDFmqSjegDbR\
134 | 9zmmOHcGKWBsMFLTvrYDEakAeoP3aP2sklfSKeF0sNDjJW0sjDjOYAcC2GXh4Cs79FVL4grL\
135 | 3FYY2r8d41pPMu24OT49cyaBw+DB43h4xHHgy9eRea/J03Y6zcfFmeOXlnvyDdcGiFTK3TgA\
136 | LmpTySpaTmcNLMkwAVRh3jdCRItTxPP9Xdjz/wFMbtvlQowNDn2+4bop8gdS0JUxEoEg6QBJ\
137 | N8qoCGBjhgmAa4aGh/GoiGM1ZvaxznANruFh8vetMFyzfXTw/2tLFr24ABbdvqoX8XGGCWBH\
138 | AsRiAjhW3mC5xxQZjMRMfwgxbvOQBJeG5SIVn4BjDAGoRTGsxSD/LpBe3BkZJgCXCNjzYxrz\
139 | z8KcbuZdS/tbhZuDZnuLUmmJQ1+zRCq6tw7tCr+hMx27g2tGEbj0Vwsqgs7IsBTwCpObrhNR\
140 | JArCYoN5GPGQOxl9jGAosk7y7NnW+1r6yCebxxKSGMZ4+mzlkPNbkkIPBrCDlrFPDoqBlTx7\
141 | hus44XYmAVydwNjDDOM9wuxjtcQbFMRUj0RPW7LHYtLYmfwAVGIp6gAca1mzMz17nYGzgwSQ\
142 | S5STKeDm8n82ofF1VrkqEr9y6GcPqXXHk+/BQWa75z1zdPJmQ9snNO1akLGNY8Y+L2gMAlPQ\
143 | IkPM4ELiqosZ9xYIc5z8f08ykQrP2isOJW+A36Iugyji60/TSD9nk9ycm9DzY45IIrXawHDu\
144 | NLQ/QPqNCaS/aSfx58RzuPhNFnz34r1DgSF5pHj5TcIEAPn2H4z7molUzJ8JEKOHeL0HRenI\
145 | JLzABkQMjYiQGvtwOS2cCnqT5SwJQLqbJyw6h/0Jrj/yL16qcwptSIxSUjCZtlnO9v9ryxk6\
146 | mullFBb7OGzpLv4QnLG3JbT+WyQ28Sw+gT9mGjNcYbNnLpUSxHGaF/+Mx6/dExW7xOia9aKD\
147 | cWpQQh5J7TymV/DJZFGKC9YZzJ861zC/N+xeEonqe5mvJFaLLIFhxVEQ7T2em2MopIt5Ma4/\
148 | PuhWnmPRqBpeqvrW5xEHfylgp+ZgG2oLUeiPR+jFB7El+U24uIyN1/gPcBBm4ZkR1x4fzuMm\
149 | S6YtOjiXtEY9SYVYn8loQDuFlG4II/9EuGXaFqRxAxf/JxE+P2BSAMMSCjudR65d+T5LIvIV\
150 | LJM4TaTS00YNmS9LlsG7hVv6Xqz/C8TUG+083PDwHBJPoJe+gvzWjvc9eBE9LEQ8pEGdSy/9\
151 | gMhCHFCZDDmokYTEXSf4rIl7SCWMJJcLyJK4mdtxNj/AUQ7Z/ABZAshClgCykCWALGQJwA+1\
152 | RKqKGJIm3yfMlap0AAkBPn1IGOGRXv/mGOeOOb1P9oD9xP3C/w+eP/ViHAcp8eBP91eR8vPb\
153 | SM/j0dibSeqBg8bVItlqKxyoS9Ia5ozoIRiNVtNcES2FGsIn6mwBaWeGDYrIkr6OigxdoOkt\
154 | MSlmTIkrYBJ9S+JZIfvOI1vA3BDKl91kp2iSkEIKzijdvFTENUzXm8g/YYXHT/C9LW2SV4mB\
155 | cA87W0NdiOm7Tti9VfF16nzekOa0lSNVtyLlC0yf8LND4kSukyYsj7cKfuFH7Hzw/4vqKr+P\
156 | ds9hwj1dDnYwWEObBv5C6VQnpp3ize90OoqQLJsFbB65atmibHVQ5EDt3SUuiEEXXkiGHVs4\
157 | 2nsJGGFeCUbkGvT/Qy3OIHHCXpUtoDqz8ccWIjjdsv1wtrmXY35gGGUuM4z5dIKL/RzjmUdl\
158 | 2C+zxAvUDnYBeLSMF8lEFKFP+MRfE3O/0KuPE/r0770TZMxwbF5muadXhpnFSSopoIpDByja\
159 | jBo4riXmbK5Ww0XpylxxEgEqkFVVXDuY8IIPt4jd5TL48uFm/l35miATCCbDNRP1IxLvCfyG\
160 | evaLDW10RIMchMtEcnUL0zCMmD0/PCXsFT8h6iEP3yaaIxgyGMa6MkVluGK9o7kGP8ROMT9n\
161 | CYnfKFUHFzgkllhI/xYqAmhON7vCFeLwil4nklzuSgDQG+h8Ej2S+xFvh+DLYnoBsIy1EYGy\
162 | 6AwOvaE4vDraMTTnRoZ2sMQt0uxqV9NXbuLSEaKmK3qFo2maiJbZFGbgIST3AwuEzSIbYETa\
163 | hWQodpL+wB/UYAIdI2TKU9DdwkTBTewxB4eNXyv6uNnSphfTkUUHcyztuykCSkrIKWcig1Fc\
164 | 6UUsGlU9JOXBXv0Gyek7I1Bwvub3tYatMw1IWYuijPDm/SV90fsUx1MakGp1ZOC3xZYxmlm0\
165 | qlcwGFwTIDytCR2h0KSup2f3f8UIEStv0B+4QYAirrEkNLCVbBtHrl9hdwAdLPHCp3hdremz\
166 | QOMG51k0jJ9JvIs8kht5qdxC9zIdacfHoAlcaxnDJZFFKa7UtAOgiFI3YQ7sREm3eyPsALqC\
167 | Uw1C9ueRSKnT8QehSJiLXuGs/5FIuap9TrsOvHEeFLykF5NiYOzWWq6f4NJZjsIIpINCYjK6\
168 | W1TBUKP+IuTD6UrFg7E6OWSfKxzF0dUJSR6FJDZHBdv8mkchgNqGe7f4dPm2EqgPhXy4fxmu\
169 | hVXUHIx5gcNCH+GWZkYHn1munx6FAOoyCAAAs+sTCSzSa4ZrEA/DFGw+VfO7znEyiWwp0C+8\
170 | HVNftrwCraIQgOkICC7Mb0W86UsEKYF01UXB1b/oqDGDZrOn5lqB5vdNMT4P9Aw9FNJGFLAV\
171 | jTwtCgGYUsUWKbRMPYV7hgobDDJcg5l6nODZIHJJJNQR9QLHncEVNpGCKu5ag+sCu3EQGluO\
172 | ciMBmLxZVPVsvyEKdwn8sOUHQln0FyzqVHjgdDDc05q0aleG4Mi3xPSicJxOJ51E3Krtf1uu\
173 | d8gUAQhSzV4r+NWxOSnp+lmYMWxzMGjMJtVnX1ICPUnHCFy3Olo48omaazZGzSVSqRFx/vOF\
174 | 3skmDMyzXOcXvA4oBkypRWy1cPoxVbAFTCXFqRTRmukUNZ0sbW/zUqluw8DfGfmFOHiJZZxZ\
175 | YRVB5ZlSgArw9Q1g0NxuJm1+HOJ44QB2jqci7FBlSCLpYmAkdQD9yPAYnmGu5XprwfT4zlFw\
176 | rbqXz/Gpg4YMumpT+pf5Dg8KE2nnGDlzyNBXWV6yjUAr+uZ2KnH4Lj5/cTh+bBXmJJQVmIxy\
177 | KQIYprlvoINCZSyddwWa8/XREOddG2LqogDSpLZnSC22ZIx7A8/Tl5iupcx5xJV7yKQ0W8He\
178 | aRXGkxvIbFlAKV1+GvKcglHiUXLIhBFlhudewiU4NxRz+tLR8RHnrmvtQF3dom8NWU7gSv5b\
179 | cqE3wYSY3MNP1pi+iynNHKufI510IWwtvx6UQm6Zd3iqexDbfLKbI8dgtZBjtFV45+LlD2C0\
180 | rUsOpgc0aVpaxrgWP/FSqfcPErFPcCX2bHi4HqqTouskOnPfEm5p9OChdL9IGc/K0xF2p8NR\
181 | kRHIEsBRDtng0CwBZCFLAFnIEkAWsgSQhaMQXIMQ4Ixxpki5f8MiB9sznCvhjlyZ7oFVEO7V\
182 | sM5NiWGOcOpA1ZCLSCSDUyZ8BMvSWHBWRTQTTK9wAJ39A3o/eEaEyXUSKbUz3NJhZoa9v4xC\
183 | Q1lM2km4kcPsDu0tHHngZDNSqR1kpm3tRmHOuxy0cMURNX8I9Pgro/hBED6KoL38viC0qEO8\
184 | eEvIw6W/kosmsAzFCURJHD025AJc50hsuhgFV00gFuhWUlvv9GnvppAauryhbXXKKjKKxkZl\
185 | 0J+RithVC/mVlwzcyyWAY8kOEBUWhnj5d8X4wFAVH8cc90RDEEkacL2zYoc02QBWksqWM4dW\
186 | DFtCFJjKIYBmjOgTLrzr+PI7ePHn6F/G2AkqSFzF7A/zu8NnABrHPA6vtcwBqeSXJpwUYrot\
187 | NhBOoZNEOPdrFbgEQoCpGZGAZIJSOEhabYrbu17wI2owv2Gk388XqWgoDjM3lhg0nZPopULv\
188 | wh4XTLcxgXGVkQfjdp/j198xYer/hWHsqRlKy1Lk6YtdjEl47OWqndBvDIKItVzwPFghdsG1\
189 | CtYxxMfBW2gniR3wtikQfNevNKCwY3/GfYj4XULiDUSiMwQvVgCOIE2F2sUMjp61MiTaYQfo\
190 | ofgduQlMhS130I4KZ9mtAVEeojIcemsQIgNKJdp5sF4f0u663bQD2Grmwb49wpcHoDw5btb1\
191 | MUP5ZKsfTLnyUCblYYrtL2/ZATgVMsBh1/RKF5cczfwKbnOMSk4KVDZ7m9RzSRIip38HQKBE\
192 | Gw314ezqSl894GKixvRXg6+xujA7lSKgAfbw1zXXNwtzQAN88FCkQWe/RoSuLTJ5lUgFTwZd\
193 | 2HV94kt7XLi5seGLQ0CKKd0MMrDeyJxDGhDytV4cyuOEtU77/VX2KfWq+3aGyr7d4z3l7ufx\
194 | KlXd6buvAWXDDAv9Q36Ftuwc2IE4mT1V+QJ1JWM30PX7HZ5vILV51ZIqLy/Du9ACVb5CP8dt\
195 | CgrxR9EgF84xEc7APwpzpg0dfGXhzHHucVyuVdKALua+Fn1JDwheiBe+sKd8O5IOqpFKPciH\
196 | JAnY3c8Kawyq7Pt3k4gTAcPWR/G7zev4IToG5hHzWUBb9D5qC2/elxnjd1f8ttQw1+Y+UdEW\
197 | OTzZx6B9LMzuX+0Vx1PSsM9EAFsNDbv6/h1H/HwnxW823/8O1A5fTksixBoWvkMFTUTpXESm\
198 | WLsu9BexEbda+g6Gm5mMYUGZP+myvS8JRUyGXxH0hWJbSkNv+gJhZUKI9n0iWtJiVbYPfNH1\
199 | RWbgTHF43MIiC/GPoH+PE4eCVVQQ9NU3RfA0VShpbrDM+2lxqJwuLH/pTC17xKFgnB3E5B4U\
200 | hxJ2bRS6eAgfQzDMwkT4GcFTPH1Nuy/JeDLLMVHUmAyKYQ8qchMXG3Lq+kXPFgFX9DS8qnim\
201 | dpakW0F38hLLvCd5/OqjzmJgR2FO+AAFz+Xi8KRL9UmB5NERUiAOhX9DXXqHRfXrB2yvT1q+\
202 | gF101u8h3EG/7ab/bydG7HcWhRZSyAdDx6dJPFdzPwpdjA4wVL8XqRg8HF2v0i4RDDk73nBk\
203 | bhSl07ohFV43yxogwmmA0Kfj90NtEgurEiIq6RPhD/MLmH+XM5RBDzAiXGtabNmrFG1aM77c\
204 | YUzKXmfpZ76izUAXKxoTaxr63K+4v6vDLgYz/YeUeg74gcTZXiqT+9eawJR0gMtf0iJhMC7g\
205 | SoOixg9FRPUzSITaQ8xYQ2JuelukBSiR/ifwWy6JQqZUdZvJA6nYMj94JJmSJa1VMIJthD5r\
206 | iEfjbnDkNaoLc6yhapearRLXEgCIqQNVVDs+A2fwBZov5nVG256ML2+apY+tGgcY067VN8QO\
207 | UN2RD0rXKN6fgXewydPUC+jt4zSTAPAZ7xvOZhtwCk/ts1yvqvnKTYkcO2dIQlkmoiXb5EId\
208 | nSKoiGTfJBQTOzRKID8TZHt5XYQ9NbvNEplrsDfooFHI59U9jymn8lCJzyRMAAUmTeA6Ooem\
209 | xfzyLxPmitrg6N9l9PUjy3VbMkWd6tf0vJ+GeOZvyTCkgjGGdh4Zkx5KkACGB6UAnYGlv8VQ\
210 | xHXL4oZFd7f0BS62iaUPJHHeYujjJkPbORrjTbOQkgC47ceJ70jzHw97vEJSwHO8eApn+Z11\
211 | BhPPw44OrkFy+s3CrYjkBtrORjA4dz9n/CbpHFQA0+z/Mvo5ifQK5wd2NuQhfM7Q7ji63pnk\
212 | +okk86+M4aurKuxp8nQAnUMP0mLm05F0TOBI2UaSVFrzt5Fk/iIadzPxGP91DHEND88hnTwW\
213 | tR2pdOv59PFbSfExn7byycJWsUINUFHfRarRZvQiUOrk/5hialAUy6cFWCPca/j9oCGbH+Ao\
214 | h2xs4FEO/xFgABM8Re+PY7oUAAAAAElFTkSuQmCC\
215 | "
216 |
217 | local mosaic_2x_png =
218 | "iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAYAAABccqhmAAAAGXRFWHRTb2Z0d2FyZQBBZG9i\
219 | ZSBJbWFnZVJlYWR5ccllPAAAA2ZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tl\
220 | dCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1l\
221 | dGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUu\
222 | My1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpS\
223 | REYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgt\
224 | bnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6\
225 | Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRv\
226 | YmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9u\
227 | cy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRp\
228 | ZDo2NDY3ODU2OTk0NkJFNTExQTg3RkQ3MTNCOTc2N0UzNyIgeG1wTU06RG9jdW1lbnRJRD0i\
229 | eG1wLmRpZDo2MUE5QkRBODg3RkMxMUU1OTRCREE0MUFFRjY2QUZCQyIgeG1wTU06SW5zdGFu\
230 | Y2VJRD0ieG1wLmlpZDo2MUE5QkRBNzg3RkMxMUU1OTRCREE0MUFFRjY2QUZCQyIgeG1wOkNy\
231 | ZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M2IChXaW5kb3dzKSI+IDx4bXBNTTpEZXJp\
232 | dmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkIyQjY0ODJDRkM4N0U1MTFBNDUy\
233 | ODc2NjFDRjM3OTIwIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjY0Njc4NTY5OTQ2QkU1\
234 | MTFBODdGRDcxM0I5NzY3RTM3Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwv\
235 | eDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+4JpC3wAAOblJREFUeNrsXQe4FcX1P48q\
236 | IDxFBEQURERiRbFXxB6VKLGXBI0VWywxKsaSqEFj7y22WCMR9W+DqHnYACuKBRQUASmCVOnI\
237 | +8/v23Pj9eaW3Zkzs7P3ze/7zsfjvd3ZnZ2ZM+ecOaWmvr6eAgICGiYahU8QEBAYQEBAQGAA\
238 | AQEBgQEEBAQEBhAQEFDdaKJxzyaKTlXUQ9HaitoqaqOosaLaBO38qOiHgt/NV4RjiSWKlila\
239 | rmixolWKFhRcs0jRCkVLmVZye7n/L+T7F/L/R4XhNkYzRb0Vbc7jju/9jaK3FX2fwubVT9Hh\
240 | irZX1I1//62ikYoeUPSi43fqoKiXoo15fXRXtBb/vp2iFgXXL+D5PEfRFEVfKZqo6BNF7xRZ\
241 | H+Ko0TgGvELRpRmbuPVB2jECGPsgRScqWrPI38Gg/0/RZYo+cvA+uyu6U9EvKlz3nKKjeBOx\
242 | ATDBgxX1VbSTog0E217FjOANRUMVvc4bXuoM4HZFAzM2gefyYAUkxw6K/qWoU4xrIYX9QdFN\
243 | Ft/nXEV/S8DQX1B0EG8CUthM0Y2K9mDJ19UcfkbRfSxxpWYDaJfBSTwvrGMtYIcdFnPx51RK\
244 | LIzfWnof7ObXJ5y3B7CaIAGoPiMUjVW0l8PFTyx5Ha/oLUXvKToEG3gaDGCtwAAqYkPeqf6p\
245 | 6Au2Z0Df+2PGvts9FNl3kuJmSmYPijvvbtO8V0JiPUvRh4p282BcYId5miK71nYmDekYAbMo\
246 | Ss938Ax8y6N5ovQu8ncY0AbzdVfFaG89Rdco2obv/Y6ZCYxDL/PPNrG1ol0MbAZHMAORwnEG\
247 | c29nigxwSzTvP56Zmm/YjpkApK6LeaOxLgFkkQHYlgB2VfSxoodKLP58/DkG14bIPZJF3o0U\
248 | dVG0raJjeCKOV/Qu/9+WGLqX4f27CL/PAQb34httoXlvR08Xfw41LG1CNegcGIB7BnCBojqq\
249 | bJHO/+a3V9DfYOhZt0I7kAweYbF0ewv96mJ4f0fh9+lqeP9Wmvfh5KN1BuZ4b5YGetpkAI0z\
250 | 8jEKMddSu1eymJ70O2LxHlTib/so2j9BWzBMvanoFOG+tTK8f5nw+5hKOr1SkDxcA5sGjJQb\
251 | 2mIAlXb/FWyceJWic0xfMMdCm7B0DzK4//wyEoWO/eEuRWcL9q+l4f3fCX/vySlJAN0zttm1\
252 | p8gBqo0NBrBmmb/Bsw/nor9m/RHW0kVVKgFA17pVwG6wdcHv4Eiyp0Gb1wvq3s0M758l/M0/\
253 | MLx/k5QkoTQAL8R7XDOAYWyIyOEt3pWqkQH8RUgVOrWIVGEqJt9BMl6PqxveP0n4m78v0J/1\
254 | NO5rQdkETmH6uWQAxY7aZlchA4Bx61ihtuCgslre/w8TaHNztiOYoonh/ZM9YwDAxtSw8LdK\
255 | 45iEAXTnna8U9qUo6CF/NzqsChnAUQKLI4faPC7d1UBMLfaOpmhjeL+0BAC/hwUpqQFZBVSB\
256 | QyUYAERTHDdtU+YaGAjho3wy2wFeKaLjpgVJI6C0VTg3QPsJtilxLGiqRkhLAKsE7ADdqOHh\
257 | NBMxrwXr8b9J8IHv9vAjSEoA2wm/2y/5O/cRbLOLQBtrGDJcG6Gsnxp+p3U07llaoKbpABvj\
258 | lxSF/EJVhl8KTsxqWArE8R0CjHY0/O7FAGMzHMumJWUAWMyIAutF2YckA5D2g2jFk1rSx3w1\
259 | gTZMzt0nWxrHLw3v12EASwS+5wEUzxkN6xGnQOewSi2BGm7rgSRiHo6S3q2SxY+daKVge0ss\
260 | vOOJmpOzFCSccEwY3VRLY2ka/9BB454FAu8d9yQB83QYq4Ow4ksdo++URM/rx/p7tcTPSzsB\
261 | TbPwjv2F25NYgCZ+ADMtjaVp1iEd92SJRahzpIpI0kOFvtsmSRjADYqaV5ERRJoBvJeBPo93\
262 | uGsVw2xPx7JNSn3RzaGBqM/HBJ7fPQkDQHqjasqfJ+0E9HoG+vxBys+3xQBWptAXCWmmg8G9\
263 | 9wk8f80kDAAGHBikrqbIvTcwgJ8Due98r6f2YcrPt5UgNI28jhLMrL3BvaMFnt806QfFEQUC\
264 | XXYWEierSQXAUc6bnvd5dMrPtxUDkkY6upkpvjdO4i6yueE0iTGRtqQo28iFZB4gUg0MALif\
265 | ovNVX/X/b1N+h+WW2u1peL9OduAZAu+NSMT8EzX4AsAw2JjXFGwTazHBJwDu3Ehg0kXouy3U\
266 | ZQAAjpSQ7vkJihwa1ggMgB5XdC1FdRF8Q51QOyvKiY4pYSfD+6dr3CPh03AoyVn0daVWY53q\
267 | c3KTWy8L+iiY4m2e9ne4UDsmufRbWugXdssDDdvQ2c2nUvYxVoIBZBWzLLV7i4cMcakgA1ho\
268 | cK+NzNH9qHKatDjqUUNkAHUNmQHYOpKCa+dNnvUVDlxSPvgmum8X4X5BFfmzzZ2wDHCKtCjD\
269 | 8x9BVM9JMYAs5gO0WbPuOpIxEknaJnyQnKTDbmGD2kygHd2KOlMyzACepTLeqzpJQQMD+AnY\
270 | bf/kST/hsz5UsL1vDO6FsU4qZwIiUQcJfZ8PUvgWaQJ+PFeUu6AhqAC2q9biSPB9D/qJ3V8y\
271 | UGmcwb0Icd1b4B1+TyWi2DQAMVjXkzCrEgAKhnzUkBnAbLKfnRjtn07pewdKn0p8bni/SRk0\
272 | HDU/yhNYao4+aXBvFhkAqgpfXOmipB93YQYZgAvAYerOFPsJy/8nwm2auhOjhHfSFOdQGwZQ\
273 | lPjjaMG+fEVRquyGwgBQPg51J1ZIM4CsxQa4NND9MUVd8a8W2pzFC9EE1/BOXikZ5/r8/cax\
274 | yN9JuC83GEqCkzM051GXA0lFYh1RJzXULMkYA5jp8FkwCCIf4jDHfcTz6iy1/R9Fmxq2cTTT\
275 | GBZLp/DOhHBjlFNDSSubyTqRRMS0SGkWJIB5LHHdl0QdTcoAlmWMAcxy/LzhPAAnOnoeBvoi\
276 | i+0PUXSGUFu9KJ0MU2fHEYUzLAFgU76Zpa3ENTCTqgBZc4iYnsIzz3OoCtxLdkN/kftgEmUX\
277 | sMu8LNDOUrJfYTopYPM5i9Wli3TfLykDmJGxCZBGYZIFLPLatpdAvbnQgYTxYEYXPxjjuYLt\
278 | fZdyfxayhIk+waaCiMFbTRlTUhVgesYmwbSUnguPMzhg/NniM1ANeK6DvqCU+R8oWzXyvqYo\
279 | 3fpSYXWyh6NNC+HcOLnAUSziF5CGDgZS8SPtpAzg84wxgDSNN8ioBGcYG3kDINo+61CKupYq\
280 | eJR5BCyefSxIqxKbH4yRI/PUChCs9d8zM4eU4dTOlpQBvJ0xBpCmCydUgCMpcj/tINjuO8Ki\
281 | bRxcy2qN77X1vmCma8NoJ2EDGOmbSpXUBgC9anRGFv9CSt9wM42ZgJQ9ABP7EGHRNg7wvOPI\
282 | XqYfCdRRVFnHlsU+rfTgXjEAAIEeyBUIP22UDXuBd7mZnvVtskcTU8JYN5t3t7TsGigUc6qn\
283 | i/96FvvnWHyGRO6Hlr59OJ2IrVWsChRTB5qyuLsm0xr8L4qMwOnjJL4OTAMli1rxNavzz0ix\
284 | JRVxONGzCboV6bu3YvHvR+aVcUwBLz0k+/ibJ991Gs+pFx08a7FAG62qgQGUAxwuplLxLCq9\
285 | 8hjACVT8WAW131DEAFbn+5g5wGMMtdlqmYPi5zb8MVvwz2342jbMcDrlGVt8AI7TfqdoQ0pe\
286 | uXcG725jPenLdbzT3kXp5gx8gOfJ946eJ5FopbbaGUA55CcTLaVL5ibUEtbffXO+MNWjD+Ld\
287 | apsEUsy+nkkzAEKgP6PIz991yW0UrTk3BQYv4Qcw17dJ6TIcuCaGPtVS0ODiI3CWvJeit2Jc\
288 | +zHbWiZ62hcsxC1ZHVjh4HmwQRxMkaEvDenuHYE2PmvIDKA2z4ZQKlihmaC+5SvA/PryLloK\
289 | yO2H6kwzPe8LxGIEoMBB5m6SP8OGpPgUReXTtyN3vg/FgCNlk7Jw2NSGNWQGsGYMMahVBRWh\
290 | WrCcbQKHFREtsZD2p2ylYJ9E0QlBJxbPTY6Kl/FCgacjsgAfrmiEJ/2EzUE3qxCCdRb4NnA1\
291 | 9fXOEtkg3nswRS6NvyjBIHLHOPCee5MaBtrwosECupwi41o1AIt3Dx5LVPTZiMd4Nf57PUs4\
292 | MBjD+PsRM45R5N7PIQmOp8hAnWTzRD1J+G/82JAZwF3M1SHeFssXh0mSczWGx9kXFBDgJ8DY\
293 | 4NbbPYakdxVFbuErfeyIy1OAnLX4yxJ/3yjv5ylhjgV4DCRKQRKTfqzGwTC5DkWnWDi5wpEt\
294 | Ivdg55nmc0dcMoBcMMWYEn/PFZOASLgkzLEAz4GTj38xZRYujYBwAtqe9adiQGpt+O//O8yt\
295 | gIDqswEEBAQ0YAkgICAgMICAgIDAAAICAgIDCDAGAouuC58hIDCAhgWcOV9Jkefc4vA5AnTQ\
296 | JHyCTAJReA/xv8A74ZMEBAbQMMYLMRWX0k+Rk8B74dNkDjUsgTflsWxOUZwEfo+AKCQ6WRkY\
297 | QEAOPXnX367g93A1nRE+j7dAjouOFLkOY+yQD2LHGPchcAgu8QiSgpMc6ioiDZ9opGxwBPIf\
298 | 2CWQgPVq3iUK8ZyiX4XP5A2woyN/46EUpXLrKNj2Eh7vf1IUYWiciCUNCQAiTleKkkjg47RT\
299 | 1Jr/Bm6EfAGzmPshg8qcBjyZuvGuv0uZa3zW/zG+SGyyGVPXvDHPzyc4mwljjkAaZENC8o2v\
300 | MzRWSHmHkO7zuH82gByYRzBB6ruDDMuDuZIAkBUYeeWR3GEr7khcTGHRB0UeXyD3FX/T0g8H\
301 | UpREolIm2f1JpgCm1Hsj3uNI3gVNC4kgchRZgJAkZYLH43WMohsUtU/h2UgcM5ifv9wnBgDD\
302 | BpJg/p3ksqHCKPIic75hVbr416coYGrvmNe394ApYnxPVnSaog0stP8jT3JUKPIpqw4k1/tZ\
303 | 3E8byKWBbNuj0mYA6/IC7We5wwgrPl/Rq5bab0LukzgMoKjWe5uY10+ytODiAu+Joidnkpuq\
304 | N8gUNJSi8lpILLMqxb53Yslrc48YEhjlJSw51rtmAFuzuNbZcaefVHQ6yeeHz00w6KKwwI4m\
305 | e6mqOvKuf0DC+4ZQlJAiDVEfu83VKYm9wHiWCB4h9zkkMV5IWbehp1LkYxSlLlvuggF05wdu\
306 | m2KHcRSGnGuSBrF+9PMstDibRWrqETz4IIliEdCXb2c7SVJcxKKxS7RnsfcATyb7FN71/hF3\
307 | 1zNEC54D25LfeIXHaLlNBnAWRXnhm3nQ4SW8mJ4T3OU+KiPiQdz6kCfDG0xJTizWZlXJRH/c\
308 | y6IKVAw7sQje3sMJP4olwQ8sPwfMeiBlA7fyGhVnAFjwMO4d61mHV7Ak8IJQe2Aoj8e8Fh8S\
309 | x5Z1LB3g31IOOihwgSSppmXDcfTkKn04JKKnPGH25XRgqCVXkJ0MvH0oygeYFWBONqcy/gI6\
310 | DACeTXBC6Otpp1GAAZ5WErX0GrOuqavrTcxjCJAU4ONwC0VHoqbAsdhGDhc/7A1NMzLx8c1x\
311 | 5Cx5OgKJEC7XW1O2sC6VSUyalAFgAjxPkYeTz8DiQO0BCSs+CnjcJ/ReeB8p56snFB3l4Ftu\
312 | xwuqRcYm/lcU1VWU8h84gOd+1rA1q6pFkTQc+M4MLH4Ahsk/CbUF45JUmnJJz8t3HXzHtrzz\
313 | t8jgxO/GjKu7UHsnUzZRVmpLwgCO490wKxgkpK/Cinq9h/1zEQF4m6L1KLuA+Atr+DqG7UDt\
314 | 3T+D/V/FkhCZqgAdWRduk7EPgCCamwXawQ44mez5eCcFBg3edwstPgM2nlepOoBqwn1I318A\
315 | qoSUu/U3LJkgwu9b+unkCJvx2systqDIjtXT8FlipwAPUOSlljXUs9gt4TEGj7e/etIvnDZs\
316 | avkZo+l/Q4+zjKtZKtTBORT52psALswnskoV1/CGMT6DoiCjpACTQTzGMlMVoDvJWK3TACy3\
317 | Ug4bsH/4UrH3fcvt962yxQ8gkUovzXslvFuPpugYNYnV/VOK4itwbLwowX0PsMpSsVx7HKMU\
318 | RIjGGR54GPF6CLQzn3XiQR70ybb+f5JwexC9YbTEMSgctlAV+Huef2vwAtuEN5saS31qzLt4\
319 | X817TQGXdZzcIJYf7uVJ3MrhkQqHNLg+9y+xcdezyvYXbj/eDllBBcAAwZllrYxz/1qSiSKD\
320 | DWASVQ7RtY2dWK+1Adg7Zgn1EYscGYvvpXixGjh1QOVdxDf0IzunDzql59GH8wTfYRkzcTBF\
321 | 5D4Yy2pdnOSusBMgUjRXbh1M9Qte9N8mFpErMIBdk3ATjwEx6i6htm5SdHaKfYGHW2uyV0AV\
322 | 7sUS9RmHsiShG6QFZoCjt/OFNyCI4YcnvOdsHnebgJ0KjmOf5DEF/DuBLMY4VGIAf+MByDq+\
323 | I3O32xw6sRSQllcc3Dp7k4ynYzHAjfZSwzbAbAcKTdy2LPpKHUEv5100iUTYh9JzAcYOjyC3\
324 | 0Sz1vZHQHqDNAHD2OZXFjGoAds0fhNrCBD8lxb7AOLStJSngGTLLMTiC9WzpWH2oBQ8JqQUI\
325 | wEpS1rspSzKtPZjHK1gqH8JjZZQQttwpQL8qWvzEuqUUriM7wSZxgeMhW85JJmm8VvHObyNR\
326 | B0T3fYSY3u4ai+4fnsxjMKM9KTqVmsKMbHcbDKA/VRd6CbYFvexJD+waNrIBdzK4F7aDzyz2\
327 | Gca7Y1OaCzeS+wxRldCE12kd05ZSDKCR8I7pA7oIt3cVuUlAUQ5IzLGucJsm3p4vOOjz0xSd\
328 | c5ugmybTv8bj+Q0pAP4hf6UEMSeNynygdlXGANYWbg873XMp9wkGskfInxqPEx09B74YJmnA\
329 | dA3CqMX4rsdzHP4K8FhF/EOtCQPYnKoPtRbavMqDfvXhQfcBzR09Zzr9PF2bjuisAzjv9HPI\
330 | 6EykgRFxNvFGFvTAhgTsBsM9eA8c3e0g1NZcg3s3dtjnV1L61rC6wxHrA8/nJuwBT1AFL8ZG\
331 | jsRlH2DLau9DgBB2tEdJJlrzW4N7XSYKHWdwr2lMx3fMBJDdyefaejgtuFiHAVQjZltqt46i\
332 | 5KFpA3abOwXaGW9wL0qY9XLUX5MU7RL+IHDnhYfgdixu+wowgA0CA4i4tg3UeKQyIeLMNHLT\
333 | NNDoRrIX0JMPk0w/kwTfA9+rDxNOKHw7KkTJ8UFJGcDsKmQA4y21u4VnKhNSjZsUrHjN8PlY\
334 | COc56KdJhp5xFt4HUsCvKTqWhY/GcIoRjutwY2iZhAGMr0IG8LGldvfyrJ8o0YVU5rqxCjBs\
335 | TjN8BxQrOchiHyFxmdRTsOmsBEkTruLIIoRj2r4shuPUYmZKc6IFlagwXSoWANxiURUtfrhy\
336 | Iu58sYW2XyQ/88XBaUX3ePAO3sVMIF2oJR+INDzY4P5tyH5SlVJAZCNcuZH/YGOW1kDdWFy3\
337 | hXuoSPxKuWCg76h6TgMQPLG7hXaRdHRuKfEqZWBgETeuk9dva6EFgpiA89kuIIXLFV1mcD+C\
338 | etqTebwCxn43ijxmN+TFO50lKATq6OSf6JTHDHowo0LQl0RMztdUzAMSDKAEHVtfPTizTD9N\
339 | aHfP+z1NUTvNvtUJvsezijoafuvGim4QeJfHBN7jbEXflnnGAkXnKqoRmGONFB3MbZpgRbH2\
340 | y50CPF0luz9cRm0F7uzled+RYRbxAjpW+WsF36MfG97O0RRzcbT4Nt9viocM7oVEjICkm6j8\
341 | yQ/ChhGtKZGRehXbD74ybKdJsXlQjgFAX36nChjAY2TvCHCvDPQfxjidYpawbfxb8D3gin0D\
342 | i6JI0FnJH7+GRWzk0IPXnUSS0skGfarlxZ/E4/JMQ1sFsSERodBbCvS/PokNgNhIMS7Dix/e\
343 | f5tZ6gMmxBzKhi/FMtYlk2YRQnm1j0m2olH+zvY6L6rP2GiIk4vObIPYk8wLehTiQtKP6HuY\
344 | 9HwskOJrC0ruMdiCjXZ/Ir3S8YWYXkxqiVMXYAH5kQlFB3eTXk71OABnH5qhb6GbRQhZZi+p\
345 | AkkQPvxIpKnjBQjj2QTSd3CCFBa3rmB7nrMDSS6NHTCMojoBsVWAHLKaGARn2RdbbH/PjH0P\
346 | 3SxCf64SVXAw6bsAb0Fm3o33UPlAKUg++7O6gzR8VwgvfqBoroY4EkANizGbZGiw0SkEprxk\
347 | 8Rmfk3nppkIgSAUGJiS8wHnx+kzr8b9d+N+OBhMSkkvSUFo8/z3enbKIMSz9rDT4ZqbS3hIe\
348 | 1xE8zq1ZskBQEY6o17DY/xU8d6brMABiPfpDS7qgDVzEHN8WoKdOEWwvlz//tpi7VDN+h/UL\
349 | mESXvP+Xyus/h3e0pFF/21OUGTdrlYJX8uIfY9AG+j4qw9IPGM8JuhJADrBo3pKBzsIN8zTL\
350 | zxhA5mmpcnopLON3klzG4hzalmEQSGihY9CCDvkcpZcSXQdQA01DtvHtJmd08S9i9e8bUwaQ\
351 | 02VO8nzxS+WjLwek4TrGcOHDGn032SvwYQu/pMjTLQuSADLmHiYwHyBxLSU3UY7SOJMlS5Jg\
352 | AI3ZUOGjYXAwc3vbi7+GdSkdIw3UBlSphXPOcsoudmY7gs8l4yCyw1ArFf8BK/4BGRsnZAQ6\
353 | utyaSMoAiMW/B7lhH4Az7tMV/d3R82APSXqeDpH7Wv5uWV74+YA68RTJVV+WBE4tUENAspoz\
354 | ouneyND4wF5zYCUGqOPEAovib8iPFMk4m93R4eIH9kq48I+n6LTgnipa/MQ6JWpH3u7Ze71M\
355 | UQiudCl3OCw9Wk2LX5cBAPCwg1fVERY+dBzAi+xmNm586PjZcRgAjgiPoiiiC7v+SqpOQPo6\
356 | g6KIuC89eJ/reeLbCmVHLMJUz8cEHov7xVV9dFSAQsBCCmOWq5h4BCmdSGbZa3XRlJ9b6oht\
357 | DNsiniI75bF8BoJ8LqAo/Ne15ygs9Kfw7m8bvVkV8M0IupgZ1D1JbpLwY4dhC5bh/rzz2QJ8\
358 | opHt5tcpLX5ghxKLHwv/EIp82J9sgIsfgJUcXoNwbrmO7CRfKaaO3siS4MuO+ok8CTAGLvDo\
359 | 2yNF+lZJF78UA8gBnlIwkKH2+utCbX5BkRMGTh+upPSzFBWK/6NZ8sHHR6XWegpAPsk/UJQb\
360 | 7xxLqgGYDXwnkBj0XJL3oYijY/ch8xBdifWB9bY3/5wYEipAKUD//RVzS1iK42TNWckcFg5H\
361 | z5J/acneosh1cwSL+i+H9V55jvH4H8JkUjwEOQEeYSlrjgd9q2W7wwnk1kfgY37uY2RoX7LJ\
362 | APKBHRyWcBwddWZm0Iz/hoFEskSE7E70WHxuzfYHlAOrC+taG51YlQIh3Hg9pvyQ14UUnTJA\
363 | t0eo8JtM33vaJ6TuuoJVYVuAlAPHplw8gQx3dsQAAgIaArDJDaDI+7CbQHtggkhgAiekYWRW\
364 | DCUwgIAAh4CqAz+J3izpgCEgmrKwgCoWIBKJwm72AUs8CGWfSw6OjwMDCAhwi0Z5Cz/1xdck\
365 | jEdAgFN4ZeNqFMYjICCIIwEBAYEBBAQEBAYQEBAQGEBAQEBgAAEBAYEBBAQEBAYQEBBQNdB1\
366 | BEJgDNwbEdADf+UpKbw7nn0UEwpHooY6MtSMp6ggyJ1UIhWyJ4CLKMJmEWKM5JrL+X0nUZRX\
367 | AXnt3uP+ZAlwd92W+7cBRcFfCPZBElUkVMnlU0BWqTlMCPJBaC1CWj/gvs8Ky7MiEHKNgj2b\
368 | MuFnhEi3o8iNGIVYEbF6N5XI1ZHUFRiTFanA9iiQHtA4cgQiHZEL90Z09HGKClyUAgInzlN0\
369 | h4cDh5TiD1C8/PpTmKEhH/9wipJg+AQkaUE8OkqAIxff+gJtYg4h5BUViocwU2ioQJQk4ggQ\
370 | W4Dahj345x787eMAzBb1Jy7inxMzAITz3kqVC27UKTqZ7OaHA9dDHsC1Y16PpBQ3eTSgkJzG\
371 | 0E/h0EmAMudITHkbpZuMohkv+ON5U2hm+XkoTXcXM83FGV/QWLT7sZSEuYw8AsguNIMlofV5\
372 | weeoVvDZSNpz2M+YABhADLq7Pj4WKzojZrs69ER9MuB9ulh8n6R0T705Vip6XNFmjt99DUWX\
373 | KJpRnw5mKzpXUTOPxrMSNVLUWVFfRc8oWlqfLv6S/35xJIC9WfRMChQQ+R3JpmuCODRTw3Zx\
374 | KUVlrl0B9gikCduaxbacDgxdfx3BHROc/F6KynfbTJaBBJjIwvxbB7t9HHzJkmadJ7s6dukN\
375 | mbqz7aMrUxdPvlkOsDX1ZPtALAbwEhWpKx4TsA0cKCiu7kd6FX+HGfQhiWiP/Gy/ZPHOZYoo\
376 | LH5kxf2XhbYhMj5E/mXBxcRFsZVBhXqtJazFzHwjFs2751G7jKkh17AtLxYDWGw4+N/xonhf\
377 | 4MV/w5MxKT7k3VgaMIQeSlH9tV08GNiH2U4joSe3YOniGM8n8zBmUgstjO1hTLtncJGXAypb\
378 | bZHrZDmsIcD527Oo1ldoUHTQ3MJHhGoE6/STniz+HINEZplOhu3AwPp2BhY/sC9FabEljWWd\
379 | uf+orffrKlv8wCamC0rH8vm8EBPQQSvBttpQZImHXWRLDwcX5+/vsGiqyyyhZvXK0ISGH8iz\
380 | Qrp2C5YqtqfqRWPXDCD3YdNkAhKA/gfnnKM9f891WerSYQIDmYlkDRDTJY57T8vfIasU36TB\
381 | AHJM4BkDfTzNdEqbs1i4UUYGeV3eyZKW8D4+wxMbi/cgwzaOperH8LQYANCaRUyd3WlBiotp\
382 | eAZ1QVircTLQNME9PTM+ue8yVPk2q/LFj2PAG+IyAFtHPzAMws2zbQY+GHwOUBCkY0YHHKLx\
383 | oATX/5jxCQ4D6PkG9zet4sW/iiW8cXEZQHOLLwNReoijD27yjIspMjJlGWAAcY16H1XBRD+b\
384 | 4vvJF+LbKl38OApHDM9jhbtbObSy/FJ4Ifi1n2L5Obr9gOh/URUMfhMWjXekysFa8LeXsoAj\
385 | cAkRfpMo8nX/gTcdnKR0oJ8caqRVUXhi4gjzbo17UXbr6IyPN3wiPmPCwn+Vf/4fVHIE6sUN\
386 | 2MaJiv4e47qDKQpoSIr5FPk0JAXcX8+qol0AxTmficEscNJhesSJsYpTzgpqJgquwlkMnpSd\
387 | hfo6kttNCgQ3/Tsj44lIUbhFj2dGO44X+uS4DVRiALuRYCHCCoYJONO8W+E6WHifc8QAMDER\
388 | d9DasG8/8kAhvj3nodeGddUOjifMfz3AKgD+6/AlaG/wrP2YASSVVOB4cyXp+zHkUM/fVyev\
389 | AAy+e3uwwDF3JlDkSl9IE0jA47OSCtDGUUfhwAFrde8KA7bQ4cff12Dx4z3vZxsHmNqyMraJ\
390 | TtxvEGIIUE9uNUt92px3xbcrXPcNqwGYaDUGz0rKAJDE4kmW8q6mKJ+DLmp4A9OJjxjAUtA6\
391 | KTMAqMe/t/mAOK7ArrAeD35jT8SrvTTvw8TZjAfuzTKLP6cjY7HhlAGGun0oOhk5lPQiMOPg\
392 | pJjXTWKJwYTZmEiEsORfZdjXTTXvQ3HOvpRuzgWg1vYDKjGANR13GEbBwZYkjKTQ8YbDDnZY\
393 | Eh2sCJbwrrUvT8JJwt+iH8U3un1i8JweAu/6mOH9JvYE6NMI6UbEYSX/k6kUeV7CjnURM/Ct\
394 | BFQ860fPlVSANcg9zmf98ynBNnX8GXTqu38kvGD/wzspcgeeSD8F+UC/fZ/7lXSXa8uqxuiY\
395 | NgNdbCzQ/yNSYPz5wML/o6LLeHMCU8OJ0nxe9BOYykl5OPnQPZJslzYDWJPSAY6iPqUSRxeO\
396 | oGMA+8LCe/zAE/Ay3lGw6Gfz73XzI/SJyQA+NXjvNXkCz9ZcuFCJLjX8dlLpw5byd9b51tNJ\
397 | 3328k+1JbssGgHPH7wzeC1wWhqBCI6TvXmqzLbc/kyWMXJYlGNl08i/GlRpMMxKPZ5vINTEX\
398 | AaST0ylKJHOpwPea6sGcMMlujE2ocZoMQFcCQJz8/mRmtYe4heQf+VbohZ4zANepn6AKDNG4\
399 | L66/+9eGTLct21Iu4EX9AM8LqAdd+W9wurmCNw0wuNs01a9S8zBtTDOU0NfLogQwjz8+cvab\
400 | RPDBmeTiDO3m66Xwni9aFC1zpxQSwE42gN93HDMXSAeP8m7fl/TrVJQS20d4wABMv1+3NBnA\
401 | WprtzuV/X2DubwI4hRySwsDpWPLTiCN/X4PJJvHv+IKyCUhGSzx4D9OjxA2yyADyd8/rWfQz\
402 | wSMUHau4hI4FvGsKUgAm+YSE9yQ5Ffkmg4sfqtENnrzL14b3b5QmA2gnwACAgWSWFLQlSxNd\
403 | HA7cKAO1xTVspgTPIgP4B7mJYYmDiYb3b5IWA8DZpa5Ra1YRfQw+3nMM3hVumQ85HLg6zfvS\
404 | yKiTtFzY8ipmAIg6PNej95lEZobUzdJiAGsbtDu7xEQ6gsyMgrUOBw6GKp0jNqgqezqeZElV\
405 | tSS68ZQMLX4wwkMtS0RJAWZrYkfpyhJwphhAqQFA+uZBGZpQuq6ofyV36daaaeiJSU44ZmZk\
406 | rLCxHKfoLQ/fzcShCsfgm6bBAHQNgAsqiKRwChmakUn1oKb4Blfbkxy94y4aqtqkBNdmIUPO\
407 | cpYun/T0/T41vL93lhhAJc8nWGh/S3l5yTwGFopuTAJOP3o4eMcTNO5JcsS5iPw4TisFONr0\
408 | IT2HqMAAyvxNNxlEHPESHn39SbZwqC3oRifCnRnJS2wmPkWg0FGa9o2ki8xHIGoSWatGej6H\
409 | xmSRAehO3LgGmM9ZEnCBZQb3IsLvGc174fKKABIbxsuWbKPQsTW8nfD6uZ4tKJyt47j1UDLz\
410 | tXeFCWSW0h6MvrlrBqA7aecnuPZptgnYxlLD+881YCLIKAw/93WFFz+qLOkcEcE+817GGQCs\
411 | 6mMpO8iFb+uiiS0pIG0GAOBU4GXLA2AaRYgd51pDEQ6xERIlylGGHE5Ke2jeP0qDIc7xbEHt\
412 | y3r15eRf2fJSMK2OvUu1MgAsziPJrlFQIooQR3sm+QnaszrwOOklvFyfotp3H5FZuq2nNe6Z\
413 | 6+GCQt5E5EgYz/OnpsoZwM7VygBy9yAttK14+nqBNpbwRFtm2E6O2T3NOmy7MmIfnIoQH4+4\
414 | fwSVoOCFSZGTFaTn27DI44W1HjPVNyiqe+Ar3ja8f0fXDMAkFFhXzAYTWGyhn/OF2hlLMm6m\
415 | CI1FhCOOGGHEwrEcsgfXUeTDPpnFdKgNiI/fh2QSQ/yT9BK1mJzWwAN0uYMFtjMvMpwMbOwh\
416 | A8CYmpymwDGvRxYYgMliwyL4NSX3bbc5gQtxB8nHJGAX24aiOn69+P/SmWCgal2dwvfDhrA9\
417 | uSs51p/tA3eQW9fxOHjTNzXAFxUgHzAIwqVTshS4tAh7MvmRbCIJ7jGwYZg4AiH3AM7B4R2J\
418 | zD8rHfQVzBOlwnHasaFHY2DqprxTQ2AAAFw6fyPIBKQZAETaX1GUvTgLgPh5UUrfr1Ge/eFy\
419 | RVuTuUEsLmBsRRh5S0/GYWSWGICuVXW60LshVdQRQvqjDWeR+aybv+v54sf3O1LQDiJhR9lB\
420 | 0SWObAOwBwz0pO+w75jYuHpIq4blGMBszck2WfD9hvAiW2DYzhhLA4pFhRpydZ4ufkhQvxXY\
421 | eUxOUYqVV4MagKo/vR1JA0d5Mh7o92iD+3Ey1M4VA9DRV54leQPeCBZ9dDOr4H1sRh+CCcAx\
422 | 5SHPFj+YMUpkPyHURxN9vBQ+YWngPLJz+pPDph6Ni+lx4FxXDOAWDe52paWPBqsurOQ6lYHR\
423 | jxkOFtsARWeQuZ+ABHD0trvQ4je1ASyKMW9u4EU6zNL3aOIRAzA5CRgrrTaVYwCvKbo1QVuY\
424 | /B9b/HA4TkIAyDkU3yo9inVNV7idomO80SlNLkg78BvYgvRzGhbDJIN7Jyd4xn4srk8X/i4+\
425 | ZTWCRKt7rPqg9MtUiiQ7m8Wzclz8axaB73bw8aCLwh12yxh697P8XksdDzC8/HBeixqHrgqZ\
426 | gCHimA8JJM8UsJkUAqnRdKsEJa1yDKmlJ4+zVCWo4R4xAIzVvRr3fc7MXRQ19fWx7DuoEHQ4\
427 | T+wOLIZ8yVLCS5ROyS6cUsBp6CS2EazOuipcQu8kvYIZ0oD/P+oinEpRfgBpQIdGyvT7yH4e\
428 | vDM11EKoQz1I3zC8BT9zd8N335zMKh1LA74R8FGIm8oN3283spCgNS4DCDBDLdsIIN5ub9AO\
429 | GC18D/5N0QmJy5BYGPOep2QRjXDGuUvg2WD011GUIDMpsBkM9HBOdOYxrDQf4Np8ii0GHxiA\
430 | e0CCQhmsbXl3RJRfF/p5tR6oXDBczmbReyzTSAvifRIg9PZmqpzvEO9/hrDOuhpLISgVF9dN\
431 | HeXVDyB/U5pBBT+aCXkjkIZvJauRdfz9rB6TBgYQoANM1tPZxtIh7/efsu0Ffvi2komuyUzg\
432 | DGYKpXA/M4zFYbgCAwiwh1Ysvcwht0eg6+YxIRwhImUWou1eYwY0MgxNYAABAQEVdJCAgIDA\
433 | AAICAgIDCAgICAwgICAgMICAgIDAAAICAgIDCAgICAwgICAgMICAgIDAAAICAgIDCAgIyBxc\
434 | 50oDw0Gu9o35324UBXV0pKj0ESK9EHJamMcdab1RWQb1Ah4kN8UldNGWotz3yFqEDD2o8oOQ\
435 | X2RzRdBMfm0/JDBBqa6pFIWAIqUakrEiqm5VmJ6ZwzoUVW/uyXM8N7fbMWG9tS6x8SLXA1KF\
436 | IaAK4cuIYkTinYX8N8yV+oJ/8fuZFCUKeYU0MnnbDgZCIoxdFfWhKPsrFsXqhm0iLh7lnyZ4\
437 | NPBIb41Cn3vz4jetVAumgLBaFPIcQTLFTQPszG/kG+jLczzNKkTYMJA0FxmovkyTAWC3O4yi\
438 | BJ47WVIzsGMi+eb3KX5whMEi5/6ZzPFt4QtFN1IU3748rDkvsCuPO6pDNfPs3X7g93rNJQOA\
439 | WItqt0i9tLujjj7MC9A1kBoLGXEup58nw7ANiHmoTPx0WH+pAZvOTQ7nuC4WsCQ60TYDQEaW\
440 | U1nsWMdxJ39kaWOaw2dCr0MBkO1THFyIeSeSnXJnpQD99SAWdWHX2Ih+qviDRCBIW4ZS5qjD\
441 | hzRckpmYoUN347FezrovbCQzHPYfTP8yijIRNaZs4HkeMysMADru8bwLrpdiJ/EODzp61oGs\
442 | k7f2YHAx+ZEo823Lz+nGY3xEAlEXhiikiL+FbRm6NhWM7X5l9OqxLAUiI/I8i98ABul/UbJk\
443 | qL4A4/d12SvAABLShoper/cDgzXeX4eOUfRjvV9YpuhIi30+S9FSg/ebr+g8RU0SPLO3olcT\
444 | Pmeeot8ramzhGzRT9J/67OLESn1MaqCDpfsjNoL4gBaOdv6HyT+fCezIj7I6IA2U6kL23+YG\
445 | beDIE6m836TK1nHYkFAs9B1WM5Kglo2kr7OaIAlIMn0ybLPYoNIFSSY1Smw9RXYKXOjC9ikA\
446 | fBUeIX8dphrxJD1csE1Yt88RbG97tg/sW4ZRPM/6tcl3xokTSrVvI/TeOGoekHGjZWMpBjBY\
447 | 0V887KDNGnywc9zLO4zPwBj+Q9GOQjrjtRbeEYsclZoK6wlgM0GRk32EnoNKTK+SuZEWks/1\
448 | lH1MkWAAOHr6o4edQ975/1hs/8gMiX9QB57mBWCCS6l8rn1TRoX6hafl7U545+0sMBvUAtzC\
449 | oA2UVe9aBQxgRMVdrsIpwB4UuRj6KAIfx+K5rck6nlWALOFFtlnoHO2g2s5McuPYAgs/jlQv\
450 | tPiMyawO6ByXSqoSaQE1MnczYQA47kJF0nU97ByOmM622P4BrJdmEb+jyGswKY5lVcIFUMa8\
451 | CZm7TFcCJIH9EjJEGBK/yfjiX842kYplxcrt7Jd5uPjhANLf8uLPSRdZxd8octxJit0cvmNT\
452 | B4uf2LZwWsJ79s344l/BElasmoKlJAB49X1lUR8EUEByEnNbEJxG4MK4kGlpwbUI/vnawQdE\
453 | n+HM0irDk+B2imrnJcEoStfD0Rbms7oxM+b1MPxKHa2u5DkONWQuL84VPLdzDmUt+ecc1bId\
454 | Q4dB1ik6j6JTl1goFQ48UHjxr2KOBAvtSP75W08nzA4WFv8Yis6pEfKbc2GFwW5T3qU2Fn4e\
455 | 3LNvomQRk12pOoEF9VdFJ8S8/hcCz4T6CB+It0g/dB3vjdDyNZlyzKEZz89WLEkt5rUEg9/U\
456 | pA8pJgGA80wREv8n8m6EOP5pQoOJo6o1mHPO5nanCE4YnEdfJdTWSxSdoIytcB0cqwaz3iYF\
457 | xCwMSHD9j1S9CWLQt54xGeJUw7k/TNH+lJEQ7mIMAHXr3zFsF+IWjg+foOKJLVAHHVZWePJ9\
458 | zxJBYRnnzhRFX23N/+L6UnEHEK/gFz+UaY7Buz9O0RGgKa6k6Fgt7kTA4ruQ75PQj1fw94or\
459 | +lZ7zoG7WTKqhMVk5mGKuIl/ZuarFPEPvtDQ//glRWuV8D3G7x9UtLLgnhWKPlVUp+gTRQsM\
460 | nr+YYwTW0vT/Hi3gg/20gf/5cYK+4IMSPFcH09jffLkDv/bXDO9fqKiVpe+Qj20dxaeIUDEJ\
461 | AJFP/TX5CXwGcIRWLHFFJ96luzjibVAPkC/gxYT3QUc3ifPHB+1BZhmLrmJVxBRQjTZgETiO\
462 | wSppqGvurLmfoiH083Rnknibn3OyojsM2sFR56OWJSGcInzBPzejn9LbtaSffCzasMTXiH8G\
463 | mudJHq3yvmUtS4Sw1+Wyaa2WZ6Nbnf8Gw/m7bMScbqICfMLGqaRAJpLuJUTORjyIrq3Mq9ii\
464 | +0CCe5YYGkBHCujyTVkt2lzgG8Ax6IUY12HSdNQwduVizg9lW4+0HQHMa6s8OwqMm7rHwC+z\
465 | fl7NqtB8Zsivx9U7C6Gb2OPhMvrmEZTOERP6h3jxJL7mpqcf7wu894qY+mocxLVnzNRoOz+/\
466 | 4xCy49n3eIER9WLSTwayB/1vwtlCLMo4A6jlsVhDlwG01Xzwq2X+dnyKHwR9hGecq0QeUl5k\
467 | kJiGCbRzUEzRXGdRFcYewAnpGeHveX0RI92tmm01p8oOT7Mp+0CG7cN0GYAu5pR5xs4pfxAc\
468 | 65zh6FkmKcubMOeGOyqOrYYK7Qh7xrhuvEbb3YvMIbgiS/l4wKFlTJHf32/wnSt9i2lUHdgs\
469 | 7oSTwmplJmBLDz4IQlEHx9DxFuQZZnSA1OcDWOJAv1txe6vz/1vyIm+V9/da+rnhRxoHsv5b\
470 | Dp9rtNuMGdVnBRsBounqBN57aBlpZURMxlaIStGH8DbdsQoYwFJdBrBSkzFsXmKSrebJB9mA\
471 | uWIlp5wlhgxgAPmXSKJPjGvGaba9QwEDIF6csNYPNHzv5yowBx0GAJ+SmjIbwWdVIgFoGwGn\
472 | aj6wVNLE6R4ZVuKkm6oWETAfm7JeWEnc1rGA713i9/CAnGzwzlAjPi7z95c1221D5VNlfVwF\
473 | 4w3p6yVdBqDrVrt7mUk2zJMP07uBMoDc+FRSfXSkAJywFPMfwLGwyUnGGxX+PtFgs9qozN9G\
474 | Z3iMceyNzNUHU8zScsVEfVixdZJ+5gpmXF3kb0gw2d+DDxTHv+HbKmUAOIYdUuEauIAnDYbB\
475 | qRHcx0cV+dtLvFPrpNSOI8LCQNhZo+1ySUoRlfplBSYRBwtYnV7I/8/V/Msxx5wRc16e6v1D\
476 | nhq6jH/O1QbM1QME4GiXc53P1RDE3+DDkygBSpMSk+BYzU6fyYt9UZHBvJf+Nyeca8TJGju+\
477 | ShnAljGuQQZfnWpLB5dgAMC5rCYk9TKM48GJCX+gxvt2rfD3lwUYAKIB/+L7pGhUYhLooiOV\
478 | zh+IxAx/T7m/7WJOqobKAOo02y535ozThdsStvckxfOn+MZgnpoyn0oA4+uQRQbwcZ6ooYOL\
479 | KIrgKwTEmBN5NxgeU0eBR9xYnhCX8USDuDnS4jf5tEoZQPsYE3+CpgqEEO1yx2uXUHzj2icU\
480 | P4uPrr2qkkEUTm1zDb83jnofIM9LiTUpsVChK/7OoE0c0eB4qFhQwitMOCeHgxCcSTrl6Taz\
481 | eBKO4wlZ6PBRQ3qxCnGBZyNEea0qZAIIUpoRY/L/RqPtI6h0GDl0W7jh3lVGWoCO+yDvnHFL\
482 | fc21xACw8TxBydOJFQJxB0hcOyBPp/cKpVKC9SHzlNs4T0Vk1FThd4Z0oetvPyfmwv4/Td0y\
483 | HzACfc2TfzHTPLaPLOZ/5+f9/APfk7s2lx4NPy9hcde08g30+4crXHMM6WVbBrPvHEOyQ7ru\
484 | /szEW/Ei/pA3nUkJn7k56R3bIVqvUhamXvxeEviEpaAXyMxTtFCdbcfzuS3/m0uU05w34jks\
485 | 0Y6gnwyQFSUAAEY7HLNsaPCCm/COcLihXaEQJnEFcc+l3xJgAAuZWa0S6vdUAQbQNcY1w3k3\
486 | TpqUBEFkfVm6K4ePSe6sfaHmfXFSvo3hebuLwHvCAe0Z3gA+4Hk4gymnbi9hlbw5E94xlw6s\
487 | LS/2DqzKtaNkznrYYJCZ6/JCRlCqEUzaKylZGG2pSQFmgoIQV1CCOOUS2NFQLPsoAQMwBeIP\
488 | dqUYxRli4juBNuIwgFk8SXtrtH9MDAbgA1aPed01Qgwg3y7QN4X+gplcoGgvpv+qTuWCgR5h\
489 | KcBYzVB0CkVZhh/kD5DU5x1cbxBPLhOjSlxJ5N1SIlNCSBbunCvQRlyrtK4VvD+ZFRT1DRDZ\
490 | R1dRfyCRPhTHBpADDDY28ptB3x3FouAk3t1yRpKmLOKswztWL9YXTSMX61lHjevpBycW05rw\
491 | 6FMnMstRmAMq9poW7cRk3iHGdbhG96SlH9tQXKAd6VX+WZlgE9qVYvrVZwiY18Mohh7xFO8G\
492 | v7QgguXEEVf4NyVz85VgANgNYVG/SeD95wm0EfdkA7Yb3ZOQwx0yAF2DWhL9GS7JD5PeyYiv\
493 | OCHHAOLsqjgOnFkFnb4l4fVS8QsnC7WzwCEDgA1IN9hmP3JT9Qdo5ug5KLYxvYoYwG5xbAA5\
494 | wFJ5NMkdX6SBdzT02vEk4xb8C9KLrbCBJLaXlzSfAbF8C0f9cZVnAlmCjqfqSZ3eIQkDAF4j\
495 | uRx1roHd7AzNwRsi9A4SUoCECrB6gmuHG0x4VwxvucN5BIlwUJUwgBVJGQAAP/4LMthZZAF6\
496 | V/PeoULvAGNqbca+G4xrH2ne29nRO35Hen4WCw3m0gNVwAC+1mEAAJI+DiQ55xbbgOHvUoP7\
497 | 4XH4lcB7NCdzg2IaSVV0vUFdicpQS8do3DfGoF+IaH0s4wzgZV0GANxJ0anAPM87+Q7vvD8a\
498 | tvOo0PuYxi98IfAOSTP0vK35nHEOx/l+jXtMdnHMJ5SPvzmjix/vf5sJA8jpQ9uTTA58G8DE\
499 | Raaa+QJtPSL0TqYWa0RFmhZBTWrZf11jN4fvw3MOxxo1/0YluB7Heg8ZPhMS8O8pMgwuzhgD\
500 | gEfuBFMGkNuR4DCCIIflHnUQO0JfocWf6+e7Qu2YTrrBhuLyjRo6dlKHILzjXIfjjX4hbmN4\
501 | jGtxEnSQoAr7IEXeda9kZPFjbfw8Y5dQkcGeiobWp4tZivpbKqJ4jOG7LVHUTuA9agy+81ma\
502 | z9w7wTOeUdQ4pUKX+DYHK3qhoLjsfEXPKzrI8vPR/hv1fuIzRUfGLQ5qgp1YInDpDLKC7RJI\
503 | v2SrqgskpTrSP95CyaxrBFUJGGPPjPmN4XaNGPt7DZ55FkWuyI3LjAFSYF1K/viL5E5d5jt+\
504 | LlzXUY7tEIryL9gEJG846U3nfi7II6wF5LZ4j8qkwpdmADkgnxrO3o+iyskXdLGQRTC42X7l\
505 | YGDbs068ccL7UMbqbJK3jG/DeigCcIrVs8cEeJi/zxSB56FA53msXq3DCx3GPgTM3Edm1ZCr\
506 | FfhOiGBFOrbuTGvxmmhTglnP48U8jwnjOCOPcgt+poSqZYsB5IAdow9PUkycnobtLWFd71mK\
507 | 4hR+cDygbXlBHRtj98Wig9/EE5bfCX7t8LxbnycVAo8m8uKsD2swIE0GUAhwPqQB24R3UlAu\
508 | s0ltgUg5jUUY7O4fsigjFaZrCrw/gkP2Ya7emg1LeFfE0sOB6GnPjKMBAakzgICAAI/QKHyC\
509 | gIDAAAICAgIDCAgIaEj4fwEGAMmhp9jyY6E0AAAAAElFTkSuQmCC\
510 | "
511 |
512 | local toast_back_png =
513 | "iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAGWElEQVR4nO2dW4hVVRjHf6Oi\
514 | I5mmRopdVEzJjLTbQ0WQJUGQDyIUQgX2li9FEAQ99thLTxW9FUS9SBD01kNQCBk9VGqWo+Ml\
515 | nVHHMW8z5e30sM7kdjxz9nHW962z96z/Dw7jbX97yfqdb61vrX3paTQaiHyZ1u0GiO4iATJH\
516 | AmSOBMgcCZA5EiBzJEDmSIDMkQCZIwEyRwJkjgTIHAmQOTO63QArtm9b3e0mtGXzh793uwkt\
517 | 6anTdnAHndwLLAEWN38uAuYD84DbWnx6gVsLx99C6y/FZeBC89cXgZHmn51qfoaBk4XfnwIG\
518 | gCPAUeDSRA3uthiVFKCko28H7gNWASuB5cCy5meRc9MmQ4MgQ1/zsw/YA/xMkOMGUkpRCQEm\
519 | 6PBpwBrgEWAtsA54EFiQrmXuDBFE+AH4DthJyDD/4y1D1wRo0ekzgSeAp4AngceBuYmb1W1G\
520 | gR3At8DnhCEE8BMhqQAtOn0psBF4HlgPzE7WmOpzCfgIeBc4Dz4SuAvQotPvAV4GXiSkdtGe\
521 | PmATsAvsJXATYFzH9wKbga3AM0CPy0mnLmeB54AfwVYC83WAcR0/B3gdeItQmonJMRf4hpAx\
522 | W1YOk8U0AxQ6fxbwNvAmsNDsBOJLYAvYZQGzpeBC5y8EvgfeQ51vzUvA/ZYBrfcCZgBfAY8Z\
523 | xxWBHkJmNVv6NhGg0Jg3CHW88GMLYTXUBMsMMB14xzCeaM0sQhltgqUAazA0U7TlFbAZBiwF\
524 | uNswlmjPw8AKi0CWAiwzjCXKecEiiKUAJkaKjtkA8cOApQDLDWOJcp7GYCXXUoAqXowxlZkD\
525 | PBobxFIAVQDpeSg2gKUA8wxjic5YFxvAUoDcrt6pAtHXU1gK0GsYS3TGSoirBHRjSL1ZQLi8\
526 | fdJIgPoTtf4iAerPXTEHS4D6syTmYAlQf+6MOVgC1J+oy+4kQP2JWoCTAPVHZWDmKANkTmUy\
527 | wIXyfyIcqEwGuGwYS3TOnJiDNQRkjqUAfxvGEp0zK+ZgSwH+NYwlOidqG95SgFHDWCIRlgKc\
528 | NYwlEmEpwJBhLJEISwGGDWOJREiAzLEU4JRhLJEIZYDMkQD1ZyTmYFUB9WfCJ5F3gqUAJwxj\
529 | ic45E3OwpQDHDWOJzonahbWeA0SlIzEpzsUcbCJA4amVJy3iiZsiahPO+nqAv4zjiXKi9mCs\
530 | BThOeEWKSEf3h4ACA+hR8KmpVAYYMI4nyqlMGQiFd9yIZFQqAxw2jifKiVqClwD1J2oJXgLU\
531 | n0plgFHgtHFM0Z5qCFBYDTxoFVN0RDUEKHAILQalpHIC9KPFoJREXYrnIcABh5iiNaeBqzEB\
532 | PATY7xBTtGYI4t4h6CFAn0NM0Zroy/C8JoFRaUl0TPRVWB4CXASOOcQVNxJ9HaapAIWx6E9U\
533 | CqagkkMAhHmASkF/orffvQTY6xRXXE/0NZheAuxziiuup7IC2LzcXpRRySoAwoaQHhvnT2Uz\
534 | wBW0IORNgypWAYVScC8qBT0ZwmDBzfNBkXtRKehJ9D4A+AqwyzG2MLob21OA3Y6xhdHd2N5D\
535 | gDaF/DB5IIenAP8QdgaFD9XNAIWJya+oEvDC5FZ878fF70aVgBfVzQAF9jjHzxmTG3G9BdCe\
536 | gB+1yADaFfSjFhngHHqZlAdnMXo/g5sAhUpAzw2y5yDELwNDmpdGHUSloDW/WAVKIcARVApa\
537 | s8MqUAoBtBpoyxXga6tgKQT4LcE5cuIDDO+7mGEVqA0/JTjHVOcKsBP4BPgUbCaAkEaAY8Bn\
538 | wKsJzlUnThAWc04S7vEfJjzybYTwEs4Rwo7fIeAP4LxHI3oaDd8J+vZtqwFmAh8DWwkVwVSf\
539 | FF4mTH4PEKqgsZ/9wFFgkHAL3U1h9a0vkiIDQPjPvgZ8AbwPrE10Xk/OEVY6+wqffkJHHyGk\
540 | 7bZ4dOjN4p4BxmhmAgjf/g3ANmAjMD1JAybHIOHbe4Brnbyf0PGlT+aoQgeXkUwAuE6CMRYA\
541 | m4D1wLPA4nF/7zlcXCXMT/oJ4+whwrd37HOYkjRdhw4uI6kARVrIALACeAC4F1gFLAUWAncA\
542 | 84HZTDxsnSeMvWe4NqkaJkyyBpo/BwmTr0HCEnXpCy6mQie3o2sCjGcCIdyZ6h1cRmUEEN0h\
543 | xUqgqDASIHMkQOZIgMyRAJkjATJHAmSOBMgcCZA5EiBzJEDmSIDM+Q8AomrrsCk4HAAAAABJ\
544 | RU5ErkJggg==\
545 | "
546 | local toast_eyes_closed_png =
547 | "iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAB3UlEQVR4nO3cMU4VURhA4X/A\
548 | 2oIFyILA1sZlsABwAWyEVihYjpaWtiY8mqfEECnnmpzvSyaZ5Db/zD2ZyTSzHQ6Hoetk9QCs\
549 | JYA4AcQJIE4AcQKIE0CcAOIEECeAOAHECSBOAHECiBNAnADiBBAngDgBxAkgTgBxAogTQJwA\
550 | 4gQQJ4A4AcQJIE4AcQKIE0CcAOIEECeAOAHECSBOAHECiHu3eoCSm8vzt9fvv+00yYvdA9i2\
551 | 7c/59cWHv9a+PHzfe5xdHa/348xczczPmXk/M79m5sfMfF4x064B3Fyev9r0mTmdmcPMPO05\
552 | y0Jfj8d/YcUr4G5mzo7nvzf9dmYeF8yyq7eecKv+2LoigE//WvDb2v1tbnqbz8A4AcQJIE4A\
553 | cQKIE0CcAOIEECeAOAHECSBOAHECiBNAnADiBBAngDgBxAkgTgBxAogTQJwA4gQQJ4A4AcQJ\
554 | IE4AcQKIE0CcAOIEECeAOAHECSBOAHECiBNAnADiBBAngDgBxAkgTgBxAogTQJwA4gQQJ4A4\
555 | AcQJIE4AcQKIE0CcAOIEECeAOAHECSBOAHECiBNAnADiBBAngDgBxAkgTgBxAogTQJwA4gQQ\
556 | J4A4AcQJIE4AcQKIE0CcAOIEECeAOAHECSDuGds2Io/iw+DXAAAAAElFTkSuQmCC\
557 | "
558 | local toast_eyes_open_png =
559 | "iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAACHUlEQVR4nO3cMU5UYRhG4TPG\
560 | DegGVCq3YFgACSHU9nYuAkJs6AyFBS0FHTRQuBCtjImbIHQMBSROCMZuPsI5TzU3U8xbnPvP\
561 | zRSzWC6XxOvF9IDMKgC5ApArALkCkCsAuQKQKwC5ApArALkCkCsAuQKQKwC5ApArALkCkCsA\
562 | uQKQKwC5ApArALkCkCsAuQKQKwC5ApArALkCkCsAuQKQKwC5ApArALkCkCsAuZfTA0z2d97+\
563 | 872Dyz9r27GqE2D9XgHfgRvgN/BhcsxaT4CneAcM2AO27l9vAKfAu6kxEyfAa+ArcAF8BhYD\
564 | GyZtPLh+M7Li3sQzwCl/74Ad7gL4NrBjyhmwu3J9PjUEZgLYeuTaFMAJcAVsA7+Ao8kxE18B\
565 | P/9z/WytPOecAZ+AQ+B6ag/MnAAfgWPgPXAJfBnYMOapPexOBPAD2Bz43Dxi0b+Fu/VDkFwB\
566 | yBWAXAHIFYBcAcgVgFwByBWAXAHIFYBcAcgVgFwByBWAXAHIFYBcAcgVgFwByBWAXAHIFYBc\
567 | AcgVgFwByBWAXAHIFYBcAcgVgFwByBWAXAHIFYBcAcgVgFwByBWAXAHIFYBcAcgVgFwByBWA\
568 | XAHIFYBcAcgVgFwByBWAXAHIFYBcAcgVgFwByBWAXAHIFYBcAcgVgFwByBWAXAHIFYBcAcgV\
569 | gFwByBWAXAHIFYBcAcgVgFwByBWAXAHIFYBcAcgVgFwByBWA3C2ytyc6Pw+oOAAAAABJRU5E\
570 | rkJggg==\
571 | "
572 | local toast_front_png =
573 | "iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAFy0lEQVR4nO2dzW8VVRiHnwJW\
574 | EZRWo2IBgwooC/xAF1ajCenCoDEmJkYjIa4krtzoxqUbNu5cuTJB/wkTdWvcoAtF1JZWpKVW\
575 | kbZCW2vVujj3Ovfaj0vb9507576/J5ncljS/M+E893zNzJmuxcVFRFw2tfsERHuRAMGRAMGR\
576 | AMGRAMGRAMGRAMGRAMGRAMGRAMGRAMGRAMGRAMGRAMGRAMGRAMGRAMGRAMHZ0u4TMGHkVLvP\
577 | YGXufrXdZ7AqXdncE9i6kjcBtwN9wE5gN3Ab0Fs7ehp+3gZ0A9tJX4IdLbKvAP8AM8ACMA1M\
578 | NRyTwAQwXjvGgIvA5RUTKyJGNQVYubJvAA4C+4F9wL3APbXPPmBzGae3BqaA74FBYAg4A3wJ\
579 | DC/7122QohoCLF/htwKPAYeBQ7VjP9Wr5PUwCZwGPgc+Bb4gtSyJEkVojwDLV/heYAB4AugH\
580 | 7i/xjNrNDPAZ8F7tM1GCCOUK0Fzx3cBTwDPAUWJV+Gp8BLwOzALuEvjPApZ+2/uB48BLwC3u\
581 | 5efHceAA8DRpsOmKXwvQXPE7gNeAE6R+XLTmQyB9/R1bAZ+FoKLye4CTwCjwLqr8tXCMNNNx\
582 | xV6AovL7gbPA26T5tlgbm4G3ANeFLq+l4IdI05udTvlROAbc7FmArQCFqR8AN5pmx2Q78IJn\
583 | AR4twF7gYYfcqLwMuHUDHgIccciMzBHgJq9wDwEGHDIj043j/6mdAEUT9ahZpqjzpFewdQvQ\
584 | TQlz14A8DriMA6wFOEhnXK2rGo+QLoWb4yGAsOc6kgTmWAtwl3GeKHjQI9RaAK38+eFyudxa\
585 | gF3GeaLApXu1FuBO4zxRcB9gPhOwFsD1wkVw9gBbrUOtBWh1e7XYGLutA60F0HV/X/qsAyVA\
586 | XlReAJfVKvEfe6wD9XBoXtxhHSgB8qLHOlAC5IUECE6vdaAEyIvKtwAVeNS4o6m8AL8b54lm\
587 | zFtsdQF5Yb7Ubi3AjHGeaKbLOtBagKvGeaIZ8+cDrAW4Ypwnmqn8GEAtQGZYCzBlnCec0TQw\
588 | ONYC/GqcJ5yxFmDlnTFFJbEW4DfjPOGMWoDgWAswbpwnmllo/SdrQwLkxax1oLUAY8Z5whlr\
589 | AeYoYXvTwMxbB9oJUGxnqm7AjznrQI/7AS44ZIqE+bUWDwF+dsgUib+sAz0EuOiQKRLmF9sk\
590 | QF78aR3oIYCmgn6YX231EGDEIVMkJEBwshBgEi0GeVHxQWCxGLT8ixHFRsmiBQB1A15IgOBk\
591 | I4C6AB+yEUAtgA/ZCKAWwIesBPjbKTsyk9aBXgIsoG7AA/Obbu0FKNYCvjPPjs08mdwQUucH\
592 | x+yIpObf+EXSngKcdcyOiMszF54CDDpmR8R8AAi+AmgMYEt2Akygq4KWZCSAZgIeuDx4671N\
593 | 3LfO+ZHIqAUo+MY5PxLZzQIAvnbOj0SWLYC6ADuyFGAMzQSsyKwLKGYCZ9zKiEWWLQBoKmhF\
594 | tgKcL6GMCGTWBRTocfGNs4jDk8FQjgCjJZTR6aRbwYwvBUM5AvxUQhmdjvnuYHXKEODHEsro\
595 | dIa8gssQYB7tGbBRPvYK9hWg6LO+ci2nM1kATgNvAie9CtniFfw/PgGeLamsKjNN2kXtAvAL\
596 | cIk0vZskvW3lau3fJkjNvlvfX6csAd4HBoDnSiqvXcyTnokYJt0W3/g5zHp2+XIY+TdSlgDz\
597 | wPPACeAdHN6CXSJ/kL6dg6Q7nwdrvw9xLdvjOFfoWulaXCzhZZ8jpxp/2wq8ArwBPOBf+LqY\
598 | Bs6RvrXnake9kkdZ7Q2pFavgVpQjQJ1mEQAOAS8CR4HDlPciyznS+sR5Un88QlHRw1zL7VeZ\
599 | VfRKlCtAI0tl2EaS4ACwr3bsIr0vt7d2XL9M0ixpsDRNqtj6oOoyxYBqnLSB5ThpSnqp5fl1\
600 | SAW3on0CNLJUBn+CVHArqiGAaBt6eXRwJEBwJEBwJEBwJEBwJEBwJEBwJEBwJEBwJEBwJEBw\
601 | JEBwJEBw/gUUgiKM2bt1ygAAAABJRU5ErkJggg==\
602 | "
603 | local toast_mouth_png =
604 | "iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAACO0lEQVR4nO3cP2oVURhA8RNJ\
605 | o+ICAiJWVoqFm7BIkTaF4DYsRNyKYGUrpBBSZweChYSkTBqx8w/PIgmYKnmPebzhnvMrh7nw\
606 | DXNmGG4xW4vFgnjd2fQA2awCkCsAuQKQKwC5ApArALkCkCsAuQKQKwC5ApArALkCkCsAuQKQ\
607 | KwC5ApArALkCkCsAuQKQKwC5ApArALkCkCsAuQKQKwC5ApArALkCkCsAuQKQKwC5ApArALkC\
608 | kCsAuQKQKwC57U0PsC7vdh+vvPb95+PJ5pi7rRF/FfvfzX8APAEeAveBu5fHriyAH8AZcAJ8\
609 | Bf6AJ4Jh3wDAPvCB5a7xBHgBnK9lohka+RvgDcsH/gh4Pf0o8zVyAM9WXPd00ilmbuQAfq64\
610 | 7tekU8zcyAEcrLjucNIpZm7kAN6y/FvgCPi0hllma+QAvgEvgdNbnv8F2AX+rm2iGRpyHwCu\
611 | 7QXcA14Be8BzYOfy+G/gOxdP/UcuAgA8ewAwcACw/G6g6cZfGTqA3Gzkb4DcQgHIFYBcAcgV\
612 | gFwByBWAXAHIFYBcAcgVgFwByBWAXAHIFYBcAcgVgFwByBWAXAHIFYBcAcgVgFwByBWAXAHI\
613 | FYBcAcgVgFwByBWAXAHIFYBcAcgVgFwByBWAXAHIFYBcAcgVgFwByBWAXAHIFYBcAcgVgFwB\
614 | yBWAXAHIFYBcAcgVgFwByBWAXAHIFYBcAcgVgFwByBWAXAHIFYBcAcgVgFwByBWAXAHIFYBc\
615 | AcgVgFwByBWAXAHI/QPwhzWTvXo9agAAAABJRU5ErkJggg==\
616 | "
617 |
618 | local class = require_30log()
619 |
620 | local Toast = class("Toast")
621 |
622 | function Toast:init()
623 | self:center()
624 |
625 | self.eyes = {}
626 | self.eyes.closed_t = 0
627 | self.eyes.blink_t = 2
628 |
629 | self.look = {}
630 | self.look.target = { x = 0.2, y = 0.2 }
631 | self.look.current = { x = 0.2, y = 0.2 }
632 | self.look.DURATION = 0.5
633 | self.look.POINTS = {
634 | { x = 0.8, y = 0.8 },
635 | { x = 0.1, y = 0.1 },
636 | { x = 0.8, y = 0.1 },
637 | { x = 0.1, y = 0.8 },
638 | }
639 | self.look.point = 0
640 | self.look.point_t = 1
641 | self.look.t = 0
642 | end
643 |
644 | local function easeOut(t, b, c, d)
645 | t = t / d - 1
646 | return c * (math.pow(t, 3) + 1) + b
647 | end
648 |
649 | function Toast:center()
650 | local ww, wh = love.window.fromPixels(love.graphics.getDimensions())
651 | self.x = math.floor(ww / 2 / 32) * 32 + 16
652 | self.y = math.floor(wh / 2 / 32) * 32 + 16
653 | end
654 |
655 | function Toast:get_look_coordinates()
656 | local t = self.look.t
657 |
658 | local src = self.look.current
659 | local dst = self.look.target
660 |
661 | local look_x = easeOut(t, src.x, dst.x - src.x, self.look.DURATION)
662 | local look_y = easeOut(t, src.y, dst.y - src.y, self.look.DURATION)
663 |
664 | return look_x, look_y
665 | end
666 |
667 | function Toast:update(dt)
668 | self.look.t = math.min(self.look.t + dt, self.look.DURATION)
669 | self.eyes.closed_t = math.max(self.eyes.closed_t - dt, 0)
670 | self.eyes.blink_t = math.max(self.eyes.blink_t - dt, 0)
671 | self.look.point_t = math.max(self.look.point_t - dt, 0)
672 |
673 | if self.eyes.blink_t == 0 then
674 | self:blink()
675 | end
676 |
677 | if self.look.point_t == 0 then
678 | self:look_at_next_point()
679 | end
680 |
681 | local look_x, look_y = self:get_look_coordinates()
682 |
683 | self.offset_x = look_x * 4
684 | self.offset_y = (1 - look_y) * -4
685 | end
686 |
687 | function Toast:draw()
688 | local x = self.x
689 | local y = self.y
690 |
691 | local look_x, look_y = self:get_look_coordinates()
692 |
693 | love.graphics.draw(g_images.toast.back, x, y, self.r, 1, 1, 64, 64)
694 | love.graphics.draw(g_images.toast.front, x + self.offset_x, y + self.offset_y, self.r, 1, 1, 64, 64)
695 | love.graphics.draw(self:get_eyes_image(), x + self.offset_x * 2.5, y + self.offset_y * 2.5, self.r, 1, 1, 64, 64)
696 | love.graphics.draw(g_images.toast.mouth, x + self.offset_x * 2, y + self.offset_y * 2, self.r, 1, 1, 64, 64)
697 | end
698 |
699 | function Toast:get_eyes_image()
700 | if self.eyes.closed_t > 0 then
701 | return g_images.toast.eyes.closed
702 | end
703 | return g_images.toast.eyes.open
704 | end
705 |
706 | function Toast:blink()
707 | if self.eyes.closed_t > 0 then
708 | return
709 | end
710 | self.eyes.closed_t = 0.1
711 | self.eyes.blink_t = self.next_blink()
712 | end
713 |
714 | function Toast:next_blink()
715 | return 5 + love.math.random(0, 3)
716 | end
717 |
718 | function Toast:look_at(tx, ty)
719 | local look_x, look_y = self:get_look_coordinates()
720 | self.look.current.x = look_x
721 | self.look.current.y = look_y
722 |
723 | self.look.t = 0
724 | self.look.point_t = 3 + love.math.random(0, 1)
725 |
726 | self.look.target.x = tx
727 | self.look.target.y = ty
728 | end
729 |
730 | function Toast:look_at_next_point()
731 | self.look.point = self.look.point + 1
732 | if self.look.point > #self.look.POINTS then
733 | self.look.point = 1
734 | end
735 | local point = self.look.POINTS[self.look.point]
736 | self:look_at(point.x, point.y)
737 | end
738 |
739 | local Mosaic = class("Mosaic")
740 |
741 | function Mosaic:init()
742 | local mosaic_image = g_images.mosaic[1]
743 |
744 | local sw, sh = mosaic_image:getDimensions()
745 | local ww, wh = love.window.fromPixels(love.graphics.getDimensions())
746 |
747 | if love.window.getPixelScale() > 1 then
748 | mosaic_image = g_images.mosaic[2]
749 | end
750 |
751 | local SIZE_X = math.floor(ww / 32 + 2)
752 | local SIZE_Y = math.floor(wh / 32 + 2)
753 | local SIZE = SIZE_X * SIZE_Y
754 |
755 | self.batch = love.graphics.newSpriteBatch(mosaic_image, SIZE, "stream")
756 | self.pieces = {}
757 | self.color_t = 1
758 | self.generation = 1
759 |
760 | local COLORS = {}
761 |
762 | local xcolors = {
763 | { 240, 240, 240 }, -- WHITE (ish)
764 | { 232, 104, 162}, -- PINK
765 | { 69, 155, 168 }, -- BLUE
766 | { 67, 93, 119 }, -- DARK BLUE
767 | }
768 |
769 | for _,color in pairs(xcolors) do
770 | table.insert(COLORS, color)
771 | table.insert(COLORS, color)
772 | end
773 |
774 | -- Insert only once. This way it appears half as often.
775 | table.insert(COLORS, { 220, 239, 113 }) -- LIME
776 |
777 | -- When using the higher-res mosaic sprite sheet, we want to draw its
778 | -- sprites at the same scale as the regular-resolution one, because
779 | -- we'll globally love.graphics.scale *everything* by the screen's
780 | -- pixel density ratio.
781 | -- We can avoid a lot of Quad scaling by taking advantage of the fact
782 | -- that Quads use normalized texture coordinates internally - if we use
783 | -- the 'source image size' and quad size of the @1x image for the Quads
784 | -- even when rendering them using the @2x image, it will automatically
785 | -- scale as expected.
786 | local QUADS = {
787 | love.graphics.newQuad(0, 0, 32, 32, sw, sh),
788 | love.graphics.newQuad(0, 32, 32, 32, sw, sh),
789 | love.graphics.newQuad(32, 32, 32, 32, sw, sh),
790 | love.graphics.newQuad(32, 0, 32, 32, sw, sh),
791 | }
792 |
793 | local exclude_left = math.floor(ww / 2 / 32)
794 | local exclude_right = exclude_left + 3
795 | local exclude_top = math.floor(wh / 2 / 32)
796 | local exclude_bottom = exclude_top + 3
797 | local exclude_width = exclude_right - exclude_left + 1
798 | local exclude_height = exclude_bottom - exclude_top + 1
799 | local exclude_area = exclude_width * exclude_height
800 |
801 | local exclude_center_x = exclude_left + 1.5
802 | local exclude_center_y = exclude_top + 1.5
803 |
804 | self.generators = {
805 | function(piece, generation)
806 | return COLORS[love.math.random(1, #COLORS)]
807 | end,
808 | function(piece, generation)
809 | return COLORS[1 + (generation + piece.grid_x - piece.grid_y) % #COLORS]
810 | end,
811 | function(piece, generation)
812 | return COLORS[1 + (piece.grid_x + generation) % #COLORS]
813 | end,
814 | function(piece, generation)
815 | local len = generation + math.sqrt(piece.grid_x ^ 2 + piece.grid_y ^ 2)
816 | return COLORS[1 + math.floor(len) % #COLORS]
817 | end,
818 | function(piece, generation)
819 | local dx = piece.grid_x - exclude_center_x
820 | local dy = piece.grid_y - exclude_center_y
821 | local len = generation - math.sqrt(dx ^ 2 + dy ^ 2)
822 | return COLORS[1 + math.floor(len) % #COLORS]
823 | end,
824 | function(piece, generation)
825 | local dx = math.abs(piece.grid_x - exclude_center_x) - generation
826 | local dy = math.abs(piece.grid_y - exclude_center_y) - generation
827 | return COLORS[1 + math.floor(math.max(dx, dy)) % #COLORS]
828 | end,
829 | }
830 |
831 | self.generator = self.generators[1]
832 |
833 | local EXCLUDE = {}
834 | for y = exclude_top,exclude_bottom do
835 | EXCLUDE[y] = {}
836 | for x = exclude_left,exclude_right do
837 | EXCLUDE[y][x] = true
838 | end
839 | end
840 |
841 | for y = 1,SIZE_Y do
842 | for x = 1,SIZE_X do
843 | if not EXCLUDE[y] or not EXCLUDE[y][x] then
844 | local piece = {
845 | grid_x = x,
846 | grid_y = y,
847 | x = (x - 1) * 32,
848 | y = (y - 1) * 32,
849 | r = love.math.random(0, 100) / 100 * math.pi,
850 | rv = 1,
851 | color = {},
852 | quad = QUADS[(x + y) % 4 + 1]
853 | }
854 |
855 | piece.color.prev = self.generator(piece, self.generation)
856 | piece.color.next = piece.color.prev
857 | table.insert(self.pieces, piece)
858 | end
859 | end
860 | end
861 |
862 | local GLYPHS = {
863 | N = love.graphics.newQuad(0, 64, 32, 32, sw, sh),
864 | O = love.graphics.newQuad(32, 64, 32, 32, sw, sh),
865 | G = love.graphics.newQuad(0, 96, 32, 32, sw, sh),
866 | A = love.graphics.newQuad(32, 96, 32, 32, sw, sh),
867 | M = love.graphics.newQuad(64, 96, 32, 32, sw, sh),
868 | E = love.graphics.newQuad(96, 96, 32, 32, sw, sh),
869 |
870 | U = love.graphics.newQuad(64, 0, 32, 32, sw, sh),
871 | P = love.graphics.newQuad(96, 0, 32, 32, sw, sh),
872 | o = love.graphics.newQuad(64, 32, 32, 32, sw, sh),
873 | S = love.graphics.newQuad(96, 32, 32, 32, sw, sh),
874 | R = love.graphics.newQuad(64, 64, 32, 32, sw, sh),
875 | T = love.graphics.newQuad(96, 64, 32, 32, sw, sh),
876 | }
877 |
878 | local INITIAL_TEXT_COLOR = { 240, 240, 240 }
879 |
880 | local put_text = function(str, offset, x, y)
881 | local idx = offset + SIZE_X * y + x
882 | for i = 1, #str do
883 | local c = str:sub(i, i)
884 | if c ~= " " then
885 | local piece = self.pieces[idx + i]
886 | if piece then
887 | piece.quad = GLYPHS[c]
888 | piece.r = 0
889 | piece.rv = 0
890 | piece.color.prev = INITIAL_TEXT_COLOR
891 | piece.color.next = INITIAL_TEXT_COLOR
892 | end
893 | end
894 | end
895 | end
896 |
897 | local text_center_x = math.floor(ww / 2 / 32)
898 |
899 | local no_game_text_offset = SIZE_X * exclude_bottom - exclude_area
900 | put_text("No TOAT", no_game_text_offset, text_center_x - 2, 1)
901 |
902 | put_text("ERTOA SUPUR", 0, text_center_x - 4, exclude_top - 3)
903 | end
904 |
905 | function Mosaic:addGeneration()
906 | self.generation = self.generation + 1
907 | if self.generation % 5 == 0 then
908 | if love.math.random(0, 100) < 60 then
909 | self.generator = self.generators[love.math.random(2, #self.generators)]
910 | else
911 | self.generator = self.generators[1]
912 | end
913 | end
914 | end
915 |
916 | function Mosaic:update(dt)
917 | self.color_t = math.max(self.color_t - dt, 0)
918 | local change_color = self.color_t == 0
919 | if change_color then
920 | self.color_t = 1
921 | self:addGeneration()
922 | end
923 | local gen = self.generator
924 | for idx,piece in ipairs(self.pieces) do
925 | piece.r = piece.r + piece.rv * dt
926 | if change_color then
927 | piece.color.prev = piece.color.next
928 | piece.color.next = gen(piece, self.generation)
929 | end
930 | end
931 | end
932 |
933 | function Mosaic:draw()
934 | self.batch:clear()
935 | love.graphics.setColor(255, 255, 255, 64)
936 | for idx,piece in ipairs(self.pieces) do
937 | local ct = 1 - self.color_t
938 | local c0 = piece.color.prev
939 | local c1 = piece.color.next
940 | local r = easeOut(ct, c0[1], c1[1] - c0[1], 1)
941 | local g = easeOut(ct, c0[2], c1[2] - c0[2], 1)
942 | local b = easeOut(ct, c0[3], c1[3] - c0[3], 1)
943 |
944 | self.batch:setColor(r, g, b)
945 | self.batch:add(piece.quad, piece.x, piece.y, piece.r, 1, 1, 16, 16)
946 | end
947 | love.graphics.setColor(255, 255, 255)
948 | love.graphics.draw(self.batch, 0, 0)
949 | end
950 |
951 | function love.load()
952 | love.graphics.setBackgroundColor(136, 193, 206)
953 |
954 | local function load_image(file, name)
955 | return love.graphics.newImage(love.filesystem.newFileData(file, name:gsub("_", "."), "base64"))
956 | end
957 |
958 | g_images = {}
959 | g_images.toast = {}
960 | g_images.toast.back = load_image(toast_back_png, "toast_back.png")
961 | g_images.toast.front = load_image(toast_front_png, "toast_front.png")
962 | g_images.toast.eyes = {}
963 | g_images.toast.eyes.open = load_image(toast_eyes_open_png, "toast_eyes_open.png")
964 | g_images.toast.eyes.closed = load_image(toast_eyes_closed_png, "toast_eyes_closed.png")
965 | g_images.toast.mouth = load_image(toast_mouth_png, "toast_mouth.png")
966 |
967 | g_images.mosaic = {}
968 | g_images.mosaic[1] = load_image(mosaic_png, "mosaic.png")
969 | g_images.mosaic[2] = load_image(mosaic_2x_png, "mosaic@2x.png")
970 |
971 | g_entities = {}
972 | g_entities.toast = Toast()
973 | g_entities.mosaic = Mosaic()
974 | end
975 |
976 | function love.update(dt)
977 | dt = math.min(dt, 1/10)
978 | g_entities.toast:update(dt)
979 | g_entities.mosaic:update(dt)
980 | if love.keyboard.isDown("z") then
981 | g_entities.mosaic = Mosaic()
982 | g_entities.toast:center()
983 | end
984 | end
985 |
986 | function love.draw()
987 | love.graphics.setColor(255, 255, 255)
988 | love.graphics.push()
989 | love.graphics.scale(love.window.getPixelScale())
990 | g_entities.mosaic:draw()
991 | g_entities.toast:draw()
992 | love.graphics.pop()
993 | end
994 |
995 | function love.resize(w, h)
996 | g_entities.mosaic = Mosaic()
997 | g_entities.toast:center()
998 | end
999 |
1000 | function love.keypressed(key)
1001 | if key == "escape" then
1002 | love.event.quit()
1003 | end
1004 | end
1005 |
1006 | function love.keyreleased(key)
1007 | if key == "f" then
1008 | local is_fs = love.window.getFullscreen()
1009 | love.window.setFullscreen(not is_fs)
1010 | end
1011 | end
1012 |
1013 | function love.mousepressed(x, y, b)
1014 | local tx = x / love.graphics.getWidth()
1015 | local ty = y / love.graphics.getHeight()
1016 | g_entities.toast:look_at(tx, ty)
1017 | end
1018 |
1019 | function love.mousemoved(x, y)
1020 | if love and love.mouse.isDown(1) then
1021 | local tx = x / love.graphics.getWidth()
1022 | local ty = y / love.graphics.getHeight()
1023 | g_entities.toast:look_at(tx, ty)
1024 | end
1025 | end
1026 |
1027 | local last_touch = {time=0, x=0, y=0}
1028 |
1029 | function love.touchpressed(id, x, y, pressure)
1030 | -- Double-tap the screen (when using a touch screen) to exit.
1031 | if #love.touch.getTouches() == 1 then
1032 | local dist = math.sqrt((x-last_touch.x)^2 + (y-last_touch.y)^2)
1033 | local difftime = love.timer.getTime() - last_touch.time
1034 | if difftime < 0.3 and dist < 50 then
1035 | if love.window.showMessageBox("Exit No-Game Screen", "", {"OK", "Cancel"}) == 1 then
1036 | love.event.quit()
1037 | end
1038 | end
1039 |
1040 | last_touch.time = love.timer.getTime()
1041 | last_touch.x = x
1042 | last_touch.y = y
1043 | end
1044 | end
--------------------------------------------------------------------------------
/project/pong/main.lua:
--------------------------------------------------------------------------------
1 | local editor = sandsmas.editor
2 | local imgui = sandsmas.imgui
3 |
4 | local class = require('libs.middleclass.middleclass')
5 | objects = { Pad = {}, Ball = {} }
6 | local scale = function(valueIn, baseMin, baseMax, limitMin, limitMax)
7 | return ((limitMax - limitMin) * (valueIn - baseMin) / (baseMax - baseMin)) + limitMin
8 | end
9 |
10 | --[[
11 | Pad
12 | ]]
13 |
14 | local Pad = class('Pad')
15 | local Ball = class('Ball')
16 | local Transform = class('Transform')
17 |
18 | function Transform:initialize(x, y, sx, sy, rx, ry)
19 | self.x = x or 0
20 | self.y = y or 0
21 | self.sx = sx or 1
22 | self.sy = sy or 1
23 | self.rx = rx or 0
24 | self.ry = ry or 0
25 | end
26 |
27 | function Transform:setPosition(x, y)
28 | self.x = x or self.x
29 | self.y = y or self.y
30 | end
31 |
32 | function Transform:getPosition()
33 | return self.x, self.y
34 | end
35 |
36 | function Transform:setScale(sx, sy)
37 | self.sx = sx or self.sx
38 | self.sy = sy or self.sy
39 | end
40 |
41 | function Transform:getScale()
42 | return self.sx, self.sy
43 | end
44 |
45 | function Transform:setRotation(rx, ry)
46 | self.rx = rx or self.rx
47 | self.ry = ry or self.ry
48 | end
49 |
50 | function Transform:getRotation()
51 | return self.rx, self.ry
52 | end
53 |
54 | Pad.static.score_to_win = 6
55 | Pad.static.color = {1.0,1.0,1.0,1.0}
56 |
57 | function Pad:initialize(upKey, downKey)
58 | self.upKey = upKey or "w"
59 | self.downKey = downKey or "s"
60 |
61 | self.honk = true
62 |
63 | self.playerNo = #objects['Pad'] + 1
64 |
65 | self.width = 60
66 |
67 | self.moveSpeed = 7
68 |
69 | self.extent = 250
70 |
71 | self.score = 0
72 |
73 | self.color = Pad.color
74 |
75 | self.sign = -1
76 | if self.playerNo > 1 then
77 | self.sign = self.sign * -1
78 | end
79 |
80 | self.transform = Transform:new(0, 0, 20, 100)
81 |
82 | table.insert(objects['Pad'], self)
83 | end
84 |
85 | function Pad:update(dt)
86 | local moveValue
87 | if love.keyboard.isDown(self.upKey) and love.keyboard.isDown(self.downKey) then
88 | -- what do you expect?
89 | elseif love.keyboard.isDown(self.upKey) then
90 | self.transform:setPosition(nil, self.transform.y - self.moveSpeed)
91 | elseif love.keyboard.isDown(self.downKey) then
92 | self.transform:setPosition(nil, self.transform.y + self.moveSpeed)
93 | end
94 |
95 | if self.transform.y < -self.extent then
96 | self.transform:setPosition(nil, -self.extent)
97 | end
98 | if self.transform.y > self.extent then
99 | self.transform:setPosition(nil, self.extent)
100 | end
101 |
102 | if self.score > Pad.score_to_win and self.color == Pad.color then
103 | self.color = {1.0,0.0,0.0,1.0}
104 | end
105 | end
106 |
107 | function Pad:draw()
108 | if self.color ~= Pad.color then
109 | love.graphics.setColor(self.color[1]*255, self.color[2]*255, self.color[3]*255, self.color[4]*255)
110 | end
111 | love.graphics.rectangle("fill", (Ball.outer_limit_x*self.sign)-10, self.transform.y - 50, 20, 100)
112 | end
113 |
114 | --[[
115 | Ball
116 | ]]
117 |
118 | Ball.static.inner_limit_x = 325
119 | Ball.static.outer_limit_x = 350
120 |
121 | function Ball:initialize()
122 | self.x = 0
123 | self.y = 0
124 | self.speed = 3
125 | self.vx = -self.speed
126 | self.vy = self.speed
127 |
128 | self.limit_y = 285
129 | self.inner_limit_x = 325
130 | self.outer_limit_x = 350
131 | self.goal_limit = 415
132 |
133 | table.insert(objects['Ball'], self)
134 | end
135 |
136 | function Ball:update(dt)
137 | self.x = self.x + self.vx
138 | self.y = self.y + self.vy
139 |
140 | if math.abs(self.y) >= self.limit_y then
141 | self.vy = -self.vy
142 | end
143 |
144 | self:check_goal(objects['Pad'][1])
145 | self:check_goal(objects['Pad'][2])
146 | end
147 |
148 | function Ball:check_goal(pad)
149 | local otherPlayerNo = (3-pad.playerNo)
150 |
151 | local boundaryCheck = false
152 | if pad.playerNo == 1 then
153 | boundaryCheck = self.x < self.inner_limit_x*pad.sign and self.x > self.outer_limit_x*pad.sign
154 | elseif pad.playerNo == 2 then
155 | boundaryCheck = self.x > self.inner_limit_x*pad.sign and self.x < self.outer_limit_x*pad.sign
156 | end
157 |
158 | if boundaryCheck and math.abs(pad.transform.y - self.y) < pad.width then
159 | self.speed = self.speed + scale(pad.playerNo, 1, 2, 0.2, 0.5)
160 | self.x = self.inner_limit_x*pad.sign
161 | self.vx = self.speed*pad.sign*-1
162 | self.vy = self.vy * scale(pad.playerNo, 1, 2, 0.5, 0.8) + math.random(-10, 10) / (otherPlayerNo*10) * self.speed
163 | end
164 |
165 | if self.x > self.goal_limit or self.x < -self.goal_limit then
166 | local scoringPlayer = 1
167 | local logStatus = 'yay'
168 | local logBanter = 'Good for you!'
169 | if self.x < -self.goal_limit then
170 | scoringPlayer = 2
171 | logStatus = 'nay'
172 | logBanter = 'Idiot.'
173 | end
174 | self.x = 0
175 | self.vx = -self.vx
176 | objects['Pad'][scoringPlayer].score = objects['Pad'][scoringPlayer].score + 1
177 | local logLine = "Player #" .. scoringPlayer .. " scored a goal! " .. logBanter
178 | editor.console:Log(logStatus, logLine)
179 | end
180 | end
181 |
182 | function Ball:draw()
183 | love.graphics.circle("fill", self.x, self.y, 15)
184 | end
185 |
186 | --[[
187 | love
188 | ]]
189 |
190 | function love.load(args)
191 | local root = editor.hierarchy:NewObject("Root")
192 | local balls = editor.hierarchy:NewObject("Balls", root)
193 | local paddles = editor.hierarchy:NewObject("Pads", root)
194 |
195 | Ball:new()
196 | Pad:new("up", "down")
197 | Pad:new("left", "right")
198 |
199 | editor.inspector:SetTypeGUI(
200 | "instance of class Transform",
201 | function(name, value)
202 | imgui.Columns(2, "visualizer", false)
203 | imgui.Text(name)
204 | imgui.NextColumn()
205 |
206 | imgui.Columns(2, "labels_and_vis")
207 | imgui.Text("Position"); imgui.NextColumn()
208 | local changed_x, new_x = imgui.InputFloat("x", value.x); imgui.SameLine()
209 | if changed_x then value.x = new_x end
210 | local changed_y, new_y = imgui.InputFloat("y", value.y)
211 | if changed_y then value.y = new_y end
212 | imgui.NextColumn()
213 |
214 | imgui.Text("Scale"); imgui.NextColumn()
215 | local changed_sx, new_sx = imgui.InputFloat("sx", value.sx); imgui.SameLine()
216 | if changed_sx then value.sx = new_sx end
217 | local changed_sy, new_sy = imgui.InputFloat("sy", value.sy)
218 | if changed_sy then value.sy = new_sy end
219 | imgui.NextColumn()
220 |
221 | imgui.Text("Rotation"); imgui.NextColumn()
222 | local changed_rx, new_rx = imgui.InputFloat("rx", value.rx); imgui.SameLine()
223 | if changed_rx then value.rx = new_rx end
224 | local changed_ry, new_ry = imgui.InputFloat("ry", value.ry)
225 | if changed_ry then value.ry = new_ry end
226 | imgui.NextColumn()
227 | imgui.Columns(1)
228 |
229 | imgui.Columns(1)
230 |
231 | return (changed_x or changed_y or
232 | changed_sx or changed_sy or
233 | changed_rx or changed_ry),
234 | value
235 | end
236 | )
237 |
238 | editor.inspector:SetTypeIdentifier(
239 | "instance of class Transform",
240 | function(name, value)
241 | return type(value) == 'table' and value.class and value.class == Transform
242 | end
243 | )
244 |
245 | -- register all the objects we made
246 | -- collect them for a selection
247 | local selection = {}
248 | for k1,v1 in pairs(objects) do
249 | for k2,v2 in pairs(v1) do
250 | editor.hierarchy:Register( v2 )
251 | local parent
252 | if k1 == 'Ball' then
253 | parent = balls
254 | elseif k1 == 'Pad' then
255 | parent = paddles
256 | end
257 |
258 | editor.hierarchy:SetObjectParent( v2, parent )
259 | end
260 | end
261 | end
262 |
263 | function love.update(dt)
264 | for k1,v1 in pairs(objects) do
265 | for k2,v2 in pairs(v1) do
266 | v2:update(dt)
267 | end
268 | end
269 | local x, y = love.window.getPosition()
270 | local w, h = love.window.getMode()
271 | local mx, my = love.mouse.getPosition()
272 | local focus
273 | if love.window.hasMouseFocus() then
274 | focus = " (!)"
275 | else
276 | focus = " (?)"
277 | end
278 | local newTitle = x .. "x" .. y .. "; " .. w .. "/" .. h .. "; " .. mx .. ":" .. my .. focus
279 | love.window.setTitle(newTitle)
280 | end
281 |
282 | function love.draw()
283 | love.graphics.translate(400, 300)
284 | love.graphics.print(objects['Pad'][1].score, -380, -280)
285 | love.graphics.print(objects['Pad'][2].score, 370, -280)
286 |
287 | for k1,v1 in pairs(objects) do
288 | for k2,v2 in pairs(v1) do
289 | love.graphics.setColor(255,255,255)
290 | v2:draw()
291 | end
292 | end
293 | end
294 |
295 | function love.keypressed(key, scan)
296 | if key == "i" then
297 | love.graphics.setColor(0,0,255)
298 | elseif key == "j" then
299 | break_everything()
300 | end
301 | end
--------------------------------------------------------------------------------
/project/pong/middleclass.lua:
--------------------------------------------------------------------------------
1 | local middleclass = {
2 | _VERSION = 'middleclass v4.1.0',
3 | _DESCRIPTION = 'Object Orientation for Lua',
4 | _URL = 'https://github.com/kikito/middleclass',
5 | _LICENSE = [[
6 | MIT LICENSE
7 |
8 | Copyright (c) 2011 Enrique García Cota
9 |
10 | Permission is hereby granted, free of charge, to any person obtaining a
11 | copy of this software and associated documentation files (the
12 | "Software"), to deal in the Software without restriction, including
13 | without limitation the rights to use, copy, modify, merge, publish,
14 | distribute, sublicense, and/or sell copies of the Software, and to
15 | permit persons to whom the Software is furnished to do so, subject to
16 | the following conditions:
17 |
18 | The above copyright notice and this permission notice shall be included
19 | in all copies or substantial portions of the Software.
20 |
21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
22 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
24 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
25 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
26 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
27 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28 | ]]
29 | }
30 |
31 | local function _createIndexWrapper(aClass, f)
32 | if f == nil then
33 | return aClass.__instanceDict
34 | else
35 | return function(self, name)
36 | local value = aClass.__instanceDict[name]
37 |
38 | if value ~= nil then
39 | return value
40 | elseif type(f) == "function" then
41 | return (f(self, name))
42 | else
43 | return f[name]
44 | end
45 | end
46 | end
47 | end
48 |
49 | local function _propagateInstanceMethod(aClass, name, f)
50 | f = name == "__index" and _createIndexWrapper(aClass, f) or f
51 | aClass.__instanceDict[name] = f
52 |
53 | for subclass in pairs(aClass.subclasses) do
54 | if rawget(subclass.__declaredMethods, name) == nil then
55 | _propagateInstanceMethod(subclass, name, f)
56 | end
57 | end
58 | end
59 |
60 | local function _declareInstanceMethod(aClass, name, f)
61 | aClass.__declaredMethods[name] = f
62 |
63 | if f == nil and aClass.super then
64 | f = aClass.super.__instanceDict[name]
65 | end
66 |
67 | _propagateInstanceMethod(aClass, name, f)
68 | end
69 |
70 | local function _tostring(self) return "class " .. self.name end
71 | local function _call(self, ...) return self:new(...) end
72 |
73 | local function _createClass(name, super)
74 | local dict = {}
75 | dict.__index = dict
76 |
77 | local aClass = { name = name, super = super, static = {},
78 | __instanceDict = dict, __declaredMethods = {},
79 | subclasses = setmetatable({}, {__mode='k'}) }
80 |
81 | if super then
82 | setmetatable(aClass.static, { __index = function(_,k) return rawget(dict,k) or super.static[k] end })
83 | else
84 | setmetatable(aClass.static, { __index = function(_,k) return rawget(dict,k) end })
85 | end
86 |
87 | setmetatable(aClass, { __index = aClass.static, __tostring = _tostring,
88 | __call = _call, __newindex = _declareInstanceMethod })
89 |
90 | return aClass
91 | end
92 |
93 | local function _includeMixin(aClass, mixin)
94 | assert(type(mixin) == 'table', "mixin must be a table")
95 |
96 | for name,method in pairs(mixin) do
97 | if name ~= "included" and name ~= "static" then aClass[name] = method end
98 | end
99 |
100 | for name,method in pairs(mixin.static or {}) do
101 | aClass.static[name] = method
102 | end
103 |
104 | if type(mixin.included)=="function" then mixin:included(aClass) end
105 | return aClass
106 | end
107 |
108 | local DefaultMixin = {
109 | __tostring = function(self) return "instance of " .. tostring(self.class) end,
110 |
111 | initialize = function(self, ...) end,
112 |
113 | isInstanceOf = function(self, aClass)
114 | return type(aClass) == 'table' and (aClass == self.class or self.class:isSubclassOf(aClass))
115 | end,
116 |
117 | static = {
118 | allocate = function(self)
119 | assert(type(self) == 'table', "Make sure that you are using 'Class:allocate' instead of 'Class.allocate'")
120 | return setmetatable({ class = self }, self.__instanceDict)
121 | end,
122 |
123 | new = function(self, ...)
124 | assert(type(self) == 'table', "Make sure that you are using 'Class:new' instead of 'Class.new'")
125 | local instance = self:allocate()
126 | instance:initialize(...)
127 | return instance
128 | end,
129 |
130 | subclass = function(self, name)
131 | assert(type(self) == 'table', "Make sure that you are using 'Class:subclass' instead of 'Class.subclass'")
132 | assert(type(name) == "string", "You must provide a name(string) for your class")
133 |
134 | local subclass = _createClass(name, self)
135 |
136 | for methodName, f in pairs(self.__instanceDict) do
137 | _propagateInstanceMethod(subclass, methodName, f)
138 | end
139 | subclass.initialize = function(instance, ...) return self.initialize(instance, ...) end
140 |
141 | self.subclasses[subclass] = true
142 | self:subclassed(subclass)
143 |
144 | return subclass
145 | end,
146 |
147 | subclassed = function(self, other) end,
148 |
149 | isSubclassOf = function(self, other)
150 | return type(other) == 'table' and
151 | type(self.super) == 'table' and
152 | ( self.super == other or self.super:isSubclassOf(other) )
153 | end,
154 |
155 | include = function(self, ...)
156 | assert(type(self) == 'table', "Make sure you that you are using 'Class:include' instead of 'Class.include'")
157 | for _,mixin in ipairs({...}) do _includeMixin(self, mixin) end
158 | return self
159 | end
160 | }
161 | }
162 |
163 | function middleclass.class(name, super)
164 | assert(type(name) == 'string', "A name (string) is needed for the new class")
165 | return super and super:subclass(name) or _includeMixin(_createClass(name), DefaultMixin)
166 | end
167 |
168 | setmetatable(middleclass, { __call = function(_, ...) return middleclass.class(...) end })
169 |
170 | return middleclass
171 |
--------------------------------------------------------------------------------
/src/classes/Console.lua:
--------------------------------------------------------------------------------
1 | --[[
2 | @class Console Puts colored text in a scrollable window.
3 | ]]
4 | local Console = require('libs.middleclass.middleclass')('Console')
5 |
6 | local imgui = require('imgui')
7 |
8 | Console.static.default_color = { 1.0, 1.0, 1.0, 1.0 }
9 | Console.static.status_colors = {
10 | nay = { 1.0, 0.4, 0.4, 1.0 },
11 | warn = { 1.0, 0.78, 0.58, 1.0 },
12 | yay = { 0.4, 1.0, 0.4, 1.0 }
13 | }
14 |
15 | function Console:initialize(args)
16 | self.console_history = {}
17 | -- entries in the form of {status=status, text=text}
18 | end
19 |
20 | --[[
21 | @method Console:Log Add text to the console.
22 | @param string status The color of the text.
23 | @param string text The text to log.
24 | ]]
25 | function Console:Log(status, text)
26 | table.insert(self.console_history, {
27 | status = status,
28 | text = text
29 | })
30 | end
31 |
32 | --[[
33 | @method Console:Render Draws the console's contents.
34 | ]]
35 | function Console:Render()
36 | imgui.PushStyleVar("ItemSpacing", 4, 1)
37 | for i,item in ipairs(self.console_history) do
38 | -- get the color
39 | local color = Console.status_colors[item.status] or Console.default_color
40 |
41 | imgui.PushStyleColor("Text", unpack(color))
42 |
43 | imgui.TextUnformatted("[" .. item.status .. "] " .. item.text)
44 |
45 | imgui.PopStyleColor()
46 | end
47 | imgui.PopStyleVar()
48 | end
49 |
50 | return Console
--------------------------------------------------------------------------------
/src/classes/Editor.lua:
--------------------------------------------------------------------------------
1 | local Editor = require('libs.middleclass.middleclass')('Editor')
2 |
3 | --[[
4 | @class Editor An editor instance, contains references to other classes as internal components.
5 | ]]
6 | function Editor:initialize(args)
7 | -- components
8 | self.console = require('src.classes.Console')()
9 | self.hierarchy = require('src.classes.Hierarchy')()
10 | self.inspector = require('src.classes.Inspector')()
11 | end
12 |
13 | return Editor
--------------------------------------------------------------------------------
/src/classes/Hierarchy.lua:
--------------------------------------------------------------------------------
1 | local Hierarchy = require('libs.middleclass.middleclass')('Hierarchy')
2 | local lume = require('libs.lume.lume')
3 |
4 | local imgui = require('imgui')
5 |
6 | -- Hierarchy.static.property = 'great'
7 | Hierarchy.static.GetObjectName = function(obj)
8 | return tostring(obj) .. " [" .. obj.id .. "]"
9 | end
10 |
11 | --[[
12 | Hierarchy
13 | ]]
14 | function Hierarchy:initialize(args)
15 | -- keyed by UIDs, contains object table references
16 | self.objects = {}
17 |
18 | -- keyed by UIDs, contains UIDs for an object's parent
19 | self.parents = {}
20 |
21 | -- keyed by UIDs, contains if a object is selected in the hierarchy
22 | self.selection = {}
23 |
24 | -- keyed by index id, contains ipairs of objects that were selected, in order
25 | self.chrono_selection = {}
26 |
27 | self.open_stack = {}
28 | end
29 |
30 | function Hierarchy:RenderObject(object)
31 | local name = Hierarchy.GetObjectName( object )
32 | local node_clicked, node_open
33 | local node_flags = {"OpenOnArrow", "OpenOnDoubleClick"}
34 | local leaf_flags = {"Leaf", "NoTreePushOnOpen"}
35 | local use_flags
36 |
37 | -- Increase spacing to differentiate leaves from expanded contents.
38 | imgui.PushStyleVar("IndentSpacing", imgui.GetFontSize()*0.5)
39 |
40 | local has_children = not self:GetObjectHasNoChildren(object)
41 | if has_children then
42 | use_flags = node_flags
43 | else
44 | use_flags = leaf_flags
45 | end
46 |
47 | if self.selection[object.id] then
48 | table.insert(use_flags, "Selected")
49 | end
50 |
51 | node_open = imgui.TreeNodeEx(name, use_flags)
52 | node_clicked = imgui.IsItemClicked()
53 |
54 | self.open_stack[object.id] = node_open
55 | if node_open and has_children then
56 | for _, child_object in pairs(self.objects) do
57 | if self.parents[child_object.id] == object.id then
58 | self:RenderObject(child_object)
59 | end
60 | end
61 | imgui.TreePop()
62 | end
63 |
64 | if node_clicked then
65 | -- Update selection state. Process outside of tree loop to avoid visual inconsistencies during the clicking-frame.
66 | if love.keyboard.isDown("lctrl", "rctrl") then
67 | -- CTRL+click to toggle
68 |
69 | if self.selection[object.id] then
70 | local found_id
71 | for i, v in ipairs(self.chrono_selection) do
72 | if v == object.id then
73 | found_id = i
74 | break
75 | end
76 | end
77 | if found_id then
78 | table.remove(self.chrono_selection, found_id)
79 | end
80 | else
81 | table.insert(self.chrono_selection, object.id)
82 | end
83 |
84 | self.selection[object.id] = not self.selection[object.id]
85 | else
86 | -- Click to single-select
87 | self.selection = { [object.id] = true }
88 | self.chrono_selection = { [1] = object.id }
89 | end
90 | end
91 |
92 | imgui.PopStyleVar()
93 | end
94 |
95 | function Hierarchy:Render()
96 | for _, object in pairs(self.objects) do
97 | if self.parents[object.id] == '' then
98 | self:RenderObject(object)
99 | end
100 | end
101 | end
102 |
103 | function Hierarchy:Register(obj)
104 | if obj.id and self.objects[id] == obj then
105 | -- already registered, soft warning
106 | assert(false, "Object '" .. tostring(obj) .. "' [" .. obj.id .. "] already has a UUID.")
107 | elseif obj.id and self.objects[id] ~= obj then
108 | assert(false, "Object '" .. tostring(obj) .. "' [" .. obj.id .. "] had an ID but was not registered.\nCollision with entity properties.")
109 | end
110 |
111 | local id = lume.uuid()
112 | if self.objects[id] then
113 | assert(false, "Generated UUID collided with existing registered object '" .. tostring(self.objects[id]) .. "' [" .. id .. "].\nAttempted to register: Object '" .. tostring(obj) .. "'.")
114 | end
115 |
116 | obj.id = id
117 | self.objects[id] = obj
118 |
119 | self.parents[id] = ''
120 | return id
121 | end
122 |
123 | function Hierarchy:NewObject(name, parent)
124 | local obj = {
125 | name = name
126 | }
127 | local omet = {
128 | __tostring = function(oself)
129 | return oself.name
130 | end,
131 | }
132 | setmetatable(obj, omet)
133 |
134 | self:Register(obj)
135 | self:SetObjectParent(obj, parent)
136 |
137 | return obj
138 | end
139 |
140 | -- Set an object's parent with a reference to both.
141 | function Hierarchy:SetObjectParent(object, parent)
142 | local toset
143 | --local oldparent = self.parents[object.id]
144 | if parent then
145 | toset = parent.id
146 | else
147 | toset = ''
148 | end
149 | self.parents[object.id] = toset
150 | end
151 |
152 | function Hierarchy:GetObjectHasNoChildren(object)
153 | for k, v in pairs(self.parents) do
154 | if object.id == v then
155 | return false
156 | end
157 | end
158 | return true
159 | end
160 |
161 | return Hierarchy
--------------------------------------------------------------------------------
/src/classes/Inspector.lua:
--------------------------------------------------------------------------------
1 | local Inspector = require('libs.middleclass.middleclass')('Inspector')
2 |
3 | local imgui = require('imgui')
4 |
5 | Inspector.static.type_guis = {
6 | default = function(name, value)
7 | return imgui.InputText(name, tostring(value), 40, {"ReadOnly"})
8 | end,
9 | string = function(name, value)
10 | return imgui.InputText(name, tostring(value), 40)
11 | end,
12 | int = imgui.InputInt,
13 | float = imgui.InputFloat,
14 | color4 = function(name, value)
15 | return imgui.ColorEdit4(name, unpack(value))
16 | end,
17 | }
18 | Inspector.static.type_identifiers = {
19 | float = function(name, value)
20 | local num, dec
21 | if type(value) == "number" then
22 | num, dec = math.modf(value)
23 | return (
24 | math.abs(dec) ~= 0
25 | )
26 | end
27 | return false
28 | end,
29 | int = function(name, value)
30 | local num, dec
31 | if type(value) == "number" then
32 | num, dec = math.modf(value)
33 | return (
34 | math.abs(dec) == 0
35 | )
36 | end
37 | return false
38 | end,
39 | color4 = function(name, value)
40 | return (
41 | type(value) == "table" and
42 | #value == 4 and
43 | type(value[1]) == "number" and
44 | type(value[2]) == "number" and
45 | type(value[3]) == "number" and
46 | type(value[4]) == "number" and
47 | value[1] >= 0 and value[1] <= 1 and
48 | value[2] >= 0 and value[2] <= 1 and
49 | value[3] >= 0 and value[3] <= 1 and
50 | value[4] >= 0 and value[4] <= 1
51 | )
52 | end,
53 | color3 = function(name, value)
54 | return (
55 | type(value) == "table" and
56 | #value == 3 and
57 | type(value[1]) == "number" and
58 | type(value[2]) == "number" and
59 | type(value[3]) == "number" and
60 | value[1] >= 0 and value[1] <= 1 and
61 | value[2] >= 0 and value[2] <= 1 and
62 | value[3] >= 0 and value[3] <= 1
63 | )
64 | end,
65 | }
66 |
67 | --[[
68 | Inspector
69 | ]]
70 | function Inspector:initialize(args)
71 | self.selection = {}
72 |
73 | self.type_guis = {}
74 | for k, v in pairs(Inspector.type_guis) do
75 | self.type_guis[k] = v
76 | end
77 |
78 | self.type_identifiers = {}
79 | for k, v in pairs(Inspector.type_identifiers) do
80 | self.type_identifiers[k] = v
81 | end
82 | end
83 |
84 | -- TypeGUI = function(name, value)
85 | -- return didChange, newValue1, newValue2, ...
86 | -- end
87 | function Inspector:SetTypeGUI(typeName, func)
88 | assert(not self.type_guis[typeName], "A type GUI already exists for " .. tostring(typeName))
89 | self.type_guis[typeName] = func
90 | end
91 |
92 | -- TypeIdentifier = function(name, value)
93 | -- return isTypeForKey
94 | -- end
95 | function Inspector:SetTypeIdentifier(typeName, func)
96 | assert(not self.type_identifiers[typeName], "A type identifier already exists for " .. tostring(typeName))
97 | self.type_identifiers[typeName] = func
98 | end
99 |
100 | function Inspector:AddSelection(...)
101 | for k,v in pairs({...}) do
102 | table.insert(self.selection, v)
103 | end
104 | end
105 |
106 | function Inspector:SetSelection(...)
107 | self.selection = {...}
108 | end
109 |
110 | function Inspector:ClearSelection()
111 | self.selection = {}
112 | end
113 |
114 | function Inspector:Render()
115 | for k,v in ipairs(self.selection) do
116 | self:RenderInspect(v)
117 | end
118 | end
119 |
120 | function Inspector:RenderInspect(var)
121 | local itsname = tostring(var)
122 | if var.id then
123 | itsname = itsname .. "\t[" .. var.id .. "]"
124 | end
125 |
126 | if imgui.CollapsingHeader(tostring(itsname)) then
127 | local value_changed, out
128 | for k, v in pairs(var) do
129 | local typ = type(v)
130 | local atype = typ
131 |
132 | -- attempt to obtain the type from a sequence of functions
133 | -- if any function returns true, it is that type
134 | for k0, v0 in pairs(self.type_identifiers) do
135 | local result = v0(k, v)
136 | if result then
137 | atype = k0
138 | end
139 | end
140 |
141 | if not self.type_guis[atype] then
142 | atype = 'default'
143 | end
144 |
145 | if self.type_guis[atype] then
146 | out = {self.type_guis[atype](k, v)}
147 |
148 | -- get the status alone
149 | value_changed = out[1]
150 | table.remove(out, 1)
151 | end
152 |
153 | -- value changed
154 | if value_changed then
155 | if #out > 1 then
156 | var[k] = out
157 | else
158 | var[k] = out[1]
159 | end
160 | end
161 | end
162 | end
163 | end
164 |
165 | return Inspector
--------------------------------------------------------------------------------
/src/classes/Template.lua:
--------------------------------------------------------------------------------
1 | local Template = require('libs.middleclass.middleclass')('Template')
2 |
3 | local imgui = require('imgui')
4 |
5 | -- Template.static.property = 'great'
6 |
7 | --[[
8 | @class Template Example class.
9 | ]]
10 | function Template:initialize(args)
11 | -- self.instance_value = {}
12 | end
13 |
14 | --[[
15 | @method Template:ExampleMethod A method that exemplifies an instance method.
16 | @param table a_table A table that stuff happens to.
17 | @param string a_string A string that things happen to.
18 | @param number a_number A number that things happen to.
19 | @return bool The status of the operation.
20 | ]]
21 | function Template:ExampleMethod(a_table, a_string, a_number)
22 | return true
23 | end
24 |
25 | return Template
--------------------------------------------------------------------------------
/src/classes/UIHelper.lua:
--------------------------------------------------------------------------------
1 | local UIHelper = require('libs.middleclass.middleclass')('UIHelper')
2 | local lume = require('libs.lume.lume')
3 | local imgui = require('imgui')
4 |
5 | function UIHelper:initialize(args)
6 | self.postRenderStack = {}
7 |
8 | -- go by keys, call final callback
9 | self.menu = {}
10 |
11 | self:Reset()
12 | end
13 |
14 | function UIHelper:RenderMenuLevel(name, value)
15 | if type(value) == "function" then
16 | if name == "" then
17 | imgui.MenuItem("(Empty)", nil, false, false)
18 | elseif imgui.MenuItem(name) then
19 | value(name)
20 | end
21 | elseif type(value) == "table" then
22 | if imgui.BeginMenu(name) then
23 | local i = 0
24 | for k, v in pairs(value) do
25 | self:RenderMenuLevel(k, v)
26 | i = i + 1
27 | end
28 | if i == 0 then
29 | imgui.MenuItem("(Empty)", nil, false, false)
30 | end
31 | imgui.EndMenu()
32 | end
33 | end
34 | end
35 |
36 | function UIHelper:RenderMenu()
37 | if imgui.BeginMainMenuBar() then
38 | for k, v in pairs(self.menu) do
39 | if type(v) == 'table' then
40 | self:RenderMenuLevel(k, v)
41 | elseif type(v) == 'function' then
42 | if imgui.BeginMenu(k) then
43 | self:RenderMenuLevel(k, v)
44 | imgui.EndMenu()
45 | end
46 | end
47 | end
48 | imgui.EndMainMenuBar()
49 | end
50 | end
51 |
52 | function UIHelper:AddMenu(path, callback, shortcut, selected, enabled)
53 | local parts = lume.split(path, "/")
54 | local ctable = self.menu
55 | local depth = #parts
56 |
57 | for k,v in pairs(parts) do
58 | if k == depth then
59 | ctable[v] = callback
60 | else
61 | if not ctable[v] then
62 | ctable[v] = {}
63 | end
64 | ctable = ctable[v]
65 | end
66 | end
67 | end
68 |
69 | function UIHelper:Dock(name, layout)
70 | local fullname = "dock_" + self.numDocks
71 | if layout ~= nil then
72 | fullname = fullname .. "_" .. layout
73 | imgui.SetNextDock(layout)
74 | end
75 |
76 | if name ~= nil then
77 | fullname = name
78 | end
79 |
80 | if imgui.BeginDock(fullname) then
81 | imgui.Text(fullname)
82 | end
83 | imgui.EndDock()
84 |
85 | self.numDocks = self.numDocks + 1
86 | end
87 |
88 | function UIHelper:Reset()
89 | self.numDocks = 0
90 | end
91 |
92 | function UIHelper:PostRender()
93 | for k,v in pairs(self.postRenderStack) do
94 | v()
95 | end
96 | self.postRenderStack = {}
97 | end
98 |
99 | function UIHelper:PushPostRender(func)
100 | table.insert(self.postRenderStack, func)
101 | end
102 |
103 | function UIHelper:Begin(...)
104 | return imgui.Begin(...)
105 | end
106 |
107 | function UIHelper:End()
108 | imgui.End()
109 | self:Reset()
110 | end
111 |
112 | return UIHelper
--------------------------------------------------------------------------------
/tools/generate_docs.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | nowpath=$( dirname "${BASH_SOURCE[0]}" );
3 | nowpath="${nowpath#./}";
4 | rm -rf "docs";
5 | mkdir -p "docs";
6 | echo "" > "docs/index.html";
7 | IFS=$'\n';
8 | excludes="";
9 | for f in $(find . -wholename './main.lua' -or -wholename '*/src/*.lua' -or -wholename '*/lasagna/*.lua'); do
10 | thepath=$(dirname "$f")
11 | filename=$(basename "$f" ".lua")
12 |
13 | infile="$thepath/$filename.lua";
14 | infile="${infile#./}";
15 | outpath="docs/${thepath#./}";
16 | outfile="$outpath/$filename.html";
17 |
18 | docfile="${thepath#./}/$filename.lua";
19 | docfile="${docfile#./}";
20 | docpath="${outfile#docs/}";
21 | if [ -f "$f" ]; then
22 | echo "$docfile " >> "docs/index.html";
23 |
24 | mkdir -p "$outpath";
25 | lua "$nowpath/rtfm/rtfm.lua" "$infile" > "$outfile";
26 | fi;
27 | done
28 | echo " " >> "docs/index.html";
--------------------------------------------------------------------------------