mirror of
https://github.com/20kdc/OC-KittenOS.git
synced 2024-11-23 19:08:05 +11:00
764 lines
19 KiB
Lua
764 lines
19 KiB
Lua
-- Copyright (C) 2018-2021 by KittenOS NEO contributors
|
|
--
|
|
-- Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
|
|
--
|
|
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
|
-- THIS SOFTWARE.
|
|
|
|
-- app-nbox2018.lua : NODEBOX 2018
|
|
-- Authors: 20kdc
|
|
|
|
-- Current layout
|
|
-- 12345678901234567890123456789012345678901234567890
|
|
-- 1 | |
|
|
-- 2 | | 3d 32x32 panel
|
|
-- 3 | |
|
|
-- 4 | |
|
|
-- 5 | |
|
|
-- 6 | |
|
|
-- 7 | |
|
|
-- 8 | |
|
|
-- 9-XY Ortho-ACTIV-+-XZ Ortho-ACTIV-+-ST:OFF-+-FILE:-
|
|
--10This was the story of someone cal|ABCDEFGH|F1 New
|
|
--11led Stanley. Stanley got very cro|IJKLMNOP|F3 Load
|
|
--12ss because someone else used his |QRSTUVWX|F4 Save
|
|
--13name for a game. Stanley's silly.|YZ[\]^_`|TAB ST.
|
|
|
|
-- F-Key uses:
|
|
-- F1: New [Global]
|
|
-- F3: Load [Global]
|
|
-- F4: Save [Global]
|
|
-- F5: RotL [Global]
|
|
-- F6: RotR [Global]
|
|
-- F7: FileStats [None ?Selected]
|
|
-- F8: Print [Global]
|
|
-- F9: Texture [None +Selected]
|
|
-- F10: Tint [None +Selected]
|
|
-- F11:
|
|
-- F12:
|
|
|
|
-- program start
|
|
|
|
local icecap = neo.requireAccess("x.neo.pub.base", "filedialogs")
|
|
local window = neo.requireAccess("x.neo.pub.window", "window")(50, 13)
|
|
local fmttext = require("fmttext")
|
|
local braille = require("braille")
|
|
|
|
-- [true] = {["A"] = {
|
|
-- tex = "",
|
|
-- -- numbers are 0 to 15:
|
|
-- minX = 0, minY = 0, minZ = 0,
|
|
-- maxX = 16, maxY = 16, maxZ = 16,
|
|
-- rgb = 0xFFFFFF
|
|
-- }}
|
|
local boxes = {
|
|
[true] = {},
|
|
[false] = {}
|
|
}
|
|
local redstone = false
|
|
local button = false
|
|
local fileLabel = "NB2018"
|
|
local fileTooltip = ""
|
|
|
|
-- program
|
|
|
|
local xyz = false
|
|
local state = false
|
|
|
|
local rotation = 0
|
|
|
|
local cx, cy, cz = 1, 1, 1
|
|
local cursorBlink = false
|
|
|
|
local selectedBox
|
|
|
|
local tintDigi = 0
|
|
local fstatSwap = false
|
|
|
|
-- minX/minY/minZ are +1 from the usual values
|
|
-- tex/rgb are defaults until edited
|
|
-- maxX/maxY/maxZ only present after 2nd point placed
|
|
-- final corrections performed on submission to boxes table
|
|
local workingOnBox = nil
|
|
|
|
local function runField(tx, l, r)
|
|
return l .. fmttext.pad(unicode.safeTextFormat(tx), 31, false, true, true) .. r
|
|
end
|
|
local function actField(tx, ka, kc)
|
|
if kc == 211 or ka == 8 then
|
|
tx = unicode.sub(tx, 1, unicode.len(tx) - 1)
|
|
elseif ka >= 32 then
|
|
tx = tx .. unicode.char(ka)
|
|
end
|
|
return tx
|
|
end
|
|
|
|
local programState = "none"
|
|
-- ["state"] = {lines, keydown, clipboard}
|
|
local programStates = {
|
|
none = {
|
|
function (miText, mxText)
|
|
-- This state handles both box selected & box not selected,
|
|
-- because the box can get deselected out of program control
|
|
if selectedBox then
|
|
local targetBox = boxes[state][selectedBox]
|
|
return {
|
|
"'" .. selectedBox .. "' " .. targetBox.tex,
|
|
"Tint #" .. string.format("%06x", targetBox.rgb),
|
|
"Enter deselects, Delete deletes.",
|
|
"F9 and F10 change texture/tint."
|
|
}
|
|
end
|
|
local str = string.format("%02i, %02i, %02i", cx, cy, cz)
|
|
return {
|
|
"No selection. " .. str,
|
|
"Enter starts a new box, while the",
|
|
" box's letter selects. Rotate w/ ",
|
|
" F5/F6, F7 for stats, F8 prints. "
|
|
}
|
|
end,
|
|
function (ka, kc)
|
|
if ka == 13 then
|
|
if selectedBox then
|
|
selectedBox = nil
|
|
else
|
|
-- Beginning box!
|
|
workingOnBox = {
|
|
minX = cx,
|
|
minY = cy,
|
|
minZ = cz,
|
|
tex = "stone",
|
|
rgb = 0xFFFFFF
|
|
}
|
|
programState = "point2"
|
|
end
|
|
elseif kc == 65 then
|
|
-- FStats
|
|
fstatSwap = false
|
|
programState = "fstats"
|
|
elseif kc == 67 then
|
|
-- Texture
|
|
if selectedBox then programState = "texture" end
|
|
elseif kc == 68 then
|
|
-- Tint
|
|
if selectedBox then tintDigi = 1 programState = "tint" end
|
|
elseif ka == 127 or ka == 8 then
|
|
-- Delete
|
|
if selectedBox then
|
|
boxes[state][selectedBox] = nil
|
|
selectedBox = nil
|
|
end
|
|
else
|
|
local cc = unicode.char(ka):upper()
|
|
if boxes[state][cc] then
|
|
selectedBox = cc
|
|
end
|
|
end
|
|
end,
|
|
function (text)
|
|
end
|
|
},
|
|
point2 = {
|
|
function (miText, mxText)
|
|
return {
|
|
"Placing Point 2:" .. miText .. "/" .. mxText,
|
|
"Enter confirms.",
|
|
"Arrows move 2nd point.",
|
|
"Delete/Backspace cancels."
|
|
}
|
|
end,
|
|
function (ka, kc)
|
|
if ka == 127 or ka == 8 then
|
|
workingOnBox = nil
|
|
programState = "none"
|
|
elseif ka == 13 then
|
|
workingOnBox.maxX = cx
|
|
workingOnBox.maxY = cy
|
|
workingOnBox.maxZ = cz
|
|
local ch = 65
|
|
while boxes[state][string.char(ch)] do
|
|
ch = ch + 1
|
|
end
|
|
local ax, ay, az = workingOnBox.minX, workingOnBox.minY, workingOnBox.minZ
|
|
local bx, by, bz = workingOnBox.maxX, workingOnBox.maxY, workingOnBox.maxZ
|
|
workingOnBox.minX = math.min(ax, bx) - 1
|
|
workingOnBox.minY = math.min(ay, by) - 1
|
|
workingOnBox.minZ = math.min(az, bz) - 1
|
|
workingOnBox.maxX = math.max(ax, bx)
|
|
workingOnBox.maxY = math.max(ay, by)
|
|
workingOnBox.maxZ = math.max(az, bz)
|
|
selectedBox = string.char(ch)
|
|
boxes[state][selectedBox] = workingOnBox
|
|
workingOnBox = nil
|
|
programState = "texture"
|
|
end
|
|
end,
|
|
function (text)
|
|
end
|
|
},
|
|
texture = {
|
|
function (miText, mxText)
|
|
local targetBox = boxes[state][selectedBox]
|
|
return {
|
|
"Texturing. Type or paste texture ",
|
|
" ID. Pasting replaces contents. ",
|
|
runField(targetBox.tex, "[", "]"),
|
|
"Enter confirms. \"\" is invisible."
|
|
}
|
|
end,
|
|
function (ka, kc)
|
|
local targetBox = boxes[state][selectedBox]
|
|
if ka == 13 then
|
|
programState = "none"
|
|
else
|
|
targetBox.tex = actField(targetBox.tex, ka, kc)
|
|
end
|
|
end,
|
|
function (text)
|
|
boxes[state][selectedBox].tex = text
|
|
end
|
|
},
|
|
tint = {
|
|
function (miText, mxText)
|
|
local targetBox = boxes[state][selectedBox]
|
|
local a = "#"
|
|
local b = " "
|
|
local rgb = targetBox.rgb
|
|
local div = 0x100000
|
|
for i = 1, 6 do
|
|
a = a .. string.format("%01x", math.floor(rgb / div) % 16)
|
|
if tintDigi == i then
|
|
b = b .. "^"
|
|
else
|
|
b = b .. " "
|
|
end
|
|
div = math.floor(div / 16)
|
|
end
|
|
return {
|
|
"Tinting. Enter 6 hex digits, ",
|
|
" which are 0 to 9, and A to F. ",
|
|
a,
|
|
b
|
|
}
|
|
end,
|
|
function (ka, kc)
|
|
local targetBox = boxes[state][selectedBox]
|
|
local shifts = {
|
|
20,
|
|
16,
|
|
12,
|
|
8,
|
|
4,
|
|
0
|
|
}
|
|
local hexChars = {
|
|
[48] = 0, [65] = 10, [97] = 10,
|
|
[49] = 1, [66] = 11, [98] = 11,
|
|
[50] = 2, [67] = 12, [99] = 12,
|
|
[51] = 3, [68] = 13, [100] = 13,
|
|
[52] = 4, [69] = 14, [101] = 14,
|
|
[53] = 5, [70] = 15, [102] = 15,
|
|
[54] = 6,
|
|
[55] = 7,
|
|
[56] = 8,
|
|
[57] = 9,
|
|
}
|
|
if hexChars[ka] then
|
|
local shift = math.floor(2^shifts[tintDigi])
|
|
local low = targetBox.rgb % shift
|
|
local high = math.floor(targetBox.rgb / (shift * 16)) * (shift * 16)
|
|
targetBox.rgb = low + high + (hexChars[ka] * shift)
|
|
tintDigi = 1 + (tintDigi or 1)
|
|
if tintDigi == 7 then
|
|
tintDigi = nil
|
|
programState = "none"
|
|
end
|
|
end
|
|
end,
|
|
function (text)
|
|
end
|
|
},
|
|
fstats = {
|
|
function (miText, mxText)
|
|
local aa, ab = "[", "]"
|
|
local ba, bb = " ", " "
|
|
if fstatSwap then
|
|
aa, ab = " ", " "
|
|
ba, bb = "[", "]"
|
|
end
|
|
return {
|
|
runField(fileLabel, aa, ab),
|
|
runField(fileTooltip, ba, bb),
|
|
"Redstone (F9): " .. ((redstone and "Y") or "N") .. " Button (F10): " .. ((button and "Y") or "N"),
|
|
"Enter to confirm."
|
|
}
|
|
end,
|
|
function (ka, kc)
|
|
if kc == 67 then
|
|
redstone = not redstone
|
|
elseif kc == 68 then
|
|
button = not button
|
|
elseif ka == 13 then
|
|
fstatSwap = not fstatSwap
|
|
if not fstatSwap then
|
|
programState = "none"
|
|
end
|
|
elseif fstatSwap then
|
|
fileTooltip = actField(fileTooltip, ka, kc)
|
|
else
|
|
fileLabel = actField(fileLabel, ka, kc)
|
|
end
|
|
end,
|
|
function (text)
|
|
end
|
|
}
|
|
}
|
|
|
|
local function onRect(x, y, minX, minY, maxX, maxY)
|
|
-- Lines
|
|
if x == minX then
|
|
return y >= minY and y <= maxY
|
|
elseif x == maxX then
|
|
return y >= minY and y <= maxY
|
|
elseif y == minY then
|
|
return x >= minX and x <= maxX
|
|
elseif y == maxY then
|
|
return x >= minX and x <= maxX
|
|
end
|
|
return false
|
|
end
|
|
|
|
local function getPixel(x, y, p)
|
|
-- the reason is obvious for plane1, but less so for plane2
|
|
-- just consider that without this, the top of the screen would be facing you, but X would remain your left/right
|
|
y = 17 - y
|
|
if p == 1 then
|
|
if x == cx and y == cy then
|
|
return cursorBlink
|
|
end
|
|
else
|
|
if x == cx and y == cz then
|
|
return cursorBlink
|
|
end
|
|
end
|
|
if workingOnBox then
|
|
local minX, minY, minZ = workingOnBox.minX, workingOnBox.minY, workingOnBox.minZ
|
|
local maxX, maxY, maxZ = cx, cy, cz
|
|
if workingOnBox.maxX then
|
|
maxX, maxY, maxZ = workingOnBox.maxX, workingOnBox.maxY, workingOnBox.maxZ
|
|
end
|
|
minX, maxX = math.min(minX, maxX), math.max(minX, maxX)
|
|
minY, maxY = math.min(minY, maxY), math.max(minY, maxY)
|
|
minZ, maxZ = math.min(minZ, maxZ), math.max(minZ, maxZ)
|
|
if p == 1 then
|
|
if onRect(x, y, minX, minY, maxX, maxY) then
|
|
return cursorBlink
|
|
end
|
|
else
|
|
if onRect(x, y, minX, minZ, maxX, maxZ) then
|
|
return cursorBlink
|
|
end
|
|
end
|
|
end
|
|
for k, v in pairs(boxes[state]) do
|
|
if (not selectedBox) or (k == selectedBox) then
|
|
if p == 1 then
|
|
if onRect(x, y, v.minX + 1, v.minY + 1, v.maxX, v.maxY) then
|
|
return true
|
|
end
|
|
else
|
|
if onRect(x, y, v.minX + 1, v.minZ + 1, v.maxX, v.maxZ) then
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
local function get3DPixel(xo, yo)
|
|
local function inLine(xa, ya, xb, yb)
|
|
xa, ya, xb, yb = math.floor(xa), math.floor(ya), math.floor(xb), math.floor(yb)
|
|
local xd = math.abs(xa - xb)
|
|
local yd = math.abs(ya - yb)
|
|
if xd > yd then
|
|
local point = math.abs(xo - xa) / xd
|
|
local cast = math.floor((point * (0.99 + yb - ya)) + ya)
|
|
if cast ~= yo then
|
|
return false
|
|
end
|
|
elseif yd ~= 0 then
|
|
local point = math.abs(yo - ya) / yd
|
|
local cast = math.floor((point * (0.99 + xb - xa)) + xa)
|
|
if cast ~= xo then
|
|
return false
|
|
end
|
|
end
|
|
-- clipping
|
|
return
|
|
xo >= math.min(xa, xb) and
|
|
xo <= math.max(xa, xb) and
|
|
yo >= math.min(ya, yb) and
|
|
yo <= math.max(ya, yb)
|
|
end
|
|
local cacheX = {}
|
|
local cacheY = {}
|
|
local function rotate(x, y)
|
|
if rotation == 0 then return x, y end
|
|
x = x - 16
|
|
y = y - 16
|
|
local a = -rotation * 3.14159 / 8
|
|
local xBX, xBY = math.cos(a), math.sin(a)
|
|
local yBX, yBY = -xBY, xBX
|
|
local xo = (xBX * x) + (yBX * y)
|
|
local yo = (xBY * x) + (yBY * y)
|
|
return xo + 16, yo + 16
|
|
end
|
|
local function point3(ax, ay, az)
|
|
ax, az = rotate(ax, az)
|
|
local k = ax .. "_" .. ay .. "_" .. az
|
|
if cacheX[k] then return cacheX[k], cacheY[k] end
|
|
local ox = 16
|
|
local oy = 15.5
|
|
oy = oy - (ay / 2)
|
|
ox = ox + (ax / 2)
|
|
ox = ox - (az / 2)
|
|
oy = oy + (ax / 4)
|
|
oy = oy + (az / 4)
|
|
cacheX[k] = ox
|
|
cacheY[k] = oy
|
|
return ox, oy
|
|
end
|
|
local function in3Line(ax, ay, az, bx, by, bz)
|
|
local sc = 1.9
|
|
ax, ay = point3(ax * sc, ay * sc, az * sc)
|
|
bx, by = point3(bx * sc, by * sc, bz * sc)
|
|
return inLine(ax, ay, bx, by)
|
|
end
|
|
local function inShape(ax, ay, az, bx, by, bz)
|
|
return
|
|
in3Line(ax, ay, az, bx, ay, az) or
|
|
in3Line(ax, ay, az, ax, ay, bz) or
|
|
in3Line(bx, ay, az, bx, ay, bz) or
|
|
in3Line(ax, ay, bz, bx, ay, bz) or
|
|
|
|
in3Line(ax, ay, az, ax, by, az) or
|
|
in3Line(ax, ay, bz, ax, by, bz) or
|
|
in3Line(bx, ay, az, bx, by, az) or
|
|
in3Line(bx, ay, bz, bx, by, bz) or
|
|
|
|
in3Line(ax, by, az, bx, by, az) or
|
|
in3Line(ax, by, az, ax, by, bz) or
|
|
in3Line(bx, by, az, bx, by, bz) or
|
|
in3Line(ax, by, bz, bx, by, bz)
|
|
end
|
|
for k, v in pairs(boxes[state]) do
|
|
if (not selectedBox) or (k == selectedBox) then
|
|
if inShape(16 - v.minZ, v.minY, 16 - v.minX, 16 - v.maxZ, v.maxY, 16 - v.maxX) then
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
local function render(line, doBraille)
|
|
if line < 9 then
|
|
local textA, textB = "", ""
|
|
local bo = (line - 1) * 2
|
|
for i = 1, 16 do
|
|
for p = 1, 2 do
|
|
local pxH, pxL = getPixel(i, bo + 1, p), getPixel(i, bo + 2, p)
|
|
local tx
|
|
if pxH then
|
|
if pxL then
|
|
tx = "█"
|
|
else
|
|
tx = "▀"
|
|
end
|
|
else
|
|
if pxL then
|
|
tx = "▄"
|
|
else
|
|
tx = " "
|
|
end
|
|
end
|
|
if p == 1 then
|
|
textA = textA .. tx
|
|
else
|
|
textB = textB .. tx
|
|
end
|
|
end
|
|
end
|
|
window.span(1, line, textA .. "|" .. textB .. "|", 0, 0xFFFFFF)
|
|
if doBraille then
|
|
braille.calcLine(35, line, 16, window.span, function (xo, yo)
|
|
if get3DPixel(xo, yo + ((line - 1) * 4)) then
|
|
return 255, 255, 255
|
|
else
|
|
return 0, 0, 0
|
|
end
|
|
end, nil)
|
|
end
|
|
elseif line == 9 then
|
|
local sts = "ON "
|
|
if not state then
|
|
sts = "OFF"
|
|
end
|
|
-- Bit odd, but makes sense in the end
|
|
local actA = "-----"
|
|
local actB = "-----"
|
|
if not xyz then
|
|
actA = "Space"
|
|
else
|
|
actB = "Space"
|
|
end
|
|
window.span(1, line, "-XY Ortho-" .. actA .. "-+-XZ Ortho-" .. actB .. "-+-ST:" .. sts .. "-+-FILE:-", 0, 0xFFFFFF)
|
|
elseif line > 9 then
|
|
local mix, miy, miz = cx, cy, cz
|
|
local mxx, mxy, mxz = cx, cy, cz
|
|
if workingOnBox then
|
|
if workingOnBox.maxX then
|
|
local ax, ay, az = workingOnBox.minX, workingOnBox.minY, workingOnBox.minZ
|
|
local bx, by, bz = workingOnBox.maxX, workingOnBox.maxY, workingOnBox.maxZ
|
|
mix = math.min(ax, bx)
|
|
miy = math.min(ay, by)
|
|
miz = math.min(az, bz)
|
|
mxx = math.max(ax, bx)
|
|
mxy = math.max(ay, by)
|
|
mxz = math.max(az, bz)
|
|
else
|
|
local ax, ay, az = workingOnBox.minX, workingOnBox.minY, workingOnBox.minZ
|
|
mix = math.min(ax, cx)
|
|
miy = math.min(ay, cy)
|
|
miz = math.min(az, cz)
|
|
mxx = math.max(ax, cx)
|
|
mxy = math.max(ay, cy)
|
|
mxz = math.max(az, cz)
|
|
end
|
|
end
|
|
local miText = mix .. "," .. miy .. "," .. miz
|
|
local mxText = mxx .. "," .. mxy .. "," .. mxz
|
|
local text = programStates[programState][1](miText, mxText)
|
|
local menu = {
|
|
"| |F1 New ",
|
|
"| |F3 Load",
|
|
"| |F4 Save",
|
|
"| |TAB ST."
|
|
}
|
|
for i = 1, 4 do
|
|
text[i] = fmttext.pad(text[i], 33, true, true) .. menu[i]
|
|
end
|
|
window.span(1, line, text[line - 9] or "", 0, 0xFFFFFF)
|
|
for i = 1, 8 do
|
|
local boxId = string.char(i + ((line - 10) * 8) + 64)
|
|
if boxes[state][boxId] then
|
|
if selectedBox == boxId then
|
|
window.span(34 + i, line, boxId, 0xFFFFFF, 0)
|
|
else
|
|
window.span(34 + i, line, boxId, 0, 0xFFFFFF)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
local function refresh(n3d)
|
|
for i = 1, 14 do
|
|
render(i, not n3d)
|
|
end
|
|
end
|
|
|
|
local function reset()
|
|
boxes = {[true] = {}, [false] = {}}
|
|
state = false
|
|
rotation = 0
|
|
selectedBox = nil
|
|
xyz = false
|
|
cx, cy, cz = 1, 1, 1
|
|
workingOnBox = nil
|
|
programState = "none"
|
|
end
|
|
|
|
local function loadObj(obj)
|
|
fileLabel = obj.label or ""
|
|
fileTooltip = obj.tooltip or ""
|
|
redstone = obj.emitRedstone or false
|
|
button = obj.buttonMode or false
|
|
local advances = {
|
|
[false] = 65,
|
|
[true] = 65
|
|
}
|
|
for k, v in ipairs(obj.shapes) do
|
|
local vs = v.state or false
|
|
boxes[vs][string.char(advances[vs])] = {
|
|
minX = v[1],
|
|
minY = v[2],
|
|
minZ = v[3],
|
|
maxX = v[4],
|
|
maxY = v[5],
|
|
maxZ = v[6],
|
|
tex = v.texture or "",
|
|
rgb = v.tint or 0xFFFFFF
|
|
}
|
|
advances[vs] = advances[vs] + 1
|
|
end
|
|
end
|
|
local function exportBoxes(shapes, st)
|
|
local order = {}
|
|
for k, v in pairs(boxes[st]) do
|
|
table.insert(order, k)
|
|
end
|
|
table.sort(order)
|
|
for _, kv in ipairs(order) do
|
|
local v = boxes[st][kv]
|
|
local tint = v.rgb
|
|
if tint == 0xFFFFFF then
|
|
tint = nil
|
|
end
|
|
table.insert(shapes, {
|
|
v.minX,
|
|
v.minY,
|
|
v.minZ,
|
|
v.maxX,
|
|
v.maxY,
|
|
v.maxZ,
|
|
texture = v.tex,
|
|
state = st,
|
|
tint = tint
|
|
})
|
|
end
|
|
end
|
|
local function makeObj()
|
|
local tbl = {
|
|
label = fileLabel,
|
|
tooltip = fileTooltip,
|
|
emitRedstone = redstone,
|
|
buttonMode = button,
|
|
shapes = {
|
|
}
|
|
}
|
|
exportBoxes(tbl.shapes, false)
|
|
exportBoxes(tbl.shapes, true)
|
|
return tbl
|
|
end
|
|
|
|
local lastFile = nil
|
|
local function waitForDialog(handle)
|
|
lastFile = nil
|
|
while true do
|
|
local event, b, c, d = coroutine.yield()
|
|
if event == "k.timer" then
|
|
neo.scheduleTimer(os.uptime() + 0.5)
|
|
end
|
|
if event == "x.neo.pub.window" then
|
|
if b == "close" then
|
|
return true
|
|
end
|
|
end
|
|
if event == "x.neo.pub.base" then
|
|
if b == "filedialog" then
|
|
if c == handle then
|
|
lastFile = d
|
|
return
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
neo.scheduleTimer(os.uptime())
|
|
while true do
|
|
local event, a, b, c, d, e = coroutine.yield()
|
|
if event == "k.timer" then
|
|
neo.scheduleTimer(os.uptime() + 0.5)
|
|
cursorBlink = not cursorBlink
|
|
refresh(true)
|
|
end
|
|
if event == "x.neo.pub.window" then
|
|
if b == "line" then
|
|
render(c, true)
|
|
end
|
|
if b == "clipboard" then
|
|
if workingOnBox and workingOnBox.maxX then
|
|
workingOnBox.tex = tostring(c)
|
|
b = "key"
|
|
c = 13
|
|
d = 0
|
|
e = true
|
|
end
|
|
end
|
|
if b == "key" then
|
|
if e then
|
|
--neo.emergency("key " .. tostring(c) .. " " .. tostring(d))
|
|
if d == 59 then
|
|
reset()
|
|
refresh()
|
|
elseif d == 61 then
|
|
-- F3 Load
|
|
local handle = icecap.showFileDialogAsync(false)
|
|
if waitForDialog(handle) then return end
|
|
if lastFile then
|
|
reset()
|
|
local obj = require("serial").deserialize("return " .. lastFile.read("*a"))
|
|
loadObj(obj)
|
|
refresh()
|
|
lastFile.close()
|
|
end
|
|
elseif d == 62 then
|
|
-- F4 Save
|
|
local handle = icecap.showFileDialogAsync(true)
|
|
if waitForDialog(handle) then return end
|
|
if lastFile then
|
|
lastFile.write(require("serial").serialize(makeObj()):sub(8))
|
|
lastFile.close()
|
|
end
|
|
elseif d == 63 then
|
|
rotation = rotation + 1
|
|
refresh()
|
|
elseif d == 64 then
|
|
rotation = rotation - 1
|
|
refresh()
|
|
elseif d == 66 then
|
|
-- F8 Print
|
|
neo.executeAsync("app-nprt2018", makeObj())
|
|
elseif c == 9 then
|
|
state = not state
|
|
selectedBox = nil
|
|
-- we can safely switch between states
|
|
-- while working on a box
|
|
refresh()
|
|
elseif d == 203 then
|
|
cx = math.max(1, cx - 1)
|
|
refresh(true)
|
|
elseif d == 200 then
|
|
if not xyz then
|
|
cy = math.min(16, cy + 1)
|
|
else
|
|
cz = math.min(16, cz + 1)
|
|
end
|
|
refresh(true)
|
|
elseif d == 205 then
|
|
cx = math.min(16, cx + 1)
|
|
refresh(true)
|
|
elseif d == 208 then
|
|
if not xyz then
|
|
cy = math.max(1, cy - 1)
|
|
else
|
|
cz = math.max(1, cz - 1)
|
|
end
|
|
refresh(true)
|
|
else
|
|
if c == 32 then
|
|
xyz = not xyz
|
|
end
|
|
local oldSB = selectedBox
|
|
programStates[programState][2](c, d)
|
|
refresh((c ~= 13) and (oldSB == selectedBox))
|
|
end
|
|
end
|
|
end
|
|
if b == "close" then
|
|
return
|
|
end
|
|
end
|
|
end
|