mirror of
https://github.com/20kdc/OC-KittenOS.git
synced 2024-12-26 19:08:06 +11:00
7bde8fee55
Since this is after the technical "release", version numbers have been bumped to 1. Changes before this commit for R1: Kernel memory usage reduction schemes, with some security fixes. Still need to deal w/ proxies (see later) Changes in this commit: Some various little things in apps CLAW inet actually works now on 192K sys-icecap no longer uses the event/neoux combination, and now handles Everest disappearance as a mass-close, but still handles Everest not being around on window create. So it still handles every situation that matters. neoux no longer handles everest crash protection. Security policy and filedialog obviously don't use neoux anymore. Kernel now only guarantees parsing, not event-loop, by executeAsync This is safer and allows app-launcher to get rid of NeoUX by any means necessary. wrapMeta cache now exists, and proxies get wrapMeta'd to deal with various low-priority security shenanigans. This is a *stopgap* until I work out how to force OCEmu to give me totally accurate boot-time memory figures, so I can create the ultimate lowmem proxy. I'm calling it "puppet". FG knows why.
376 lines
9.5 KiB
Lua
376 lines
9.5 KiB
Lua
-- This is released into the public domain.
|
|
-- No warranty is provided, implied or otherwise.
|
|
|
|
-- neoux: Implements utilities on top of Everest & event:
|
|
-- Everest crash protection
|
|
|
|
-- Control reference
|
|
-- x/y/w/h: ints, position/size, 1,1 TL
|
|
-- selectable: boolean
|
|
-- key(window, update, char, code, down)
|
|
-- touch(window, update, x, y, xI, yI, button)
|
|
-- drag(window, update, x, y, xI, yI, button)
|
|
-- drop(window, update, x, y, xI, yI, button)
|
|
-- scroll(window, update, x, y, xI, yI, amount)
|
|
-- clipboard(window, update, contents)
|
|
|
|
-- Global forces reference. Otherwise, nasty duplication happens.
|
|
newNeoux = function (event, neo)
|
|
-- id -> callback
|
|
local lclEvToW = {}
|
|
local everest = neo.requireAccess("x.neo.pub.window", "windowing")
|
|
event.listen("x.neo.pub.window", function (_, window, tp, ...)
|
|
if lclEvToW[window] then
|
|
lclEvToW[window](tp, ...)
|
|
end
|
|
end)
|
|
local neoux = {}
|
|
neoux.fileDialog = function (forWrite, callback)
|
|
local sync = false
|
|
local rtt = nil
|
|
if not callback then
|
|
sync = true
|
|
callback = function (rt)
|
|
sync = false
|
|
rtt = rt
|
|
end
|
|
end
|
|
local tag = neo.requestAccess("x.neo.pub.base").showFileDialogAsync(forWrite)
|
|
local f
|
|
f = function (_, fd, tg, re)
|
|
if fd == "filedialog" then
|
|
if tg == tag then
|
|
callback(re)
|
|
event.ignore(f)
|
|
end
|
|
end
|
|
end
|
|
event.listen("x.neo.pub.base", f)
|
|
while sync do
|
|
event.pull()
|
|
end
|
|
return rtt
|
|
end
|
|
-- Creates a wrapper around a window.
|
|
neoux.create = function (w, h, title, callback)
|
|
local window = {}
|
|
local windowCore = everest(w, h, title)
|
|
-- res is the window!
|
|
lclEvToW[windowCore.id] = function (...) callback(window, ...) end
|
|
-- API convenience: args compatible with .create
|
|
window.reset = function (nw, nh, _, cb)
|
|
callback = cb
|
|
w = nw or w
|
|
h = nh or h
|
|
windowCore.setSize(w, h)
|
|
end
|
|
window.getSize = function ()
|
|
return w, h
|
|
end
|
|
window.getDepth = windowCore.getDepth
|
|
window.setSize = function (nw, nh)
|
|
w = nw
|
|
h = nh
|
|
windowCore.setSize(w, h)
|
|
end
|
|
window.span = windowCore.span
|
|
window.close = function ()
|
|
windowCore.close()
|
|
lclEvToW[windowCore.id] = nil
|
|
windowCore = nil
|
|
end
|
|
return window
|
|
end
|
|
-- Padding function
|
|
neoux.pad = require("fmttext").pad
|
|
-- Text dialog formatting function.
|
|
-- Assumes you've run unicode.safeTextFormat if need be
|
|
neoux.fmtText = function (...)
|
|
local fmt = require("fmttext")
|
|
return fmt.fmtText(...)
|
|
end
|
|
-- UI FRAMEWORK --
|
|
neoux.tcwindow = function (w, h, controls, closing, bg, fg, selIndex)
|
|
local function rotateSelIndex()
|
|
local original = selIndex
|
|
while true do
|
|
selIndex = selIndex + 1
|
|
if not controls[selIndex] then
|
|
selIndex = 1
|
|
end
|
|
if controls[selIndex] then
|
|
if controls[selIndex].selectable then
|
|
return
|
|
end
|
|
end
|
|
if selIndex == original then
|
|
return
|
|
end
|
|
end
|
|
end
|
|
if not selIndex then
|
|
selIndex = #controls
|
|
if #controls == 0 then
|
|
selIndex = 1
|
|
end
|
|
rotateSelIndex()
|
|
end
|
|
local function moveIndex(vertical, negative)
|
|
if not controls[selIndex] then return end
|
|
local currentMA, currentOA = controls[selIndex].y, controls[selIndex].x
|
|
local currentMAX = controls[selIndex].y + controls[selIndex].h - 1
|
|
if vertical then
|
|
currentMA, currentOA = controls[selIndex].x, controls[selIndex].y
|
|
currentMAX = controls[selIndex].x + controls[selIndex].w - 1
|
|
end
|
|
local bestOA = 9001
|
|
local bestSI = selIndex
|
|
if negative then
|
|
bestOA = -9000
|
|
end
|
|
for k, v in ipairs(controls) do
|
|
if (k ~= selIndex) and v.selectable then
|
|
local ma, oa = v.y, v.x
|
|
local max = v.y + v.h - 1
|
|
if vertical then
|
|
ma, oa = v.x, v.y
|
|
max = v.x + v.w - 1
|
|
end
|
|
if (ma >= currentMA and ma <= currentMAX) or (max >= currentMA and max <= currentMAX)
|
|
or (currentMA >= ma and currentMA <= max) or (currentMAX >= ma and currentMAX <= max) then
|
|
if negative then
|
|
if (oa < currentOA) and (oa > bestOA) then
|
|
bestOA = oa
|
|
bestSI = k
|
|
end
|
|
else
|
|
if (oa > currentOA) and (oa < bestOA) then
|
|
bestOA = oa
|
|
bestSI = k
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
selIndex = bestSI
|
|
end
|
|
|
|
local function doLine(window, a)
|
|
window.span(1, a, (" "):rep(w), bg, fg)
|
|
for k, v in ipairs(controls) do
|
|
if a >= v.y then
|
|
if a < (v.y + v.h) then
|
|
v.line(window, v.x, a, (a - v.y) + 1, bg, fg, selIndex == k)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
local function doZone(window, control, cache)
|
|
for i = 1, control.h do
|
|
local l = i + control.y - 1
|
|
if (not cache) or (not cache[l]) then
|
|
doLine(window, l)
|
|
if cache then cache[l] = true end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function moveIndexAU(window, vertical, negative)
|
|
local c1 = controls[selIndex]
|
|
moveIndex(vertical, negative)
|
|
local c2 = controls[selIndex]
|
|
local cache = {}
|
|
if c1 then doZone(window, c1, cache) end
|
|
if c2 then doZone(window, c2, cache) end
|
|
end
|
|
-- Attach .update for external interference
|
|
for k, v in ipairs(controls) do
|
|
v.update = function (window) doZone(window, v, {}) end
|
|
end
|
|
return function (window, ev, a, b, c, d, e)
|
|
-- X,Y,Xi,Yi,B
|
|
if ev == "touch" then
|
|
local found = nil
|
|
for k, v in ipairs(controls) do
|
|
if v.selectable then
|
|
if a >= v.x then
|
|
if a < (v.x + v.w) then
|
|
if b >= v.y then
|
|
if b < (v.y + v.h) then
|
|
found = k
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if found then
|
|
local c1 = controls[selIndex]
|
|
selIndex = found
|
|
local c2 = controls[selIndex]
|
|
local cache = {}
|
|
if c1 then doZone(window, c1, cache) end
|
|
if c2 then
|
|
doZone(window, c2, cache)
|
|
if c2.touch then
|
|
c2.touch(window, function () doZone(window, c2) end, (a - c2.x) + 1, (b - c2.y) + 1, c, d, e)
|
|
end
|
|
end
|
|
end
|
|
-- X,Y,Xi,Yi,B (or D for scroll)
|
|
elseif ev == "drag" or ev == "drop" or ev == "scroll" then
|
|
if controls[selIndex] then
|
|
if controls[selIndex][ev] then
|
|
controls[selIndex][ev](window, function () doZone(window, controls[selIndex]) end, (a - controls[selIndex].x) + 1, (b - controls[selIndex].y) + 1, c, d, e)
|
|
end
|
|
end
|
|
elseif ev == "key" then
|
|
if b == 203 then
|
|
if c then
|
|
moveIndexAU(window, false, true)
|
|
end
|
|
elseif b == 205 then
|
|
if c then
|
|
moveIndexAU(window, false, false)
|
|
end
|
|
elseif b == 200 then
|
|
if c then
|
|
moveIndexAU(window, true, true)
|
|
end
|
|
elseif b == 208 then
|
|
if c then
|
|
moveIndexAU(window, true, false)
|
|
end
|
|
elseif a == 9 then
|
|
if c then
|
|
local c1 = controls[selIndex]
|
|
rotateSelIndex()
|
|
local c2 = controls[selIndex]
|
|
local cache = {}
|
|
if c1 then doZone(window, c1, cache) end
|
|
if c2 then doZone(window, c2, cache) end
|
|
end
|
|
elseif controls[selIndex] then
|
|
if controls[selIndex].key then
|
|
controls[selIndex].key(window, function () doZone(window, controls[selIndex]) end, a, b, c)
|
|
end
|
|
end
|
|
elseif ev == "clipboard" then
|
|
if controls[selIndex] then
|
|
if controls[selIndex].clipboard then
|
|
controls[selIndex].clipboard(window, function () doZone(window, controls[selIndex]) end, a)
|
|
end
|
|
end
|
|
elseif ev == "line" then
|
|
doLine(window, a)
|
|
elseif ev == "close" then
|
|
closing(window)
|
|
end
|
|
end, doZone
|
|
end
|
|
neoux.tcrawview = function (x, y, lines)
|
|
return {
|
|
x = x,
|
|
y = y,
|
|
w = unicode.len(lines[1]),
|
|
h = #lines,
|
|
selectable = false,
|
|
line = function (window, x, y, lined, bg, fg, selected)
|
|
-- Can't be selected normally so ignore that flag
|
|
window.span(x, y, lines[lined], bg, fg)
|
|
end
|
|
}
|
|
end
|
|
neoux.tchdivider = function (x, y, w)
|
|
return neoux.tcrawview(x, y, {("-"):rep(w)})
|
|
end
|
|
neoux.tcvdivider = function (x, y, h)
|
|
local n = {}
|
|
for i = 1, h do
|
|
n[i] = "|"
|
|
end
|
|
return neoux.tcrawview(x, y, n)
|
|
end
|
|
neoux.tcbutton = function (x, y, text, callback)
|
|
text = "<" .. text .. ">"
|
|
return {
|
|
x = x,
|
|
y = y,
|
|
w = unicode.len(text),
|
|
h = 1,
|
|
selectable = true,
|
|
key = function (window, update, a, c, d)
|
|
if d then
|
|
if a == 13 or a == 32 then
|
|
callback(window)
|
|
end
|
|
end
|
|
end,
|
|
touch = function (window, update, x, y, button)
|
|
callback(window)
|
|
end,
|
|
line = function (window, x, y, lind, bg, fg, selected)
|
|
local fg1 = fg
|
|
if selected then
|
|
fg = bg
|
|
bg = fg1
|
|
end
|
|
window.span(x, y, text, bg, fg)
|
|
end
|
|
}
|
|
end
|
|
-- Note: w should be at least 2 - this is similar to buttons.
|
|
neoux.tcfield = function (x, y, w, textprop)
|
|
return {
|
|
x = x,
|
|
y = y,
|
|
w = w,
|
|
h = 1,
|
|
selectable = true,
|
|
key = function (window, update, a, c, d)
|
|
if d then
|
|
if a == 13 then
|
|
elseif a == 8 then
|
|
local str = textprop()
|
|
textprop(unicode.sub(str, 1, unicode.len(str) - 1))
|
|
update()
|
|
elseif a ~= 0 then
|
|
textprop(textprop() .. unicode.char(a))
|
|
update()
|
|
end
|
|
end
|
|
end,
|
|
line = function (window, x, y, lind, bg, fg, selected)
|
|
local fg1 = fg
|
|
if selected then
|
|
fg = bg
|
|
bg = fg1
|
|
end
|
|
local text = unicode.safeTextFormat(textprop())
|
|
local txl = unicode.len(text)
|
|
local start = math.max(1, (txl - (w - 2)) + 1)
|
|
text = "[" .. neoux.pad(unicode.sub(text, start, start + (w - 3)), w - 2, false, true) .. "]"
|
|
window.span(x, y, text, bg, fg)
|
|
end
|
|
}
|
|
end
|
|
neoux.startDialog = function (fmt, title, wait)
|
|
fmt = neoux.fmtText(unicode.safeTextFormat(fmt), 40)
|
|
neoux.create(40, #fmt, title, function (window, ev, a, b, c)
|
|
if ev == "line" then
|
|
window.span(1, a, fmt[a], 0xFFFFFF, 0)
|
|
end
|
|
if ev == "close" then
|
|
window.close()
|
|
wait = nil
|
|
end
|
|
end)
|
|
while wait do
|
|
event.pull()
|
|
end
|
|
end
|
|
return neoux
|
|
end
|
|
return newNeoux
|