Compare commits
216 Commits
f06a979536
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4ba1932138 | ||
|
|
3282023edd | ||
|
|
8d3b351083 | ||
|
|
6b78404821 | ||
|
|
0ff69b53f6 | ||
|
|
4997a94cfb | ||
|
|
8a4e99aa13 | ||
|
|
cd80a9af22 | ||
|
|
1d4500a4d4 | ||
|
|
f6b76a522f | ||
|
|
408fed1ad2 | ||
|
|
d80ff4e679 | ||
|
|
147dc40365 | ||
|
|
7dca84b128 | ||
|
|
65f6555f44 | ||
|
|
b10bc73833 | ||
|
|
9177394fd0 | ||
|
|
8db86ee45c | ||
|
|
3d293d3e68 | ||
|
|
98018e4a8c | ||
|
|
c0d65f7f38 | ||
|
|
739149d852 | ||
|
|
4c2e732607 | ||
|
|
c053a792a4 | ||
|
|
3f372d4674 | ||
|
|
e98014d29b | ||
|
|
686c1e5808 | ||
|
|
0ff986a4f2 | ||
|
|
407082dd2c | ||
|
|
226190b1f2 | ||
|
|
cd73609a2e | ||
|
|
30b49215a6 | ||
|
|
91583b1ca2 | ||
|
|
a2f8f000db | ||
|
|
e62ab1d4f8 | ||
|
|
8533d837cd | ||
|
|
71406dfe11 | ||
|
|
3d01e66364 | ||
|
|
609013a9fa | ||
|
|
b0e5877417 | ||
|
|
86c23b8433 | ||
| 4175ea2c8c | |||
| 7837ed573a | |||
| 3ef0bd371d | |||
| d45d2acd95 | |||
| 7f1f2eeae3 | |||
| f2b6f737da | |||
| da30f475c9 | |||
| dff74ce8b4 | |||
| fc121adcb8 | |||
| 5eafe25b1a | |||
| 1456dc332f | |||
| 2f72ef4904 | |||
| 14153522b6 | |||
| f3f600892d | |||
|
|
d294355a73 | ||
|
|
a0a7c96093 | ||
|
|
b5fd24740b | ||
|
|
569a4370c0 | ||
|
|
6a4d2d0375 | ||
|
|
f24d33eb77 | ||
|
|
7430c24798 | ||
|
|
c826598ea0 | ||
|
|
9dbc45e96e | ||
|
|
457d6d775c | ||
|
|
0f18f97cfa | ||
|
|
f759cfe229 | ||
|
|
27d1bf5a31 | ||
|
|
421c9b04b9 | ||
|
|
3e69183f78 | ||
|
|
da7bbe21c2 | ||
|
|
3e654df2ac | ||
|
|
3ed702a1b5 | ||
|
|
abbe22f949 | ||
|
|
b219cebb32 | ||
|
|
cbb6f57daf | ||
|
|
f533382022 | ||
|
|
e788002b37 | ||
|
|
45112a5d66 | ||
|
|
798c3e4993 | ||
|
|
a0a5829d84 | ||
|
|
0b31ec58da | ||
|
|
e86c43ac72 | ||
|
|
0d096aecde | ||
|
|
9c4605cda2 | ||
|
|
726667399f | ||
|
|
e193427e4c | ||
|
|
ccb2134c2f | ||
|
|
a0b995c288 | ||
|
|
c4493e349a | ||
|
|
4bfe62633d | ||
|
|
73aa371813 | ||
|
|
7c122d6e29 | ||
|
|
dd0d215b1c | ||
|
|
29adfecea6 | ||
|
|
f185e40f49 | ||
|
|
6bb6c0e822 | ||
|
|
ce0f82c778 | ||
|
|
0d47cfcf41 | ||
|
|
9185d1dad4 | ||
|
|
053b59cf00 | ||
|
|
5b11cb12f5 | ||
|
|
2e9eefb240 | ||
|
|
b9ac063a35 | ||
|
|
f5ea075a37 | ||
|
|
a8652adafe | ||
|
|
33e0e54477 | ||
|
|
3e5e46cb74 | ||
|
|
6f7775f602 | ||
|
|
3496346583 | ||
|
|
3c339a372a | ||
|
|
ba057b20c6 | ||
|
|
cedf602e89 | ||
|
|
ea4de6e988 | ||
|
|
86f378b5f4 | ||
|
|
3943bbcc19 | ||
|
|
86e251152c | ||
|
|
0d8c7ab16b | ||
|
|
9ba0ada978 | ||
|
|
ab65943ee0 | ||
|
|
6f606d3758 | ||
|
|
0574e0ab4b | ||
|
|
343aa69a46 | ||
|
|
04be409a29 | ||
|
|
c18ee0e1f8 | ||
|
|
c51a46135a | ||
|
|
97b29a30a5 | ||
|
|
3a7b2cd252 | ||
|
|
17a6d0f9fa | ||
|
|
22301dfe77 | ||
|
|
6777df634d | ||
|
|
8ed9e48dfe | ||
|
|
70c55a42f0 | ||
|
|
27f87b4bae | ||
|
|
f7754f4609 | ||
|
|
f79b615ff9 | ||
|
|
f8121a0727 | ||
|
|
d606eccc15 | ||
|
|
a07a801cab | ||
|
|
e9a68f4614 | ||
|
|
b12510a4ac | ||
|
|
ad81301ef1 | ||
|
|
5f8cf98b8a | ||
|
|
2afa27bdac | ||
|
|
fdfa8d21bf | ||
|
|
220db8a867 | ||
|
|
0e86863c34 | ||
|
|
2e1f54c56f | ||
|
|
3a6cb08707 | ||
|
|
40213b6b18 | ||
|
|
a938ed6133 | ||
|
|
e05cc0594e | ||
|
|
b3fdbb898b | ||
|
|
1c2f50a3e0 | ||
|
|
9e2e36bcd9 | ||
|
|
7fa0861084 | ||
|
|
6112a3561b | ||
|
|
fd25c3ee19 | ||
|
|
40728b4860 | ||
|
|
40cf649c16 | ||
|
|
3a46e08f37 | ||
|
|
9ac62319fd | ||
|
|
43bfca07c6 | ||
|
|
4ee78dd7c2 | ||
|
|
bd2f688914 | ||
|
|
d623d338d7 | ||
|
|
065940ff2c | ||
|
|
8483ffeaaf | ||
|
|
059f9f2938 | ||
|
|
ee1ba9585a | ||
|
|
c084dc55aa | ||
|
|
df3810cce7 | ||
|
|
5da99116ce | ||
|
|
2e622295b8 | ||
|
|
82e0044ba0 | ||
|
|
f6a6357c48 | ||
|
|
fe7ed25d74 | ||
|
|
bb5f246687 | ||
|
|
26ecd2a90d | ||
|
|
c2996cf1ff | ||
|
|
dd80c013c8 | ||
|
|
8197d0a6f6 | ||
|
|
05116679d2 | ||
|
|
233d3119e5 | ||
|
|
c01b258e51 | ||
|
|
4425e1b0c7 | ||
|
|
3095b399b2 | ||
|
|
6bbe23983d | ||
|
|
145bc90eae | ||
|
|
1a63a7054f | ||
|
|
63e1e54462 | ||
|
|
2b4baf7b76 | ||
|
|
4acde58d5c | ||
|
|
7cd370e8ef | ||
|
|
0bd63f6804 | ||
|
|
7248f777a0 | ||
|
|
685303e4e5 | ||
|
|
0a8209d3fe | ||
|
|
b816a81f3b | ||
|
|
99c9158488 | ||
|
|
3ab4c97fae | ||
|
|
a483a319e6 | ||
|
|
b9cb08bff5 | ||
|
|
658b69dbf8 | ||
|
|
4071e380b1 | ||
|
|
bad85564b1 | ||
|
|
fece7532c2 | ||
|
|
31f53ef521 | ||
|
|
f6c9801635 | ||
|
|
25a998a542 | ||
|
|
2b24904c6f | ||
|
|
dd04c0c449 | ||
|
|
705ad20481 | ||
|
|
655ac6f4b2 | ||
|
|
3960eb0cd2 | ||
|
|
fe5f08f62f |
60
debugger.lua
Normal file
60
debugger.lua
Normal file
@@ -0,0 +1,60 @@
|
||||
local function copyObject(o)
|
||||
local obj = {}
|
||||
for k,v in pairs(o) do
|
||||
obj[k] = v
|
||||
end
|
||||
setmetatable(obj, getmetatable(o))
|
||||
return obj
|
||||
end
|
||||
|
||||
local function execDebug(env)
|
||||
local input = io.input()
|
||||
local result = { pcall(input.read, input, "*l") }
|
||||
if not result[1] then
|
||||
return false, result[2]
|
||||
end
|
||||
|
||||
local func = load("return " .. result[2], "debug", "bt", env)
|
||||
if type(func) ~= "function" then
|
||||
func = load(result[2], "debug", "bt", env)
|
||||
if type(func) ~= "function" then
|
||||
return false, func
|
||||
end
|
||||
end
|
||||
|
||||
return pcall(func)
|
||||
end
|
||||
|
||||
local function debugREPL(onResult, debugArgs)
|
||||
local globalEnv = copyObject(_ENV)
|
||||
globalEnv._debug = debugArgs
|
||||
local shouldExit = false
|
||||
globalEnv.exit = function()
|
||||
shouldExit = true
|
||||
end
|
||||
local result, retval
|
||||
repeat
|
||||
result, retval = execDebug(globalEnv)
|
||||
if result and type(onResult) == "function" then
|
||||
onResult(retval)
|
||||
end
|
||||
until shouldExit or not result
|
||||
local success = result and shouldExit
|
||||
return success, (not success and retval) or nil
|
||||
end
|
||||
|
||||
local function hookDebugger(context, Logger)
|
||||
Logger = Logger or require("logging").getGlobalLogger()
|
||||
|
||||
local result, retval
|
||||
repeat
|
||||
result, retval = debugREPL(function(r)
|
||||
Logger:error("->", r)
|
||||
end, context)
|
||||
if not result then
|
||||
Logger:error("x>", retval)
|
||||
end
|
||||
until result
|
||||
end
|
||||
|
||||
return { debugREPL = debugREPL, execDebug = execDebug, hookDebugger = hookDebugger }
|
||||
@@ -2,18 +2,40 @@ local Prop = require("gfx.prop")
|
||||
local Children = require("gfx.prop.children")
|
||||
local Element = require("gfx.element")
|
||||
|
||||
local Container = Prop.attach(Element:new(), Children)
|
||||
local Container = Element:new()
|
||||
|
||||
function Container:_reload()
|
||||
Element._reload(self)
|
||||
|
||||
-- Reload child windows
|
||||
local win = self:_getWindow()
|
||||
function Container:getHeight()
|
||||
if self:isStrict() then
|
||||
return Element.getHeight(self)
|
||||
end
|
||||
local max = 0
|
||||
for _,child in self:_iterateChildren() do
|
||||
if child:_getWindow() ~= win then
|
||||
child:setParent(win)
|
||||
max = math.max(max, child:getY() + child:getHeight())
|
||||
end
|
||||
return max
|
||||
end
|
||||
|
||||
function Container:getWidth()
|
||||
if self:isStrict() then
|
||||
return Element.getWidth(self)
|
||||
end
|
||||
local max = 0
|
||||
for _,child in self:_iterateChildren() do
|
||||
max = math.max(max, child:getX() + child:getWidth())
|
||||
end
|
||||
return max
|
||||
end
|
||||
|
||||
function Container:isStrict()
|
||||
return self.strict == true
|
||||
end
|
||||
|
||||
function Container:setStrict(strict)
|
||||
local needReload = (not not self.strict) ~= strict
|
||||
self.strict = strict
|
||||
if needReload then
|
||||
self:_reload()
|
||||
end
|
||||
end
|
||||
|
||||
return Container
|
||||
return Prop.attach(Container, Children)
|
||||
@@ -1,4 +1,5 @@
|
||||
local Event = require("gfx.event")
|
||||
local Logger = require("logging").getGlobalLogger()
|
||||
|
||||
local Element = {
|
||||
x = 1,
|
||||
@@ -12,7 +13,10 @@ local Element = {
|
||||
visible = true,
|
||||
dirty = true,
|
||||
id = "",
|
||||
onClick = nil
|
||||
onClick = nil,
|
||||
onKey = nil,
|
||||
onChar = nil,
|
||||
onEvent = nil
|
||||
}
|
||||
Element.__index = Element
|
||||
|
||||
@@ -31,6 +35,7 @@ function Element:new(o)
|
||||
end
|
||||
|
||||
function Element:draw()
|
||||
Logger:trace("draw")
|
||||
local dirty = self:_isDirty()
|
||||
if dirty then
|
||||
local win = self:_getWindow()
|
||||
@@ -51,52 +56,62 @@ function Element:getY()
|
||||
end
|
||||
|
||||
function Element:setPos(x, y)
|
||||
Logger:trace("setPos", x, y)
|
||||
if (x ~= nil and self.x ~= x) or (y ~= nil and self.y ~= y) then
|
||||
self:setDirty()
|
||||
self.x = x or self.x
|
||||
self.y = y or self.y
|
||||
|
||||
self:setDirty()
|
||||
self:_reload()
|
||||
end
|
||||
end
|
||||
|
||||
function Element:setX(x)
|
||||
if self.x ~= x then
|
||||
self:setDirty()
|
||||
self.x = x
|
||||
self:setDirty()
|
||||
self:_reload()
|
||||
end
|
||||
end
|
||||
|
||||
function Element:setY(y)
|
||||
if self.y ~= y then
|
||||
self:setDirty()
|
||||
self.y = y
|
||||
self:setDirty()
|
||||
self:_reload()
|
||||
end
|
||||
end
|
||||
|
||||
function Element:setParent(parent)
|
||||
Logger:trace("setParent", self:getId(), parent, self.parent ~= parent)
|
||||
if self.parent ~= parent then
|
||||
self:setDirty()
|
||||
self.parent = parent
|
||||
self:setDirty()
|
||||
self:_reload()
|
||||
end
|
||||
end
|
||||
|
||||
function Element:setFgColor(color)
|
||||
if color ~= self.fgColor then
|
||||
self:setDirty()
|
||||
self.fgColor = color
|
||||
self:_getWindow().setTextColor(color)
|
||||
|
||||
self:setDirty()
|
||||
local win = self:_getWindow()
|
||||
if win ~= nil then
|
||||
win.setTextColor(color)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Element:setBgColor(color)
|
||||
if color ~= self.bgColor then
|
||||
self:setDirty()
|
||||
self.bgColor = color
|
||||
self:_getWindow().setBackgroundColor(color)
|
||||
|
||||
self:setDirty()
|
||||
local win = self:_getWindow()
|
||||
if win ~= nil then
|
||||
win.setBackgroundColor(color)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -117,7 +132,13 @@ function Element:_setWindow(window)
|
||||
end
|
||||
|
||||
function Element:setVisible(visible)
|
||||
self:_getWindow().setVisible(visible)
|
||||
self.visible = visible
|
||||
self:setDirty()
|
||||
|
||||
local win = self:_getWindow()
|
||||
if win ~= nil then
|
||||
win.setVisible(visible)
|
||||
end
|
||||
end
|
||||
|
||||
function Element:_isDirty()
|
||||
@@ -126,25 +147,25 @@ end
|
||||
|
||||
function Element:resize(opts)
|
||||
if (opts.width ~= nil and self.x ~= opts.width) or (opts.height ~= nil and self.y ~= opts.height) then
|
||||
self:setDirty()
|
||||
self.width = opts.width or self.width
|
||||
self.height = opts.height or self.height
|
||||
self:setDirty()
|
||||
self:_reload()
|
||||
end
|
||||
end
|
||||
|
||||
function Element:setWidth(width)
|
||||
if width ~= self.width then
|
||||
self:setDirty()
|
||||
self.width = width
|
||||
self:setDirty()
|
||||
self:_reload()
|
||||
end
|
||||
end
|
||||
|
||||
function Element:setHeight(height)
|
||||
if height ~= self.height then
|
||||
self:setDirty()
|
||||
self.height = height
|
||||
self:setDirty()
|
||||
self:_reload()
|
||||
end
|
||||
end
|
||||
@@ -181,16 +202,18 @@ function Element:getId()
|
||||
return self.id
|
||||
end
|
||||
|
||||
function Element:redraw()
|
||||
self:_getWindow().redraw()
|
||||
end
|
||||
|
||||
function Element:handleEvent(evt)
|
||||
if Event.isClickEvent(evt) and Event.containsClick(self, evt) and self.onClick ~= nil then
|
||||
local x, y, source = Event.getClickParams(evt)
|
||||
return not not self.onClick(self, x, y, source)
|
||||
elseif Event.isKeyEvent(evt) and self.onKey ~= nil then
|
||||
local key, held = Event.getKeyParams(evt)
|
||||
return not not self.onKey(self, key, held)
|
||||
elseif Event.isCharEvent(evt) and self.onChar ~= nil then
|
||||
local keyChar = Event.getCharValue(evt)
|
||||
return not not self.onChar(self, keyChar)
|
||||
end
|
||||
return false
|
||||
return self.onEvent ~= nil and self.onEvent(self, evt)
|
||||
end
|
||||
|
||||
function Element:setOnClick(onClick)
|
||||
@@ -198,11 +221,14 @@ function Element:setOnClick(onClick)
|
||||
end
|
||||
|
||||
function Element:_reload()
|
||||
Logger:trace("_reload", self:getId(), self:getX(), self:getY(), self:getWidth(), self:getHeight(), self:isVisible(), Logger.plain({bg=self:getBgColor(), fg=self:getFgColor()}))
|
||||
if self.parent ~= nil then
|
||||
local win = window.create(self.parent, self:getX(), self:getY(), self:getWidth(), self:getHeight(), self:isVisible())
|
||||
win.setBackgroundColor(self:getBgColor())
|
||||
win.setTextColor(self:getFgColor())
|
||||
win.setVisible(self.visible)
|
||||
self:_setWindow(win)
|
||||
self:setDirty(true)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
local Event = {}
|
||||
|
||||
function Event.isKeyEvent(evt, key)
|
||||
return evt[1] == "key" and (key == nil or evt[2] == key)
|
||||
end
|
||||
|
||||
function Event.getKeyParams(evt)
|
||||
return evt[2], evt[3]
|
||||
end
|
||||
|
||||
function Event.isCharEvent(evt, char)
|
||||
return evt[1] == "char" and (char == nil or evt[2] == char)
|
||||
end
|
||||
|
||||
function Event.getCharValue(evt)
|
||||
return evt[2]
|
||||
end
|
||||
|
||||
function Event.isClickEvent(evt)
|
||||
return evt[1] == "monitor_touch" or evt[1] == "mouse_click"
|
||||
end
|
||||
@@ -12,7 +28,7 @@ function Event.repositionEvent(evt, dX, dY)
|
||||
if Event.isClickEvent(evt) then
|
||||
return evt[1], evt[2], evt[3] + dX, evt[4] + dY
|
||||
else
|
||||
return evt
|
||||
return table.unpack(evt)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
232
gfx/keyboard.lua
Normal file
232
gfx/keyboard.lua
Normal file
@@ -0,0 +1,232 @@
|
||||
-- On-screen keyboard with hooks for standard key input events
|
||||
|
||||
local List = require("gfx.list")
|
||||
local Padding = require("gfx.padding")
|
||||
local Text = require("gfx.text")
|
||||
local Orientation = require("gfx.prop.orientation")
|
||||
local Children = require("gfx.prop.children")
|
||||
|
||||
local DEFAULT_COLOR_BG = colors.gray
|
||||
local DEFAULT_COLOR_KEY = colors.gray
|
||||
local DEFAULT_PADDING_H = 1
|
||||
local DEFAULT_PADDING_V = 1
|
||||
local HANDLER_IGNORE_CLICK = function() return true end
|
||||
|
||||
local ID_KEY_LIST = "$Keyboard$List"
|
||||
local ID_PADDED_KEY = "$Keyboard$List$Padding"
|
||||
|
||||
local Keyboard = Padding:new{
|
||||
bgColor = DEFAULT_COLOR_BG,
|
||||
keyColor = DEFAULT_COLOR_KEY,
|
||||
left = DEFAULT_PADDING_H,
|
||||
right = DEFAULT_PADDING_H,
|
||||
top = DEFAULT_PADDING_V,
|
||||
bottom = DEFAULT_PADDING_V,
|
||||
onKeyPress = function(key) end,
|
||||
onBackspace = function() end
|
||||
}
|
||||
|
||||
function Keyboard:new(o)
|
||||
local template = o or {}
|
||||
|
||||
if type(template.layout) == "function" then
|
||||
template.layout = template.layout()
|
||||
elseif type(template.layout) ~= "table" then
|
||||
template.layout = Keyboard.Layout.English()
|
||||
end
|
||||
|
||||
template.colorKey = template.colorKey or DEFAULT_COLOR_KEY
|
||||
template.keySlop = not not template.keySlop
|
||||
template.onClick = HANDLER_IGNORE_CLICK
|
||||
template.onKey = function(_, keyCode, _)
|
||||
if keyCode == keys.backspace then
|
||||
template.onBackspace()
|
||||
return true
|
||||
elseif keyCode == keys.enter then
|
||||
template.onKeyPress("\n")
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
template.onChar = function(_, charCode)
|
||||
template.onKeyPress(charCode)
|
||||
return true
|
||||
end
|
||||
|
||||
template.element = List:new{
|
||||
bgColor = template.bgColor,
|
||||
[Orientation:getId()] = Orientation.VERTICAL,
|
||||
[Children:getId()] = {}
|
||||
}
|
||||
|
||||
local obj = Padding.new(self, template)
|
||||
|
||||
self:setLayout(self.layout)
|
||||
|
||||
return obj
|
||||
end
|
||||
|
||||
function Keyboard:setLayout(layout)
|
||||
self.layout = layout
|
||||
|
||||
local this = self
|
||||
|
||||
local KEY_BACKSPACE = "backspace"
|
||||
local KEY_ENTER = "enter"
|
||||
local function charInputKeyList(chars, backspace, enter, spacebar)
|
||||
local keysEntries = { }
|
||||
for i=1,#chars do
|
||||
local key = chars:sub(i, i)
|
||||
local keyFunc = function()
|
||||
this.onKeyPress(key)
|
||||
return true
|
||||
end
|
||||
local keySlopFunc = this.keySlop and keyFunc or HANDLER_IGNORE_CLICK
|
||||
|
||||
-- ((not backspace) and i == #keys and 0) or 1
|
||||
table.insert(keysEntries, Padding:new{ onClick = keySlopFunc, bgColor = this.bgColor, right = 1, element = Text:new{
|
||||
id = key,
|
||||
text = key,
|
||||
bgColor = this.colorKey,
|
||||
onClick = keyFunc
|
||||
}})
|
||||
end
|
||||
if enter then
|
||||
table.insert(keysEntries, Text:new{
|
||||
id = KEY_ENTER,
|
||||
text = "[<]",
|
||||
bgColor = this.colorKey,
|
||||
onClick = function()
|
||||
this.onKeyPress("\n")
|
||||
return true
|
||||
end
|
||||
})
|
||||
end
|
||||
if backspace then
|
||||
table.insert(keysEntries, Text:new{
|
||||
id = KEY_BACKSPACE,
|
||||
text = "[x]",
|
||||
bgColor = this.colorKey,
|
||||
onClick = function()
|
||||
this.onBackspace()
|
||||
return true
|
||||
end
|
||||
})
|
||||
end
|
||||
if spacebar then
|
||||
table.insert(keysEntries, Text:new{
|
||||
id = " ",
|
||||
text = "[SPACE]",
|
||||
bgColor = this.colorKey,
|
||||
onClick = function()
|
||||
this.onKeyPress(" ")
|
||||
return true
|
||||
end
|
||||
})
|
||||
end
|
||||
return List:new{
|
||||
id = ID_KEY_LIST,
|
||||
bgColor = this.colorBg,
|
||||
[Orientation:getId()] = Orientation.HORIZONTAL,
|
||||
[Children:getId()] = keysEntries
|
||||
}
|
||||
end
|
||||
|
||||
local keyboardLines = {}
|
||||
for _,line in ipairs(this.layout) do
|
||||
local keyLineList = charInputKeyList(line[1], line.backspace, line.enter)
|
||||
table.insert(keyboardLines, keyLineList)
|
||||
end
|
||||
|
||||
self.element[Children:getId()] = keyboardLines
|
||||
self:setDirty()
|
||||
end
|
||||
|
||||
function Keyboard:setKeyColor(color)
|
||||
if color ~= self.keyColor then
|
||||
self.keyColor = color
|
||||
self:setDirty()
|
||||
end
|
||||
end
|
||||
|
||||
function Keyboard:setDirty(fullInvalidate)
|
||||
local keyList = self:findById(ID_KEY_LIST)
|
||||
for _,key in pairs(keyList[Children:getId()]) do
|
||||
if key:getId() == ID_PADDED_KEY then
|
||||
key:setBgColor(self.bgColor)
|
||||
key.element:setBgColor(self.keyColor)
|
||||
else
|
||||
key:setBgColor(self.keyColor)
|
||||
end
|
||||
end
|
||||
Padding.setDirty(self, fullInvalidate)
|
||||
end
|
||||
|
||||
local function layoutNumbers(layout)
|
||||
table.insert(layout, 1, { "1234567890" })
|
||||
return layout
|
||||
end
|
||||
|
||||
local function layoutEnter(rowIndex, layout)
|
||||
for index,layoutRow in pairs(layout) do
|
||||
if index == rowIndex then
|
||||
layoutRow.enter = true
|
||||
else
|
||||
layoutRow.enter = nil
|
||||
end
|
||||
end
|
||||
return layout
|
||||
end
|
||||
|
||||
local function layoutBackspace(rowIndex, layout)
|
||||
for index,layoutRow in pairs(layout) do
|
||||
if index == rowIndex then
|
||||
layoutRow.backspace = true
|
||||
else
|
||||
layoutRow.backspace = nil
|
||||
end
|
||||
end
|
||||
return layout
|
||||
end
|
||||
|
||||
local function layoutSpacebar(layout)
|
||||
table.insert(layout, { spacebar = true, "" })
|
||||
return layout
|
||||
end
|
||||
|
||||
local function appendNumberLayout(cond, layout)
|
||||
return cond and layoutNumbers(layout) or layout
|
||||
end
|
||||
|
||||
Keyboard.Layout = {
|
||||
ItemSearch = function()
|
||||
return layoutBackspace(1, layoutNumbers({
|
||||
{ "qwertyuiop" },
|
||||
{ "asdfghjkl" },
|
||||
{ "zxcvbnm_:" }
|
||||
}))
|
||||
end,
|
||||
English = function(numberRow)
|
||||
return layoutSpacebar(layoutBackspace(1, appendNumberLayout(numberRow, layoutEnter(2, {
|
||||
{ "qwertyuiop" },
|
||||
{ "asdfghjkl" },
|
||||
{ "zxcvbnm" }
|
||||
}))))
|
||||
end,
|
||||
Swedish = function(numberRow)
|
||||
return layoutSpacebar(layoutBackspace(1, appendNumberLayout(numberRow, layoutEnter(2, {
|
||||
{ "qwertyuiopå" },
|
||||
{ "asdfghjklöä" },
|
||||
{ "zxcvbnm" }
|
||||
}))))
|
||||
end,
|
||||
Special = function(numberRow)
|
||||
return layoutSpacebar(layoutBackspace(1, appendNumberLayout(numberRow, {
|
||||
{ "!\"#¤%&/()=?" },
|
||||
{ "@£${[]}\\+^" },
|
||||
{ "§<>|;:,.-_'*" }
|
||||
})))
|
||||
end
|
||||
}
|
||||
|
||||
return Keyboard
|
||||
26
gfx/list.lua
26
gfx/list.lua
@@ -1,23 +1,26 @@
|
||||
-- TODO: Rename to "Column" to better represent functionality
|
||||
|
||||
local Event = require("gfx.event")
|
||||
local Element = require("gfx.element")
|
||||
local Prop = require("gfx.prop")
|
||||
local Orientation = require("gfx.prop.orientation")
|
||||
local Children = require("gfx.prop.children")
|
||||
local List = Prop.attach(Prop.attach(Element:new(), Orientation), Children)
|
||||
local List = Element:new()
|
||||
|
||||
function List:adjustPositions(from)
|
||||
from = from or 1
|
||||
local vertical = self:isVertical()
|
||||
local newDims = 1
|
||||
local getDim = vertical and function(e) return e:getHeight() end or function(e) return e:getWidth() end
|
||||
for _,element in self:_iterateChildren{ to = from } do
|
||||
newDims = newDims + getDim(element)
|
||||
local children = self:_children()
|
||||
for i=1,from-1 do
|
||||
newDims = newDims + getDim(children[i])
|
||||
end
|
||||
|
||||
local setDim = vertical and function(e, dim) e:setY(dim) end or function(e, dim) e:setX(dim) end
|
||||
for _, element in self:_iterateChildren{ from = from } do
|
||||
setDim(element, newDims)
|
||||
newDims = newDims + getDim(element)
|
||||
for i=from,#children do
|
||||
setDim(children[i], newDims)
|
||||
newDims = newDims + getDim(children[i])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -57,16 +60,7 @@ end
|
||||
|
||||
function List:_reload()
|
||||
Element._reload(self)
|
||||
|
||||
-- Reload child windows
|
||||
local win = self:_getWindow()
|
||||
for _,child in self:_iterateChildren() do
|
||||
if child:_getWindow() ~= win then
|
||||
child:setParent(win)
|
||||
end
|
||||
end
|
||||
|
||||
self:adjustPositions()
|
||||
end
|
||||
|
||||
return List
|
||||
return Prop.attach(Prop.attach(List, Orientation), Children)
|
||||
@@ -8,13 +8,17 @@ local Padding = Element:new{
|
||||
element = nil
|
||||
}
|
||||
|
||||
function Padding:_repositionElement()
|
||||
self.element:setPos(self:getPaddingLeft() + 1, self:getPaddingTop() + 1)
|
||||
self:resize{
|
||||
width = self:getWidth(),
|
||||
height = self:getHeight()
|
||||
}
|
||||
end
|
||||
|
||||
function Padding:new(opts)
|
||||
local obj = Element.new(self, opts)
|
||||
obj.element:setPos(obj:getPaddingLeft() + 1, obj:getPaddingTop() + 1)
|
||||
obj:resize{
|
||||
width = obj:getWidth(),
|
||||
height = obj:getHeight()
|
||||
}
|
||||
obj:_repositionElement()
|
||||
obj.element:setParent(obj:_getWindow())
|
||||
return obj
|
||||
end
|
||||
@@ -45,19 +49,40 @@ function Padding:setDirty(fullInvalidate)
|
||||
end
|
||||
|
||||
function Padding:getPaddingLeft()
|
||||
return self.left
|
||||
return self.left or 0
|
||||
end
|
||||
|
||||
function Padding:getPaddingRight()
|
||||
return self.right
|
||||
return self.right or 0
|
||||
end
|
||||
|
||||
function Padding:getPaddingTop()
|
||||
return self.top
|
||||
return self.top or 0
|
||||
end
|
||||
|
||||
function Padding:getPaddingBottom()
|
||||
return self.bottom
|
||||
return self.bottom or 0
|
||||
end
|
||||
|
||||
local function compareSet(value, default, didChange)
|
||||
return value or default, didChange or (value ~= nil and value ~= default)
|
||||
end
|
||||
|
||||
function Padding:setPadding(opts)
|
||||
if type(opts) ~= "table" then
|
||||
return
|
||||
end
|
||||
|
||||
local changed = false
|
||||
self.left, changed = compareSet(opts.left, self.left, changed)
|
||||
self.right, changed = compareSet(opts.right, self.right, changed)
|
||||
self.top, changed = compareSet(opts.top, self.top, changed)
|
||||
self.bottom, changed = compareSet(opts.bottom, self.bottom, changed)
|
||||
|
||||
if changed then
|
||||
self:_reload()
|
||||
self:_repositionElement()
|
||||
end
|
||||
end
|
||||
|
||||
function Padding:getInnerWidth()
|
||||
@@ -68,6 +93,10 @@ function Padding:getInnerHeight()
|
||||
return self.element:getHeight()
|
||||
end
|
||||
|
||||
function Padding:getInnerElement()
|
||||
return self.element
|
||||
end
|
||||
|
||||
function Padding:getWidth()
|
||||
return self:getInnerWidth() + self:getPaddingLeft() + self:getPaddingRight()
|
||||
end
|
||||
@@ -81,16 +110,18 @@ function Padding:findById(id)
|
||||
end
|
||||
|
||||
function Padding:handleEvent(evt)
|
||||
if Element.handleEvent(self, evt) then
|
||||
return true
|
||||
end
|
||||
|
||||
local evtLocalCoords = Event.toElementLocalPosition(evt, self)
|
||||
if Event.isClickEvent(evt) then
|
||||
return Event.containsClick(self, evt) and self.element:handleEvent(evtLocalCoords)
|
||||
else
|
||||
return self.element:handleEvent(evtLocalCoords)
|
||||
if Event.containsClick(self, evt) and self.element:handleEvent(evtLocalCoords) then
|
||||
return true
|
||||
end
|
||||
else
|
||||
if self.element:handleEvent(evtLocalCoords) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return Element.handleEvent(self, evt)
|
||||
end
|
||||
|
||||
function Padding:_reload()
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
local Element = require("gfx.element")
|
||||
local Prop = require("gfx.prop")
|
||||
local Orientation = require("gfx.prop.orientation")
|
||||
|
||||
local BlankElement = Element:new()
|
||||
@@ -15,30 +16,62 @@ local function round(value, biasDown)
|
||||
return (((not biasDown) and decimal < 0.5) or ((not not biasDown) and decimal <= 0.5)) and math.floor(value) or math.ceil(value)
|
||||
end
|
||||
|
||||
local Progress = Orientation.with(Element:new{
|
||||
local Progress = Element:new{
|
||||
progress = 0.0,
|
||||
active = nil,
|
||||
inactive = nil
|
||||
})
|
||||
}
|
||||
|
||||
function Progress:new(o)
|
||||
local obj = o or {}
|
||||
local template = o or {}
|
||||
template.fgColor = template.fgColor or colors.red
|
||||
template.bgColor = template.bgColor or colors.gray
|
||||
|
||||
local obj = Element.new(self, template)
|
||||
obj.active = BlankElement:new()
|
||||
obj.inactive = BlankElement:new()
|
||||
return Element.new(self, obj)
|
||||
obj:_refreshColors()
|
||||
return obj
|
||||
end
|
||||
|
||||
function Progress:setFgColor(color)
|
||||
self.active:setBgColor(color)
|
||||
end
|
||||
|
||||
function Progress:setBgColor(color)
|
||||
self.inactive:setBgColor(color)
|
||||
end
|
||||
|
||||
function Progress:_updateProgress()
|
||||
local width, height = self:getWidth(), self:getHeight()
|
||||
if self:isVertical() then
|
||||
self.active:resize(width, round(self:getProgress() * height))
|
||||
self.inactive:resize(width, round((1 - self:getProgress()) * height, true))
|
||||
self.active:resize{
|
||||
width = width,
|
||||
height = round(self:getProgress() * height)
|
||||
}
|
||||
self.inactive:resize{
|
||||
width = width,
|
||||
height = round((1 - self:getProgress()) * height, true)
|
||||
}
|
||||
self.inactive:setY(self.active:getY() + self.active:getHeight())
|
||||
else
|
||||
self.active:resize(round(self:getProgress() * width), height)
|
||||
self.active:resize(round((1 - self:getProgress()) * width, true), height)
|
||||
self.inactive:setX(self.active:setX() + self.active:getWidth())
|
||||
self.active:resize{
|
||||
width = round(self:getProgress() * width),
|
||||
height = height
|
||||
}
|
||||
self.inactive:resize{
|
||||
width = round((1 - self:getProgress()) * width, true),
|
||||
height = height
|
||||
}
|
||||
self.inactive:setX(self.active:getX() + self.active:getWidth())
|
||||
end
|
||||
|
||||
self:_refreshColors()
|
||||
end
|
||||
|
||||
function Progress:_refreshColors()
|
||||
self:setFgColor(self:getFgColor())
|
||||
self:setBgColor(self:getBgColor())
|
||||
end
|
||||
|
||||
function Progress:setProgress(progress)
|
||||
@@ -64,6 +97,12 @@ function Progress:_isDirty()
|
||||
return Element._isDirty(self) or self.active:_isDirty() or self.inactive:_isDirty()
|
||||
end
|
||||
|
||||
function Progress:setDirty(fullInvalidate)
|
||||
Element.setDirty(self, fullInvalidate)
|
||||
self.active:setDirty(self, fullInvalidate)
|
||||
self.inactive:setDirty(self, fullInvalidate)
|
||||
end
|
||||
|
||||
function Progress:_reload()
|
||||
Element._reload(self)
|
||||
|
||||
@@ -74,4 +113,4 @@ function Progress:_reload()
|
||||
self:_updateProgress()
|
||||
end
|
||||
|
||||
return Progress
|
||||
return Prop.attach(Progress, Orientation, Orientation.HORIZONTAL)
|
||||
@@ -1,26 +1,35 @@
|
||||
local Prop = require("gfx.prop")
|
||||
local Element = require("gfx.element")
|
||||
local Event = require("gfx.event")
|
||||
local Children = Prop:new{ defaultState = {} }
|
||||
local Children = Prop:new{ defaultState = {}, uid = "CHILDREN" }
|
||||
|
||||
function Children:with(elementType)
|
||||
local propSelf = self
|
||||
function elementType:_iterateChildren(opts)
|
||||
local func, tbl, start = ipairs(propSelf:getState(self))
|
||||
return function(t, i)
|
||||
if i == opts.to then
|
||||
local defaultReload = elementType._reload
|
||||
function elementType:_iterateChildren()
|
||||
return ipairs(self:_children())
|
||||
end
|
||||
|
||||
function elementType:_iterateChildrenReversed()
|
||||
local children = self:_children()
|
||||
return function(tbl, idx)
|
||||
if idx <= 1 then
|
||||
return nil, nil
|
||||
end
|
||||
return func(t, i)
|
||||
end, tbl, (opts and opts.from) or start
|
||||
return idx - 1, tbl[idx - 1]
|
||||
end, children, #children + 1
|
||||
end
|
||||
|
||||
function elementType:_children()
|
||||
return propSelf:getState(self)
|
||||
end
|
||||
|
||||
function elementType:childCount()
|
||||
return #propSelf:getState(self)
|
||||
return #self:_children()
|
||||
end
|
||||
|
||||
function elementType:_childAt(index)
|
||||
return propSelf:getState()[elementType:_validChildIndex(index)]
|
||||
return self:_children()[elementType:_validChildIndex(index)]
|
||||
end
|
||||
|
||||
function elementType:_validChildIndex(from)
|
||||
@@ -73,12 +82,10 @@ function Children:with(elementType)
|
||||
|
||||
function elementType:setDirty(fullInvalidate)
|
||||
Element.setDirty(self, fullInvalidate)
|
||||
if fullInvalidate then
|
||||
for _,child in self:_iterateChildren() do
|
||||
child:setDirty(fullInvalidate)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function elementType:_isDirty()
|
||||
if Element._isDirty(self) then
|
||||
@@ -94,11 +101,14 @@ function Children:with(elementType)
|
||||
end
|
||||
|
||||
function elementType:draw()
|
||||
local wasDirty = Element._isDirty(self);
|
||||
local dirty = Element.draw(self)
|
||||
if dirty then
|
||||
if wasDirty then
|
||||
self:_getWindow().clear()
|
||||
end
|
||||
if dirty then
|
||||
for _,child in self:_iterateChildren() do
|
||||
child:draw()
|
||||
dirty = child:draw() or dirty
|
||||
end
|
||||
end
|
||||
return dirty
|
||||
@@ -114,7 +124,7 @@ function Children:with(elementType)
|
||||
end
|
||||
|
||||
function elementType:findChildById(id)
|
||||
for _,child in self:_iterateChildren() do
|
||||
for _,child in self:_iterateChildrenReversed() do
|
||||
local result = child:findById(id)
|
||||
if result then
|
||||
return result
|
||||
@@ -124,10 +134,6 @@ function Children:with(elementType)
|
||||
end
|
||||
|
||||
function elementType:handleEvent(evt)
|
||||
if Element.handleEvent(self, evt) then
|
||||
return true
|
||||
end
|
||||
|
||||
local evtLocalCoords = Event.toElementLocalPosition(evt, self)
|
||||
|
||||
if Event.isClickEvent(evt) then
|
||||
@@ -136,19 +142,31 @@ function Children:with(elementType)
|
||||
return false
|
||||
end
|
||||
|
||||
for _,child in self:_iterateChildren() do
|
||||
for _,child in self:_iterateChildrenReversed() do
|
||||
if child:handleEvent(evtLocalCoords) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
else
|
||||
for _,child in self:_iterateChildren() do
|
||||
for _,child in self:_iterateChildrenReversed() do
|
||||
if child:handleEvent(evtLocalCoords) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
return Element.handleEvent(self, evt)
|
||||
end
|
||||
|
||||
function elementType:_reload()
|
||||
if defaultReload ~= nil then
|
||||
defaultReload(self)
|
||||
end
|
||||
|
||||
-- Reload child windows
|
||||
local win = self:_getWindow()
|
||||
for _,child in self:_iterateChildren() do
|
||||
child:setParent(win)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -2,11 +2,15 @@ local Prop = {}
|
||||
Prop.__index = Prop
|
||||
|
||||
function Prop:getState(element)
|
||||
return element[self]
|
||||
return element[self:getId()]
|
||||
end
|
||||
|
||||
function Prop:setState(element, state)
|
||||
element[self] = state
|
||||
element[self:getId()] = state
|
||||
end
|
||||
|
||||
function Prop:getId()
|
||||
return self.uid
|
||||
end
|
||||
|
||||
function Prop.attach(elementType, prop, defaultState)
|
||||
@@ -15,10 +19,12 @@ function Prop.attach(elementType, prop, defaultState)
|
||||
end
|
||||
|
||||
function Prop:new(o)
|
||||
local obj = o or {}
|
||||
setmetatable(obj, Prop)
|
||||
obj.__index = obj
|
||||
return obj
|
||||
if o.uid == nil then
|
||||
error("Property must carry a unique identifier")
|
||||
end
|
||||
setmetatable(o, Prop)
|
||||
o.__index = o
|
||||
return o
|
||||
end
|
||||
|
||||
return Prop
|
||||
@@ -1,5 +1,5 @@
|
||||
local Prop = require("gfx.prop")
|
||||
local Orientation = Prop:new{ defaultState = false }
|
||||
local Orientation = Prop:new{ defaultState = false, uid = "ORIENTATION" }
|
||||
|
||||
function Orientation:with(elementType)
|
||||
local propSelf = self
|
||||
@@ -22,4 +22,7 @@ function Orientation:with(elementType)
|
||||
return elementType
|
||||
end
|
||||
|
||||
Orientation.VERTICAL = true
|
||||
Orientation.HORIZONTAL = false
|
||||
|
||||
return Orientation
|
||||
0
gfx/render/init.lua
Normal file
0
gfx/render/init.lua
Normal file
243
gfx/render/windowbuffer.lua
Normal file
243
gfx/render/windowbuffer.lua
Normal file
@@ -0,0 +1,243 @@
|
||||
local Util = require("util")
|
||||
|
||||
local DEFAULT_C = " "
|
||||
local DEFAULT_FG = colors.white
|
||||
local DEFAULT_BG = colors.black
|
||||
|
||||
local function ensureC(cVal)
|
||||
return (cVal ~= nil and #cVal == 1 and cVal) or DEFAULT_C
|
||||
end
|
||||
|
||||
local WindowBuffer = {}
|
||||
|
||||
-- Create a shallow wrapper for window object
|
||||
function WindowBuffer.wrapWindow(w, defaultC, defaultFg, defaultBg, clearColor)
|
||||
defaultC = ensureC(defaultC)
|
||||
defaultFg = defaultFg or DEFAULT_FG
|
||||
defaultBg = defaultBg or DEFAULT_BG
|
||||
clearColor = clearColor or DEFAULT_BG
|
||||
|
||||
local obj = {}
|
||||
|
||||
function obj.blit(text, fg, bg)
|
||||
w.blit(text, fg, bg)
|
||||
end
|
||||
|
||||
function obj.flush() end
|
||||
function obj.flushPartial() end
|
||||
function obj.getSize()
|
||||
return w.getSize()
|
||||
end
|
||||
|
||||
function obj._setPixel(x, y, c, fg, bg)
|
||||
w.setCursorPos(x, y)
|
||||
w.setFgColor(fg or defaultFg)
|
||||
w.setBgColor(bg or defaultBg)
|
||||
w.write(c or defaultC)
|
||||
end
|
||||
|
||||
function obj.setPixel(x, y, c, fg, bg)
|
||||
local width, height = obj.getSize()
|
||||
Util.boundCheck(x, y, width, height)
|
||||
obj._setPixel(x, y, c, fg, bg)
|
||||
end
|
||||
|
||||
-- Partial writes are not possible for native window access
|
||||
obj._writePixel = obj._setPixel
|
||||
obj.writePixel = obj.setPixel
|
||||
|
||||
function obj.setCursorPos(x, y)
|
||||
w.setCursorPos(x, y)
|
||||
end
|
||||
|
||||
function obj.getCursorPos()
|
||||
return w.getCursorPos()
|
||||
end
|
||||
|
||||
function obj.clear()
|
||||
w.setBgColor(clearColor)
|
||||
--w.setFgColor(clearColor) -- Shouldn't matter for native clear
|
||||
w.clear()
|
||||
end
|
||||
|
||||
return obj
|
||||
end
|
||||
|
||||
function WindowBuffer.new(width, height, clearC, clearFg, clearBg)
|
||||
clearC = ensureC(clearC)
|
||||
clearFg = clearFg or DEFAULT_FG
|
||||
clearBg = clearBg or DEFAULT_BG
|
||||
|
||||
local obj = {}
|
||||
|
||||
local function setState(o, c, fg, bg)
|
||||
o.c = c
|
||||
o.bg = bg
|
||||
o.fg = fg
|
||||
return o
|
||||
end
|
||||
|
||||
local function writeState(o, c, fg, bg)
|
||||
o.c = c or o.c
|
||||
o.bg = bg or o.bg
|
||||
o.fg = fg or o.fg
|
||||
return o
|
||||
end
|
||||
|
||||
local buffer = {}
|
||||
for i=1,height do
|
||||
local line = {}
|
||||
for j=1,width do
|
||||
line[j] = setState({})
|
||||
end
|
||||
buffer[i] = line
|
||||
end
|
||||
|
||||
local pX, pY = 1, 1
|
||||
|
||||
function obj.blit(text, fg, bg)
|
||||
assert(#text == #fg, "Foreground color blit length must match text length")
|
||||
assert(#text == #bg, "Background color blit length must match text length")
|
||||
|
||||
local line = buffer[pY]
|
||||
local baseX = pX
|
||||
for i=1,math.min(#text, width - baseX - 1) do
|
||||
local blitChar = line[i]
|
||||
blitChar.c = text:sub(i, i)
|
||||
blitChar.fg = fg:sub(i, i)
|
||||
blitChar.bg = bg:sub(i, i)
|
||||
end
|
||||
|
||||
pX = pX + #text
|
||||
end
|
||||
|
||||
function obj._setPixel(x, y, c, fg, bg)
|
||||
setState(buffer[y][x], c, fg, bg)
|
||||
end
|
||||
|
||||
function obj.setPixel(x, y, c, fg, bg)
|
||||
Util.boundCheck(x, y, width, height)
|
||||
obj._setPixel(x, y, c, fg, bg)
|
||||
end
|
||||
|
||||
function obj._writePixel(x, y, c, fg, bg)
|
||||
writeState(buffer[y][x], c, fg, bg)
|
||||
end
|
||||
|
||||
function obj.writePixel(x, y, c, fg, bg)
|
||||
Util.boundCheck(x, y, width, height)
|
||||
obj._writePixel(x, y, c, fg, bg)
|
||||
end
|
||||
|
||||
function obj.setCursorPos(x, y)
|
||||
pX, pY = x, y
|
||||
end
|
||||
|
||||
function obj.getCursorPos()
|
||||
return pX, pY
|
||||
end
|
||||
|
||||
function obj.clear()
|
||||
for i=1,height do
|
||||
local line = buffer[i]
|
||||
for j=1,width do
|
||||
setState(line[j], clearC, clearFg, clearBg)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function obj.flush(win, offsetX, offsetY)
|
||||
for i=1,height do
|
||||
local line = buffer[i]
|
||||
for j=1,width do
|
||||
local entry = line[j]
|
||||
win._writePixel(j + offsetX, i + offsetY, entry.c, entry.fg, entry.bg)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function obj.flushPartial(win, winOffsetX, winOffsetY, baseX, baseY, flushWidth, flushHeight)
|
||||
for i=baseY,baseY+flushHeight do
|
||||
local line = buffer[i]
|
||||
for j=baseX,baseX+flushWidth do
|
||||
local entry = line[j]
|
||||
win._writePixel(j + winOffsetX, i + winOffsetY, entry.c, entry.fg, entry.bg)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return obj
|
||||
end
|
||||
|
||||
function WindowBuffer.shallowChild(windowBuffer, width, height, offsetX, offsetY, clearC, clearFg, clearBg)
|
||||
-- Assume static for now
|
||||
local parentWidth, parentHeight = windowBuffer.getSize()
|
||||
Util.boundCheck(offsetX, offsetY, parentWidth, parentHeight)
|
||||
Util.boundCheck(offsetX + width, offsetY + height, parentWidth, parentHeight)
|
||||
|
||||
clearC = ensureC(clearC)
|
||||
clearFg = clearFg or DEFAULT_FG
|
||||
clearBg = clearBg or DEFAULT_BG
|
||||
|
||||
local obj = {}
|
||||
|
||||
local pX, pY = 1, 1
|
||||
|
||||
local function setCursorPos()
|
||||
windowBuffer.setCursorPos(pX + offsetX, pY + offsetY)
|
||||
end
|
||||
|
||||
function obj.blit(text, fg, bg)
|
||||
setCursorPos()
|
||||
windowBuffer.blit(text, fg, bg)
|
||||
pX = pX + #text
|
||||
end
|
||||
|
||||
function obj._setPixel(c, fg, bg)
|
||||
setCursorPos()
|
||||
windowBuffer._setPixel(c, fg, bg)
|
||||
end
|
||||
|
||||
function obj.setPixel(c, fg, bg)
|
||||
setCursorPos()
|
||||
windowBuffer.setPixel(c, fg, bg)
|
||||
end
|
||||
|
||||
function obj._writePixel(c, fg, bg)
|
||||
setCursorPos()
|
||||
windowBuffer._writePixel(c, fg, bg)
|
||||
end
|
||||
|
||||
function obj.writePixel(c, fg, bg)
|
||||
setCursorPos()
|
||||
windowBuffer.writePixel(c, fg, bg)
|
||||
end
|
||||
|
||||
function obj.setCursorPos(x, y)
|
||||
pX, pY = x, y
|
||||
end
|
||||
|
||||
function obj.getCursorPos()
|
||||
return pX, pY
|
||||
end
|
||||
|
||||
function obj.getSize()
|
||||
return width, height
|
||||
end
|
||||
|
||||
function obj.clear()
|
||||
for i=1,height do
|
||||
for j=1,width do
|
||||
windowBuffer._setPixel(j + offsetX, i + offsetY, clearC, clearFg, clearBg)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Reconsider implementation?
|
||||
obj.flush = windowBuffer.flush
|
||||
obj.flushPartial = windowBuffer.flushPartial
|
||||
|
||||
return obj
|
||||
end
|
||||
|
||||
return WindowBuffer
|
||||
@@ -1,13 +1,6 @@
|
||||
local Element = require("gfx.element")
|
||||
local Text = Element:new{ text = "" }
|
||||
|
||||
function Text:new(o)
|
||||
o.width = nil
|
||||
o.height = nil
|
||||
|
||||
return Element.new(self, o)
|
||||
end
|
||||
|
||||
function Text:setText(text)
|
||||
local current = self:getText()
|
||||
if current ~= text then
|
||||
|
||||
33
gfx/textprogress.lua
Normal file
33
gfx/textprogress.lua
Normal file
@@ -0,0 +1,33 @@
|
||||
local Text = require("gfx.text")
|
||||
local TextProgress = Text:new()
|
||||
|
||||
function TextProgress:getProgress()
|
||||
return math.max(0, math.min(self.progress or 0, 1))
|
||||
end
|
||||
|
||||
function TextProgress:setProgress(progress)
|
||||
self.progress = progress
|
||||
self:setDirty()
|
||||
end
|
||||
|
||||
function TextProgress:draw()
|
||||
getmetatable(getmetatable(getmetatable(self))).draw(self)
|
||||
local text = self:getText()
|
||||
if #text == 0 then
|
||||
return
|
||||
end
|
||||
|
||||
local activePart = math.ceil(#text * self:getProgress())
|
||||
|
||||
local fg = { }
|
||||
local bg = { }
|
||||
|
||||
for i=1,#text do
|
||||
table.insert(fg, colors.toBlit(i > activePart and self:getFgColor() or self:getBgColor()))
|
||||
table.insert(bg, colors.toBlit(i > activePart and self:getBgColor() or self:getFgColor()))
|
||||
end
|
||||
|
||||
self:_getWindow().blit(text, table.concat(fg), table.concat(bg))
|
||||
end
|
||||
|
||||
return TextProgress
|
||||
1081
itemcontroller.lua
1081
itemcontroller.lua
File diff suppressed because it is too large
Load Diff
339
logging.lua
Normal file
339
logging.lua
Normal file
@@ -0,0 +1,339 @@
|
||||
local Logging = {}
|
||||
|
||||
local LogLevel = {
|
||||
TRACE = 1,
|
||||
DEBUG = 2,
|
||||
INFO = 3,
|
||||
WARNING = 4,
|
||||
CRITICAL = 5,
|
||||
ERROR = 6
|
||||
}
|
||||
|
||||
LogLevel._DEFAULT = LogLevel.TRACE
|
||||
|
||||
for k,v in pairs(LogLevel) do
|
||||
LogLevel[v] = k
|
||||
end
|
||||
|
||||
function LogLevel.isValid(value)
|
||||
return LogLevel[LogLevel[value]] ~= nil
|
||||
end
|
||||
|
||||
function LogLevel.asNumber(value)
|
||||
return type(value) == "number" and value or LogLevel[value]
|
||||
end
|
||||
|
||||
function LogLevel.isGreaterOrEqual(a, b)
|
||||
return a >= b
|
||||
end
|
||||
|
||||
local Logger = {
|
||||
ignoreGlobals = true
|
||||
}
|
||||
Logger.__index = Logger
|
||||
|
||||
|
||||
local function combineOutputs(...)
|
||||
local args = {...}
|
||||
if #args == 0 then
|
||||
return function() end
|
||||
elseif #args == 1 then
|
||||
return args[1]
|
||||
end
|
||||
return function(text, level)
|
||||
for _,v in ipairs(args) do
|
||||
v(text, level)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- TODO: Split logger outputs and specify LogLevel for each
|
||||
function Logger:new(...)
|
||||
local logger = { output = combineOutputs(...) or function() end }
|
||||
setmetatable(logger, self)
|
||||
logger.__index = logger
|
||||
|
||||
return logger
|
||||
end
|
||||
|
||||
function Logger:setIgnoreGlobals(ignoreGlobals)
|
||||
self.ignoreGlobals = ignoreGlobals
|
||||
end
|
||||
|
||||
local LogPlain = {}
|
||||
function LogPlain.of(value, deep)
|
||||
local obj = { value, deep = deep }
|
||||
setmetatable(obj, LogPlain)
|
||||
return obj
|
||||
end
|
||||
|
||||
function LogPlain.is(value)
|
||||
return type(value) == "table" and getmetatable(value) == LogPlain
|
||||
end
|
||||
|
||||
function LogPlain.isDeep(value)
|
||||
return LogPlain.is(value) and value.deep
|
||||
end
|
||||
|
||||
function LogPlain.getValue(value)
|
||||
return LogPlain.is(value) and value[1] or value
|
||||
end
|
||||
|
||||
local RecursionSentinel = {}
|
||||
function RecursionSentinel.make(table, index)
|
||||
local obj = { table = table, index = index }
|
||||
setmetatable(obj, RecursionSentinel)
|
||||
return obj
|
||||
end
|
||||
|
||||
function RecursionSentinel.isSentinel(value)
|
||||
return type(value) == "table" and getmetatable(value) == RecursionSentinel
|
||||
end
|
||||
|
||||
function RecursionSentinel.isKnown(knownTables, value)
|
||||
return knownTables[value] ~= nil
|
||||
end
|
||||
|
||||
function RecursionSentinel.getSentinel(knownTables, value)
|
||||
return knownTables[value] or RecursionSentinel.add(knownTables, value)
|
||||
end
|
||||
|
||||
function RecursionSentinel.add(knownTables, value)
|
||||
local sentinel = RecursionSentinel.make(value, #knownTables)
|
||||
knownTables[value] = sentinel
|
||||
return sentinel
|
||||
end
|
||||
|
||||
function RecursionSentinel.remove(knownTables, value)
|
||||
local sentinel = knownTables[value]
|
||||
if sentinel then
|
||||
sentinel.index = nil
|
||||
end
|
||||
knownTables[value] = nil
|
||||
end
|
||||
|
||||
local function cloneNonRecursive(inValue, sentinels)
|
||||
local value = LogPlain.getValue(inValue)
|
||||
if type(value) == "table" then
|
||||
local wrapLogPlain = LogPlain.is(inValue) and function(v) return LogPlain.of(v, inValue.deep) end or function(v) return v end
|
||||
if RecursionSentinel.isKnown(sentinels, value) then
|
||||
return wrapLogPlain(RecursionSentinel.getSentinel(sentinels, value))
|
||||
end
|
||||
|
||||
local sentinel = RecursionSentinel.getSentinel(sentinels, value)
|
||||
local clone = {}
|
||||
for i,v in ipairs(value) do
|
||||
clone[i] = cloneNonRecursive(v, sentinels)
|
||||
end
|
||||
for k,v in pairs(value) do
|
||||
clone[cloneNonRecursive(k, sentinels)] = cloneNonRecursive(v, sentinels)
|
||||
end
|
||||
RecursionSentinel.remove(sentinels, sentinel.table)
|
||||
|
||||
sentinel.value = clone
|
||||
return wrapLogPlain(sentinel.value)
|
||||
else
|
||||
return inValue
|
||||
end
|
||||
end
|
||||
|
||||
local G_ = {}
|
||||
for k,v in pairs(_G) do
|
||||
G_[v] = k
|
||||
end
|
||||
|
||||
local function _simpleStringify(inValue, builder, ignoreGlobals, skipFunctions, plain)
|
||||
local value = LogPlain.getValue(inValue)
|
||||
local isPlain = plain or LogPlain.is(inValue)
|
||||
local isDeep = plain or LogPlain.isDeep(inValue)
|
||||
if type(value) == "table" then
|
||||
if not isPlain then
|
||||
table.insert(builder, "<")
|
||||
end
|
||||
if ignoreGlobals and G_[value] ~= nil then
|
||||
table.insert(builder, "_G.")
|
||||
table.insert(builder, G_[value])
|
||||
elseif RecursionSentinel.isSentinel(value) then
|
||||
if isPlain then
|
||||
table.insert(builder, "<recurse_")
|
||||
table.insert(builder, tostring(value.index))
|
||||
else
|
||||
table.insert(builder, "recurse ")
|
||||
table.insert(builder, tostring(value.value))
|
||||
end
|
||||
table.insert(builder, ">")
|
||||
else
|
||||
if not isPlain then
|
||||
table.insert(builder, tostring(value))
|
||||
table.insert(builder, ">")
|
||||
end
|
||||
table.insert(builder, "{")
|
||||
local first = true
|
||||
for _,v in ipairs(value) do
|
||||
if not first then
|
||||
table.insert(builder, ",")
|
||||
else
|
||||
first = false
|
||||
end
|
||||
_simpleStringify(v, builder, ignoreGlobals, skipFunctions, isDeep)
|
||||
end
|
||||
first = #value == 0
|
||||
local len = #value
|
||||
for k,v in pairs(value) do
|
||||
-- Array elements already printed
|
||||
if type(k) == "number" and k <= len then
|
||||
goto continue
|
||||
end
|
||||
if not first then
|
||||
table.insert(builder, ",")
|
||||
else
|
||||
first = false
|
||||
end
|
||||
-- String key values are always plain-printed
|
||||
_simpleStringify(k, builder, ignoreGlobals, skipFunctions, isDeep or type(k) == "string")
|
||||
table.insert(builder, "=")
|
||||
_simpleStringify(v, builder, ignoreGlobals, skipFunctions, isDeep)
|
||||
::continue::
|
||||
end
|
||||
table.insert(builder, "}")
|
||||
end
|
||||
elseif type(value) == "string" then
|
||||
if not isPlain then
|
||||
table.insert(builder, "\"")
|
||||
end
|
||||
table.insert(builder, tostring(value))
|
||||
if not isPlain then
|
||||
table.insert(builder, "\"")
|
||||
end
|
||||
elseif type(value) == "function" then
|
||||
if not skipFunctions then
|
||||
local info = debug.getinfo(value, "u")
|
||||
if not isPlain then
|
||||
table.insert(builder, tostring(value))
|
||||
end
|
||||
table.insert(builder, "(")
|
||||
table.insert(builder, tostring(info.nparams))
|
||||
if info.isvararg then
|
||||
table.insert(builder, "*")
|
||||
end
|
||||
table.insert(builder, ")")
|
||||
end
|
||||
elseif not skipFunctions or type(value) ~= "function" then
|
||||
table.insert(builder, tostring(value))
|
||||
end
|
||||
end
|
||||
|
||||
function Logging.deepStringify(value)
|
||||
local collect = {}
|
||||
_simpleStringify(cloneNonRecursive(value, {}), collect, false, false)
|
||||
return table.concat(collect)
|
||||
end
|
||||
|
||||
function Logger:_doPrint(level, message, ...)
|
||||
local result = { tostring(LogLevel[level]), Logging.deepStringify(message) }
|
||||
for _,v in ipairs({...}) do
|
||||
table.insert(result, Logging.deepStringify(v))
|
||||
end
|
||||
self.output(table.concat(result, " "), level)
|
||||
end
|
||||
|
||||
function Logger:trace(message, ...)
|
||||
self:_doPrint(LogLevel.TRACE, message, ...)
|
||||
end
|
||||
|
||||
function Logger:debug(message, ...)
|
||||
self:_doPrint(LogLevel.DEBUG, message, ...)
|
||||
end
|
||||
|
||||
function Logger:info(message, ...)
|
||||
self:_doPrint(LogLevel.INFO, message, ...)
|
||||
end
|
||||
|
||||
function Logger:warn(message, ...)
|
||||
self:_doPrint(LogLevel.WARNING, message, ...)
|
||||
end
|
||||
|
||||
function Logger:critical(message, ...)
|
||||
self:_doPrint(LogLevel.CRITICAL, message, ...)
|
||||
end
|
||||
|
||||
function Logger:error(message, ...)
|
||||
self:_doPrint(LogLevel.ERROR, message, ...)
|
||||
end
|
||||
|
||||
function Logger:setOutput(...)
|
||||
self.output = combineOutputs(...)
|
||||
end
|
||||
|
||||
function Logger.plain(value, deep)
|
||||
return LogPlain.of(value, deep)
|
||||
end
|
||||
|
||||
Logging.Logger = Logger
|
||||
Logging.LogLevel = LogLevel
|
||||
|
||||
function Logging.getGlobalLogger()
|
||||
if _G._GLOBAL_LOGGER == nil then
|
||||
_G._GLOBAL_LOGGER = Logger:new{ output = function() end }
|
||||
end
|
||||
return _G._GLOBAL_LOGGER
|
||||
end
|
||||
|
||||
function Logging.firstLoad(...)
|
||||
Logging.resetAll()
|
||||
local logger = Logging.getGlobalLogger()
|
||||
logger:setOutput(...)
|
||||
return logger
|
||||
end
|
||||
|
||||
Logging.OUTPUTS = {}
|
||||
function Logging.OUTPUTS.file(name, logLevel)
|
||||
logLevel = logLevel or LogLevel._DEFAULT
|
||||
|
||||
local doWrite = function(file, text)
|
||||
file.write(text)
|
||||
file.write("\n")
|
||||
file.flush()
|
||||
end
|
||||
local file = fs.open(name, "w+")
|
||||
return function(text, level)
|
||||
if level < logLevel then
|
||||
return
|
||||
end
|
||||
|
||||
if not pcall(doWrite, file, text) then
|
||||
local dRes, dRet = pcall(fs.delete, name)
|
||||
if not dRes then
|
||||
print("Error deleting logfile ("..name.."): "..tostring(dRet))
|
||||
else
|
||||
file = fs.open(name, "w+")
|
||||
local wRes, wRet = pcall(doWrite, file, text)
|
||||
if not wRes then
|
||||
print("Error writing to logfile ("..name.."): "..tostring(wRet))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Logging.OUTPUTS.transmit(modem, channel, logLevel)
|
||||
logLevel = logLevel or LogLevel._DEFAULT
|
||||
end
|
||||
|
||||
function Logging.OUTPUTS.stdout(logLevel)
|
||||
logLevel = logLevel or LogLevel._DEFAULT
|
||||
|
||||
return function(text, level)
|
||||
if level < logLevel then
|
||||
return
|
||||
end
|
||||
|
||||
print(text)
|
||||
end
|
||||
end
|
||||
|
||||
function Logging.resetAll()
|
||||
_G._GLOBAL_LOGGER = nil
|
||||
end
|
||||
|
||||
return Logging
|
||||
185
quarry.lua
Normal file
185
quarry.lua
Normal file
@@ -0,0 +1,185 @@
|
||||
local args = {...}
|
||||
local Logging = require("logging")
|
||||
local Logger = Logging.firstLoad(
|
||||
Logging.OUTPUTS.file("tunnel.log"),
|
||||
Logging.OUTPUTS.stdout()
|
||||
)
|
||||
|
||||
local CHEST_SLOT = #args == 1 and tonumber(args[1]) or 1
|
||||
if type(CHEST_SLOT) ~= "number" or CHEST_SLOT < 1 or CHEST_SLOT > 16 then
|
||||
Logger:error("Slot number is not valid:", CHEST_SLOT)
|
||||
end
|
||||
local CHEST_PICKUP = #args == 2 and (args[2]:lower() )
|
||||
|
||||
local CHEST_DETAIL = turtle.getItemDetail(CHEST_SLOT)
|
||||
if CHEST_DETAIL == nil then
|
||||
Logger:error("No chest in slot! Quitting...")
|
||||
return
|
||||
end
|
||||
local CHEST_NAME = CHEST_DETAIL.name
|
||||
|
||||
local function refuel(minFuel)
|
||||
local fuelLevel = turtle.getFuelLevel()
|
||||
while fuelLevel < minFuel do
|
||||
Logger:debug("Checking fuel level:", fuelLevel)
|
||||
for i=1,16 do
|
||||
if i == CHEST_SLOT then
|
||||
goto continue
|
||||
end
|
||||
|
||||
if turtle.getItemCount(i) > 0 then
|
||||
turtle.select(i)
|
||||
turtle.refuel()
|
||||
end
|
||||
::continue::
|
||||
end
|
||||
fuelLevel = turtle.getFuelLevel()
|
||||
end
|
||||
Logger:debug("Fuel level is sufficient:", fuelLevel)
|
||||
turtle.select(1)
|
||||
end
|
||||
|
||||
local function isFull(minEmpty)
|
||||
local emptyCount = 0
|
||||
for i=1,16 do
|
||||
if i == CHEST_SLOT then
|
||||
goto continue
|
||||
end
|
||||
|
||||
if turtle.getItemCount(i) == 0 then
|
||||
emptyCount = emptyCount + 1
|
||||
if emptyCount >= minEmpty then
|
||||
return false
|
||||
end
|
||||
end
|
||||
::continue::
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
local function placeChest()
|
||||
if not turtle.select(CHEST_SLOT) then
|
||||
Logger:error("Cannot select chest slot", CHEST_SLOT)
|
||||
return false, nil, nil, nil
|
||||
end
|
||||
|
||||
if turtle.placeUp() or (turtle.digUp() and turtle.placeUp()) then
|
||||
return true, turtle.dropUp, turtle.digUp, function() end
|
||||
end
|
||||
|
||||
if turtle.turnLeft() and turtle.turnLeft() and (turtle.place() or (turtle.dig() and turtle.place())) then
|
||||
return true, turtle.drop, turtle.dig, function()
|
||||
turtle.turnRight()
|
||||
turtle.turnRight()
|
||||
end
|
||||
end
|
||||
return false, nil, nil, nil
|
||||
end
|
||||
|
||||
local function handleFullInv(minEmpty)
|
||||
local didPlace = false
|
||||
|
||||
local result, drop, dig, onComplete = false, nil, nil, nil
|
||||
-- Empty inventory
|
||||
while isFull(minEmpty) do
|
||||
if not didPlace then
|
||||
local detail = turtle.getItemDetail(CHEST_SLOT)
|
||||
if type(detail) ~= "table" or detail.name ~= CHEST_NAME then
|
||||
Logger:error("Can't find chest :(")
|
||||
os.sleep(5)
|
||||
goto continue
|
||||
end
|
||||
|
||||
-- Try: place, check block above is empty or dig it, place
|
||||
-- If all fails, print error, wait and repeat
|
||||
result, drop, dig, onComplete = placeChest()
|
||||
if not result then
|
||||
Logger:error("Can't place chest :(")
|
||||
os.sleep(5)
|
||||
goto continue
|
||||
end
|
||||
didPlace = true
|
||||
end
|
||||
|
||||
assert(drop ~= nil, "Placed chest, but drop operation is nil")
|
||||
for i=1,16 do
|
||||
if i == CHEST_SLOT then
|
||||
goto continue_SLOT
|
||||
end
|
||||
if turtle.getItemCount(i) > 0 and not (turtle.select(i) and drop()) then
|
||||
Logger:error("Couldn't drop items into chest!")
|
||||
goto continue
|
||||
end
|
||||
|
||||
::continue_SLOT::
|
||||
end
|
||||
|
||||
::continue::
|
||||
end
|
||||
|
||||
if result then
|
||||
assert(dig ~= nil, "Placed chest, but dig operation is nil")
|
||||
if didPlace and CHEST_PICKUP then
|
||||
turtle.select(CHEST_SLOT)
|
||||
dig()
|
||||
end
|
||||
|
||||
assert(onComplete ~= nil, "Placed chest, but onComplete operation is nil")
|
||||
onComplete()
|
||||
end
|
||||
end
|
||||
|
||||
local function dig(checkRefuel)
|
||||
while not turtle.forward() do
|
||||
turtle.dig()
|
||||
checkRefuel()
|
||||
end
|
||||
turtle.digUp()
|
||||
turtle.digDown()
|
||||
end
|
||||
|
||||
local function line(length, turn, checkRefuel)
|
||||
turtle.digDown()
|
||||
for _=2,length do
|
||||
dig(checkRefuel)
|
||||
end
|
||||
turn()
|
||||
end
|
||||
|
||||
local function panel(width, length, leftFirst, checkRefuel, checkFullInv)
|
||||
Logger:trace("Panel:", width, length)
|
||||
local turn, otherTurn = leftFirst and turtle.turnLeft or turtle.turnRight, leftFirst and turtle.turnRight or turtle.turnLeft
|
||||
for _=2,width do
|
||||
checkFullInv()
|
||||
line(length, turn, checkRefuel)
|
||||
dig(checkRefuel)
|
||||
turn()
|
||||
turn, otherTurn = otherTurn, turn
|
||||
end
|
||||
line(length, turn, checkRefuel)
|
||||
turn()
|
||||
end
|
||||
|
||||
local function rectPrism(depth, width, length, leftFirst)
|
||||
local refuelTarget = width * length * 1.5
|
||||
local function checkRefuel()
|
||||
refuel(refuelTarget)
|
||||
end
|
||||
local invEmptyTarget = 3
|
||||
local function checkFullInv()
|
||||
Logger:debug("Handling full inventory with target:", invEmptyTarget, " handled:", handleFullInv(invEmptyTarget))
|
||||
end
|
||||
Logger:trace("RectPrism:", depth, width, length)
|
||||
for _=1,depth do
|
||||
panel(width, length, leftFirst, checkRefuel, checkFullInv)
|
||||
for __=1,3 do
|
||||
while not turtle.down() do
|
||||
turtle.digDown()
|
||||
checkRefuel()
|
||||
end
|
||||
end
|
||||
leftFirst = (not not leftFirst) ~= (width % 2 == 0)
|
||||
end
|
||||
end
|
||||
|
||||
rectPrism(200, 16, 16)
|
||||
27
reload.lua
Normal file
27
reload.lua
Normal file
@@ -0,0 +1,27 @@
|
||||
local CC_UTILS_DIR = "/cc-utilities"
|
||||
|
||||
local function onResume(scriptArgs)
|
||||
if #scriptArgs > 0 then
|
||||
scriptArgs[1] = CC_UTILS_DIR .. "/" .. scriptArgs[1]
|
||||
shell.run(table.unpack(scriptArgs))
|
||||
end
|
||||
end
|
||||
|
||||
if pcall(debug.getlocal, 4, 1) then
|
||||
return { onResume = onResume }
|
||||
end
|
||||
|
||||
local result, retval = pcall(fs.delete, CC_UTILS_DIR)
|
||||
if fs.exists(CC_UTILS_DIR) then
|
||||
if result then
|
||||
print("Wat? Delete succeeded but dir persists???")
|
||||
else
|
||||
print(retval)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
shell.run("clone https://gitea.tofvesson.se/GabrielTofvesson/cc-utilities.git")
|
||||
|
||||
local nextVersion = require(CC_UTILS_DIR .. "/reload")
|
||||
local _ = ((type(nextVersion) == "table" and type(nextVersion.onResume) == "function") and nextVersion.onResume or onResume)({...})
|
||||
@@ -1,6 +1,7 @@
|
||||
local ItemStack = require("storage.itemstack")
|
||||
local Sentinel = require("storage.sentinel")
|
||||
|
||||
local Chest = {}
|
||||
local Chest = Sentinel:tag({}, Sentinel.CHEST)
|
||||
Chest.__index = Chest
|
||||
|
||||
-- Homogeneity allows chest scan to clone empty itemDetail slot to all empty slots in chest
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
local Chest = require("storage.chest")
|
||||
local Storage = {}
|
||||
local Sentinel = require("storage.sentinel")
|
||||
|
||||
local Storage = Sentinel:tag({}, Sentinel.STORAGE)
|
||||
Storage.__index = Storage
|
||||
|
||||
local function _attach(stor, name, homogeneous)
|
||||
table.insert(stor, Chest:fromPeripheral(name, homogeneous))
|
||||
end
|
||||
|
||||
function Storage.assumeHomogeneous(names)
|
||||
local mappings = {}
|
||||
for _,name in ipairs(names) do
|
||||
@@ -10,12 +16,13 @@ function Storage.assumeHomogeneous(names)
|
||||
return mappings
|
||||
end
|
||||
|
||||
function Storage:fromPeripherals(names)
|
||||
function Storage:fromPeripherals(peripheralDefs)
|
||||
local obj = {
|
||||
count = #names
|
||||
count = #peripheralDefs
|
||||
}
|
||||
for name,homogeneous in pairs(names) do
|
||||
table.insert(obj, Chest:fromPeripheral(name, homogeneous))
|
||||
|
||||
for name,homogeneous in pairs(peripheralDefs) do
|
||||
_attach(obj, name, homogeneous)
|
||||
end
|
||||
|
||||
setmetatable(obj, self)
|
||||
@@ -28,6 +35,7 @@ function Storage:toSerializable()
|
||||
local ser = {
|
||||
count = self.count
|
||||
}
|
||||
|
||||
for _,chest in ipairs(self) do
|
||||
table.insert(ser, chest:toSerializable())
|
||||
end
|
||||
@@ -65,7 +73,14 @@ function Storage:attach(name, homogeneous)
|
||||
return
|
||||
end
|
||||
|
||||
table.insert(self, Chest:fromPeripheral(name, homogeneous))
|
||||
_attach(self, name, homogeneous)
|
||||
end
|
||||
|
||||
function Storage:attachAll(peripheralDefs)
|
||||
for name,homogeneous in pairs(peripheralDefs) do
|
||||
self:attach(name, homogeneous)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
function Storage:detach(name)
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
local ItemGroup = {}
|
||||
local Sentinel = require("storage.sentinel")
|
||||
|
||||
local ItemGroup = Sentinel:tag({}, Sentinel.ITEMGROUP)
|
||||
ItemGroup.__index = ItemGroup
|
||||
|
||||
--- Create a group of managed itemstacks
|
||||
@@ -53,6 +55,14 @@ function ItemGroup:getNBT()
|
||||
return self:_getIdentityStack():getNBT()
|
||||
end
|
||||
|
||||
function ItemGroup:getDamage()
|
||||
return self:_getIdentityStack():getDamage()
|
||||
end
|
||||
|
||||
function ItemGroup:getMaxDamage()
|
||||
return self:_getIdentityStack():getMaxDamage()
|
||||
end
|
||||
|
||||
function ItemGroup:_getIdentityStack()
|
||||
return self[1]
|
||||
end
|
||||
@@ -85,7 +95,6 @@ function ItemGroup:canAddStack(stack)
|
||||
return true
|
||||
end
|
||||
end
|
||||
self:_addStack(stack)
|
||||
return true
|
||||
end
|
||||
return false
|
||||
@@ -99,6 +108,56 @@ function ItemGroup:addStack(stack)
|
||||
return false
|
||||
end
|
||||
|
||||
function ItemGroup:transferTo(target, itemCount)
|
||||
local targetGroup = nil
|
||||
if Sentinel:is(target, Sentinel.CHEST) or Sentinel:is(target, Sentinel.STORAGE) then
|
||||
local identity = self:_getIdentityStack()
|
||||
local find = target:find(function(stack) return identity:canTransfer(stack) end)
|
||||
if #find == 0 then
|
||||
return itemCount == nil or itemCount == 0, 0
|
||||
end
|
||||
|
||||
targetGroup = ItemGroup:from(find[1])
|
||||
for i=2,#find do
|
||||
targetGroup:_addStack(find[i])
|
||||
end
|
||||
elseif Sentinel:is(target, Sentinel.ITEMSTACK) then
|
||||
targetGroup = ItemGroup:from(target)
|
||||
elseif Sentinel:is(target, Sentinel.ITEMGROUP) then
|
||||
targetGroup = target
|
||||
end
|
||||
|
||||
if targetGroup == nil then
|
||||
error("Unexpected transfer target for ItemGroup:transferTo")
|
||||
end
|
||||
|
||||
local targetCap = 0
|
||||
for _,stack in targetGroup:_iterateStacks() do
|
||||
targetCap = targetCap + (stack:getMaxCount() - stack:getCount())
|
||||
end
|
||||
|
||||
-- TODO: Not efficient
|
||||
local transferMax = math.min(itemCount or targetCap, targetCap, self:getItemCount())
|
||||
local transfer = 0
|
||||
for _,stack in targetGroup:_iterateStacks() do
|
||||
for _,from in self:_iterateStacks() do
|
||||
if transfer >= transferMax then
|
||||
goto complete
|
||||
end
|
||||
|
||||
if stack:getCount() == stack:getMaxCount() then
|
||||
goto continue
|
||||
end
|
||||
local _, xfer = from:transferTo(stack, math.min(stack:getMaxCount() - stack:getCount(), transferMax - transfer))
|
||||
transfer = transfer + xfer
|
||||
end
|
||||
::continue::
|
||||
end
|
||||
::complete::
|
||||
|
||||
return itemCount == nil or (itemCount == transfer), transfer
|
||||
end
|
||||
|
||||
function ItemGroup:getItemCount()
|
||||
if self:_getIdentityStack():isEmpty() then
|
||||
return 0
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
local Inventory = require("storage.inventory")
|
||||
local Sentinel = require("storage.sentinel")
|
||||
local Logging = require("logging")
|
||||
local Util = require("util")
|
||||
local Logger = Logging.getGlobalLogger()
|
||||
|
||||
local ItemStack = {}
|
||||
local ItemStack = Sentinel:tag({}, Sentinel.ITEMSTACK)
|
||||
ItemStack.__index = ItemStack
|
||||
|
||||
function ItemStack:fromDetail(inv, detail, slot)
|
||||
@@ -10,13 +14,13 @@ function ItemStack:fromDetail(inv, detail, slot)
|
||||
}
|
||||
|
||||
if detail == nil then
|
||||
obj.maxCount = inv.getItemLimit(slot)
|
||||
obj.maxCount = Util.getItemLimit(inv, slot)
|
||||
else
|
||||
obj.name = detail.name
|
||||
obj.damage = detail.damage
|
||||
obj.maxDamage = detail.maxDamage
|
||||
obj.count = detail.count
|
||||
obj.maxCount = detail.maxCount
|
||||
obj.maxCount = detail.maxCount or 64
|
||||
obj.enchantments = detail.enchantments
|
||||
obj.displayName = detail.displayName
|
||||
obj.nbt = detail.nbt
|
||||
@@ -30,16 +34,16 @@ end
|
||||
|
||||
function ItemStack:clone(withSlot)
|
||||
local obj = {
|
||||
slot = withSlot or self.slot,
|
||||
inv = self.inv,
|
||||
name = self.name,
|
||||
damage = self.damage,
|
||||
maxDamage = self.maxDamage,
|
||||
count = self.count,
|
||||
maxCount = self.maxCount,
|
||||
enchantments = self.enchantments,
|
||||
displayName = self.displayName,
|
||||
nbt = self.nbt
|
||||
slot = withSlot or self:getSlot(),
|
||||
inv = self:getInventory(),
|
||||
name = self:getName(),
|
||||
damage = self:getDamage(),
|
||||
maxDamage = self:getMaxDamage(),
|
||||
count = self:getCount(),
|
||||
maxCount = self:getMaxCount(),
|
||||
enchantments = self:getEnchantments(),
|
||||
displayName = self:getDisplayName(),
|
||||
nbt = self:getNBT()
|
||||
}
|
||||
|
||||
setmetatable(obj, self)
|
||||
@@ -50,19 +54,19 @@ end
|
||||
|
||||
function ItemStack:toSerializable()
|
||||
local ser = {
|
||||
slot = self.slot,
|
||||
maxCount = self.maxCount
|
||||
slot = self:getSlot(),
|
||||
maxCount = self:getMaxCount()
|
||||
}
|
||||
|
||||
if not self:isEmpty() then
|
||||
-- Not empty
|
||||
ser.name = self.name
|
||||
ser.damage = self.damage
|
||||
ser.maxDamage = self.maxDamage
|
||||
ser.count = self.count
|
||||
ser.enchantments = self.enchantments
|
||||
ser.displayName = self.displayName
|
||||
ser.nbt = self.nbt
|
||||
ser.name = self:getName()
|
||||
ser.damage = self:getDamage()
|
||||
ser.maxDamage = self:getMaxDamage()
|
||||
ser.count = self:getCount()
|
||||
ser.enchantments = self:getEnchantments()
|
||||
ser.displayName = self:getDisplayName()
|
||||
ser.nbt = self:getNBT()
|
||||
end
|
||||
|
||||
return ser
|
||||
@@ -114,7 +118,8 @@ function ItemStack:getNBT()
|
||||
end
|
||||
|
||||
function ItemStack:isEmpty()
|
||||
return self.count == nil or self.count == 0
|
||||
local count = self:getCount()
|
||||
return count == nil or count == 0
|
||||
end
|
||||
|
||||
function ItemStack:getDisplayName()
|
||||
@@ -146,12 +151,14 @@ function ItemStack:getSimpleName()
|
||||
end
|
||||
|
||||
function ItemStack:hasChanged(listObj, thorough)
|
||||
local listItem = listObj[self.slot]
|
||||
if listItem == nil or listItem.name ~= self.name or listItem.count ~= self.count then
|
||||
local listItem = listObj[self:getSlot()]
|
||||
if listItem == nil or listItem.name ~= self:getName() or listItem.count ~= self:getCount() then
|
||||
return true
|
||||
end
|
||||
|
||||
return thorough and (self ~= self:fromDetail(self.inv, self.inv.getItemDetail(self.slot), self.slot))
|
||||
local inv = self:getInventory()
|
||||
local slot = self:getSlot()
|
||||
return thorough and (self ~= self:fromDetail(inv, inv.getItemDetail(slot), slot))
|
||||
end
|
||||
|
||||
function ItemStack:_modify(countDelta, stack)
|
||||
@@ -162,26 +169,24 @@ function ItemStack:_modify(countDelta, stack)
|
||||
|
||||
if newCount == 0 then
|
||||
-- Clear data
|
||||
self.maxCount = self.inv.getItemLimit(self.slot)
|
||||
self.maxCount = Util.getItemLimit(self:getInventory(), self:getSlot())
|
||||
self.name = nil
|
||||
self.damage = nil
|
||||
self.maxDamage = nil
|
||||
self.count = nil
|
||||
self.maxCount = nil
|
||||
self.enchantments = nil
|
||||
self.displayName = nil
|
||||
self.nbt = nil
|
||||
else
|
||||
-- If stack is empty, copy stack data from source
|
||||
if self:isEmpty() then
|
||||
self.maxCount = stack.maxCount
|
||||
self.name = stack.name
|
||||
self.damage = stack.damage
|
||||
self.maxDamage = stack.maxDamage
|
||||
self.maxCount = stack.maxCount
|
||||
self.enchantments = stack.enchantments
|
||||
self.displayName = stack.displayName
|
||||
self.nbt = stack.nbt
|
||||
self.name = stack:getName()
|
||||
self.damage = stack:getDamage()
|
||||
self.maxDamage = stack:getMaxDamage()
|
||||
self.maxCount = stack:getMaxCount()
|
||||
self.enchantments = stack:getEnchantments()
|
||||
self.displayName = stack:getDisplayName()
|
||||
self.nbt = stack:getNBT()
|
||||
end
|
||||
|
||||
self.count = newCount
|
||||
@@ -196,16 +201,21 @@ function ItemStack:transferTo(target, count)
|
||||
return count == 0, 0
|
||||
end
|
||||
|
||||
local result, xfer = pcall(self:getInventory().pushItems, peripheral.getName(target:getInventory()), self:getSlot(), cap, target:getSlot())
|
||||
local errC = 0
|
||||
local result
|
||||
repeat
|
||||
result = { pcall(self:getInventory().pushItems, peripheral.getName(target:getInventory()), self:getSlot(), cap, target:getSlot()) }
|
||||
errC = errC + 1
|
||||
until (not result[1]) or (result[2] ~= nil) or (errC > 8)
|
||||
|
||||
if not result then
|
||||
return false, xfer
|
||||
if not result[1] then
|
||||
return false, result[2]
|
||||
end
|
||||
|
||||
target:_modify(xfer, self)
|
||||
self:_modify(-xfer, self)
|
||||
target:_modify(result[2], self)
|
||||
self:_modify(-result[2], self)
|
||||
|
||||
return xfer == count, xfer
|
||||
return result[2] == count, result[2]
|
||||
end
|
||||
|
||||
local function objEquals(o1, o2)
|
||||
@@ -237,23 +247,45 @@ local function objEquals(o1, o2)
|
||||
end
|
||||
end
|
||||
|
||||
function ItemStack:__eq(other)
|
||||
return type(other) == "table" and
|
||||
self.count == other.count and
|
||||
self.maxCount == other.maxCount and
|
||||
self:canTransfer(other)
|
||||
local function runGetter(obj, name)
|
||||
local fun = obj[name]
|
||||
if type(fun) ~= "function" then
|
||||
return false, ("Function '%s' cannot be found in object"):format(name)
|
||||
end
|
||||
return pcall(fun, obj)
|
||||
end
|
||||
|
||||
local function checkEqual(obj1, obj2, funcName)
|
||||
local result1 = { runGetter(obj1, funcName) }
|
||||
local result2 = { runGetter(obj2, funcName) }
|
||||
return result1[1] and result2[1] and (result1[2] == result2[2])
|
||||
end
|
||||
|
||||
local function checkEnchantments(obj1, obj2)
|
||||
local GETTER_FUNC_NAME = "getEnchantments"
|
||||
local result1 = { runGetter(obj1, GETTER_FUNC_NAME) }
|
||||
local result2 = { runGetter(obj2, GETTER_FUNC_NAME) }
|
||||
return result1[1] and result2[1] and objEquals(result1[2], result2[2])
|
||||
end
|
||||
|
||||
function ItemStack:__eq(other)
|
||||
return type(other) == "table" and
|
||||
checkEqual(self, other, "getCount") and
|
||||
checkEqual(self, other, "getMaxCount") and
|
||||
self:canTransfer(other)
|
||||
end
|
||||
|
||||
-- Determines if two stacks can be transferred to eachother
|
||||
-- Empty stacks are always valid transfer nodes
|
||||
function ItemStack:canTransfer(stack)
|
||||
return self:isEmpty() or stack:isEmpty() or (self.name == stack.name and
|
||||
self.damage == stack.damage and
|
||||
self.maxDamage == stack.maxDamage and
|
||||
self.displayName == stack.displayName and
|
||||
self.nbt == stack.nbt and
|
||||
objEquals(self.enchantments, stack.enchantments))
|
||||
return self:isEmpty() or stack:isEmpty() or (
|
||||
checkEqual(self, stack, "getName") and
|
||||
checkEqual(self, stack, "getDamage") and
|
||||
checkEqual(self, stack, "getMaxDamage") and
|
||||
checkEqual(self, stack, "getDisplayName") and
|
||||
checkEqual(self, stack, "getNBT") and
|
||||
checkEnchantments(self, stack)
|
||||
)
|
||||
end
|
||||
|
||||
local function queryField(query, field)
|
||||
@@ -276,14 +308,14 @@ function ItemStack:matches(query)
|
||||
return query(self)
|
||||
end
|
||||
|
||||
return queryField(query.name, self.name) and
|
||||
queryField(query.damage, self.damage) and
|
||||
queryField(query.count, self.count) and
|
||||
queryField(query.maxDamage, self.maxDamage) and
|
||||
queryField(query.enchantments, self.enchantments) and
|
||||
queryField(query.maxCount, self.maxCount) and
|
||||
queryField(query.nbt, self.nbt) and
|
||||
queryField(query.displayName, self.displayName)
|
||||
return queryField(query.name, self:getName()) and
|
||||
queryField(query.damage, self:getDamage()) and
|
||||
queryField(query.count, self:getCount()) and
|
||||
queryField(query.maxDamage, self:getMaxDamage()) and
|
||||
queryField(query.enchantments, self:getEnchantments()) and
|
||||
queryField(query.maxCount, self:getMaxCount()) and
|
||||
queryField(query.nbt, self:getNBT()) and
|
||||
queryField(query.displayName, self:getDisplayName())
|
||||
end
|
||||
|
||||
return ItemStack
|
||||
19
storage/sentinel.lua
Normal file
19
storage/sentinel.lua
Normal file
@@ -0,0 +1,19 @@
|
||||
local TABLE_KEY = "!__SENTINEL"
|
||||
|
||||
local Sentinel = {
|
||||
CHEST = "CHEST",
|
||||
ITEMSTACK = "ITEMSTACK",
|
||||
ITEMGROUP = "ITEMGROUP",
|
||||
STORAGE = "STORAGE"
|
||||
}
|
||||
|
||||
function Sentinel:tag(table, sentinel)
|
||||
table[TABLE_KEY] = sentinel
|
||||
return table
|
||||
end
|
||||
|
||||
function Sentinel:is(table, sentinel)
|
||||
return table[TABLE_KEY] == sentinel
|
||||
end
|
||||
|
||||
return Sentinel
|
||||
34
util/init.lua
Normal file
34
util/init.lua
Normal file
@@ -0,0 +1,34 @@
|
||||
local Util = {}
|
||||
|
||||
function Util.toHexChar(v)
|
||||
return ("0123456789ABCDEF"):sub(v, v)
|
||||
end
|
||||
|
||||
function Util.fromHexChar(c)
|
||||
local index = ({("0123456789ABCDEF"):find(c)})[1]
|
||||
if index ~= nil then
|
||||
return index - 1
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function Util.boundCheck(x, y, w, h)
|
||||
if x < 1 or x > w or y < 1 or y > h then
|
||||
error(("Index out of range: (%d %d) is not within ([1,%d], [1,%d])"):format(x, y, w, h))
|
||||
end
|
||||
end
|
||||
|
||||
-- Workaround for a bug in CC: Tweaked causing inventory.getItemLimit() to return nil immediately after item transfers
|
||||
function Util.getItemLimit(inv, slot, maxTries)
|
||||
local i = 1
|
||||
while not maxTries or i <= maxTries do
|
||||
local result = inv.getItemLimit(slot)
|
||||
if result ~= nil then
|
||||
return result
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
return Util
|
||||
Reference in New Issue
Block a user