From 427b871e2a7628a9ec8e55e7fc27799f0abb155b Mon Sep 17 00:00:00 2001 From: "kepler155c@gmail.com" Date: Thu, 15 Dec 2016 09:45:27 -0500 Subject: [PATCH 1/9] transitions --- apps/recorder.lua | 2 +- sys/apis/ui.lua | 103 ++++++++++++++++++++++++++++------------------ 2 files changed, 64 insertions(+), 41 deletions(-) diff --git a/apps/recorder.lua b/apps/recorder.lua index ea70a10..a1070e5 100644 --- a/apps/recorder.lua +++ b/apps/recorder.lua @@ -15,7 +15,7 @@ local version = "Version 1.1.6" -- Modified to integrate with opus os local calls, recTerm, oldTerm, arg, showInput, skipLast, lastDelay, curInput, callCount, callListCount = {{["delay"] = 0}}, {}, Util.shallowCopy(multishell.term), {...}, false, false, 2, "", 1, 2 -local curBlink, oldBlink, curCalls, tTerm, buffer, colourNum, xPos, yPos, oldXPos, oldYPos, tCol, bCol, xSize, ySize = false, false, calls[1], {}, {}, {}, 1, 1, 1, 1, colours.white, colours.black, term.getSize() +local curBlink, oldBlink, curCalls, tTerm, buffer, colourNum, xPos, yPos, oldXPos, oldYPos, tCol, bCol, xSize, ySize = false, false, calls[1], {}, {}, {}, 1, 1, 1, 1, colours.white, colours.black, oldTerm.getSize() local greys, buttons = {["0"] = true, ["7"] = true, ["8"] = true, ["f"] = true}, {"l", "r", "m"} local charW, charH, chars, resp local filename diff --git a/sys/apis/ui.lua b/sys/apis/ui.lua index c356841..c05a626 100644 --- a/sys/apis/ui.lua +++ b/sys/apis/ui.lua @@ -91,11 +91,26 @@ end --[[-- Top Level Manager --]]-- local Manager = class() +Manager.effect = { + slideLeft = { + type = 'slideLeft', + ticks = 12, + easing = 'outBounce', + }, + slideRight = { + type = 'slideRight', + ticks = 12, + easing = 'outBounce', + }, +} + function Manager:init(args) local control = false local shift = false local pages = { } + self.effectsEnabled = true + Event.addHandler('term_resize', function(h, side) if self.currentPage then -- the parent doesn't have any children set... @@ -198,7 +213,6 @@ function Manager:configure(appName, ...) textScale = { arg = 't', type = 'number', desc = 'Text scale' }, } - local defaults = Util.loadTable('/config/' .. appName) or { } if not defaults.device then defaults.device = { } @@ -241,10 +255,14 @@ function Manager:configure(appName, ...) end end +function Manager:disableEffects() + self.effectsEnabled = false +end + function Manager:loadTheme(filename) local theme, err = Util.loadTable(filename) if not theme then - error(theme) + error(err) end for k,v in pairs(theme) do if self[k] and self[k].defaults then @@ -835,7 +853,6 @@ UI.Device.defaults = { textColor = colors.white, textScale = 1, lines = { }, - transitionsEnabled = true, } function UI.Device:init(args) @@ -903,8 +920,13 @@ function UI.Device:setTransition(effect, y, height) end end -function UI.Device:runTransition(transition) - if transition == 'left' or transition == 'right' then +function UI.Device:runTransition(effect) + + if not self.Tween then + self.Tween = require('tween') + end + + if effect.type == 'slideLeft' or effect.type == 'slideRight' then for y, line in ipairs(self.lines) do if not line.transition then self.device.setCursorPos(1, y) @@ -912,36 +934,37 @@ function UI.Device:runTransition(transition) end end - local c = os.clock() - local steps = math.floor(self.width * .34) -- 150 ms + local pos = { x = 1 } + local tween = self.Tween.new(effect.ticks, pos, { x = self.width }, effect.easing) - for i = 1, self.width do - for y, line in pairs(self.lines) do - if line.transition then - if transition == 'left' then - local text = self.lastScreen[y].text .. line.text - local bg = self.lastScreen[y].bg .. line.bg - local fg = self.lastScreen[y].fg .. line.fg - self.device.setCursorPos(1 - i, y) - self.device.blit(text, fg, bg) - else - local text = line.text .. self.lastScreen[y].text - local bg = line.bg .. self.lastScreen[y].bg - local fg = line.fg .. self.lastScreen[y].fg - self.device.setCursorPos(-self.width + i + 1, y) - self.device.blit(text, fg, bg) + local lastx = 0 + repeat + tween:update(1) + local x = math.floor(pos.x) + if x ~= lastx then + lastx = x + for y, line in pairs(self.lines) do + if line.transition then + if effect.type == 'slideLeft' then + local text = self.lastScreen[y].text .. line.text + local bg = self.lastScreen[y].bg .. line.bg + local fg = self.lastScreen[y].fg .. line.fg + self.device.setCursorPos(1 - x, y) + self.device.blit(text, fg, bg) + else + local text = line.text .. self.lastScreen[y].text + local bg = line.bg .. self.lastScreen[y].bg + local fg = line.fg .. self.lastScreen[y].fg + self.device.setCursorPos(-self.width + x + 1, y) + self.device.blit(text, fg, bg) + end end end end - if (i + math.floor(steps / 2)) % steps == 0 then - if c == os.clock() then - os.sleep(0) - c = os.clock() - end - end - end + os.sleep() + until pos.x == self.width - elseif transition == 'explode' then + elseif effect.type == 'explode' then local half = math.floor(self.width / 2) local c = os.clock() local steps = math.floor(self.width * .5) @@ -973,7 +996,7 @@ end function UI.Device:sync() local transition - if self.transition then + if self.transition and UI.effectsEnabled then for y, line in pairs(self.lines) do if line.dirty then transition = self.transition @@ -983,7 +1006,7 @@ function UI.Device:sync() self.transition = nil end - if transition and self.transitionsEnabled then + if transition then self:runTransition(transition) else for y, line in pairs(self.lines) do @@ -2070,9 +2093,9 @@ function UI.Tabs:eventHandler(event) for _,tab in ipairs(self.children) do if tab ~= self.tabBar then if event.current > event.last then - tab:setTransition('left') + tab:setTransition(UI.effect.slideLeft) else - tab:setTransition('right') + tab:setTransition(UI.effect.slideRight) end break end @@ -2102,7 +2125,7 @@ function UI.WindowScroller:nextChild() for i = 1, #self.children do if self.children[i].enabled then if i < #self.children then - self:setTransition('left') + self:setTransition(UI.effect.slideLeft) self.children[i]:disable() self.children[i + 1]:enable() end @@ -2115,7 +2138,7 @@ function UI.WindowScroller:prevChild() for i = 1, #self.children do if self.children[i].enabled then if i - 1 > 0 then - self:setTransition('right') + self:setTransition(UI.effect.slideRight) self.children[i]:disable() self.children[i - 1]:enable() end @@ -2924,7 +2947,7 @@ function UI.Image:setParent() end function UI.Image:draw() - self:clear(bg) + self:clear() if self.image then for y = 1, #self.image do local line = self.image[y] @@ -2968,7 +2991,7 @@ function UI.NftImage:setParent() end function UI.NftImage:draw() --- self:clear(bg) +-- self:clear() if self.image then for y = 1, self.image.height do for x = 1, #self.image.text[y] do @@ -2984,10 +3007,10 @@ function UI.NftImage:setImage(image) self.image = image end -UI:setDefaultDevice(UI.Device({ device = term.current() })) - if fs.exists('/config/ui.theme') then UI:loadTheme('/config/ui.theme') end +UI:setDefaultDevice(UI.Device({ device = term.current() })) + return UI From f8f0a1beedc98070a728f4e22b4fc7711c57d85e Mon Sep 17 00:00:00 2001 From: "kepler155c@gmail.com" Date: Thu, 15 Dec 2016 09:56:25 -0500 Subject: [PATCH 2/9] transitions --- sys/apis/tween.lua | 367 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 367 insertions(+) create mode 100644 sys/apis/tween.lua diff --git a/sys/apis/tween.lua b/sys/apis/tween.lua new file mode 100644 index 0000000..48a1143 --- /dev/null +++ b/sys/apis/tween.lua @@ -0,0 +1,367 @@ +local tween = { + _VERSION = 'tween 2.1.1', + _DESCRIPTION = 'tweening for lua', + _URL = 'https://github.com/kikito/tween.lua', + _LICENSE = [[ + MIT LICENSE + + Copyright (c) 2014 Enrique GarcĂ­a Cota, Yuichi Tateno, Emmanuel Oga + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + ]] +} + +-- easing + +-- Adapted from https://github.com/EmmanuelOga/easing. See LICENSE.txt for credits. +-- For all easing functions: +-- t = time == how much time has to pass for the tweening to complete +-- b = begin == starting property value +-- c = change == ending - beginning +-- d = duration == running time. How much time has passed *right now* + +local pow, sin, cos, pi, sqrt, abs, asin = math.pow, math.sin, math.cos, math.pi, math.sqrt, math.abs, math.asin + +-- linear +local function linear(t, b, c, d) return c * t / d + b end + +-- quad +local function inQuad(t, b, c, d) return c * pow(t / d, 2) + b end +local function outQuad(t, b, c, d) + t = t / d + return -c * t * (t - 2) + b +end +local function inOutQuad(t, b, c, d) + t = t / d * 2 + if t < 1 then return c / 2 * pow(t, 2) + b end + return -c / 2 * ((t - 1) * (t - 3) - 1) + b +end +local function outInQuad(t, b, c, d) + if t < d / 2 then return outQuad(t * 2, b, c / 2, d) end + return inQuad((t * 2) - d, b + c / 2, c / 2, d) +end + +-- cubic +local function inCubic (t, b, c, d) return c * pow(t / d, 3) + b end +local function outCubic(t, b, c, d) return c * (pow(t / d - 1, 3) + 1) + b end +local function inOutCubic(t, b, c, d) + t = t / d * 2 + if t < 1 then return c / 2 * t * t * t + b end + t = t - 2 + return c / 2 * (t * t * t + 2) + b +end +local function outInCubic(t, b, c, d) + if t < d / 2 then return outCubic(t * 2, b, c / 2, d) end + return inCubic((t * 2) - d, b + c / 2, c / 2, d) +end + +-- quart +local function inQuart(t, b, c, d) return c * pow(t / d, 4) + b end +local function outQuart(t, b, c, d) return -c * (pow(t / d - 1, 4) - 1) + b end +local function inOutQuart(t, b, c, d) + t = t / d * 2 + if t < 1 then return c / 2 * pow(t, 4) + b end + return -c / 2 * (pow(t - 2, 4) - 2) + b +end +local function outInQuart(t, b, c, d) + if t < d / 2 then return outQuart(t * 2, b, c / 2, d) end + return inQuart((t * 2) - d, b + c / 2, c / 2, d) +end + +-- quint +local function inQuint(t, b, c, d) return c * pow(t / d, 5) + b end +local function outQuint(t, b, c, d) return c * (pow(t / d - 1, 5) + 1) + b end +local function inOutQuint(t, b, c, d) + t = t / d * 2 + if t < 1 then return c / 2 * pow(t, 5) + b end + return c / 2 * (pow(t - 2, 5) + 2) + b +end +local function outInQuint(t, b, c, d) + if t < d / 2 then return outQuint(t * 2, b, c / 2, d) end + return inQuint((t * 2) - d, b + c / 2, c / 2, d) +end + +-- sine +local function inSine(t, b, c, d) return -c * cos(t / d * (pi / 2)) + c + b end +local function outSine(t, b, c, d) return c * sin(t / d * (pi / 2)) + b end +local function inOutSine(t, b, c, d) return -c / 2 * (cos(pi * t / d) - 1) + b end +local function outInSine(t, b, c, d) + if t < d / 2 then return outSine(t * 2, b, c / 2, d) end + return inSine((t * 2) -d, b + c / 2, c / 2, d) +end + +-- expo +local function inExpo(t, b, c, d) + if t == 0 then return b end + return c * pow(2, 10 * (t / d - 1)) + b - c * 0.001 +end +local function outExpo(t, b, c, d) + if t == d then return b + c end + return c * 1.001 * (-pow(2, -10 * t / d) + 1) + b +end +local function inOutExpo(t, b, c, d) + if t == 0 then return b end + if t == d then return b + c end + t = t / d * 2 + if t < 1 then return c / 2 * pow(2, 10 * (t - 1)) + b - c * 0.0005 end + return c / 2 * 1.0005 * (-pow(2, -10 * (t - 1)) + 2) + b +end +local function outInExpo(t, b, c, d) + if t < d / 2 then return outExpo(t * 2, b, c / 2, d) end + return inExpo((t * 2) - d, b + c / 2, c / 2, d) +end + +-- circ +local function inCirc(t, b, c, d) return(-c * (sqrt(1 - pow(t / d, 2)) - 1) + b) end +local function outCirc(t, b, c, d) return(c * sqrt(1 - pow(t / d - 1, 2)) + b) end +local function inOutCirc(t, b, c, d) + t = t / d * 2 + if t < 1 then return -c / 2 * (sqrt(1 - t * t) - 1) + b end + t = t - 2 + return c / 2 * (sqrt(1 - t * t) + 1) + b +end +local function outInCirc(t, b, c, d) + if t < d / 2 then return outCirc(t * 2, b, c / 2, d) end + return inCirc((t * 2) - d, b + c / 2, c / 2, d) +end + +-- elastic +local function calculatePAS(p,a,c,d) + p, a = p or d * 0.3, a or 0 + if a < abs(c) then return p, c, p / 4 end -- p, a, s + return p, a, p / (2 * pi) * asin(c/a) -- p,a,s +end +local function inElastic(t, b, c, d, a, p) + local s + if t == 0 then return b end + t = t / d + if t == 1 then return b + c end + p,a,s = calculatePAS(p,a,c,d) + t = t - 1 + return -(a * pow(2, 10 * t) * sin((t * d - s) * (2 * pi) / p)) + b +end +local function outElastic(t, b, c, d, a, p) + local s + if t == 0 then return b end + t = t / d + if t == 1 then return b + c end + p,a,s = calculatePAS(p,a,c,d) + return a * pow(2, -10 * t) * sin((t * d - s) * (2 * pi) / p) + c + b +end +local function inOutElastic(t, b, c, d, a, p) + local s + if t == 0 then return b end + t = t / d * 2 + if t == 2 then return b + c end + p,a,s = calculatePAS(p,a,c,d) + t = t - 1 + if t < 0 then return -0.5 * (a * pow(2, 10 * t) * sin((t * d - s) * (2 * pi) / p)) + b end + return a * pow(2, -10 * t) * sin((t * d - s) * (2 * pi) / p ) * 0.5 + c + b +end +local function outInElastic(t, b, c, d, a, p) + if t < d / 2 then return outElastic(t * 2, b, c / 2, d, a, p) end + return inElastic((t * 2) - d, b + c / 2, c / 2, d, a, p) +end + +-- back +local function inBack(t, b, c, d, s) + s = s or 1.70158 + t = t / d + return c * t * t * ((s + 1) * t - s) + b +end +local function outBack(t, b, c, d, s) + s = s or 1.70158 + t = t / d - 1 + return c * (t * t * ((s + 1) * t + s) + 1) + b +end +local function inOutBack(t, b, c, d, s) + s = (s or 1.70158) * 1.525 + t = t / d * 2 + if t < 1 then return c / 2 * (t * t * ((s + 1) * t - s)) + b end + t = t - 2 + return c / 2 * (t * t * ((s + 1) * t + s) + 2) + b +end +local function outInBack(t, b, c, d, s) + if t < d / 2 then return outBack(t * 2, b, c / 2, d, s) end + return inBack((t * 2) - d, b + c / 2, c / 2, d, s) +end + +-- bounce +local function outBounce(t, b, c, d) + t = t / d + if t < 1 / 2.75 then return c * (7.5625 * t * t) + b end + if t < 2 / 2.75 then + t = t - (1.5 / 2.75) + return c * (7.5625 * t * t + 0.75) + b + elseif t < 2.5 / 2.75 then + t = t - (2.25 / 2.75) + return c * (7.5625 * t * t + 0.9375) + b + end + t = t - (2.625 / 2.75) + return c * (7.5625 * t * t + 0.984375) + b +end +local function inBounce(t, b, c, d) return c - outBounce(d - t, 0, c, d) + b end +local function inOutBounce(t, b, c, d) + if t < d / 2 then return inBounce(t * 2, 0, c, d) * 0.5 + b end + return outBounce(t * 2 - d, 0, c, d) * 0.5 + c * .5 + b +end +local function outInBounce(t, b, c, d) + if t < d / 2 then return outBounce(t * 2, b, c / 2, d) end + return inBounce((t * 2) - d, b + c / 2, c / 2, d) +end + +tween.easing = { + linear = linear, + inQuad = inQuad, outQuad = outQuad, inOutQuad = inOutQuad, outInQuad = outInQuad, + inCubic = inCubic, outCubic = outCubic, inOutCubic = inOutCubic, outInCubic = outInCubic, + inQuart = inQuart, outQuart = outQuart, inOutQuart = inOutQuart, outInQuart = outInQuart, + inQuint = inQuint, outQuint = outQuint, inOutQuint = inOutQuint, outInQuint = outInQuint, + inSine = inSine, outSine = outSine, inOutSine = inOutSine, outInSine = outInSine, + inExpo = inExpo, outExpo = outExpo, inOutExpo = inOutExpo, outInExpo = outInExpo, + inCirc = inCirc, outCirc = outCirc, inOutCirc = inOutCirc, outInCirc = outInCirc, + inElastic = inElastic, outElastic = outElastic, inOutElastic = inOutElastic, outInElastic = outInElastic, + inBack = inBack, outBack = outBack, inOutBack = inOutBack, outInBack = outInBack, + inBounce = inBounce, outBounce = outBounce, inOutBounce = inOutBounce, outInBounce = outInBounce +} + + + +-- private stuff + +local function copyTables(destination, keysTable, valuesTable) + valuesTable = valuesTable or keysTable + local mt = getmetatable(keysTable) + if mt and getmetatable(destination) == nil then + setmetatable(destination, mt) + end + for k,v in pairs(keysTable) do + if type(v) == 'table' then + destination[k] = copyTables({}, v, valuesTable[k]) + else + destination[k] = valuesTable[k] + end + end + return destination +end + +local function checkSubjectAndTargetRecursively(subject, target, path) + path = path or {} + local targetType, newPath + for k,targetValue in pairs(target) do + targetType, newPath = type(targetValue), copyTables({}, path) + table.insert(newPath, tostring(k)) + if targetType == 'number' then + assert(type(subject[k]) == 'number', "Parameter '" .. table.concat(newPath,'/') .. "' is missing from subject or isn't a number") + elseif targetType == 'table' then + checkSubjectAndTargetRecursively(subject[k], targetValue, newPath) + else + assert(targetType == 'number', "Parameter '" .. table.concat(newPath,'/') .. "' must be a number or table of numbers") + end + end +end + +local function checkNewParams(duration, subject, target, easing) + assert(type(duration) == 'number' and duration > 0, "duration must be a positive number. Was " .. tostring(duration)) + local tsubject = type(subject) + assert(tsubject == 'table' or tsubject == 'userdata', "subject must be a table or userdata. Was " .. tostring(subject)) + assert(type(target)== 'table', "target must be a table. Was " .. tostring(target)) + assert(type(easing)=='function', "easing must be a function. Was " .. tostring(easing)) + checkSubjectAndTargetRecursively(subject, target) +end + +local function getEasingFunction(easing) + easing = easing or "linear" + if type(easing) == 'string' then + local name = easing + easing = tween.easing[name] + if type(easing) ~= 'function' then + error("The easing function name '" .. name .. "' is invalid") + end + end + return easing +end + +local function performEasingOnSubject(subject, target, initial, clock, duration, easing) + local t,b,c,d + for k,v in pairs(target) do + if type(v) == 'table' then + performEasingOnSubject(subject[k], v, initial[k], clock, duration, easing) + else + t,b,c,d = clock, initial[k], v - initial[k], duration + subject[k] = easing(t,b,c,d) + end + end +end + +-- Tween methods + +local Tween = {} +local Tween_mt = {__index = Tween} + +function Tween:set(clock) + assert(type(clock) == 'number', "clock must be a positive number or 0") + + self.initial = self.initial or copyTables({}, self.target, self.subject) + self.clock = clock + + if self.clock <= 0 then + + self.clock = 0 + copyTables(self.subject, self.initial) + + elseif self.clock >= self.duration then -- the tween has expired + + self.clock = self.duration + copyTables(self.subject, self.target) + + else + + performEasingOnSubject(self.subject, self.target, self.initial, self.clock, self.duration, self.easing) + + end + + return self.clock >= self.duration +end + +function Tween:reset() + return self:set(0) +end + +function Tween:update(dt) + assert(type(dt) == 'number', "dt must be a number") + return self:set(self.clock + dt) +end + + +-- Public interface + +function tween.new(duration, subject, target, easing) + easing = getEasingFunction(easing) + checkNewParams(duration, subject, target, easing) + return setmetatable({ + duration = duration, + subject = subject, + target = target, + easing = easing, + clock = 0 + }, Tween_mt) +end + +return tween \ No newline at end of file From ddeaddae3d08895fcbadab08eb2e09236560904a Mon Sep 17 00:00:00 2001 From: "kepler155c@gmail.com" Date: Thu, 15 Dec 2016 13:32:33 -0500 Subject: [PATCH 3/9] transitions --- apps/Overview.lua | 35 ++++++++++++++++++++---- sys/apis/ui.lua | 68 ++++++++++++++++++++++++++++++----------------- 2 files changed, 74 insertions(+), 29 deletions(-) diff --git a/apps/Overview.lua b/apps/Overview.lua index a17aa5c..77556dc 100644 --- a/apps/Overview.lua +++ b/apps/Overview.lua @@ -6,6 +6,7 @@ local Config = require('config') local NFT = require('nft') local class = require('class') local FileUI = require('fileui') +local Tween = require('tween') multishell.setTitle(multishell.getCurrent(), 'Overview') UI:configure('Overview', ...) @@ -190,8 +191,9 @@ function page.container:setCategory(categoryName) -- reposition all children for k,child in ipairs(self.children) do - child.x = col - child.y = row + child.x = -10 + child.y = math.floor(self.height) + child.tween = Tween.new(6, child, { x = col, y = row }, 'outSine') if k < count then col = col + child.width @@ -203,6 +205,25 @@ function page.container:setCategory(categoryName) end self:initChildren() + self.animate = true +end + +function page.container:draw() + if self.animate then + self.animate = false + for i = 1, 6 do + for _,child in ipairs(self.children) do + child.tween:update(1) + child.x = math.floor(child.x) + child.y = math.floor(child.y) + end + UI.ViewportWindow.draw(self) + self:sync() + os.sleep() + end + else + UI.ViewportWindow.draw(self) + end end function page:refresh() @@ -223,6 +244,8 @@ function page:eventHandler(event) self.tabBar:selectTab(event.button.text) self.container:setCategory(event.button.text) self.container:draw() + self:sync() + config.currentCategory = event.button.text Config.update('Overview', config) @@ -263,9 +286,9 @@ function page:eventHandler(event) elseif event.type == 'tab_change' then if event.current > event.last then - self.container:setTransition('left') + --self.container:setTransition(UI.effect.slideLeft) else - self.container:setTransition('right') + --self.container:setTransition(UI.effect.slideRight) end elseif event.type == 'refresh' then @@ -391,7 +414,9 @@ function editor:eventHandler(event) self.statusBar:draw() elseif event.type == 'loadIcon' then - UI:setPage(FileUI(), fs.getDir(self.iconFile), function(fileName) + local fileui = FileUI() + --fileui:setTransition(UI.effect.explode) + UI:setPage(fileui, fs.getDir(self.iconFile), function(fileName) if fileName then self.iconFile = fileName local s, m = pcall(function() diff --git a/sys/apis/ui.lua b/sys/apis/ui.lua index c05a626..528323a 100644 --- a/sys/apis/ui.lua +++ b/sys/apis/ui.lua @@ -102,6 +102,11 @@ Manager.effect = { ticks = 12, easing = 'outBounce', }, + explode = { + type = 'explode', + ticks = 12, + easing = 'outBounce', + }, } function Manager:init(args) @@ -109,8 +114,6 @@ function Manager:init(args) local shift = false local pages = { } - self.effectsEnabled = true - Event.addHandler('term_resize', function(h, side) if self.currentPage then -- the parent doesn't have any children set... @@ -256,7 +259,7 @@ function Manager:configure(appName, ...) end function Manager:disableEffects() - self.effectsEnabled = false + self.defaultDevice.effectsEnabled = false end function Manager:loadTheme(filename) @@ -819,11 +822,13 @@ function UI.Window:scrollIntoView() end end -function UI.Window:setTransition(effect, y, height) +function UI.Window:setTransition(effect, x, y, width, height) if self.parent then + x = x or 1 y = y or 1 + width = width or self.width height = height or self.height - self.parent:setTransition(effect, y + self.y - 1, height) + self.parent:setTransition(effect, x + self.x, y + self.y - 1, width, height) end end @@ -852,6 +857,7 @@ UI.Device.defaults = { backgroundColor = colors.black, textColor = colors.white, textScale = 1, + effectsEnabled = true, lines = { }, } @@ -908,9 +914,13 @@ function UI.Device:reset() self.device.setCursorPos(1, 1) end -function UI.Device:setTransition(effect, y, height) +function UI.Device:setTransition(effect, x, y, width, height) if not self.transition then self.transition = effect + effect.x = x + effect.y = y + effect.width = width + effect.height = height for i = y, y + height - 1 do local line = self.lines[i] if line then @@ -965,26 +975,36 @@ function UI.Device:runTransition(effect) until pos.x == self.width elseif effect.type == 'explode' then - local half = math.floor(self.width / 2) - local c = os.clock() - local steps = math.floor(self.width * .5) - for i = 1, half do - for y, line in pairs(self.lines) do - local width = i * 2 - local mid = half - i + 1 - self.device.setCursorPos(mid, y) - self.device.blit( - line.text:sub(mid, mid + width), - line.fg:sub(mid, mid + width), - line.bg:sub(mid, mid + width)) + local pos = { x = 1 } + local tween = self.Tween.new(effect.ticks, pos, { x = 100 }, effect.easing) + local mx = math.floor(effect.width / 2) + local my = math.floor(effect.height / 2) + + local function replace(sstr, pos, rstr, width) + return sstr:sub(1, pos-1) .. rstr .. sstr:sub(pos+width) end - if (i + math.floor(steps / 2)) % steps == 0 then - if c == os.clock() then - os.sleep(0) - c = os.clock() + +debug('running') + repeat + tween:update(1) + local ux = math.floor(effect.width * pos.x / 200) + local uy = math.floor(effect.height * pos.x / 200) + local width = ux * 2 + local sx = mx - ux + 1 +debug({ pos.x, ux, uy }) + for y = my - uy, my + uy do + local line = self.lines[y] + if line then + self.device.setCursorPos(1, y) + self.device.blit( + replace(self.lastScreen[y].text, sx, line.text:sub(sx, sx + width - 1), width), + replace(self.lastScreen[y].fg, sx, line.fg:sub(sx, sx + width - 1), width), + replace(self.lastScreen[y].bg, sx, line.bg:sub(sx, sx + width - 1), width)) end end - end + os.sleep(.4) + until pos.x == 100 +debug('done running') end for y, line in ipairs(self.lines) do @@ -996,7 +1016,7 @@ end function UI.Device:sync() local transition - if self.transition and UI.effectsEnabled then + if self.transition and self.effectsEnabled then for y, line in pairs(self.lines) do if line.dirty then transition = self.transition From 3fdd7336f13d4f8b53d190ae80cf87df2cb78599 Mon Sep 17 00:00:00 2001 From: "kepler155c@gmail.com" Date: Thu, 15 Dec 2016 20:12:40 -0500 Subject: [PATCH 4/9] script bug, improve recorder --- apps/Script.lua | 6 ++-- apps/recorder.lua | 76 +++++++++++++++++++++++++---------------------- 2 files changed, 44 insertions(+), 38 deletions(-) diff --git a/apps/Script.lua b/apps/Script.lua index 3a69aec..4b5cd1b 100644 --- a/apps/Script.lua +++ b/apps/Script.lua @@ -8,7 +8,7 @@ local GROUPS_PATH = '/apps/groups' local SCRIPTS_PATH = '/apps/scripts' multishell.setTitle(multishell.getCurrent(), 'Script') -UI:configure('Script', ...) +UI:configure('script', ...) local config = { showGroups = false, @@ -17,7 +17,7 @@ local config = { }]], } -Config.load('Script', config) +Config.load('script', config) local width = math.floor(UI.term.width / 2) - 1 if UI.term.width % 2 ~= 0 then @@ -453,7 +453,7 @@ function mainPage:eventHandler(event) -- self.statusBar.toggleButton.text = text self:draw() - Config.update('Script', config) + Config.update('script', config) elseif event.type == 'grid_focus_row' then local computer = self.computers:getSelected() diff --git a/apps/recorder.lua b/apps/recorder.lua index a1070e5..660a970 100644 --- a/apps/recorder.lua +++ b/apps/recorder.lua @@ -14,12 +14,17 @@ local version = "Version 1.1.6" -- Original code by Bomb Bloke -- Modified to integrate with opus os -local calls, recTerm, oldTerm, arg, showInput, skipLast, lastDelay, curInput, callCount, callListCount = {{["delay"] = 0}}, {}, Util.shallowCopy(multishell.term), {...}, false, false, 2, "", 1, 2 -local curBlink, oldBlink, curCalls, tTerm, buffer, colourNum, xPos, yPos, oldXPos, oldYPos, tCol, bCol, xSize, ySize = false, false, calls[1], {}, {}, {}, 1, 1, 1, 1, colours.white, colours.black, oldTerm.getSize() +local recTerm, oldTerm, arg, showInput, skipLast, lastDelay, curInput = {}, Util.shallowCopy(multishell.term), {...}, false, false, 2, "" +local curBlink, oldBlink, tTerm, buffer, colourNum, xPos, yPos, oldXPos, oldYPos, tCol, bCol, xSize, ySize = false, false, {}, {}, {}, 1, 1, 1, 1, colours.white, colours.black, oldTerm.getSize() local greys, buttons = {["0"] = true, ["7"] = true, ["8"] = true, ["f"] = true}, {"l", "r", "m"} local charW, charH, chars, resp local filename +local calls = { } +local curCalls = { delay = 0 } +local callListCount = 0 +local callCount = 0 + local function showSyntax() print('Gif Recorder by Bomb Bloke\n') print('Syntax: recGif [-i] [-s] [-ld:] filename') @@ -123,13 +128,14 @@ recTerm = multishell.term for key, func in pairs(oldTerm) do recTerm[key] = function(...) - local result = {pcall(func, ...)} + local result = { func(...) } - if result[1] then - curCalls[callCount] = {key, ...} - callCount = callCount + 1 - return unpack(result, 2) - else error(result[2], 2) end + if callCount == 0 then + os.queueEvent('capture_frame') + end + callCount = callCount + 1 + curCalls[callCount] = { key, ... } + return unpack(result) end end @@ -149,36 +155,27 @@ for _,tab in pairs(tabs) do end end -do - local curTime = os.clock() - 1 +local curTime = os.clock() - 1 - while true do - local event = { os.pullEventRaw() } +while true do + local event = { os.pullEventRaw() } - if event[1] == 'recorder_stop' or event[1] == 'terminate' then - break - end + if event[1] == 'recorder_stop' or event[1] == 'terminate' then + break + end + if event[1] == 'capture_frame' then local newTime = os.clock() - if newTime ~= curTime then - local delay = curCalls.delay + (newTime - curTime) - curTime = newTime - - if callCount > 1 then - curCalls.delay = curCalls.delay + delay - curCalls, callCount = {["delay"] = 0}, 1 - calls[callListCount] = curCalls - callListCount = callListCount + 1 - elseif callListCount > 2 then - calls[callListCount - 2].delay = calls[callListCount - 2].delay + delay - end + if callListCount > 0 then + calls[callListCount].delay = (newTime - curTime) end - if showInput and (event[1] == "key" or event[1] == "mouse_click") then - curCalls[callCount] = {unpack(event)} - callCount = callCount + 1 - end + curTime = newTime + callListCount = callListCount + 1 + calls[callListCount] = curCalls + + curCalls, callCount = { delay = 0 }, 0 end end @@ -196,8 +193,6 @@ if skipLast and #calls > 1 then calls[#calls] = nil end calls[#calls].delay = lastDelay --- Recording done, bug user as to whether to encode it: - print(string.format("Encoding %d frames...", #calls)) --Util.writeTable('tmp/raw.txt', calls) @@ -463,7 +458,14 @@ for i = 1, #calls do oldBlink, oldXPos, oldYPos = curBlink, xPos, yPos - local thisFrame = {["xstart"] = (xMin - 1) * charW, ["ystart"] = (yMin - 1) * charH, ["xend"] = (xMax - xMin + 1) * charW, ["yend"] = (yMax - yMin + 1) * charH, ["delay"] = curCalls.delay, ["disposal"] = 1} + local thisFrame = { + ["xstart"] = (xMin - 1) * charW, + ["ystart"] = (yMin - 1) * charH, + ["xend"] = (xMax - xMin + 1) * charW, + ["yend"] = (yMax - yMin + 1) * charH, + ["delay"] = curCalls.delay, + ["disposal"] = 1 + } for y = 1, (yMax - yMin + 1) * charH do local row = {} @@ -515,7 +517,11 @@ for i = 1, #calls do snooze() end - if changed then image[#image + 1] = thisFrame else image[#image].delay = image[#image].delay + curCalls.delay end + if changed then + image[#image + 1] = thisFrame + else + image[#image].delay = image[#image].delay + curCalls.delay + end end buffer = nil From 4435a14ed15c3ba95016a7ab0231416f35d30291 Mon Sep 17 00:00:00 2001 From: "kepler155c@gmail.com" Date: Sun, 18 Dec 2016 14:38:48 -0500 Subject: [PATCH 5/9] transitions and Lua improvements --- apps/Lua.lua | 38 ++-- apps/System.lua | 2 +- apps/simpleMiner.lua | 10 +- sys/apis/injector.lua | 164 ++++++++++++----- sys/apis/terminal.lua | 12 ++ sys/apis/ui.lua | 410 ++++++++++++++++++++++++------------------ sys/apis/util.lua | 44 ++--- 7 files changed, 420 insertions(+), 260 deletions(-) diff --git a/apps/Lua.lua b/apps/Lua.lua index c4493d1..6171783 100644 --- a/apps/Lua.lua +++ b/apps/Lua.lua @@ -15,7 +15,7 @@ UI:configure('Lua', ...) local command = '' local history = History.load('.lua_history', 25) -local resultsPage = UI.Page({ +local page = UI.Page({ menuBar = UI.MenuBar({ buttons = { { text = 'Local', event = 'local' }, @@ -47,7 +47,7 @@ local resultsPage = UI.Page({ notification = UI.Notification(), }) -function resultsPage:setPrompt(value, focus) +function page:setPrompt(value, focus) self.prompt:setValue(value) self.prompt.scroll = 0 self.prompt:setPosition(#value) @@ -59,29 +59,29 @@ function resultsPage:setPrompt(value, focus) self.prompt:draw() if focus then - resultsPage:setFocus(self.prompt) + page:setFocus(self.prompt) end end -function resultsPage:enable() +function page:enable() self:setFocus(self.prompt) UI.Page.enable(self) end -function resultsPage:eventHandler(event) +function page:eventHandler(event) if event.type == 'global' then - resultsPage:setPrompt('', true) + page:setPrompt('', true) self:executeStatement('getfenv(0)') command = nil elseif event.type == 'local' then - resultsPage:setPrompt('', true) + page:setPrompt('', true) self:executeStatement('getfenv(1)') command = nil elseif event.type == 'device' then - resultsPage:setPrompt('device', true) + page:setPrompt('device', true) self:executeStatement('device') elseif event.type == 'history_back' then @@ -128,7 +128,7 @@ function resultsPage:eventHandler(event) return true end -function resultsPage:setResult(result) +function page:setResult(result) local t = { } local function safeValue(v) @@ -169,7 +169,7 @@ function resultsPage:setResult(result) self:draw() end -function resultsPage.grid:eventHandler(event) +function page.grid:eventHandler(event) local entry = self:getSelected() @@ -199,18 +199,18 @@ function resultsPage.grid:eventHandler(event) if event.type == 'grid_focus_row' then if self.focused then - resultsPage:setPrompt(commandAppend()) + page:setPrompt(commandAppend()) end elseif event.type == 'grid_select' then - resultsPage:setPrompt(commandAppend(), true) - resultsPage:executeStatement(commandAppend()) + page:setPrompt(commandAppend(), true) + page:executeStatement(commandAppend()) else return UI.Grid.eventHandler(self, event) end return true end -function resultsPage:rawExecute(s) +function page:rawExecute(s) local fn, m = loadstring("return (" .. s .. ')', 'lua') if not fn then @@ -225,7 +225,7 @@ function resultsPage:rawExecute(s) return fn, m end -function resultsPage:executeStatement(statement) +function page:executeStatement(statement) command = statement @@ -242,6 +242,12 @@ function resultsPage:executeStatement(statement) end end -UI:setPage(resultsPage) +sandboxEnv.args = { ... } +if sandboxEnv.args[1] then + command = 'args[1]' + page:setResult(sandboxEnv.args[1]) +end + +UI:setPage(page) Event.pullEvents() UI.term:reset() diff --git a/apps/System.lua b/apps/System.lua index 4aaa1ee..94b4717 100644 --- a/apps/System.lua +++ b/apps/System.lua @@ -75,7 +75,7 @@ local systemPage = UI.Page { value = 'Label' }, label = UI.TextEntry { - x = 9, y = 2, rex = -12, + x = 9, y = 2, rex = -4, limit = 32, value = os.getComputerLabel(), backgroundFocusColor = colors.black, diff --git a/apps/simpleMiner.lua b/apps/simpleMiner.lua index b9ad668..4882fc1 100644 --- a/apps/simpleMiner.lua +++ b/apps/simpleMiner.lua @@ -212,7 +212,7 @@ function enderChestUnload() turtle.select(1) turtle.drop(64) - turtle.digDown() + turtle.digDown() end end @@ -246,8 +246,9 @@ function makeWalkableTunnel(action, tpt, pt) if action ~= 'turn' and not Point.compare(tpt, { x = 0, z = 0 }) then -- not at source if not Point.compare(tpt, pt) then -- not at dest local r, block = turtle.inspectUp() - if r and block.name ~= 'minecraft:cobblestone' then - if block.name ~= 'minecraft:chest' then + if r and not turtle.isTurtleAtSide('top') then + if block.name ~= 'minecraft:cobblestone' and + block.name ~= 'minecraft:chest' then turtle.digUp() end end @@ -483,7 +484,10 @@ function boreCommand() turtle.clearMoveCallback() + -- location is either mined, currently being mined or is the + -- dropoff point for a turtle if inspect(turtle.getAction('up'), 'minecraft:cobblestone') or + inspect(turtle.getAction('up'), 'minecraft:chest') or inspect(turtle.getAction('down'), 'minecraft:cobblestone') then return true end diff --git a/sys/apis/injector.lua b/sys/apis/injector.lua index bacd665..a120ea7 100644 --- a/sys/apis/injector.lua +++ b/sys/apis/injector.lua @@ -1,68 +1,144 @@ -local resolver, loader +local DEFAULT_UPATH = 'https://raw.githubusercontent.com/kepler155c/opus/master/sys/apis' +local PASTEBIN_URL = 'http://pastebin.com/raw' +local GIT_URL = 'https://raw.githubusercontent.com/' -local function resolveFile(filename, dir, lua_path) +local function shellSearcher(modname, env, shell) + local fname = modname:gsub('%.', '/') .. '.lua' - if filename:sub(1, 1) == "/" then - if not fs.exists(filename) then - error('Unable to load: ' .. filename, 2) - end - return filename - end - - if dir then - local path = fs.combine(dir, filename) + if shell and type(shell.dir) == 'function' then + local path = shell.resolve(fname) if fs.exists(path) and not fs.isDir(path) then - return path + return loadfile(path, env) end end +end - if lua_path then - for dir in string.gmatch(lua_path, "[^:]+") do - local path = fs.combine(dir, filename) - if fs.exists(path) and not fs.isDir(path) then - return path +local function pathSearcher(modname, env, shell) + local fname = modname:gsub('%.', '/') .. '.lua' + + for dir in string.gmatch(package.path, "[^:]+") do + local path = fs.combine(dir, fname) + if fs.exists(path) and not fs.isDir(path) then + return loadfile(path, env) + end + end +end + +-- fix broken http get +local syncLocks = { } + +local function sync(obj, fn) + local key = tostring(obj) + if syncLocks[key] then + local cos = tostring(coroutine.running()) + table.insert(syncLocks[key], cos) + repeat + local _, co = os.pullEvent('sync_lock') + until co == cos + else + syncLocks[key] = { } + end + local s, m = pcall(fn) + local co = table.remove(syncLocks[key], 1) + if co then + os.queueEvent('sync_lock', co) + else + syncLocks[key] = nil + end + if not s then + error(m) + end +end + +local function loadUrl(url) + local c + sync(url, function() + local h = http.get(url) + if h then + c = h.readAll() + h.close() + end + end) + if c and #c > 0 then + return c + end +end + +-- require('BniCQPVf') +local function pastebinSearcher(modname, env, shell) + if #modname == 8 and not modname:match('%W') then + local url = PASTEBIN_URL .. '/' .. modname + local c = loadUrl(url) + if c then + return load(c, modname, nil, env) + end + end +end + +-- require('kepler155c.opus.master.sys.apis.util') +local function gitSearcher(modname, env, shell) + local fname = modname:gsub('%.', '/') .. '.lua' + local _, count = fname:gsub("/", "") + if count >= 3 then + local url = GIT_URL .. '/' .. modname + local c = loadUrl(url) + if c then + return load(c, modname, nil, env) + end + end +end + +local function urlSearcher(modname, env, shell) + local fname = modname:gsub('%.', '/') .. '.lua' + + if fname:sub(1, 1) ~= '/' then + for entry in string.gmatch(package.upath, "[^;]+") do + local url = entry .. '/' .. fname + local c = loadUrl(url) + if c then + return load(c, modname, nil, env) end end end - - error('Unable to load: ' .. filename, 2) end +_G.package = { + path = LUA_PATH or 'sys/apis', + upath = LUA_UPATH or DEFAULT_UPATH, + loaders = { + shellSearcher, + pathSearcher, + pastebinSearcher, + gitSearcher, + urlSearcher, + } +} + local function requireWrapper(env) - local modules = { } + local loaded = { } - return function(filename) + return function(modname) - local dir = DIR - if not dir and shell and type(shell.dir) == 'function' then - dir = shell.dir() + if loaded[modname] then + return loaded[modname] end - local fname = resolver(filename:gsub('%.', '/') .. '.lua', - dir or '', LUA_PATH or '/sys/apis') - - local rname = fname:gsub('%/', '.'):gsub('%.lua', '') - - local module = modules[rname] - if not module then - - local f, err = loader(fname, env) - if not f then - error(err) - end - module = f(rname) - modules[rname] = module + for _,searcher in ipairs(package.loaders) do + local fn = searcher(modname, env, shell) + if fn then + local module, msg = fn(modname, env) + if not module then + error(msg) + end + loaded[modname] = module + return module + end end - - return module + error('Unable to find module ' .. modname) end end -local args = { ... } -resolver = args[1] or resolveFile -loader = args[2] or loadfile - return function(env) setfenv(requireWrapper, env) return requireWrapper(env) diff --git a/sys/apis/terminal.lua b/sys/apis/terminal.lua index 705bdef..b391110 100644 --- a/sys/apis/terminal.lua +++ b/sys/apis/terminal.lua @@ -120,6 +120,18 @@ function Terminal.toGrayscale(ct) end end +function Terminal.getNullTerm(ct) + local nt = Terminal.copy(ct) + + local methods = { 'blit', 'clear', 'clearLine', 'scroll', + 'setCursorBlink', 'setCursorPos', 'write' } + for _,v in pairs(methods) do + nt[v] = function() end + end + + return nt +end + function Terminal.copy(ot) local ct = { } for k,v in pairs(ot) do diff --git a/sys/apis/ui.lua b/sys/apis/ui.lua index 528323a..b966642 100644 --- a/sys/apis/ui.lua +++ b/sys/apis/ui.lua @@ -1,6 +1,8 @@ local Util = require('util') local class = require('class') local Event = require('event') +local Tween = require('tween') +local Region = require('region') local mapColorToGray = { [ colors.white ] = colors.white, @@ -21,43 +23,16 @@ local mapColorToGray = { [ colors.black ] = colors.black, } -local mapColorToPaint = { - [ colors.white ] = '0', - [ colors.orange ] = '1', - [ colors.magenta ] = '2', - [ colors.lightBlue ] = '3', - [ colors.yellow ] = '4', - [ colors.lime ] = '5', - [ colors.pink ] = '6', - [ colors.gray ] = '7', - [ colors.lightGray ] = '8', - [ colors.cyan ] = '9', - [ colors.purple ] = 'a', - [ colors.blue ] = 'b', - [ colors.brown ] = 'c', - [ colors.green ] = 'd', - [ colors.red ] = 'e', - [ colors.black ] = 'f', -} +local mapColorToPaint = { } +for n = 1, 16 do + mapColorToPaint[2 ^ (n - 1)] = string.sub("0123456789abcdef", n, n) +end -local mapGrayToPaint = { - [ colors.white ] = '0', - [ colors.orange ] = '8', - [ colors.magenta ] = '8', - [ colors.lightBlue ] = '8', - [ colors.yellow ] = '8', - [ colors.lime ] = '8', - [ colors.pink ] = '8', - [ colors.gray ] = '7', - [ colors.lightGray ] = '8', - [ colors.cyan ] = '8', - [ colors.purple ] = '7', - [ colors.blue ] = '7', - [ colors.brown ] = '7', - [ colors.green ] = '8', - [ colors.red ] = '7', - [ colors.black ] = 'f', -} +local mapGrayToPaint = { } +for n = 0, 15 do + local gs = mapColorToGray[2 ^ n] + mapGrayToPaint[2 ^ n] = mapColorToPaint[gs] +end local function colorToGrayScale(c) return mapColorToGray[c] @@ -91,24 +66,6 @@ end --[[-- Top Level Manager --]]-- local Manager = class() -Manager.effect = { - slideLeft = { - type = 'slideLeft', - ticks = 12, - easing = 'outBounce', - }, - slideRight = { - type = 'slideRight', - ticks = 12, - easing = 'outBounce', - }, - explode = { - type = 'explode', - ticks = 12, - easing = 'outBounce', - }, -} - function Manager:init(args) local control = false local shift = false @@ -153,7 +110,13 @@ function Manager:init(args) end) Event.addHandler('mouse_click', function(h, button, x, y) - if self.currentPage then + + if button == 1 and shift and control then -- hack + + local event = self:pointToChild(self.target, x, y) + multishell.openTab({ path = 'apps/Lua.lua', args = { event.element }, focused = true }) + + elseif self.currentPage then if not self.currentPage.parent.device.side then self:click(button, x, y) end @@ -411,6 +374,7 @@ function Manager:setPage(pageOrName, ...) if page == self.currentPage then page:draw() else + local needSync if self.currentPage then if self.currentPage.focused then self.currentPage.focused.focused = false @@ -418,6 +382,8 @@ function Manager:setPage(pageOrName, ...) end self.currentPage:disable() page.previousPage = self.currentPage + else + needSync = true end self.currentPage = page self.currentPage:clear(page.backgroundColor) @@ -428,7 +394,9 @@ function Manager:setPage(pageOrName, ...) self.currentPage.focused:focus() end self:capture(self.currentPage) - page:sync() + if needSync then + page:sync() -- first time a page has been set + end end end @@ -734,6 +702,17 @@ function UI.Window:print(text, bg, fg, indent) end end + --[[ + TODO + local test = "\027[0;1;33mYou tell foo, \"// Test string.\"\027[0;37mbar" + for sequence, text in string.gmatch (test, "\027%[([0-9;]+)m([^\027]+)") do + for ansi in string.gmatch (sequence, "%d+") do + print ("ANSI code: ", ansi) + end -- for + print ("Text: ", text) + end + --]] + local lines = Util.split(text) for k,line in pairs(lines) do local lx = 1 @@ -822,13 +801,13 @@ function UI.Window:scrollIntoView() end end -function UI.Window:setTransition(effect, x, y, width, height) +function UI.Window:addTransition(effect, x, y, width, height) if self.parent then x = x or 1 y = y or 1 width = width or self.width height = height or self.height - self.parent:setTransition(effect, x + self.x, y + self.y - 1, width, height) + self.parent:addTransition(effect, x + self.x - 1, y + self.y - 1, width, height) end end @@ -850,6 +829,144 @@ function UI.Window:eventHandler(event) return false end +--[[-- Blit data manipulation --]]-- +local Blob = class() +function Blob:init(args) + self.x = 1 + self.y = 1 + self.lines = { } + Util.merge(self, args) + + for i = 1, self.ey - self.y + 1 do + self.lines[i] = { } + end +end + +function Blob:copy() + local b = Blob({ x = self.x, y = self.y, ex = self.ex, ey = self.ey }) + for i = 1, self.ey - self.y + 1 do + b.lines[i].text = self.lines[i].text + b.lines[i].fg = self.lines[i].fg + b.lines[i].bg = self.lines[i].bg + end + return b +end + +function Blob:write(y, text, fg, bg) + self.lines[y].dirty = true + self.lines[y].text = text + self.lines[y].fg = fg + self.lines[y].bg = bg +end + +function Blob:reset() + self.region = nil +end + +function Blob:punch(rect) + if not self.regions then + self.regions = Region.new(self.x, self.y, self.ex, self.ey) + end + self.regions:subRect(rect.x, rect.y, rect.ex, rect.ey) +end + +function Blob:blitClipped(device) + for _,region in ipairs(self.regions.region) do + self:blit(device, + { x = region[1], y = region[2], ex = region[3], ey = region[4] }, + { x = region[1], y = region[2] }) + end +end + +function Blob:blit(device, src, tgt) + for i = 0, src.ey - src.y do + local line = self.lines[src.y + i] + local t, fg, bg = line.text, line.fg, line.bg + if src.x > 1 or src.ex < self.ex then + t = t:sub(src.x, src.ex) + fg = fg:sub(src.x, src.ex) + bg = bg:sub(src.x, src.ex) + end + device.setCursorPos(tgt.x, tgt.y + i) + device.blit(t, fg, bg) + end +end + +--[[-- TransitionSlideLeft --]]-- +UI.TransitionSlideLeft = class() +UI.TransitionSlideLeft.defaults = { + UIElement = 'TransitionSlideLeft', + ticks = 12, + easing = 'outBounce', +} +function UI.TransitionSlideLeft:init(args) + local defaults = UI:getDefaults(UI.TransitionSlideLeft, args) + UI.setProperties(self, defaults) + + self.pos = { x = self.ex } + self.tween = Tween.new(self.ticks, self.pos, { x = self.x }, self.easing) + self.lastx = 0 +end + +function UI.TransitionSlideLeft:update(device, screen, lastScreen) + self.tween:update(1) + local x = math.floor(self.pos.x) + if x ~= self.lastx then + self.lastx = x + lastScreen:blit(device, { + x = self.ex - x + self.x, + y = self.y, + ex = self.ex, + ey = self.ey }, + { x = self.x, y = self.y }) + + screen:blit(device, { + x = self.x, + y = self.y, + ex = self.ex - x + self.x + 1, + ey = self.ey }, + { x = x, y = self.y }) + end + return self.pos.x ~= self.x +end + +--[[-- TransitionSlideRight --]]-- +UI.TransitionSlideRight = class() +UI.TransitionSlideRight.defaults = { + UIElement = 'TransitionSlideRight', + ticks = 12, + easing = 'outBounce', +} +function UI.TransitionSlideRight:init(args) + local defaults = UI:getDefaults(UI.TransitionSlideLeft, args) + UI.setProperties(self, defaults) + + self.pos = { x = self.x } + self.tween = Tween.new(self.ticks, self.pos, { x = self.ex }, self.easing) + self.lastx = 0 +end + +function UI.TransitionSlideRight:update(device, screen, lastScreen) + self.tween:update(1) + local x = math.floor(self.pos.x) + if x ~= self.lastx then + self.lastx = x + lastScreen:blit(device, { + x = self.x, + y = self.y, + ex = self.ex - x + self.x + 1, + ey = self.ey }, + { x = x, y = self.y }) + screen:blit(device, { + x = self.ex - x + self.x, + y = self.y, + ex = self.ex + 1, + ey = self.ey }, + { x = self.x, y = self.y }) + end + return self.pos.x ~= self.ex +end + --[[-- Terminal for computer / advanced computer / monitor --]]-- UI.Device = class(UI.Window) UI.Device.defaults = { @@ -858,9 +975,7 @@ UI.Device.defaults = { textColor = colors.white, textScale = 1, effectsEnabled = true, - lines = { }, } - function UI.Device:init(args) local defaults = UI:getDefaults(UI.Device) defaults.device = term.current() @@ -879,6 +994,16 @@ function UI.Device:init(args) UI.Window.init(self, defaults) + self.blob = Blob({ + x = 1, y = 1, ex = self.width, ey = self.height + }) + for i = 1, self.height do + self.blob:write(i, + string.rep(' ', self.width), + string.rep(colorToPaintColor(self.backgroundColor, self.isColor), self.width), + string.rep(colorToPaintColor(self.textColor, self.isColor), self.width)) + end + self.isColor = self.device.isColor() end @@ -914,131 +1039,74 @@ function UI.Device:reset() self.device.setCursorPos(1, 1) end -function UI.Device:setTransition(effect, x, y, width, height) - if not self.transition then - self.transition = effect - effect.x = x - effect.y = y - effect.width = width - effect.height = height - for i = y, y + height - 1 do - local line = self.lines[i] - if line then - line.transition = true - end - end +function UI.Device:addTransition(effect, x, y, width, height) + if not self.transitions then + self.transitions = { } end + + if type(effect) == 'string' then + local c + if effect == 'slideLeft' then + c = UI.TransitionSlideLeft + else + c = UI.TransitionSlideRight + end + effect = c { + x = x, + y = y, + ex = x + width - 1, + ey = y + height - 1, + } + end + table.insert(self.transitions, effect) end -function UI.Device:runTransition(effect) +function UI.Device:runTransitions(transitions) - if not self.Tween then - self.Tween = require('tween') + for _,t in ipairs(transitions) do + self.blob:punch(t) -- punch out the effect areas end + self.blob:blitClipped(self.device) -- and blit the remainder + self.blob:reset() - if effect.type == 'slideLeft' or effect.type == 'slideRight' then - for y, line in ipairs(self.lines) do - if not line.transition then - self.device.setCursorPos(1, y) - self.device.blit(line.text, line.fg, line.bg) + while true do + for _,k in ipairs(Util.keys(transitions)) do + local transition = transitions[k] + if not transition:update(self.device, self.blob, self.lastScreen) then + transitions[k] = nil end end - - local pos = { x = 1 } - local tween = self.Tween.new(effect.ticks, pos, { x = self.width }, effect.easing) - - local lastx = 0 - repeat - tween:update(1) - local x = math.floor(pos.x) - if x ~= lastx then - lastx = x - for y, line in pairs(self.lines) do - if line.transition then - if effect.type == 'slideLeft' then - local text = self.lastScreen[y].text .. line.text - local bg = self.lastScreen[y].bg .. line.bg - local fg = self.lastScreen[y].fg .. line.fg - self.device.setCursorPos(1 - x, y) - self.device.blit(text, fg, bg) - else - local text = line.text .. self.lastScreen[y].text - local bg = line.bg .. self.lastScreen[y].bg - local fg = line.fg .. self.lastScreen[y].fg - self.device.setCursorPos(-self.width + x + 1, y) - self.device.blit(text, fg, bg) - end - end - end - end - os.sleep() - until pos.x == self.width - - elseif effect.type == 'explode' then - local pos = { x = 1 } - local tween = self.Tween.new(effect.ticks, pos, { x = 100 }, effect.easing) - local mx = math.floor(effect.width / 2) - local my = math.floor(effect.height / 2) - - local function replace(sstr, pos, rstr, width) - return sstr:sub(1, pos-1) .. rstr .. sstr:sub(pos+width) - end - -debug('running') - repeat - tween:update(1) - local ux = math.floor(effect.width * pos.x / 200) - local uy = math.floor(effect.height * pos.x / 200) - local width = ux * 2 - local sx = mx - ux + 1 -debug({ pos.x, ux, uy }) - for y = my - uy, my + uy do - local line = self.lines[y] - if line then - self.device.setCursorPos(1, y) - self.device.blit( - replace(self.lastScreen[y].text, sx, line.text:sub(sx, sx + width - 1), width), - replace(self.lastScreen[y].fg, sx, line.fg:sub(sx, sx + width - 1), width), - replace(self.lastScreen[y].bg, sx, line.bg:sub(sx, sx + width - 1), width)) - end - end - os.sleep(.4) - until pos.x == 100 -debug('done running') - end - - for y, line in ipairs(self.lines) do - line.dirty = false - line.transition = false + if Util.empty(transitions) then + break + end + Event.sleep() end end function UI.Device:sync() - local transition - if self.transition and self.effectsEnabled then - for y, line in pairs(self.lines) do - if line.dirty then - transition = self.transition - break - end - end - self.transition = nil + local transitions + if self.transitions and self.effectsEnabled then + transitions = self.transitions + self.transitions = nil end - if transition then - self:runTransition(transition) + if transitions then + self:runTransitions(transitions) else - for y, line in pairs(self.lines) do + for y, line in pairs(self.blob.lines) do if line.dirty then self.device.setCursorPos(1, y) self.device.blit(line.text, line.fg, line.bg) - line.dirty = false end end end - self.lastScreen = Util.deepCopy(self.lines) + self.lastScreen = self.blob:copy() + + for y, line in ipairs(self.blob.lines) do + line.dirty = false + end if self:getCursorBlink() then self.device.setCursorPos(self.cursorX, self.cursorY) @@ -1049,14 +1117,6 @@ function UI.Device:write(x, y, text, bg, tc) if y > 0 and y <= self.height and x <= self.width then - if not self.lines[y] then - self.lines[y] = { - text = string.rep(' ', self.width), - bg = string.rep(colorToPaintColor(self.backgroundColor, self.isColor), self.width), - fg = string.rep(colorToPaintColor(self.textColor, self.isColor), self.width), - } - end - local width = #text if x < 1 then @@ -1080,7 +1140,7 @@ function UI.Device:write(x, y, text, bg, tc) return sstr:sub(1, pos-1) .. string.rep(rstr, width) .. sstr:sub(pos+width) end - local line = self.lines[y] + local line = self.blob.lines[y] line.dirty = true line.text = replace(line.text, x, text, width) if bg then @@ -2113,9 +2173,9 @@ function UI.Tabs:eventHandler(event) for _,tab in ipairs(self.children) do if tab ~= self.tabBar then if event.current > event.last then - tab:setTransition(UI.effect.slideLeft) + tab:addTransition('slideLeft') else - tab:setTransition(UI.effect.slideRight) + tab:addTransition('slideRight') end break end @@ -2145,7 +2205,7 @@ function UI.WindowScroller:nextChild() for i = 1, #self.children do if self.children[i].enabled then if i < #self.children then - self:setTransition(UI.effect.slideLeft) + self:addTransition('slideLeft') self.children[i]:disable() self.children[i + 1]:enable() end @@ -2158,7 +2218,7 @@ function UI.WindowScroller:prevChild() for i = 1, #self.children do if self.children[i].enabled then if i - 1 > 0 then - self:setTransition(UI.effect.slideRight) + self:addTransition('slideRight') self.children[i]:disable() self.children[i - 1]:enable() end diff --git a/sys/apis/util.lua b/sys/apis/util.lua index 22644c2..450df87 100644 --- a/sys/apis/util.lua +++ b/sys/apis/util.lua @@ -457,28 +457,30 @@ end local function getopt( arg, options ) local tab = {} for k, v in ipairs(arg) do - if string.sub( v, 1, 2) == "--" then - local x = string.find( v, "=", 1, true ) - if x then tab[ string.sub( v, 3, x-1 ) ] = string.sub( v, x+1 ) - else tab[ string.sub( v, 3 ) ] = true - end - elseif string.sub( v, 1, 1 ) == "-" then - local y = 2 - local l = string.len(v) - local jopt - while ( y <= l ) do - jopt = string.sub( v, y, y ) - if string.find( options, jopt, 1, true ) then - if y < l then - tab[ jopt ] = string.sub( v, y+1 ) - y = l - else - tab[ jopt ] = arg[ k + 1 ] - end - else - tab[ jopt ] = true + if type(v) == 'string' then + if string.sub( v, 1, 2) == "--" then + local x = string.find( v, "=", 1, true ) + if x then tab[ string.sub( v, 3, x-1 ) ] = string.sub( v, x+1 ) + else tab[ string.sub( v, 3 ) ] = true + end + elseif string.sub( v, 1, 1 ) == "-" then + local y = 2 + local l = string.len(v) + local jopt + while ( y <= l ) do + jopt = string.sub( v, y, y ) + if string.find( options, jopt, 1, true ) then + if y < l then + tab[ jopt ] = string.sub( v, y+1 ) + y = l + else + tab[ jopt ] = arg[ k + 1 ] + end + else + tab[ jopt ] = true + end + y = y + 1 end - y = y + 1 end end end From 977998ebdb8bb211b39dbb5e99f902748ca9537a Mon Sep 17 00:00:00 2001 From: "kepler155c@gmail.com" Date: Sun, 18 Dec 2016 14:41:38 -0500 Subject: [PATCH 6/9] transitions and Lua improvements --- sys/apis/region.lua | 358 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 358 insertions(+) create mode 100644 sys/apis/region.lua diff --git a/sys/apis/region.lua b/sys/apis/region.lua new file mode 100644 index 0000000..f5abcea --- /dev/null +++ b/sys/apis/region.lua @@ -0,0 +1,358 @@ +------------------------------------------------------------------------------- +-- +-- tek.lib.region +-- Written by Timm S. Mueller +-- +-- Copyright 2008 - 2016 by the authors and contributors: +-- +-- * Timm S. Muller +-- * Franciska Schulze +-- * Tobias Schwinger +-- +-- Permission is hereby granted, free of charge, to any person obtaining +-- a copy of this software and associated documentation files (the +-- "Software"), to deal in the Software without restriction, including +-- without limitation the rights to use, copy, modify, merge, publish, +-- distribute, sublicense, and/or sell copies of the Software, and to +-- permit persons to whom the Software is furnished to do so, subject to +-- the following conditions: +-- +-- The above copyright notice and this permission notice shall be +-- included in all copies or substantial portions of the Software. +-- +-- === Disclaimer === +-- +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +-- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +-- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +-- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +-- CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +-- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +-- SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +-- +-- OVERVIEW:: +-- This library implements the management of regions, which are +-- collections of non-overlapping rectangles. +-- +-- FUNCTIONS:: +-- - Region:andRect() - ''And''s a rectangle to a region +-- - Region:andRegion() - ''And''s a region to a region +-- - Region:checkIntersect() - Checks if a rectangle intersects a region +-- - Region:forEach() - Calls a function for each rectangle in a region +-- - Region:get() - Get region's min/max extents +-- - Region.intersect() - Returns the intersection of two rectangles +-- - Region:isEmpty() - Checks if a Region is empty +-- - Region.new() - Creates a new Region +-- - Region:orRect() - ''Or''s a rectangle to a region +-- - Region:orRegion() - ''Or''s a region to a region +-- - Region:setRect() - Resets a region to the given rectangle +-- - Region:shift() - Displaces a region +-- - Region:subRect() - Subtracts a rectangle from a region +-- - Region:subRegion() - Subtracts a region from a region +-- - Region:xorRect() - ''Exclusive Or''s a rectangle to a region +-- +------------------------------------------------------------------------------- + +local insert = table.insert +local ipairs = ipairs +local max = math.max +local min = math.min +local setmetatable = setmetatable +local unpack = unpack or table.unpack + +local Region = { } +Region._VERSION = "Region 11.3" + +Region.__index = Region + +------------------------------------------------------------------------------- +-- x0, y0, x1, y1 = Region.intersect(d1, d2, d3, d4, s1, s2, s3, s4): +-- Returns the coordinates of a rectangle where a rectangle specified by +-- the coordinates s1, s2, s3, s4 overlaps with the rectangle specified +-- by the coordinates d1, d2, d3, d4. The return value is '''nil''' if +-- the rectangles do not overlap. +------------------------------------------------------------------------------- + +function Region.intersect(d1, d2, d3, d4, s1, s2, s3, s4) + if s3 >= d1 and s1 <= d3 and s4 >= d2 and s2 <= d4 then + return max(s1, d1), max(s2, d2), min(s3, d3), min(s4, d4) + end +end + +------------------------------------------------------------------------------- +-- insertrect: insert rect to table, merging with an existing one if possible +------------------------------------------------------------------------------- + +local function insertrect(d, s1, s2, s3, s4) + for i = 1, min(4, #d) do + local a = d[i] + local a1, a2, a3, a4 = a[1], a[2], a[3], a[4] + if a2 == s2 and a4 == s4 then + if a3 + 1 == s1 then + a[3] = s3 + return + elseif a1 == s3 + 1 then + a[1] = s1 + return + end + elseif a1 == s1 and a3 == s3 then + if a4 + 1 == s2 then + a[4] = s4 + return + elseif a2 == s4 + 1 then + a[2] = s2 + return + end + end + end + insert(d, 1, { s1, s2, s3, s4 }) +end + +------------------------------------------------------------------------------- +-- cutrect: cut rect d into table of new rects, using rect s as a punch +------------------------------------------------------------------------------- + +local function cutrect(d1, d2, d3, d4, s1, s2, s3, s4) + if not Region.intersect(d1, d2, d3, d4, s1, s2, s3, s4) then + return { { d1, d2, d3, d4 } } + end + local r = { } + if d1 < s1 then + insertrect(r, d1, d2, s1 - 1, d4) + d1 = s1 + end + if d2 < s2 then + insertrect(r, d1, d2, d3, s2 - 1) + d2 = s2 + end + if d3 > s3 then + insertrect(r, s3 + 1, d2, d3, d4) + d3 = s3 + end + if d4 > s4 then + insertrect(r, d1, s4 + 1, d3, d4) + end + return r +end + +------------------------------------------------------------------------------- +-- cutregion: cut region d, using s as a punch +------------------------------------------------------------------------------- + +local function cutregion(d, s1, s2, s3, s4) + local r = { } + for _, dr in ipairs(d) do + local d1, d2, d3, d4 = dr[1], dr[2], dr[3], dr[4] + for _, t in ipairs(cutrect(d1, d2, d3, d4, s1, s2, s3, s4)) do + insertrect(r, t[1], t[2], t[3], t[4]) + end + end + return r +end + +------------------------------------------------------------------------------- +-- region = Region.new(r1, r2, r3, r4): Creates a new region from the given +-- coordinates. +------------------------------------------------------------------------------- + +function Region.new(r1, r2, r3, r4) + if r1 then + return setmetatable({ region = { { r1, r2, r3, r4 } } }, Region) + end + return setmetatable({ region = { } }, Region) +end + +------------------------------------------------------------------------------- +-- self = region:setRect(r1, r2, r3, r4): Resets an existing region +-- to the specified rectangle. +------------------------------------------------------------------------------- + +function Region:setRect(r1, r2, r3, r4) + self.region = { { r1, r2, r3, r4 } } + return self +end + +------------------------------------------------------------------------------- +-- region:orRect(r1, r2, r3, r4): Logical ''or''s a rectangle to a region +------------------------------------------------------------------------------- + +function Region:orRect(s1, s2, s3, s4) + self.region = cutregion(self.region, s1, s2, s3, s4) + insertrect(self.region, s1, s2, s3, s4) +end + +------------------------------------------------------------------------------- +-- region:orRegion(region): Logical ''or''s another region to a region +------------------------------------------------------------------------------- + +function Region:orRegion(s) + for _, r in ipairs(s) do + self:orRect(r[1], r[2], r[3], r[4]) + end +end + +------------------------------------------------------------------------------- +-- region:andRect(r1, r2, r3, r4): Logical ''and''s a rectange to a region +------------------------------------------------------------------------------- + +function Region:andRect(s1, s2, s3, s4) + local r = { } + for _, d in ipairs(self.region) do + local t1, t2, t3, t4 = + Region.intersect(d[1], d[2], d[3], d[4], s1, s2, s3, s4) + if t1 then + insertrect(r, t1, t2, t3, t4) + end + end + self.region = r +end + +------------------------------------------------------------------------------- +-- region:xorRect(r1, r2, r3, r4): Logical ''xor''s a rectange to a region +------------------------------------------------------------------------------- + +function Region:xorRect(s1, s2, s3, s4) + local r1 = { } + local r2 = { { s1, s2, s3, s4 } } + for _, d in ipairs(self.region) do + local d1, d2, d3, d4 = d[1], d[2], d[3], d[4] + for _, t in ipairs(cutrect(d1, d2, d3, d4, s1, s2, s3, s4)) do + insertrect(r1, t[1], t[2], t[3], t[4]) + end + r2 = cutregion(r2, d1, d2, d3, d4) + end + self.region = r1 + self:orRegion(r2) +end + +------------------------------------------------------------------------------- +-- self = region:subRect(r1, r2, r3, r4): Subtracts a rectangle from a region +------------------------------------------------------------------------------- + +function Region:subRect(s1, s2, s3, s4) + local r1 = { } + for _, d in ipairs(self.region) do + local d1, d2, d3, d4 = d[1], d[2], d[3], d[4] + for _, t in ipairs(cutrect(d1, d2, d3, d4, s1, s2, s3, s4)) do + insertrect(r1, t[1], t[2], t[3], t[4]) + end + end + self.region = r1 + return self +end + +------------------------------------------------------------------------------- +-- region:getRect - gets an iterator on the rectangles in a region [internal] +------------------------------------------------------------------------------- + +function Region:getRects() + local index = 0 + return function(object) + index = index + 1 + if object[index] then + return unpack(object[index]) + end + end, self.region +end + +------------------------------------------------------------------------------- +-- success = region:checkIntersect(x0, y0, x1, y1): Returns a boolean +-- indicating whether a rectangle specified by its coordinates overlaps +-- with a region. +------------------------------------------------------------------------------- + +function Region:checkIntersect(s1, s2, s3, s4) + for _, d in ipairs(self.region) do + if Region.intersect(d[1], d[2], d[3], d[4], s1, s2, s3, s4) then + return true + end + end + return false +end + +------------------------------------------------------------------------------- +-- region:subRegion(region2): Subtracts {{region2}} from {{region}}. +------------------------------------------------------------------------------- + +function Region:subRegion(region) + if region then + for r1, r2, r3, r4 in region:getRects() do + self:subRect(r1, r2, r3, r4) + end + end +end + +------------------------------------------------------------------------------- +-- region:andRegion(r): Logically ''and''s a region to a region +------------------------------------------------------------------------------- + +function Region:andRegion(s) + local r = { } + for _, s in ipairs(s.region) do + for _, d in ipairs(self.region) do + local t1, t2, t3, t4 = + Region.intersect(d[1], d[2], d[3], d[4], + s[1], s[2], s[3], s[4]) + if t1 then + insertrect(r, t1, t2, t3, t4) + end + end + end + self.region = r +end + +------------------------------------------------------------------------------- +-- region:forEach(func, obj, ...): For each rectangle in a region, calls the +-- specified function according the following scheme: +-- func(obj, x0, y0, x1, y1, ...) +-- Extra arguments are passed through to the function. +------------------------------------------------------------------------------- + +function Region:forEach(func, obj, ...) + for x0, y0, x1, y1 in self:getRects() do + func(obj, x0, y0, x1, y1, ...) + end +end + +------------------------------------------------------------------------------- +-- region:shift(dx, dy): Shifts a region by delta x and y. +------------------------------------------------------------------------------- + +function Region:shift(dx, dy) + for _, r in ipairs(self.region) do + r[1] = r[1] + dx + r[2] = r[2] + dy + r[3] = r[3] + dx + r[4] = r[4] + dy + end +end + +------------------------------------------------------------------------------- +-- region:isEmpty(): Returns '''true''' if a region is empty. +------------------------------------------------------------------------------- + +function Region:isEmpty() + return #self.region == 0 +end + +------------------------------------------------------------------------------- +-- minx, miny, maxx, maxy = region:get(): Get region's min/max extents +------------------------------------------------------------------------------- + +function Region:get() + if #self.region > 0 then + local minx = 1000000 -- ui.HUGE + local miny = 1000000 + local maxx = 0 + local maxy = 0 + for _, r in ipairs(self.region) do + minx = min(minx, r[1]) + miny = min(miny, r[2]) + maxx = max(maxx, r[3]) + maxy = max(maxy, r[4]) + end + return minx, miny, maxx, maxy + end +end + +return Region From d61260ec9b9f1148bedd60a207048abf62aabe00 Mon Sep 17 00:00:00 2001 From: "kepler155c@gmail.com" Date: Thu, 22 Dec 2016 23:22:04 -0500 Subject: [PATCH 7/9] Overlapping windows --- apps/Files.lua | 34 +-- apps/Lua.lua | 19 +- apps/Overview.lua | 103 ++++---- apps/Peripherals.lua | 3 +- apps/edit.lua | 41 +++- autorun/gps.lua | 1 + sys/apis/fileui.lua | 35 +-- sys/apis/git.lua | 2 +- sys/apis/injector.lua | 27 ++- sys/apis/ui.lua | 455 +++++++++++++++++++++++------------ sys/extensions/scheduler.lua | 3 + sys/extensions/tl3.lua | 3 +- sys/network/snmp.lua | 20 +- 13 files changed, 492 insertions(+), 254 deletions(-) diff --git a/apps/Files.lua b/apps/Files.lua index 0424c09..28ca3e9 100644 --- a/apps/Files.lua +++ b/apps/Files.lua @@ -35,26 +35,32 @@ local Browser = UI.Page { }, fileMenu = UI.DropMenu { buttons = { - { text = 'Run', event = 'run' }, - { text = 'Edit e', event = 'edit' }, - { text = 'Shell s', event = 'shell' }, - { text = 'Quit q', event = 'quit' }, + { text = 'Run', event = 'run' }, + { text = 'Edit e', event = 'edit' }, + { text = 'Shell s', event = 'shell' }, + UI.Text { value = ' ------------ ' }, + { text = 'Quit q', event = 'quit' }, + UI.Text { }, } }, editMenu = UI.DropMenu { buttons = { - { text = 'Mark m', event = 'mark' }, - { text = 'Cut ^x', event = 'cut' }, - { text = 'Copy ^c', event = 'copy' }, - { text = 'Paste ^v', event = 'paste' }, - { text = 'Delete del', event = 'delete' }, - { text = 'Unmark all u', event = 'unmark' }, + { text = 'Cut ^x', event = 'cut' }, + { text = 'Copy ^c', event = 'copy' }, + { text = 'Paste ^v', event = 'paste' }, + UI.Text { value = ' --------------- ' }, + { text = 'Mark m', event = 'mark' }, + { text = 'Unmark all u', event = 'unmark' }, + UI.Text { value = ' --------------- ' }, + { text = 'Delete del', event = 'delete' }, + UI.Text { }, } }, viewMenu = UI.DropMenu { buttons = { - { text = 'Refresh r', event = 'refresh' }, - { text = 'Hidden ^h', event = 'toggle_hidden' }, + { text = 'Refresh r', event = 'refresh' }, + { text = 'Hidden ^h', event = 'toggle_hidden' }, + UI.Text { }, } }, grid = UI.ScrollingGrid { @@ -69,8 +75,8 @@ local Browser = UI.Page { }, statusBar = UI.StatusBar { columns = { - { '', 'status', UI.term.width - 19 }, - { '', 'info', 10 }, + { '', 'status', UI.term.width - 8 }, + --{ '', 'info', 10 }, { 'Size: ', 'totalSize', 8 }, }, }, diff --git a/apps/Lua.lua b/apps/Lua.lua index 6171783..b6c45ac 100644 --- a/apps/Lua.lua +++ b/apps/Lua.lua @@ -1,4 +1,5 @@ -require = requireInjector(getfenv(1)) +local injector = requireInjector or load(http.get('http://pastebin.com/raw/c0TWsScv').readAll())() +require = injector(getfenv(1)) local Util = require('util') local UI = require('ui') local Event = require('event') @@ -6,7 +7,7 @@ local History = require('history') local sandboxEnv = Util.shallowCopy(getfenv(1)) sandboxEnv.exit = function() Event.exitPullEvents() end -sandboxEnv.require = requireInjector(sandboxEnv) +sandboxEnv.require = injector(sandboxEnv) setmetatable(sandboxEnv, { __index = _G }) multishell.setTitle(multishell.getCurrent(), 'Lua') @@ -20,7 +21,7 @@ local page = UI.Page({ buttons = { { text = 'Local', event = 'local' }, { text = 'Global', event = 'global' }, - { text = 'Device', event = 'device' }, + { text = 'Device', event = 'device', name = 'Device' }, }, }), prompt = UI.TextEntry({ @@ -66,6 +67,9 @@ end function page:enable() self:setFocus(self.prompt) UI.Page.enable(self) + if not device then + self.menuBar.Device:disable() + end end function page:eventHandler(event) @@ -233,6 +237,8 @@ function page:executeStatement(statement) if s and m then self:setResult(m) + elseif s and type(m) == 'boolean' then + self:setResult(m) else self.grid:setValues({ }) self.grid:draw() @@ -242,10 +248,11 @@ function page:executeStatement(statement) end end -sandboxEnv.args = { ... } -if sandboxEnv.args[1] then +local args = { ... } +if args[1] then command = 'args[1]' - page:setResult(sandboxEnv.args[1]) + sandboxEnv.args = args + page:setResult(args[1]) end UI:setPage(page) diff --git a/apps/Overview.lua b/apps/Overview.lua index 77556dc..80b7045 100644 --- a/apps/Overview.lua +++ b/apps/Overview.lua @@ -331,64 +331,68 @@ function page:eventHandler(event) end local formWidth = math.max(UI.term.width - 14, 26) -local gutter = math.floor((UI.term.width - formWidth) / 2) + 1 -local editor = UI.Page({ - backgroundColor = colors.blue, - form = UI.Form({ - fields = { - { label = 'Title', key = 'title', width = 15, limit = 11, display = UI.Form.D.entry, - help = 'Application title' }, - { label = 'Run', key = 'run', width = formWidth - 11, limit = 100, display = UI.Form.D.entry, - help = 'Full path to application' }, - { label = 'Category', key = 'category', width = 15, limit = 11, display = UI.Form.D.entry, - help = 'Category of application' }, - { text = 'Accept', event = 'accept', display = UI.Form.D.button, - x = 1, y = 9, width = 10 }, - { text = 'Cancel', event = 'cancel', display = UI.Form.D.button, - x = formWidth - 11, y = 9, width = 10 }, +local editor = UI.Page { + backgroundColor = colors.white, + x = math.ceil((UI.term.width - formWidth) / 2) + 1, + y = math.ceil((UI.term.height - 11) / 2) + 1, + z = 2, + height = 11, + width = formWidth, + titleBar = UI.TitleBar { + title = 'Edit application', + }, + inset = UI.Window { + x = 2, + y = 3, + rex = -2, + rey = -3, + form = UI.Form { + textColor = colors.black, + fields = { + { label = 'Title', key = 'title', width = formWidth - 11, limit = 11, display = UI.Form.D.entry, + help = 'Application title' }, + { label = 'Run', key = 'run', width = formWidth - 11, limit = 100, display = UI.Form.D.entry, + help = 'Full path to application' }, + { label = 'Category', key = 'category', width = formWidth - 11, limit = 11, display = UI.Form.D.entry, + help = 'Category of application' }, + { text = 'Icon', event = 'loadIcon', display = UI.Form.D.button, + x = 10, y = 5, textColor = colors.white, help = 'Select icon' }, + { text = 'Ok', event = 'accept', display = UI.Form.D.button, + x = formWidth - 14, y = 7, textColor = colors.white }, + { text = 'Cancel', event = 'cancel', display = UI.Form.D.button, + x = formWidth - 9, y = 7, textColor = colors.white }, + }, + labelWidth = 8, + image = UI.NftImage { + y = 5, + x = 1, + height = 3, + width = 8, + }, }, - labelWidth = 8, - x = gutter + 1, - y = math.max(2, math.floor((UI.term.height - 9) / 2)), - height = 9, - width = UI.term.width - (gutter * 2), - image = UI.NftImage({ - y = 5, - x = 1, - height = 3, - width = 8, - }), - button = UI.Button({ - x = 10, - y = 6, - text = 'Load icon', - width = 11, - event = 'loadIcon', - }), - }), + }, statusBar = UI.StatusBar(), notification = UI.Notification(), iconFile = '', -}) +} function editor:enable(app) if app then self.original = app - self.form:setValues(Util.shallowCopy(app)) + self.inset.form:setValues(Util.shallowCopy(app)) local icon if app.icon then icon = parseIcon(app.icon) end - self.form.image:setImage(icon) - - self:setFocus(self.form.children[1]) + self.inset.form.image:setImage(icon) end UI.Page.enable(self) + self:focusFirst() end -function editor.form.image:draw() +function editor.inset.form.image:draw() self:clear() UI.NftImage.draw(self) end @@ -414,7 +418,13 @@ function editor:eventHandler(event) self.statusBar:draw() elseif event.type == 'loadIcon' then - local fileui = FileUI() + local fileui = FileUI({ + x = self.x, + y = self.y, + z = 2, + width = self.width, + height = self.height, + }) --fileui:setTransition(UI.effect.explode) UI:setPage(fileui, fs.getDir(self.iconFile), function(fileName) if fileName then @@ -428,18 +438,19 @@ function editor:eventHandler(event) if not icon then error(m) end - self.form.values.icon = iconLines - self.form.image:setImage(icon) - self.form.image:draw() + self.inset.form.values.icon = iconLines + self.inset.form.image:setImage(icon) + self.inset.form.image:draw() end) if not s and m then - self.notification:error(m:gsub('.*: (.*)', '%1')) + local msg = m:gsub('.*: (.*)', '%1') + self.notification:error(msg) end end end) elseif event.type == 'accept' then - local values = self.form.values + local values = self.inset.form.values if #values.run > 0 and #values.title > 0 and #values.category > 0 then UI:setPreviousPage() self:updateApplications(values, self.original) diff --git a/apps/Peripherals.lua b/apps/Peripherals.lua index 682bbff..bdab0a3 100644 --- a/apps/Peripherals.lua +++ b/apps/Peripherals.lua @@ -1,4 +1,5 @@ -require = requireInjector(getfenv(1)) +local injector = requireInjector or load(http.get('http://pastebin.com/raw/c0TWsScv').readAll())() +require = injector(getfenv(1)) local Util = require('util') local Event = require('event') local UI = require('ui') diff --git a/apps/edit.lua b/apps/edit.lua index b64107e..55f47e9 100644 --- a/apps/edit.lua +++ b/apps/edit.lua @@ -66,7 +66,7 @@ local keyMapping = { pageUp = 'pageUp', [ 'control-b' ] = 'pageUp', pageDown = 'pageDown', - [ 'control-f' ] = 'pageDown', +-- [ 'control-f' ] = 'pageDown', home = 'home', [ 'end' ] = 'toend', [ 'control-home' ] = 'top', @@ -101,6 +101,7 @@ local keyMapping = { paste = 'paste', tab = 'tab', [ 'control-z' ] = 'undo', + [ 'control-space' ] = 'autocomplete', -- copy/paste [ 'control-x' ] = 'cut', @@ -114,6 +115,7 @@ local keyMapping = { [ 'control-enter' ] = 'run', -- search + [ 'control-f' ] = 'find_prompt', [ 'control-slash' ] = 'find_prompt', [ 'control-n' ] = 'find_next', @@ -476,6 +478,41 @@ local __actions = { end end, + autocomplete = function() + local sLine = tLines[y]:sub(1, x - 1) + local nStartPos = sLine:find("[a-zA-Z0-9_%.]+$") + if nStartPos then + sLine = sLine:sub(nStartPos) + end + if #sLine > 0 then + local results = textutils.complete(sLine) + + if #results == 0 then + setError('No completions available') + + elseif #results == 1 then + actions.insertText(x, y, results[1]) + + elseif #results > 1 then + local prefix = results[1] + for n = 1, #results do + local result = results[n] + while #prefix > 0 do + if result:find(prefix, 1, true) == 1 then + break + end + prefix = prefix:sub(1, #prefix - 1) + end + end + if #prefix > 0 then + actions.insertText(x, y, prefix) + else + setStatus('Too many results') + end + end + end + end, + refresh = function() actions.dirty_all() mark.continue = mark.active @@ -528,7 +565,7 @@ local __actions = { find_prompt = function() local text = actions.input('/') if #text > 0 then - searchPattern = text + searchPattern = text:lower() if searchPattern then actions.unmark() actions.find(searchPattern, x) diff --git a/autorun/gps.lua b/autorun/gps.lua index c01cb72..f9dec4c 100644 --- a/autorun/gps.lua +++ b/autorun/gps.lua @@ -36,6 +36,7 @@ if turtle and device.wireless_modem then Util.print('Setting turtle point to %d %d %d', pt.x, pt.y, pt.z) turtle.setPoint(pt) + turtle.getState().coordSystem = 'GPS' if not turtle.pathfind(homePt) then error('Failed to return home') diff --git a/sys/apis/fileui.lua b/sys/apis/fileui.lua index 680dcda..f9a6dc0 100644 --- a/sys/apis/fileui.lua +++ b/sys/apis/fileui.lua @@ -1,6 +1,6 @@ local UI = require('ui') -return function() +return function(args) local columns = { { heading = 'Name', key = 'name', width = UI.term.width - 9 }, @@ -13,18 +13,23 @@ return function() ) end - local selectFile = UI.Page({ - x = 3, - y = 2, - rex = -3, - rey = -3, + args = args or { } + + local selectFile = UI.Page { + x = args.x or 3, + y = args.y or 2, + z = args.z or 2, +-- rex = args.rex or -3, +-- rey = args.rey or -3, + height = args.height, + width = args.width, backgroundColor = colors.brown, - titleBar = UI.TitleBar({ + titleBar = UI.TitleBar { title = 'Select file', previousPage = true, event = 'cancel', - }), - grid = UI.ScrollingGrid({ + }, + grid = UI.ScrollingGrid { x = 2, y = 2, rex = -2, @@ -32,8 +37,8 @@ return function() path = '', sortColumn = 'name', columns = columns, - }), - path = UI.TextEntry({ + }, + path = UI.TextEntry { x = 2, ry = -1, rex = -11, @@ -41,14 +46,14 @@ return function() accelerators = { enter = 'path_enter', } - }), - cancel = UI.Button({ + }, + cancel = UI.Button { text = 'Cancel', rx = -8, ry = -1, event = 'cancel', - }), - }) + }, + } function selectFile:enable(path, fn) self:setPath(path) diff --git a/sys/apis/git.lua b/sys/apis/git.lua index ccaaf5f..3200194 100644 --- a/sys/apis/git.lua +++ b/sys/apis/git.lua @@ -1,4 +1,4 @@ -local json = require('json') +local json = require('craigmj.json4lua.master.json.json') local Util = require('util') local TREE_URL = 'https://api.github.com/repos/%s/%s/git/trees/%s?recursive=1' diff --git a/sys/apis/injector.lua b/sys/apis/injector.lua index a120ea7..3fa6b58 100644 --- a/sys/apis/injector.lua +++ b/sys/apis/injector.lua @@ -1,6 +1,14 @@ local DEFAULT_UPATH = 'https://raw.githubusercontent.com/kepler155c/opus/master/sys/apis' local PASTEBIN_URL = 'http://pastebin.com/raw' -local GIT_URL = 'https://raw.githubusercontent.com/' +local GIT_URL = 'https://raw.githubusercontent.com' + +local function standardSearcher(modname, env, shell) + if package.loaded[modname] then + return function() + return package.loaded[modname] + end + end +end local function shellSearcher(modname, env, shell) local fname = modname:gsub('%.', '/') .. '.lua' @@ -80,7 +88,8 @@ local function gitSearcher(modname, env, shell) local fname = modname:gsub('%.', '/') .. '.lua' local _, count = fname:gsub("/", "") if count >= 3 then - local url = GIT_URL .. '/' .. modname + local url = GIT_URL .. '/' .. fname + debug(url) local c = loadUrl(url) if c then return load(c, modname, nil, env) @@ -105,7 +114,16 @@ end _G.package = { path = LUA_PATH or 'sys/apis', upath = LUA_UPATH or DEFAULT_UPATH, + config = '/\n:\n?\n!\n-', + loaded = { + math = math, + string = string, + table = table, + io = io, + os = os, + }, loaders = { + standardSearcher, shellSearcher, pathSearcher, pastebinSearcher, @@ -125,7 +143,7 @@ local function requireWrapper(env) end for _,searcher in ipairs(package.loaders) do - local fn = searcher(modname, env, shell) + local fn, msg = searcher(modname, env, shell) if fn then local module, msg = fn(modname, env) if not module then @@ -134,6 +152,9 @@ local function requireWrapper(env) loaded[modname] = module return module end + if msg then + error(msg) + end end error('Unable to find module ' .. modname) end diff --git a/sys/apis/ui.lua b/sys/apis/ui.lua index b966642..0a0c885 100644 --- a/sys/apis/ui.lua +++ b/sys/apis/ui.lua @@ -112,7 +112,6 @@ function Manager:init(args) Event.addHandler('mouse_click', function(h, button, x, y) if button == 1 and shift and control then -- hack - local event = self:pointToChild(self.target, x, y) multishell.openTab({ path = 'apps/Lua.lua', args = { event.element }, focused = true }) @@ -226,13 +225,15 @@ function Manager:disableEffects() end function Manager:loadTheme(filename) - local theme, err = Util.loadTable(filename) - if not theme then - error(err) - end - for k,v in pairs(theme) do - if self[k] and self[k].defaults then - Util.merge(self[k].defaults, v) + if fs.exists(filename) then + local theme, err = Util.loadTable(filename) + if not theme then + error(err) + end + for k,v in pairs(theme) do + if self[k] and self[k].defaults then + Util.merge(self[k].defaults, v) + end end end end @@ -292,7 +293,6 @@ function Manager:click(button, x, y) if x < self.target.x or y < self.target.y or x > self.target.x + self.target.width - 1 or y > self.target.y + self.target.height - 1 then - target:emit({ type = 'mouse_out' }) target = self.currentPage @@ -658,8 +658,12 @@ function UI.Window:write(x, y, text, bg, tc) x = x - self.offx y = y - self.offy if y <= self.height and y > 0 then - self.parent:write( - self.x + x - 1, self.y + y - 1, tostring(text), bg, tc) + if self.canvas then + self.canvas:write(x, y, text, bg, tc) + else + self.parent:write( + self.x + x - 1, self.y + y - 1, tostring(text), bg, tc) + end end end @@ -830,20 +834,24 @@ function UI.Window:eventHandler(event) end --[[-- Blit data manipulation --]]-- -local Blob = class() -function Blob:init(args) +local Canvas = class() +function Canvas:init(args) self.x = 1 self.y = 1 - self.lines = { } + Util.merge(self, args) - for i = 1, self.ey - self.y + 1 do + self.height = self.ey - self.y + 1 + self.width = self.ex - self.x + 1 + + self.lines = { } + for i = 1, self.height do self.lines[i] = { } end end -function Blob:copy() - local b = Blob({ x = self.x, y = self.y, ex = self.ex, ey = self.ey }) +function Canvas:copy() + local b = Canvas({ x = self.x, y = self.y, ex = self.ex, ey = self.ey }) for i = 1, self.ey - self.y + 1 do b.lines[i].text = self.lines[i].text b.lines[i].fg = self.lines[i].fg @@ -852,43 +860,170 @@ function Blob:copy() return b end -function Blob:write(y, text, fg, bg) +function Canvas:addLayer(layer, bg, fg) + local canvas = Canvas({ + x = layer.x, + y = layer.y, + ex = layer.x + layer.width - 1, + ey = layer.y + layer.height - 1, + isColor = self.isColor, + }) + canvas:clear(colorToPaintColor(bg, self.isColor), + colorToPaintColor(fg, self.isColor)) + + canvas.parent = self + if not self.layers then + self.layers = { } + end + table.insert(self.layers, canvas) + return canvas +end + +function Canvas:removeLayer() + for k, layer in pairs(self.parent.layers) do + if layer == self then + self:setVisible(false) + table.remove(self.parent.layers, k) + break + end + end +end + +function Canvas:setVisible(visible) + self.visible = visible + if not visible then + self.parent:dirty() + -- set parent's lines to dirty for each line in self + end +end + +function Canvas:write(x, y, text, bg, tc) + + if y > 0 and y <= self.height and x <= self.width then + + local width = #text + + if x < 1 then + text = text:sub(2 - x) + width = width + x - 1 + x = 1 + end + + if x + width - 1 > self.width then + text = text:sub(1, self.width - x + 1) + width = #text + end + + if width > 0 then + + local function replace(sstr, pos, rstr, width) + return sstr:sub(1, pos-1) .. rstr .. sstr:sub(pos+width) + end + + local function fill(sstr, pos, rstr, width) + return sstr:sub(1, pos-1) .. string.rep(rstr, width) .. sstr:sub(pos+width) + end + + local line = self.lines[y] + line.dirty = true + line.text = replace(line.text, x, text, width) + if bg then + line.bg = fill(line.bg, x, colorToPaintColor(bg, self.isColor), width) + end + if tc then + line.fg = fill(line.fg, x, colorToPaintColor(tc, self.isColor), width) + end + end + end +end + +function Canvas:writeLine(y, text, fg, bg) self.lines[y].dirty = true self.lines[y].text = text self.lines[y].fg = fg self.lines[y].bg = bg end -function Blob:reset() +function Canvas:reset() self.region = nil end -function Blob:punch(rect) +function Canvas:clear(bg, fg) + local width = self.ex - self.x + 1 + local text = string.rep(' ', width) + fg = string.rep(fg, width) + bg = string.rep(bg, width) + for i = 1, self.ey - self.y + 1 do + self:writeLine(i, text, fg, bg) + end +end + +function Canvas:punch(rect) if not self.regions then self.regions = Region.new(self.x, self.y, self.ex, self.ey) end self.regions:subRect(rect.x, rect.y, rect.ex, rect.ey) end -function Blob:blitClipped(device) +function Canvas:blitClipped(device) for _,region in ipairs(self.regions.region) do self:blit(device, - { x = region[1], y = region[2], ex = region[3], ey = region[4] }, + { x = region[1] - self.x + 1, + y = region[2] - self.y + 1, + ex = region[3]- self.x + 1, + ey = region[4] - self.y + 1 }, { x = region[1], y = region[2] }) end end -function Blob:blit(device, src, tgt) +function Canvas:dirty() + for _, line in pairs(self.lines) do + line.dirty = true + end +end + +function Canvas:clean() + for y, line in ipairs(self.lines) do + line.dirty = false + end +end + +function Canvas:render(device, layers) + layers = layers or self.layers + if layers then + self.regions = Region.new(self.x, self.y, self.ex, self.ey) + local l = Util.shallowCopy(layers) + for _, canvas in ipairs(layers) do + table.remove(l, 1) + if canvas.visible then + self:punch(canvas) + canvas:render(device, l) + end + end + self:blitClipped(device) + self:reset() + else + self:blit(device) + end + self:clean() +end + +function Canvas:blit(device, src, tgt) + src = src or { x = 1, y = 1, ex = self.ex - self.x + 1, ey = self.ey - self.y + 1 } + tgt = tgt or self + for i = 0, src.ey - src.y do local line = self.lines[src.y + i] - local t, fg, bg = line.text, line.fg, line.bg - if src.x > 1 or src.ex < self.ex then - t = t:sub(src.x, src.ex) - fg = fg:sub(src.x, src.ex) - bg = bg:sub(src.x, src.ex) + if line.dirty then + local t, fg, bg = line.text, line.fg, line.bg + if src.x > 1 or src.ex < self.ex then + t = t:sub(src.x, src.ex) + fg = fg:sub(src.x, src.ex) + bg = bg:sub(src.x, src.ex) + end + device.setCursorPos(tgt.x, tgt.y + i) + device.blit(t, fg, bg) end - device.setCursorPos(tgt.x, tgt.y + i) - device.blit(t, fg, bg) end end @@ -906,21 +1041,23 @@ function UI.TransitionSlideLeft:init(args) self.pos = { x = self.ex } self.tween = Tween.new(self.ticks, self.pos, { x = self.x }, self.easing) self.lastx = 0 + self.lastScreen = self.canvas:copy() end -function UI.TransitionSlideLeft:update(device, screen, lastScreen) +function UI.TransitionSlideLeft:update(device) self.tween:update(1) local x = math.floor(self.pos.x) if x ~= self.lastx then self.lastx = x - lastScreen:blit(device, { + self.lastScreen:dirty() + self.lastScreen:blit(device, { x = self.ex - x + self.x, y = self.y, ex = self.ex, ey = self.ey }, { x = self.x, y = self.y }) - screen:blit(device, { + self.canvas:blit(device, { x = self.x, y = self.y, ex = self.ex - x + self.x + 1, @@ -944,20 +1081,22 @@ function UI.TransitionSlideRight:init(args) self.pos = { x = self.x } self.tween = Tween.new(self.ticks, self.pos, { x = self.ex }, self.easing) self.lastx = 0 + self.lastScreen = self.canvas:copy() end -function UI.TransitionSlideRight:update(device, screen, lastScreen) +function UI.TransitionSlideRight:update(device) self.tween:update(1) local x = math.floor(self.pos.x) if x ~= self.lastx then self.lastx = x - lastScreen:blit(device, { + self.lastScreen:dirty() + self.lastScreen:blit(device, { x = self.x, y = self.y, ex = self.ex - x + self.x + 1, ey = self.ey }, { x = x, y = self.y }) - screen:blit(device, { + self.canvas:blit(device, { x = self.ex - x + self.x, y = self.y, ex = self.ex + 1, @@ -967,6 +1106,26 @@ function UI.TransitionSlideRight:update(device, screen, lastScreen) return self.pos.x ~= self.ex end +--[[-- TransitionExpandUp --]]-- +UI.TransitionExpandUp = class() +UI.TransitionExpandUp.defaults = { + UIElement = 'TransitionExpandUp', + ticks = 3, + easing = 'linear', +} +function UI.TransitionExpandUp:init(args) + local defaults = UI:getDefaults(UI.TransitionExpandUp, args) + UI.setProperties(self, defaults) + self.pos = { y = self.ey + 1 } + self.tween = Tween.new(self.ticks, self.pos, { y = self.y }, self.easing) +end + +function UI.TransitionExpandUp:update(device) + self.tween:update(1) + self.canvas:blit(device, nil, { x = self.x, y = math.floor(self.pos.y) }) + return self.pos.y ~= self.y +end + --[[-- Terminal for computer / advanced computer / monitor --]]-- UI.Device = class(UI.Window) UI.Device.defaults = { @@ -994,22 +1153,19 @@ function UI.Device:init(args) UI.Window.init(self, defaults) - self.blob = Blob({ - x = 1, y = 1, ex = self.width, ey = self.height - }) - for i = 1, self.height do - self.blob:write(i, - string.rep(' ', self.width), - string.rep(colorToPaintColor(self.backgroundColor, self.isColor), self.width), - string.rep(colorToPaintColor(self.textColor, self.isColor), self.width)) - end - self.isColor = self.device.isColor() + + self.canvas = Canvas({ + x = 1, y = 1, ex = self.width, ey = self.height, + isColor = self.isColor, + }) + self.canvas:clear(colorToPaintColor(self.backgroundColor, self.isColor), + colorToPaintColor(self.textColor, self.isColor)) end function UI.Device:resize() self.width, self.height = self.device.getSize() - self.lines = { } + self.lines = { } -- TODO -- resize canvas UI.Window.resize(self) end @@ -1056,30 +1212,31 @@ function UI.Device:addTransition(effect, x, y, width, height) y = y, ex = x + width - 1, ey = y + height - 1, + canvas = self.canvas, } end table.insert(self.transitions, effect) end -function UI.Device:runTransitions(transitions) +function UI.Device:runTransitions(transitions, canvas) for _,t in ipairs(transitions) do - self.blob:punch(t) -- punch out the effect areas + canvas:punch(t) -- punch out the effect areas end - self.blob:blitClipped(self.device) -- and blit the remainder - self.blob:reset() + canvas:blitClipped(self.device) -- and blit the remainder + canvas:reset() while true do for _,k in ipairs(Util.keys(transitions)) do local transition = transitions[k] - if not transition:update(self.device, self.blob, self.lastScreen) then + if not transition:update(self.device) then transitions[k] = nil end end if Util.empty(transitions) then break end - Event.sleep() + os.sleep() -- ? end end @@ -1091,68 +1248,22 @@ function UI.Device:sync() self.transitions = nil end - if transitions then - self:runTransitions(transitions) - else - for y, line in pairs(self.blob.lines) do - if line.dirty then - self.device.setCursorPos(1, y) - self.device.blit(line.text, line.fg, line.bg) - end - end + if self:getCursorBlink() then + self.device.setCursorBlink(false) end - self.lastScreen = self.blob:copy() - - for y, line in ipairs(self.blob.lines) do - line.dirty = false + if transitions then + self:runTransitions(transitions, self.canvas) + else + self.canvas:render(self.device) end if self:getCursorBlink() then + self.device.setCursorBlink(true) self.device.setCursorPos(self.cursorX, self.cursorY) end end -function UI.Device:write(x, y, text, bg, tc) - - if y > 0 and y <= self.height and x <= self.width then - - local width = #text - - if x < 1 then - text = text:sub(2 - x) - width = width + x - 1 - x = 1 - end - - if x + width - 1 > self.width then - text = text:sub(1, self.width - x + 1) - width = #text - end - - if width > 0 then - - local function replace(sstr, pos, rstr, width) - return sstr:sub(1, pos-1) .. rstr .. sstr:sub(pos+width) - end - - local function fill(sstr, pos, rstr, width) - return sstr:sub(1, pos-1) .. string.rep(rstr, width) .. sstr:sub(pos+width) - end - - local line = self.blob.lines[y] - line.dirty = true - line.text = replace(line.text, x, text, width) - if bg then - line.bg = fill(line.bg, x, colorToPaintColor(bg, self.isColor), width) - end - if tc then - line.fg = fill(line.fg, x, colorToPaintColor(tc, self.isColor), width) - end - end - end -end - --[[-- StringBuffer --]]-- UI.StringBuffer = class() function UI.StringBuffer:init(bufSize) @@ -1198,9 +1309,16 @@ function UI.Page:init(args) defaults.parent = UI.defaultDevice UI.setProperties(defaults, args) UI.Window.init(self, defaults) + + if self.z then + self.canvas = self.parent.canvas:addLayer(self, self.backgroundColor, self.textColor) + else + self.canvas = self.parent.canvas + end end function UI.Page:enable() + self.canvas.visible = true UI.Window.enable(self) if not self.focused or not self.focused.enabled then @@ -1208,6 +1326,12 @@ function UI.Page:enable() end end +function UI.Page:disable() + if self.z then + self.canvas.visible = false + end +end + function UI.Page:getFocused() return self.focused end @@ -1305,6 +1429,8 @@ UI.Grid.defaults = { unfocusedTextSelectedColor = colors.white, unfocusedBackgroundSelectedColor = colors.gray, focusIndicator = '>', + sortIndicator = ' ', + inverseSortIndicator = '^', values = { }, columns = { }, } @@ -1461,8 +1587,12 @@ function UI.Grid:drawHeadings() local sb = UI.StringBuffer(self.width) for _,col in ipairs(self.columns) do local ind = ' ' - if self.inverseSort and col.key == self.sortColumn then - ind = '^' + if col.key == self.sortColumn then + if self.inverseSort then + ind = self.inverseSortIndicator + else + ind = self.sortIndicator + end end sb:insert(ind .. col.heading, col.width + 1) end @@ -1652,7 +1782,11 @@ end UI.ScrollingGrid = class(UI.Grid) UI.ScrollingGrid.defaults = { UIElement = 'ScrollingGrid', - scrollOffset = 1 + scrollOffset = 1, + lineChar = '|', + sliderChar = '#', + upArrowChar = '^', + downArrowChar = 'v', } function UI.ScrollingGrid:init(args) local defaults = UI:getDefaults(UI.ScrollingGrid, args) @@ -1683,25 +1817,25 @@ function UI.ScrollingGrid:drawScrollbar() local x = self.width if self.scrollOffset > 1 then - self:write(x, 2, '^') + self:write(x, 2, self.upArrowChar) else self:write(x, 2, ' ') end local row = 0 for i = 1, sp - 1 do - self:write(x, row+3, '|') + self:write(x, row+3, self.lineChar) row = row + 1 end for i = 1, sa do - self:write(x, row+3, '#') + self:write(x, row+3, self.sliderChar) row = row + 1 end for i = row, sbSize do - self:write(x, row+3, '|') + self:write(x, row+3, self.lineChar) row = row + 1 end if self.scrollOffset + self.pageSize - 1 < Util.size(self.values) then - self:write(x, self.pageSize + 1, 'v') + self:write(x, self.pageSize + 1, self.downArrowChar) else self:write(x, self.pageSize + 1, ' ') end @@ -1946,19 +2080,23 @@ function UI.MenuBar:init(args) local x = 1 for k,button in pairs(self.buttons) do - local buttonProperties = { - x = x, - width = #button.text + self.spacing, - backgroundColor = self.backgroundColor, - textColor = self.textColor, - centered = false, - } - x = x + buttonProperties.width - UI.setProperties(buttonProperties, button) - if button.name then - self[button.name] = UI.Button(buttonProperties) + if button.UIElement then + table.insert(self.children, button) else - table.insert(self.children, UI.Button(buttonProperties)) + local buttonProperties = { + x = x, + width = #button.text + self.spacing, + backgroundColor = self.backgroundColor, + textColor = self.textColor, + centered = false, + } + x = x + buttonProperties.width + UI.setProperties(buttonProperties, button) + if button.name then + self[button.name] = UI.Button(buttonProperties) + else + table.insert(self.children, UI.Button(buttonProperties)) + end end end if self.showBackButton then @@ -1997,6 +2135,7 @@ end UI.DropMenu = class(UI.MenuBar) UI.DropMenu.defaults = { UIElement = 'DropMenu', + backgroundColor = colors.white, } function UI.DropMenu:init(args) local defaults = UI:getDefaults(UI.DropMenu, args) @@ -2011,7 +2150,7 @@ function UI.DropMenu:setParent() for y,child in ipairs(self.children) do child.x = 1 child.y = y - if #child.text > maxWidth then + if #(child.text or '') > maxWidth then maxWidth = #child.text end end @@ -2155,9 +2294,9 @@ function UI.Tabs:activateTab(tab) child:disable() end end + self.tabBar:selectTab(tab.tabTitle) tab:enable() tab:draw() - self.tabBar:selectTab(tab.tabTitle) self:emit({ type = 'tab_activate', activated = tab, element = self }) end @@ -2232,33 +2371,21 @@ UI.Notification = class(UI.Window) UI.Notification.defaults = { UIElement = 'Notification', backgroundColor = colors.gray, - height = 1, + height = 3, } function UI.Notification:init(args) local defaults = UI:getDefaults(UI.Notification, args) UI.Window.init(self, defaults) + Util.print(self) end function UI.Notification:draw() - if self.enabled then - local lines = Util.wordWrap(self.value, self.width - 2) - self.height = #lines -- + 2 - self.y = UI.term.height - self.height + 1 - self:clear() - for k,v in pairs(lines) do - self:write(2, k, v) - end - end end function UI.Notification:enable() self.enabled = false end -function UI.Notification:resize() - self.y = UI.term.height + 1 -end - function UI.Notification:error(value, timeout) self.backgroundColor = colors.red self:display(value, timeout) @@ -2275,16 +2402,32 @@ function UI.Notification:success(value, timeout) end function UI.Notification:display(value, timeout) - self.value = value self.enabled = true - self:draw() + local lines = Util.wordWrap(value, self.width - 2) + self.height = #lines + 1 + self.y = self.parent.height - self.height + 1 + if self.canvas then + self.canvas:removeLayer() + end + self.canvas = UI.term.canvas:addLayer(self, self.backgroundColor, self.textColor or colors.white) + self:addTransition(UI.TransitionExpandUp { + x = self.x, + y = self.y, + ex = self.x + self.width - 1, + ey = self.y + self.height - 1, + canvas = self.canvas, + ticks = self.height, + }) + self.canvas:setVisible(true) + self:clear() + for k,v in pairs(lines) do + self:write(2, k, v) + end + Event.addNamedTimer('notificationTimer', timeout or 3, false, function() --- self.y = UI.term.height + 1 self.enabled = false - if self.parent.enabled then - self.parent:draw() - self:sync() - end + self.canvas:removeLayer() + self:sync() end) end @@ -2592,7 +2735,7 @@ UI.TextEntry.defaults = { shadowText = '', focused = false, backgroundColor = colors.lightGray, - backgroundFocusColor = colors.green, + backgroundFocusColor = colors.lightGray, height = 1, limit = 6, pos = 0, @@ -2940,6 +3083,9 @@ function UI.Form:createFields() if field.limit then width = field.limit + 2 end + if width == 0 then + width = nil + end local fieldProperties = { x = self.labelWidth + 2, y = k, @@ -2952,17 +3098,13 @@ function UI.Form:createFields() end function UI.Form:eventHandler(event) - if event.type == 'accept' then for _,child in pairs(self.children) do if child.key then self.values[child.key] = child.value end end - return false end - - return false end --[[-- Dialog --]]-- @@ -3087,8 +3229,9 @@ function UI.NftImage:setImage(image) self.image = image end -if fs.exists('/config/ui.theme') then - UI:loadTheme('/config/ui.theme') +UI:loadTheme('config/ui.theme') +if _HOST and string.find(_HOST, 'CCEmuRedux') then + UI:loadTheme('config/ccemuredux.theme') end UI:setDefaultDevice(UI.Device({ device = term.current() })) diff --git a/sys/extensions/scheduler.lua b/sys/extensions/scheduler.lua index 78913a4..6fad73b 100644 --- a/sys/extensions/scheduler.lua +++ b/sys/extensions/scheduler.lua @@ -57,10 +57,12 @@ function turtle.run(fn, ...) local e, id, abort = os.pullEventRaw('turtle_ticket') if e == 'terminate' then releaseTicket(ticketId) + os.queueEvent('turtle_response') error('Terminated') end if abort then -- the function was queued, but the queue was cleared + os.queueEvent('turtle_response') return false, 'aborted' end if id == ticketId then @@ -72,6 +74,7 @@ function turtle.run(fn, ...) if not s and m then printError(m) end + os.queueEvent('turtle_response') return s, m end end diff --git a/sys/extensions/tl3.lua b/sys/extensions/tl3.lua index 07734ac..0f195d6 100644 --- a/sys/extensions/tl3.lua +++ b/sys/extensions/tl3.lua @@ -17,6 +17,7 @@ local state = { moveDig = noop, moveCallback = noop, locations = {}, + coordSystem = 'relative', -- type of coordinate system being used } function turtle.getState() @@ -44,6 +45,7 @@ function turtle.reset() state.moveDig = noop state.moveCallback = noop state.locations = {} + state.coordSystem = 'relative' return true end @@ -231,7 +233,6 @@ turtle.digPolicies = { if not turtle.isTurtleAtSide(action.side) then return action.dig() end - return Util.tryTimes(6, function() -- if not turtle.isTurtleAtSide(action.side) then -- return true --action.dig() diff --git a/sys/network/snmp.lua b/sys/network/snmp.lua index 961fe9c..c31087f 100644 --- a/sys/network/snmp.lua +++ b/sys/network/snmp.lua @@ -117,15 +117,19 @@ process:newThread('discovery_server', function() end end) +local info = { + id = os.getComputerID() +} + local function sendInfo() - local info = { - id = os.getComputerID(), - label = os.getComputerLabel(), - uptime = math.floor(os.clock()), - } + info.label = os.getComputerLabel() + info.uptime = math.floor(os.clock()) if turtle then info.fuel = turtle.getFuelLevel() info.status = turtle.status + info.point = turtle.point + info.inventory = turtle.getInventory() + info.coordSystem = turtle.getState().coordSystem end device.wireless_modem.transmit(999, os.getComputerID(), info) end @@ -152,13 +156,11 @@ end) if os.isTurtle() then process:newThread('turtle_heartbeat', function() - local lastUpdate = os.clock() os.sleep(1) - while true do os.pullEvent('turtle_response') - if os.clock() - lastUpdate >= 1 then - lastUpdate = os.clock() + if turtle.status ~= info.status or + turtle.fuel ~= info.fuel then sendInfo() end end From 5c12b20faeefe29bd0934b870a5776a369f0270c Mon Sep 17 00:00:00 2001 From: "kepler155c@gmail.com" Date: Mon, 26 Dec 2016 22:26:43 -0500 Subject: [PATCH 8/9] Forms - dialogs --- apps/Lua.lua | 60 +++++++++-- apps/Network.lua | 6 +- apps/Overview.lua | 109 ++++++++----------- apps/edit.lua | 6 ++ apps/storageManager.lua | 200 ++++++++++++++++------------------ sys/apis/event.lua | 17 ++- sys/apis/fileui.lua | 8 +- sys/apis/fs/urlfs.lua | 4 +- sys/apis/git.lua | 2 +- sys/apis/injector.lua | 3 +- sys/apis/process.lua | 7 +- sys/apis/ui.lua | 228 ++++++++++++++++++++++----------------- sys/apis/util.lua | 4 + sys/boot/multishell.boot | 3 +- 14 files changed, 365 insertions(+), 292 deletions(-) diff --git a/apps/Lua.lua b/apps/Lua.lua index b6c45ac..112a9ea 100644 --- a/apps/Lua.lua +++ b/apps/Lua.lua @@ -30,10 +30,11 @@ local page = UI.Page({ backgroundFocusColor = colors.black, limit = 256, accelerators = { - enter = 'command_enter', - up = 'history_back', - down = 'history_forward', - mouse_rightclick = 'clear_prompt', + enter = 'command_enter', + up = 'history_back', + down = 'history_forward', + mouse_rightclick = 'clear_prompt', + [ 'control-space' ] = 'autocomplete', }, }), grid = UI.ScrollingGrid({ @@ -72,20 +73,65 @@ function page:enable() end end +function autocomplete(env, oLine, x) + + local sLine = oLine:sub(1, x) + local nStartPos = sLine:find("[a-zA-Z0-9_%.]+$") + if nStartPos then + sLine = sLine:sub(nStartPos) + end + + if #sLine > 0 then + local results = textutils.complete(sLine, env) + + if #results == 0 then +-- setError('No completions available') + + elseif #results == 1 then + return Util.insertString(oLine, results[1], x + 1) + + elseif #results > 1 then + local prefix = results[1] + for n = 1, #results do + local result = results[n] + while #prefix > 0 do + if result:find(prefix, 1, true) == 1 then + break + end + prefix = prefix:sub(1, #prefix - 1) + end + end + if #prefix > 0 then + return Util.insertString(oLine, prefix, x + 1) + else +-- setStatus('Too many results') + end + end + end + return oLine +end + function page:eventHandler(event) if event.type == 'global' then - page:setPrompt('', true) + self:setPrompt('', true) self:executeStatement('getfenv(0)') command = nil elseif event.type == 'local' then - page:setPrompt('', true) + self:setPrompt('', true) self:executeStatement('getfenv(1)') command = nil + elseif event.type == 'autocomplete' then + local sz = #self.prompt.value + local pos = self.prompt.pos + self:setPrompt(autocomplete(sandboxEnv, self.prompt.value, self.prompt.pos)) + self.prompt:setPosition(pos + #self.prompt.value - sz) + self.prompt:updateCursor() + elseif event.type == 'device' then - page:setPrompt('device', true) + self:setPrompt('device', true) self:executeStatement('device') elseif event.type == 'history_back' then diff --git a/apps/Network.lua b/apps/Network.lua index fc0d4d8..ab337e9 100644 --- a/apps/Network.lua +++ b/apps/Network.lua @@ -121,14 +121,14 @@ function page.grid:draw() end end -function updateComputers() +Event.addThread(function() while true do page.grid:update() page.grid:draw() page:sync() os.sleep(1) end -end +end) Event.addHandler('device_attach', function(h, deviceName) if deviceName == 'wireless_modem' then @@ -149,5 +149,5 @@ if not device.wireless_modem then end UI:setPage(page) -Event.pullEvents(updateComputers) +Event.pullEvents() UI.term:reset() diff --git a/apps/Overview.lua b/apps/Overview.lua index 80b7045..faf72ee 100644 --- a/apps/Overview.lua +++ b/apps/Overview.lua @@ -332,76 +332,64 @@ end local formWidth = math.max(UI.term.width - 14, 26) -local editor = UI.Page { - backgroundColor = colors.white, - x = math.ceil((UI.term.width - formWidth) / 2) + 1, - y = math.ceil((UI.term.height - 11) / 2) + 1, - z = 2, +local editor = UI.Dialog { height = 11, width = formWidth, - titleBar = UI.TitleBar { - title = 'Edit application', - }, - inset = UI.Window { - x = 2, - y = 3, - rex = -2, - rey = -3, - form = UI.Form { - textColor = colors.black, - fields = { - { label = 'Title', key = 'title', width = formWidth - 11, limit = 11, display = UI.Form.D.entry, - help = 'Application title' }, - { label = 'Run', key = 'run', width = formWidth - 11, limit = 100, display = UI.Form.D.entry, - help = 'Full path to application' }, - { label = 'Category', key = 'category', width = formWidth - 11, limit = 11, display = UI.Form.D.entry, - help = 'Category of application' }, - { text = 'Icon', event = 'loadIcon', display = UI.Form.D.button, - x = 10, y = 5, textColor = colors.white, help = 'Select icon' }, - { text = 'Ok', event = 'accept', display = UI.Form.D.button, - x = formWidth - 14, y = 7, textColor = colors.white }, - { text = 'Cancel', event = 'cancel', display = UI.Form.D.button, - x = formWidth - 9, y = 7, textColor = colors.white }, - }, - labelWidth = 8, - image = UI.NftImage { - y = 5, - x = 1, - height = 3, - width = 8, - }, + title = 'Edit application', + form = UI.Form { + y = 2, + height = 9, + title = UI.TextEntry { + formLabel = 'Title', formKey = 'title', limit = 11, help = 'Application title', + required = true, + }, + run = UI.TextEntry { + formLabel = 'Run', formKey = 'run', limit = 100, help = 'Full path to application', + required = true, + }, + category = UI.TextEntry { + formLabel = 'Category', formKey = 'category', limit = 11, help = 'Category of application', + required = true, + }, + loadIcon = UI.Button { + x = 11, y = 6, + text = 'Icon', event = 'loadIcon', help = 'Select icon' + }, + image = UI.NftImage { + y = 6, + x = 2, + height = 3, + width = 8, }, }, statusBar = UI.StatusBar(), - notification = UI.Notification(), iconFile = '', } function editor:enable(app) if app then - self.original = app - self.inset.form:setValues(Util.shallowCopy(app)) + self.form:setValues(app) local icon if app.icon then icon = parseIcon(app.icon) end - self.inset.form.image:setImage(icon) + self.form.image:setImage(icon) end UI.Page.enable(self) self:focusFirst() end -function editor.inset.form.image:draw() +function editor.form.image:draw() self:clear() UI.NftImage.draw(self) end -function editor:updateApplications(app, original) - if original.run then - local _,k = Util.find(applications, 'run', original.run) - if k then +function editor:updateApplications(app) + for k,v in pairs(applications) do + if v == app then applications[k] = nil + break end end table.insert(applications, app) @@ -410,7 +398,7 @@ end function editor:eventHandler(event) - if event.type == 'cancel' then + if event.type == 'form_cancel' or event.type == 'cancel' then UI:setPreviousPage() elseif event.type == 'focus_change' then @@ -438,29 +426,26 @@ function editor:eventHandler(event) if not icon then error(m) end - self.inset.form.values.icon = iconLines - self.inset.form.image:setImage(icon) - self.inset.form.image:draw() + self.form.values.icon = iconLines + self.form.image:setImage(icon) + self.form.image:draw() end) if not s and m then local msg = m:gsub('.*: (.*)', '%1') - self.notification:error(msg) + page.notification:error(msg) end end end) - elseif event.type == 'accept' then - local values = self.inset.form.values - if #values.run > 0 and #values.title > 0 and #values.category > 0 then - UI:setPreviousPage() - self:updateApplications(values, self.original) - page:refresh() - page:draw() - else - self.notification:error('Require fields missing') - --self.statusBar:setStatus('Require fields missing') - --self.statusBar:draw() - end + elseif event.type == 'form_invalid' then + page.notification:error(event.message) + + elseif event.type == 'form_complete' then + local values = self.form.values + UI:setPreviousPage() + self:updateApplications(values) + page:refresh() + page:draw() else return UI.Page.eventHandler(self, event) end diff --git a/apps/edit.lua b/apps/edit.lua index 55f47e9..2d8d634 100644 --- a/apps/edit.lua +++ b/apps/edit.lua @@ -33,6 +33,12 @@ local clipboard = { size, internal } local searchPattern local undo = { chain = { }, pointer = 0 } +if _G.__CLIPBOARD then + clipboard = _G.__CLIPBOARD +else + _G.__CLIPBOARD = clipboard +end + local color = { textColor = '0', keywordColor = '4', diff --git a/apps/storageManager.lua b/apps/storageManager.lua index 1532f9b..d2ea572 100644 --- a/apps/storageManager.lua +++ b/apps/storageManager.lua @@ -452,51 +452,54 @@ function watchResources(items) return itemList end -itemPage = UI.Page({ +itemPage = UI.Page { backgroundColor = colors.lightGray, - titleBar = UI.TitleBar({ + titleBar = UI.TitleBar { title = 'Limit Resource', previousPage = true, + event = 'form_cancel', backgroundColor = colors.green - }), - idField = UI.Text({ - x = 5, - y = 3, - width = UI.term.width - 10 - }), - form = UI.Form({ - fields = { - { label = 'Min', key = 'low', width = 7, display = UI.Form.D.entry, - help = 'Craft if below min' }, - { label = 'Max', key = 'limit', width = 7, display = UI.Form.D.entry, - validation = UI.Form.V.number, dataType = UI.Form.T.number, - help = 'Eject if above max' }, - { label = 'Autocraft', key = 'auto', width = 7, display = UI.Form.D.chooser, - nochoice = 'No', - choices = { - { name = 'Yes', value = 'yes' }, - { name = 'No', value = 'no' }, - }, - help = 'Craft until out of ingredients' }, - { label = 'Ignore Dmg', key = 'ignore_dmg', width = 7, display = UI.Form.D.chooser, - nochoice = 'No', - choices = { - { name = 'Yes', value = 'yes' }, - { name = 'No', value = 'no' }, - }, - help = 'Ignore damage of item' }, - { text = 'Accept', event = 'accept', display = UI.Form.D.button, - x = 1, y = 6, width = 10 }, - { text = 'Cancel', event = 'cancel', display = UI.Form.D.button, - x = 21, y = 6, width = 10 }, + }, + idField = UI.Text { + x = 5, y = 3, width = UI.term.width - 10, + }, + form = UI.Form { + x = 4, y = 4, height = 8, rex = -4, + [1] = UI.TextEntry { + width = 7, + backgroundColor = colors.gray, + backgroundFocusColor = colors.gray, + formLabel = 'Min', formKey = 'low', help = 'Craft if below min' }, - labelWidth = 10, - x = 5, - y = 5, - height = 6 - }), - statusBar = UI.StatusBar() -}) + [2] = UI.TextEntry { + width = 7, + backgroundColor = colors.gray, + backgroundFocusColor = colors.gray, + formLabel = 'Max', formKey = 'limit', help = 'Eject if above max' + }, + [3] = UI.Chooser { + width = 7, + formLabel = 'Autocraft', formKey = 'auto', + nochoice = 'No', + choices = { + { name = 'Yes', value = 'yes' }, + { name = 'No', value = 'no' }, + }, + help = 'Craft until out of ingredients' + }, + [4] = UI.Chooser { + width = 7, + formLabel = 'Ignore Dmg', formKey = 'ignore_dmg', + nochoice = 'No', + choices = { + { name = 'Yes', value = 'yes' }, + { name = 'No', value = 'no' }, + }, + help = 'Ignore damage of item' + }, + }, + statusBar = UI.StatusBar { } +} function itemPage:enable() UI.Page.enable(self) @@ -504,12 +507,14 @@ function itemPage:enable() end function itemPage:eventHandler(event) - if event.type == 'cancel' then + if event.type == 'form_cancel' then UI:setPreviousPage() + elseif event.type == 'focus_change' then self.statusBar:setStatus(event.focused.help) self.statusBar:draw() - elseif event.type == 'accept' then + + elseif event.type == 'form_complete' then local values = self.form.values local t = Util.readTable('resource.limits') or { } for k,v in pairs(t) do @@ -527,55 +532,52 @@ function itemPage:eventHandler(event) table.insert(t, filtered) Util.writeTable('resource.limits', t) UI:setPreviousPage() + else return UI.Page.eventHandler(self, event) end return true end -listingPage = UI.Page({ - menuBar = UI.MenuBar({ +listingPage = UI.Page { + menuBar = UI.MenuBar { buttons = { { text = 'Learn', event = 'learn' }, { text = 'Forget', event = 'forget' }, }, - }), - grid = UI.Grid({ + }, + grid = UI.Grid { + y = 2, height = UI.term.height - 2, columns = { { heading = 'Name', key = 'name' , width = 22 }, { heading = 'Qty', key = 'qty' , width = 5 }, { heading = 'Min', key = 'low' , width = 4 }, { heading = 'Max', key = 'limit', width = 4 }, }, - y = 2, sortColumn = 'name', - height = UI.term.height-2, - }), - statusBar = UI.StatusBar({ + }, + statusBar = UI.StatusBar { backgroundColor = colors.gray, width = UI.term.width, - filterText = UI.Text({ + filterText = UI.Text { + x = 2, width = 6, value = 'Filter', - x = 2, - width = 6, - }), - filter = UI.TextEntry({ - width = 19, + }, + filter = UI.TextEntry { + x = 9, width = 19, limit = 50, - x = 9, - }), - refresh = UI.Button({ + }, + refresh = UI.Button { + x = 31, width = 8, text = 'Refresh', event = 'refresh', - x = 31, - width = 8 - }), - }), + }, + }, accelerators = { r = 'refresh', q = 'quit', } -}) +} function listingPage.grid:getRowTextColor(row, selected) if row.is_craftable then @@ -622,24 +624,26 @@ end function listingPage:eventHandler(event) if event.type == 'quit' then Event.exitPullEvents() + elseif event.type == 'grid_select' then local selected = event.selected itemPage.form:setValues(selected) itemPage.titleBar.title = selected.name itemPage.idField.value = selected.id UI:setPage('item') + elseif event.type == 'refresh' then self:refresh() self.grid:draw() + elseif event.type == 'learn' then if not duckAntenna then self.statusBar:timedStatus('Missing peripherals', 3) else - UI:getPage('craft').form:setValues( { ignore_dmg = 'no' } ) UI:setPage('craft') end - elseif event.type == 'forget' then + elseif event.type == 'forget' then local item = self.grid:getSelected() if item then local recipes = Util.readTable('recipes') or { } @@ -673,6 +677,7 @@ function listingPage:eventHandler(event) self:applyFilter() self.grid:draw() self.statusBar.filter:focus() + else UI.Page.eventHandler(self, event) end @@ -747,7 +752,7 @@ local function filter(t, filter) end end -local function learnRecipe(page, ignore_dmg) +local function learnRecipe(page) local t = Util.readTable('recipes') or { } local recipe = { } local ingredients = duckAntenna.getAllStacks(false) -- getTurtleInventory() @@ -773,7 +778,7 @@ local function learnRecipe(page, ignore_dmg) end end recipe.ingredients = ingredients - recipe.ignore_dmg = 'no' -- ignore_dmg + recipe.ignore_dmg = 'no' t[key] = recipe @@ -794,69 +799,52 @@ local function learnRecipe(page, ignore_dmg) end end -craftPage = UI.Page({ - x = 4, - y = math.floor((UI.term.height - 8) / 2) + 1, - height = 7, - width = UI.term.width - 6, +craftPage = UI.Dialog { + height = 7, width = UI.term.width - 6, backgroundColor = colors.lightGray, - titleBar = UI.TitleBar({ + titleBar = UI.TitleBar { title = 'Learn Recipe', previousPage = true, - }), - idField = UI.Text({ + }, + idField = UI.Text { x = 5, y = 3, width = UI.term.width - 10, value = 'Place recipe in turtle' - }), - form = UI.Form({ - fields = { - --[[ - { label = 'Ignore Damage', key = 'ignore_dmg', width = 7, display = UI.Form.D.chooser, - nochoice = 'No', - choices = { - { name = 'Yes', value = 'yes' }, - { name = 'No', value = 'no' }, - }, - help = 'Ignore damage of ingredients' }, - --]] - { text = 'Accept', event = 'accept', display = UI.Form.D.button, - x = 1, y = 1, width = 10 }, - { text = 'Cancel', event = 'cancel', display = UI.Form.D.button, - x = 16, y = 1, width = 10 }, - }, - labelWidth = 13, - x = 5, - y = 5, - height = 2 - }), - statusBar = UI.StatusBar({ + }, + accept = UI.Button { + rx = -13, ry = -2, + text = 'Ok', event = 'accept', + }, + cancel = UI.Button { + rx = -8, ry = -2, + text = 'Cancel', event = 'cancel' + }, + statusBar = UI.StatusBar { status = 'Crafting paused' - }) -}) + } +} function craftPage:enable() craftingPaused = true self:focusFirst() - UI.Page.enable(self) + UI.Dialog.enable(self) end function craftPage:disable() craftingPaused = false + UI.Dialog.disable(self) end function craftPage:eventHandler(event) if event.type == 'cancel' then UI:setPreviousPage() elseif event.type == 'accept' then - local values = self.form.values - - if learnRecipe(self, values.ignore_dmg) then + if learnRecipe(self) then UI:setPreviousPage() end else - return UI.Page.eventHandler(self, event) + return UI.Dialog.eventHandler(self, event) end return true end diff --git a/sys/apis/event.lua b/sys/apis/event.lua index 6144424..e110286 100644 --- a/sys/apis/event.lua +++ b/sys/apis/event.lua @@ -1,4 +1,5 @@ local Util = require('util') +local Process = require('process') local Event = { uid = 1, -- unique id for handlers @@ -123,8 +124,9 @@ local function _pullEvents() --exitPullEvents = false while true do - local e = Event.pullEvent() - if exitPullEvents or e == 'terminate' then + local e = { Process:pullEvent() } + Event.processEvent(e) + if exitPullEvents or e[1] == 'terminate' then break end end @@ -137,13 +139,18 @@ function Event.sleep(t) until event == 'timer' and id == timerId end +function Event.addThread(fn) + return Process:addThread(fn) +end + function Event.pullEvents(...) local routines = { ... } if #routines > 0 then - parallel.waitForAny(_pullEvents, ...) - else - _pullEvents() + for _, routine in ipairs(routines) do + Process:addThread(routine) + end end + _pullEvents() end function Event.exitPullEvents() diff --git a/sys/apis/fileui.lua b/sys/apis/fileui.lua index f9a6dc0..163a59d 100644 --- a/sys/apis/fileui.lua +++ b/sys/apis/fileui.lua @@ -15,7 +15,7 @@ return function(args) args = args or { } - local selectFile = UI.Page { + local selectFile = UI.Dialog { x = args.x or 3, y = args.y or 2, z = args.z or 2, @@ -24,11 +24,7 @@ return function(args) height = args.height, width = args.width, backgroundColor = colors.brown, - titleBar = UI.TitleBar { - title = 'Select file', - previousPage = true, - event = 'cancel', - }, + title = 'Select file', grid = UI.ScrollingGrid { x = 2, y = 2, diff --git a/sys/apis/fs/urlfs.lua b/sys/apis/fs/urlfs.lua index 1864908..2e9afad 100644 --- a/sys/apis/fs/urlfs.lua +++ b/sys/apis/fs/urlfs.lua @@ -46,13 +46,13 @@ function urlfs.open(node, fn, fl) synchronized(node.url, function() c = Util.download(node.url) end) - if c and #c > 0 then + if c then node.cache = c node.size = #c end end - if not c or #c == 0 then + if not c then return end diff --git a/sys/apis/git.lua b/sys/apis/git.lua index 3200194..ccaaf5f 100644 --- a/sys/apis/git.lua +++ b/sys/apis/git.lua @@ -1,4 +1,4 @@ -local json = require('craigmj.json4lua.master.json.json') +local json = require('json') local Util = require('util') local TREE_URL = 'https://api.github.com/repos/%s/%s/git/trees/%s?recursive=1' diff --git a/sys/apis/injector.lua b/sys/apis/injector.lua index 3fa6b58..f0014c3 100644 --- a/sys/apis/injector.lua +++ b/sys/apis/injector.lua @@ -89,7 +89,6 @@ local function gitSearcher(modname, env, shell) local _, count = fname:gsub("/", "") if count >= 3 then local url = GIT_URL .. '/' .. fname - debug(url) local c = loadUrl(url) if c then return load(c, modname, nil, env) @@ -153,7 +152,7 @@ local function requireWrapper(env) return module end if msg then - error(msg) + error(msg, 2) end end error('Unable to find module ' .. modname) diff --git a/sys/apis/process.lua b/sys/apis/process.lua index 6cbdc3e..2e69869 100644 --- a/sys/apis/process.lua +++ b/sys/apis/process.lua @@ -27,6 +27,11 @@ function Process:threadEvent(...) end end +function Process:addThread(fn, ...) + return self:newThread(nil, fn, ...) +end + +-- deprecated function Process:newThread(name, fn, ...) self.uid = self.uid + 1 @@ -45,7 +50,7 @@ function Process:newThread(name, fn, ...) local s, m = pcall(function() fn(unpack(args)) end) if not s and m then if m == 'Terminated' then - printError(thread.name .. ' terminated') + --printError(thread.name .. ' terminated') else printError(m) end diff --git a/sys/apis/ui.lua b/sys/apis/ui.lua index 0a0c885..c2dac11 100644 --- a/sys/apis/ui.lua +++ b/sys/apis/ui.lua @@ -64,6 +64,12 @@ local function getPosition(element) return x, y end +local function assertElement(el, msg) + if not el or not type(el) == 'table' or not el.UIElement then + error(msg, 3) + end +end + --[[-- Top Level Manager --]]-- local Manager = class() function Manager:init(args) @@ -430,6 +436,14 @@ function Manager:getDefaults(element, args) return defaults end +function Manager:pullEvents(...) + Event.pullEvents(...) +end + +function Manager:exitPullEvents() + Event.exitPullEvents() +end + -- inconsistent function Manager.setProperties(obj, args) if args then @@ -743,6 +757,7 @@ function UI.Window:print(text, bg, fg, indent) end function UI.Window:setFocus(focus) + assertElement(focus, 'UI.Window:setFocus: Invalid element passed') if self.parent then self.parent:setFocus(focus) end @@ -1378,6 +1393,8 @@ function UI.Page:focusNext() end function UI.Page:setFocus(child) + assertElement(child, 'UI.Page:setFocus: Invalid element passed') + if not child.focus then return end @@ -1443,16 +1460,12 @@ function UI.Grid:init(args) h.heading = '' end end - self:update() -end - -function UI.Grid:enable() - UI.Window.enable(self) end function UI.Grid:setParent() UI.Window.setParent(self) - self:adjustWidth() + self:update() + if not self.pageSize then if self.disableHeader then self.pageSize = self.height @@ -1529,7 +1542,7 @@ end function UI.Grid:getSelected() if self.sorted then - return self.values[self.sorted[self.index]] + return self.values[self.sorted[self.index]], self.sorted[self.index] end end @@ -1581,6 +1594,8 @@ function UI.Grid:update() return order(self.values[a], self.values[b]) end) end + + self:adjustWidth() end function UI.Grid:drawHeadings() @@ -2376,7 +2391,6 @@ UI.Notification.defaults = { function UI.Notification:init(args) local defaults = UI:getDefaults(UI.Notification, args) UI.Window.init(self, defaults) - Util.print(self) end function UI.Notification:draw() @@ -2401,6 +2415,15 @@ function UI.Notification:success(value, timeout) self:display(value, timeout) end +function UI.Notification:cancel() + if self.canvas then + Event.cancelNamedTimer('notificationTimer') + self.enabled = false + self.canvas:removeLayer() + self.canvas = nil + end +end + function UI.Notification:display(value, timeout) self.enabled = true local lines = Util.wordWrap(value, self.width - 2) @@ -2409,6 +2432,8 @@ function UI.Notification:display(value, timeout) if self.canvas then self.canvas:removeLayer() end + + -- need to get the current canvas - not ui.term.canvas self.canvas = UI.term.canvas:addLayer(self, self.backgroundColor, self.textColor or colors.white) self:addTransition(UI.TransitionExpandUp { x = self.x, @@ -2425,8 +2450,7 @@ function UI.Notification:display(value, timeout) end Event.addNamedTimer('notificationTimer', timeout or 3, false, function() - self.enabled = false - self.canvas:removeLayer() + self:cancel() self:sync() end) end @@ -3012,134 +3036,146 @@ UI.Form = class(UI.Window) UI.Form.defaults = { UIElement = 'Form', values = { }, - fields = { }, - labelWidth = 20, - accept = function() end, - cancel = function() end, + margin = 2, + event = 'form_complete', } - -UI.Form.D = { -- display - static = UI.Text, - entry = UI.TextEntry, - chooser = UI.Chooser, - button = UI.Button -} - -UI.Form.V = { -- validation - number = function(value) - return type(value) == 'number' - end -} - -UI.Form.T = { -- data types - number = function(value) - return tonumber(value) - end -} - function UI.Form:init(args) local defaults = UI:getDefaults(UI.Form, args) UI.Window.init(self, defaults) + self:createForm() +end - self:createFields() - self:initChildren() +function UI.Form:reset() + for _,child in pairs(self.children) do + if child.reset then + child:reset() + end + end end function UI.Form:setValues(values) + self:reset() self.values = values for k,child in pairs(self.children) do - if child.key then - child.value = self.values[child.key] - if not child.value then - child.value = '' + if child.formKey then + child.value = self.values[child.formKey] or '' + end + end +end + +function UI.Form:createForm() + self.children = self.children or { } + + if not self.labelWidth then + self.labelWidth = 1 + for _, child in pairs(self) do + if type(child) == 'table' and child.UIElement then + if child.formLabel then + self.labelWidth = math.max(self.labelWidth, #child.formLabel + 2) + end end end end + + local y = self.margin + for _, child in pairs(self) do + if type(child) == 'table' and child.UIElement then + if child.formKey then + child.x = self.labelWidth + self.margin - 1 + child.y = y + if not child.width and not child.rex then + child.rex = -self.margin + end + child.value = self.values[child.formKey] or '' + end + if child.formLabel then + table.insert(self.children, UI.Text { + x = self.margin, + y = y, + textColor = colors.black, + width = #child.formLabel, + value = child.formLabel, + }) + end + if child.formKey or child.formLabel then + y = y + 1 + end + end + end + + table.insert(self.children, UI.Button { + ry = -self.margin + 1, rx = -12 - self.margin + 1, + text = 'Ok', + event = 'form_ok', + }) + table.insert(self.children, UI.Button { + ry = -self.margin + 1, rx = -7 - self.margin + 1, + text = 'Cancel', + event = 'form_cancel', + }) end -function UI.Form:createFields() - - if not self.children then - self.children = { } - end - for k,field in pairs(self.fields) do - if field.label then - table.insert(self.children, UI.Text({ - x = 1, - y = k, - width = #field.label, - value = field.label, - })) - end - local value - if field.key then - value = self.values[field.key] - end - if not value then - value = '' - end - value = tostring(value) - local width = #value - if field.limit then - width = field.limit + 2 - end - if width == 0 then - width = nil - end - local fieldProperties = { - x = self.labelWidth + 2, - y = k, - width = width, - value = value, - } - UI.setProperties(fieldProperties, field) - table.insert(self.children, field.display(fieldProperties)) +function UI.Form:validateField(field) + if field.required then + if not field.value or #field.value == 0 then + return false, 'Field is required' + end end + return true end function UI.Form:eventHandler(event) - if event.type == 'accept' then + if event.type == 'form_ok' then for _,child in pairs(self.children) do - if child.key then - self.values[child.key] = child.value + if child.formKey then + local s, m = self:validateField(child) + if not s then + self:setFocus(child) + self:emit({ type = 'form_invalid', message = m, field = child }) + return false + end end end + for _,child in pairs(self.children) do + if child.formKey then + self.values[child.formKey] = child.value + end + end + self:emit({ type = self.event, UIElement = self }) + else + return UI.Window.eventHandler(self, event) end + return true end --[[-- Dialog --]]-- UI.Dialog = class(UI.Page) UI.Dialog.defaults = { + UIElement = 'Dialog', x = 7, y = 4, + z = 2, height = 7, - backgroundColor = colors.lightBlue, + backgroundColor = colors.white, } function UI.Dialog:init(args) - local defaults = UI:getDefaults(UI.Dialog) + local defaults = UI:getDefaults(UI.Dialog, args) UI.setProperties(defaults, { width = UI.term.width-11, - titleBar = UI.TitleBar({ previousPage = true }), - acceptButton = UI.Button({ - text = 'Accept', - event = 'accept', - x = 5, - y = 5 - }), - cancelButton = UI.Button({ - text = 'Cancel', - event = 'cancel', - x = 17, - y = 5 - }), - statusBar = UI.StatusBar(), + titleBar = UI.TitleBar({ previousPage = true, title = defaults.title }), }) UI.setProperties(defaults, args) UI.Page.init(self, defaults) end +function UI.Dialog:setParent() + UI.Window.setParent(self) + self.x = math.floor((self.parent.width - self.width) / 2) + 1 + self.y = math.floor((self.parent.height - self.height) / 2) + 1 +end + function UI.Dialog:eventHandler(event) if event.type == 'cancel' then UI:setPreviousPage() diff --git a/sys/apis/util.lua b/sys/apis/util.lua index 450df87..1a6f781 100644 --- a/sys/apis/util.lua +++ b/sys/apis/util.lua @@ -365,6 +365,10 @@ function Util.toBytes(n) return tostring(n) end +function Util.insertString(os, is, pos) + return os:sub(1, pos - 1) .. is .. os:sub(pos) +end + function Util.split(str, pattern) pattern = pattern or "(.-)\n" local t = {} diff --git a/sys/boot/multishell.boot b/sys/boot/multishell.boot index 9c8f565..d218ab2 100644 --- a/sys/boot/multishell.boot +++ b/sys/boot/multishell.boot @@ -4,8 +4,8 @@ LUA_PATH = '/sys/apis' math.randomseed(os.clock()) -_G.debug = function() end _G.Util = dofile('/sys/apis/util.lua') +_G.debug = function(...) Util.print(...) end _G.requireInjector = dofile('/sys/apis/injector.lua') os.run(Util.shallowCopy(getfenv(1)), '/sys/extensions/device.lua') @@ -21,6 +21,7 @@ local mounts = Util.readFile('config/fstab') if mounts then for _,l in ipairs(Util.split(mounts)) do if l:sub(1, 1) ~= '#' then + print('mounting ' .. l) fs.mount(unpack(Util.matches(l))) end end From c2500f0167f01fbd9043531b7dc9beb8872679c4 Mon Sep 17 00:00:00 2001 From: "kepler155c@gmail.com" Date: Tue, 27 Dec 2016 16:01:06 -0500 Subject: [PATCH 9/9] threads --- apps/Lua.lua | 4 ++-- apps/Network.lua | 32 ++++++++++++-------------------- apps/Tabs.lua | 5 ----- sys/apis/event.lua | 15 ++++++++------- sys/apis/process.lua | 11 +++++++---- 5 files changed, 29 insertions(+), 38 deletions(-) diff --git a/apps/Lua.lua b/apps/Lua.lua index 112a9ea..fa5ca6d 100644 --- a/apps/Lua.lua +++ b/apps/Lua.lua @@ -34,7 +34,7 @@ local page = UI.Page({ up = 'history_back', down = 'history_forward', mouse_rightclick = 'clear_prompt', - [ 'control-space' ] = 'autocomplete', +-- [ 'control-space' ] = 'autocomplete', }, }), grid = UI.ScrollingGrid({ @@ -73,7 +73,7 @@ function page:enable() end end -function autocomplete(env, oLine, x) +local function autocomplete(env, oLine, x) local sLine = oLine:sub(1, x) local nStartPos = sLine:find("[a-zA-Z0-9_%.]+$") diff --git a/apps/Network.lua b/apps/Network.lua index ab337e9..7a77fa7 100644 --- a/apps/Network.lua +++ b/apps/Network.lua @@ -7,37 +7,37 @@ multishell.setTitle(multishell.getCurrent(), 'Network') UI:configure('Network', ...) local gridColumns = { - { heading = 'Label', key = 'label' }, - { heading = 'Dist', key = 'distance' }, - { heading = 'Status', key = 'status' }, + { heading = 'Label', key = 'label' }, + { heading = 'Dist', key = 'distance' }, + { heading = 'Status', key = 'status' }, } if UI.term.width >= 30 then - table.insert(gridColumns, { heading = 'Fuel', key = 'fuel' }) + table.insert(gridColumns, { heading = 'Fuel', key = 'fuel' }) table.insert(gridColumns, { heading = 'Uptime', key = 'uptime' }) end -local page = UI.Page({ - menuBar = UI.MenuBar({ +local page = UI.Page { + menuBar = UI.MenuBar { buttons = { { text = 'Telnet', event = 'telnet' }, { text = 'VNC', event = 'vnc' }, { text = 'Reboot', event = 'reboot' }, }, - }), - grid = UI.ScrollingGrid({ + }, + grid = UI.ScrollingGrid { y = 2, values = network, columns = gridColumns, sortColumn = 'label', autospace = true, - }), - notification = UI.Notification(), + }, + notification = UI.Notification { }, accelerators = { q = 'quit', c = 'clear', }, -}) +} function sendCommand(host, command) @@ -60,7 +60,7 @@ function sendCommand(host, command) end function page:eventHandler(event) - local t = self.grid.selected + local t = self.grid:getSelected() if t then if event.type == 'telnet' or event.type == 'grid_select' then multishell.openTab({ @@ -113,14 +113,6 @@ function page.grid:getDisplayValues(row) return row end -function page.grid:draw() - self:adjustWidth() - UI.Grid.draw(self) - if page.notification.enabled then - page.notification:draw() - end -end - Event.addThread(function() while true do page.grid:update() diff --git a/apps/Tabs.lua b/apps/Tabs.lua index 070ae34..7b31d9e 100644 --- a/apps/Tabs.lua +++ b/apps/Tabs.lua @@ -62,11 +62,6 @@ function page.grid:getDisplayValues(row) return row end -function page.grid:draw() - self:adjustWidth() - UI.Grid.draw(self) -end - Event.addTimer(1, true, function() page.grid:update() page.grid:draw() diff --git a/sys/apis/event.lua b/sys/apis/event.lua index e110286..1d10479 100644 --- a/sys/apis/event.lua +++ b/sys/apis/event.lua @@ -121,14 +121,9 @@ end local exitPullEvents = false local function _pullEvents() - - --exitPullEvents = false while true do - local e = { Process:pullEvent() } + local e = { os.pullEvent() } Event.processEvent(e) - if exitPullEvents or e[1] == 'terminate' then - break - end end end @@ -144,13 +139,19 @@ function Event.addThread(fn) end function Event.pullEvents(...) + Process:addThread(_pullEvents) local routines = { ... } if #routines > 0 then for _, routine in ipairs(routines) do Process:addThread(routine) end end - _pullEvents() + while true do + local e = Process:pullEvent() + if exitPullEvents or e == 'terminate' then + break + end + end end function Event.exitPullEvents() diff --git a/sys/apis/process.lua b/sys/apis/process.lua index 2e69869..b851585 100644 --- a/sys/apis/process.lua +++ b/sys/apis/process.lua @@ -87,8 +87,11 @@ function Process:resume(event, ...) return true, self.filter end -function Process:pullEvent(filter) +-- confusing... +-- pull either one event if no filter or until event matches filter +-- or until terminated (regardless of filter) +function Process:pullEvent(filter) while true do local e = { os.pullEventRaw() } self:threadEvent(unpack(e)) @@ -99,12 +102,12 @@ function Process:pullEvent(filter) end end +-- pull events until either the filter is matched or terminated function Process:pullEvents(filter) - while true do - local e = { os.pullEventRaw(filter) } + local e = { os.pullEventRaw() } self:threadEvent(unpack(e)) - if e[1] == 'terminate' then + if (filter and e[1] == filter) or e[1] == 'terminate' then return unpack(e) end end