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