Overlapping windows

This commit is contained in:
kepler155c@gmail.com 2016-12-22 23:22:04 -05:00
parent 977998ebdb
commit d61260ec9b
13 changed files with 492 additions and 254 deletions

View File

@ -35,26 +35,32 @@ local Browser = UI.Page {
}, },
fileMenu = UI.DropMenu { fileMenu = UI.DropMenu {
buttons = { buttons = {
{ text = 'Run', event = 'run' }, { text = 'Run', event = 'run' },
{ text = 'Edit e', event = 'edit' }, { text = 'Edit e', event = 'edit' },
{ text = 'Shell s', event = 'shell' }, { text = 'Shell s', event = 'shell' },
{ text = 'Quit q', event = 'quit' }, UI.Text { value = ' ------------ ' },
{ text = 'Quit q', event = 'quit' },
UI.Text { },
} }
}, },
editMenu = UI.DropMenu { editMenu = UI.DropMenu {
buttons = { buttons = {
{ text = 'Mark m', event = 'mark' }, { text = 'Cut ^x', event = 'cut' },
{ text = 'Cut ^x', event = 'cut' }, { text = 'Copy ^c', event = 'copy' },
{ text = 'Copy ^c', event = 'copy' }, { text = 'Paste ^v', event = 'paste' },
{ text = 'Paste ^v', event = 'paste' }, UI.Text { value = ' --------------- ' },
{ text = 'Delete del', event = 'delete' }, { text = 'Mark m', event = 'mark' },
{ text = 'Unmark all u', event = 'unmark' }, { text = 'Unmark all u', event = 'unmark' },
UI.Text { value = ' --------------- ' },
{ text = 'Delete del', event = 'delete' },
UI.Text { },
} }
}, },
viewMenu = UI.DropMenu { viewMenu = UI.DropMenu {
buttons = { buttons = {
{ text = 'Refresh r', event = 'refresh' }, { text = 'Refresh r', event = 'refresh' },
{ text = 'Hidden ^h', event = 'toggle_hidden' }, { text = 'Hidden ^h', event = 'toggle_hidden' },
UI.Text { },
} }
}, },
grid = UI.ScrollingGrid { grid = UI.ScrollingGrid {
@ -69,8 +75,8 @@ local Browser = UI.Page {
}, },
statusBar = UI.StatusBar { statusBar = UI.StatusBar {
columns = { columns = {
{ '', 'status', UI.term.width - 19 }, { '', 'status', UI.term.width - 8 },
{ '', 'info', 10 }, --{ '', 'info', 10 },
{ 'Size: ', 'totalSize', 8 }, { 'Size: ', 'totalSize', 8 },
}, },
}, },

View File

@ -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 Util = require('util')
local UI = require('ui') local UI = require('ui')
local Event = require('event') local Event = require('event')
@ -6,7 +7,7 @@ local History = require('history')
local sandboxEnv = Util.shallowCopy(getfenv(1)) local sandboxEnv = Util.shallowCopy(getfenv(1))
sandboxEnv.exit = function() Event.exitPullEvents() end sandboxEnv.exit = function() Event.exitPullEvents() end
sandboxEnv.require = requireInjector(sandboxEnv) sandboxEnv.require = injector(sandboxEnv)
setmetatable(sandboxEnv, { __index = _G }) setmetatable(sandboxEnv, { __index = _G })
multishell.setTitle(multishell.getCurrent(), 'Lua') multishell.setTitle(multishell.getCurrent(), 'Lua')
@ -20,7 +21,7 @@ local page = UI.Page({
buttons = { buttons = {
{ text = 'Local', event = 'local' }, { text = 'Local', event = 'local' },
{ text = 'Global', event = 'global' }, { text = 'Global', event = 'global' },
{ text = 'Device', event = 'device' }, { text = 'Device', event = 'device', name = 'Device' },
}, },
}), }),
prompt = UI.TextEntry({ prompt = UI.TextEntry({
@ -66,6 +67,9 @@ end
function page:enable() function page:enable()
self:setFocus(self.prompt) self:setFocus(self.prompt)
UI.Page.enable(self) UI.Page.enable(self)
if not device then
self.menuBar.Device:disable()
end
end end
function page:eventHandler(event) function page:eventHandler(event)
@ -233,6 +237,8 @@ function page:executeStatement(statement)
if s and m then if s and m then
self:setResult(m) self:setResult(m)
elseif s and type(m) == 'boolean' then
self:setResult(m)
else else
self.grid:setValues({ }) self.grid:setValues({ })
self.grid:draw() self.grid:draw()
@ -242,10 +248,11 @@ function page:executeStatement(statement)
end end
end end
sandboxEnv.args = { ... } local args = { ... }
if sandboxEnv.args[1] then if args[1] then
command = 'args[1]' command = 'args[1]'
page:setResult(sandboxEnv.args[1]) sandboxEnv.args = args
page:setResult(args[1])
end end
UI:setPage(page) UI:setPage(page)

View File

@ -331,64 +331,68 @@ function page:eventHandler(event)
end end
local formWidth = math.max(UI.term.width - 14, 26) local formWidth = math.max(UI.term.width - 14, 26)
local gutter = math.floor((UI.term.width - formWidth) / 2) + 1
local editor = UI.Page({ local editor = UI.Page {
backgroundColor = colors.blue, backgroundColor = colors.white,
form = UI.Form({ x = math.ceil((UI.term.width - formWidth) / 2) + 1,
fields = { y = math.ceil((UI.term.height - 11) / 2) + 1,
{ label = 'Title', key = 'title', width = 15, limit = 11, display = UI.Form.D.entry, z = 2,
help = 'Application title' }, height = 11,
{ label = 'Run', key = 'run', width = formWidth - 11, limit = 100, display = UI.Form.D.entry, width = formWidth,
help = 'Full path to application' }, titleBar = UI.TitleBar {
{ label = 'Category', key = 'category', width = 15, limit = 11, display = UI.Form.D.entry, title = 'Edit application',
help = 'Category of application' }, },
{ text = 'Accept', event = 'accept', display = UI.Form.D.button, inset = UI.Window {
x = 1, y = 9, width = 10 }, x = 2,
{ text = 'Cancel', event = 'cancel', display = UI.Form.D.button, y = 3,
x = formWidth - 11, y = 9, width = 10 }, 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(), statusBar = UI.StatusBar(),
notification = UI.Notification(), notification = UI.Notification(),
iconFile = '', iconFile = '',
}) }
function editor:enable(app) function editor:enable(app)
if app then if app then
self.original = app self.original = app
self.form:setValues(Util.shallowCopy(app)) self.inset.form:setValues(Util.shallowCopy(app))
local icon local icon
if app.icon then if app.icon then
icon = parseIcon(app.icon) icon = parseIcon(app.icon)
end end
self.form.image:setImage(icon) self.inset.form.image:setImage(icon)
self:setFocus(self.form.children[1])
end end
UI.Page.enable(self) UI.Page.enable(self)
self:focusFirst()
end end
function editor.form.image:draw() function editor.inset.form.image:draw()
self:clear() self:clear()
UI.NftImage.draw(self) UI.NftImage.draw(self)
end end
@ -414,7 +418,13 @@ function editor:eventHandler(event)
self.statusBar:draw() self.statusBar:draw()
elseif event.type == 'loadIcon' then 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) --fileui:setTransition(UI.effect.explode)
UI:setPage(fileui, fs.getDir(self.iconFile), function(fileName) UI:setPage(fileui, fs.getDir(self.iconFile), function(fileName)
if fileName then if fileName then
@ -428,18 +438,19 @@ function editor:eventHandler(event)
if not icon then if not icon then
error(m) error(m)
end end
self.form.values.icon = iconLines self.inset.form.values.icon = iconLines
self.form.image:setImage(icon) self.inset.form.image:setImage(icon)
self.form.image:draw() self.inset.form.image:draw()
end) end)
if not s and m then if not s and m then
self.notification:error(m:gsub('.*: (.*)', '%1')) local msg = m:gsub('.*: (.*)', '%1')
self.notification:error(msg)
end end
end end
end) end)
elseif event.type == 'accept' then 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 if #values.run > 0 and #values.title > 0 and #values.category > 0 then
UI:setPreviousPage() UI:setPreviousPage()
self:updateApplications(values, self.original) self:updateApplications(values, self.original)

View File

@ -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 Util = require('util')
local Event = require('event') local Event = require('event')
local UI = require('ui') local UI = require('ui')

View File

@ -66,7 +66,7 @@ local keyMapping = {
pageUp = 'pageUp', pageUp = 'pageUp',
[ 'control-b' ] = 'pageUp', [ 'control-b' ] = 'pageUp',
pageDown = 'pageDown', pageDown = 'pageDown',
[ 'control-f' ] = 'pageDown', -- [ 'control-f' ] = 'pageDown',
home = 'home', home = 'home',
[ 'end' ] = 'toend', [ 'end' ] = 'toend',
[ 'control-home' ] = 'top', [ 'control-home' ] = 'top',
@ -101,6 +101,7 @@ local keyMapping = {
paste = 'paste', paste = 'paste',
tab = 'tab', tab = 'tab',
[ 'control-z' ] = 'undo', [ 'control-z' ] = 'undo',
[ 'control-space' ] = 'autocomplete',
-- copy/paste -- copy/paste
[ 'control-x' ] = 'cut', [ 'control-x' ] = 'cut',
@ -114,6 +115,7 @@ local keyMapping = {
[ 'control-enter' ] = 'run', [ 'control-enter' ] = 'run',
-- search -- search
[ 'control-f' ] = 'find_prompt',
[ 'control-slash' ] = 'find_prompt', [ 'control-slash' ] = 'find_prompt',
[ 'control-n' ] = 'find_next', [ 'control-n' ] = 'find_next',
@ -476,6 +478,41 @@ local __actions = {
end end
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() refresh = function()
actions.dirty_all() actions.dirty_all()
mark.continue = mark.active mark.continue = mark.active
@ -528,7 +565,7 @@ local __actions = {
find_prompt = function() find_prompt = function()
local text = actions.input('/') local text = actions.input('/')
if #text > 0 then if #text > 0 then
searchPattern = text searchPattern = text:lower()
if searchPattern then if searchPattern then
actions.unmark() actions.unmark()
actions.find(searchPattern, x) actions.find(searchPattern, x)

View File

@ -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) Util.print('Setting turtle point to %d %d %d', pt.x, pt.y, pt.z)
turtle.setPoint(pt) turtle.setPoint(pt)
turtle.getState().coordSystem = 'GPS'
if not turtle.pathfind(homePt) then if not turtle.pathfind(homePt) then
error('Failed to return home') error('Failed to return home')

View File

@ -1,6 +1,6 @@
local UI = require('ui') local UI = require('ui')
return function() return function(args)
local columns = { local columns = {
{ heading = 'Name', key = 'name', width = UI.term.width - 9 }, { heading = 'Name', key = 'name', width = UI.term.width - 9 },
@ -13,18 +13,23 @@ return function()
) )
end end
local selectFile = UI.Page({ args = args or { }
x = 3,
y = 2, local selectFile = UI.Page {
rex = -3, x = args.x or 3,
rey = -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, backgroundColor = colors.brown,
titleBar = UI.TitleBar({ titleBar = UI.TitleBar {
title = 'Select file', title = 'Select file',
previousPage = true, previousPage = true,
event = 'cancel', event = 'cancel',
}), },
grid = UI.ScrollingGrid({ grid = UI.ScrollingGrid {
x = 2, x = 2,
y = 2, y = 2,
rex = -2, rex = -2,
@ -32,8 +37,8 @@ return function()
path = '', path = '',
sortColumn = 'name', sortColumn = 'name',
columns = columns, columns = columns,
}), },
path = UI.TextEntry({ path = UI.TextEntry {
x = 2, x = 2,
ry = -1, ry = -1,
rex = -11, rex = -11,
@ -41,14 +46,14 @@ return function()
accelerators = { accelerators = {
enter = 'path_enter', enter = 'path_enter',
} }
}), },
cancel = UI.Button({ cancel = UI.Button {
text = 'Cancel', text = 'Cancel',
rx = -8, rx = -8,
ry = -1, ry = -1,
event = 'cancel', event = 'cancel',
}), },
}) }
function selectFile:enable(path, fn) function selectFile:enable(path, fn)
self:setPath(path) self:setPath(path)

View File

@ -1,4 +1,4 @@
local json = require('json') local json = require('craigmj.json4lua.master.json.json')
local Util = require('util') local Util = require('util')
local TREE_URL = 'https://api.github.com/repos/%s/%s/git/trees/%s?recursive=1' local TREE_URL = 'https://api.github.com/repos/%s/%s/git/trees/%s?recursive=1'

View File

@ -1,6 +1,14 @@
local DEFAULT_UPATH = 'https://raw.githubusercontent.com/kepler155c/opus/master/sys/apis' local DEFAULT_UPATH = 'https://raw.githubusercontent.com/kepler155c/opus/master/sys/apis'
local PASTEBIN_URL = 'http://pastebin.com/raw' 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 function shellSearcher(modname, env, shell)
local fname = modname:gsub('%.', '/') .. '.lua' local fname = modname:gsub('%.', '/') .. '.lua'
@ -80,7 +88,8 @@ local function gitSearcher(modname, env, shell)
local fname = modname:gsub('%.', '/') .. '.lua' local fname = modname:gsub('%.', '/') .. '.lua'
local _, count = fname:gsub("/", "") local _, count = fname:gsub("/", "")
if count >= 3 then if count >= 3 then
local url = GIT_URL .. '/' .. modname local url = GIT_URL .. '/' .. fname
debug(url)
local c = loadUrl(url) local c = loadUrl(url)
if c then if c then
return load(c, modname, nil, env) return load(c, modname, nil, env)
@ -105,7 +114,16 @@ end
_G.package = { _G.package = {
path = LUA_PATH or 'sys/apis', path = LUA_PATH or 'sys/apis',
upath = LUA_UPATH or DEFAULT_UPATH, upath = LUA_UPATH or DEFAULT_UPATH,
config = '/\n:\n?\n!\n-',
loaded = {
math = math,
string = string,
table = table,
io = io,
os = os,
},
loaders = { loaders = {
standardSearcher,
shellSearcher, shellSearcher,
pathSearcher, pathSearcher,
pastebinSearcher, pastebinSearcher,
@ -125,7 +143,7 @@ local function requireWrapper(env)
end end
for _,searcher in ipairs(package.loaders) do for _,searcher in ipairs(package.loaders) do
local fn = searcher(modname, env, shell) local fn, msg = searcher(modname, env, shell)
if fn then if fn then
local module, msg = fn(modname, env) local module, msg = fn(modname, env)
if not module then if not module then
@ -134,6 +152,9 @@ local function requireWrapper(env)
loaded[modname] = module loaded[modname] = module
return module return module
end end
if msg then
error(msg)
end
end end
error('Unable to find module ' .. modname) error('Unable to find module ' .. modname)
end end

View File

@ -112,7 +112,6 @@ function Manager:init(args)
Event.addHandler('mouse_click', function(h, button, x, y) Event.addHandler('mouse_click', function(h, button, x, y)
if button == 1 and shift and control then -- hack if button == 1 and shift and control then -- hack
local event = self:pointToChild(self.target, x, y) local event = self:pointToChild(self.target, x, y)
multishell.openTab({ path = 'apps/Lua.lua', args = { event.element }, focused = true }) multishell.openTab({ path = 'apps/Lua.lua', args = { event.element }, focused = true })
@ -226,13 +225,15 @@ function Manager:disableEffects()
end end
function Manager:loadTheme(filename) function Manager:loadTheme(filename)
local theme, err = Util.loadTable(filename) if fs.exists(filename) then
if not theme then local theme, err = Util.loadTable(filename)
error(err) if not theme then
end error(err)
for k,v in pairs(theme) do end
if self[k] and self[k].defaults then for k,v in pairs(theme) do
Util.merge(self[k].defaults, v) if self[k] and self[k].defaults then
Util.merge(self[k].defaults, v)
end
end end
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 if x < self.target.x or y < self.target.y or
x > self.target.x + self.target.width - 1 or x > self.target.x + self.target.width - 1 or
y > self.target.y + self.target.height - 1 then y > self.target.y + self.target.height - 1 then
target:emit({ type = 'mouse_out' }) target:emit({ type = 'mouse_out' })
target = self.currentPage target = self.currentPage
@ -658,8 +658,12 @@ function UI.Window:write(x, y, text, bg, tc)
x = x - self.offx x = x - self.offx
y = y - self.offy y = y - self.offy
if y <= self.height and y > 0 then if y <= self.height and y > 0 then
self.parent:write( if self.canvas then
self.x + x - 1, self.y + y - 1, tostring(text), bg, tc) 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
end end
@ -830,20 +834,24 @@ function UI.Window:eventHandler(event)
end end
--[[-- Blit data manipulation --]]-- --[[-- Blit data manipulation --]]--
local Blob = class() local Canvas = class()
function Blob:init(args) function Canvas:init(args)
self.x = 1 self.x = 1
self.y = 1 self.y = 1
self.lines = { }
Util.merge(self, args) 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] = { } self.lines[i] = { }
end end
end end
function Blob:copy() function Canvas:copy()
local b = Blob({ x = self.x, y = self.y, ex = self.ex, ey = self.ey }) local b = Canvas({ x = self.x, y = self.y, ex = self.ex, ey = self.ey })
for i = 1, self.ey - self.y + 1 do for i = 1, self.ey - self.y + 1 do
b.lines[i].text = self.lines[i].text b.lines[i].text = self.lines[i].text
b.lines[i].fg = self.lines[i].fg b.lines[i].fg = self.lines[i].fg
@ -852,43 +860,170 @@ function Blob:copy()
return b return b
end 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].dirty = true
self.lines[y].text = text self.lines[y].text = text
self.lines[y].fg = fg self.lines[y].fg = fg
self.lines[y].bg = bg self.lines[y].bg = bg
end end
function Blob:reset() function Canvas:reset()
self.region = nil self.region = nil
end 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 if not self.regions then
self.regions = Region.new(self.x, self.y, self.ex, self.ey) self.regions = Region.new(self.x, self.y, self.ex, self.ey)
end end
self.regions:subRect(rect.x, rect.y, rect.ex, rect.ey) self.regions:subRect(rect.x, rect.y, rect.ex, rect.ey)
end end
function Blob:blitClipped(device) function Canvas:blitClipped(device)
for _,region in ipairs(self.regions.region) do for _,region in ipairs(self.regions.region) do
self:blit(device, 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] }) { x = region[1], y = region[2] })
end end
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 for i = 0, src.ey - src.y do
local line = self.lines[src.y + i] local line = self.lines[src.y + i]
local t, fg, bg = line.text, line.fg, line.bg if line.dirty then
if src.x > 1 or src.ex < self.ex then local t, fg, bg = line.text, line.fg, line.bg
t = t:sub(src.x, src.ex) if src.x > 1 or src.ex < self.ex then
fg = fg:sub(src.x, src.ex) t = t:sub(src.x, src.ex)
bg = bg: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
device.setCursorPos(tgt.x, tgt.y + i)
device.blit(t, fg, bg)
end end
end end
@ -906,21 +1041,23 @@ function UI.TransitionSlideLeft:init(args)
self.pos = { x = self.ex } self.pos = { x = self.ex }
self.tween = Tween.new(self.ticks, self.pos, { x = self.x }, self.easing) self.tween = Tween.new(self.ticks, self.pos, { x = self.x }, self.easing)
self.lastx = 0 self.lastx = 0
self.lastScreen = self.canvas:copy()
end end
function UI.TransitionSlideLeft:update(device, screen, lastScreen) function UI.TransitionSlideLeft:update(device)
self.tween:update(1) self.tween:update(1)
local x = math.floor(self.pos.x) local x = math.floor(self.pos.x)
if x ~= self.lastx then if x ~= self.lastx then
self.lastx = x self.lastx = x
lastScreen:blit(device, { self.lastScreen:dirty()
self.lastScreen:blit(device, {
x = self.ex - x + self.x, x = self.ex - x + self.x,
y = self.y, y = self.y,
ex = self.ex, ex = self.ex,
ey = self.ey }, ey = self.ey },
{ x = self.x, y = self.y }) { x = self.x, y = self.y })
screen:blit(device, { self.canvas:blit(device, {
x = self.x, x = self.x,
y = self.y, y = self.y,
ex = self.ex - x + self.x + 1, ex = self.ex - x + self.x + 1,
@ -944,20 +1081,22 @@ function UI.TransitionSlideRight:init(args)
self.pos = { x = self.x } self.pos = { x = self.x }
self.tween = Tween.new(self.ticks, self.pos, { x = self.ex }, self.easing) self.tween = Tween.new(self.ticks, self.pos, { x = self.ex }, self.easing)
self.lastx = 0 self.lastx = 0
self.lastScreen = self.canvas:copy()
end end
function UI.TransitionSlideRight:update(device, screen, lastScreen) function UI.TransitionSlideRight:update(device)
self.tween:update(1) self.tween:update(1)
local x = math.floor(self.pos.x) local x = math.floor(self.pos.x)
if x ~= self.lastx then if x ~= self.lastx then
self.lastx = x self.lastx = x
lastScreen:blit(device, { self.lastScreen:dirty()
self.lastScreen:blit(device, {
x = self.x, x = self.x,
y = self.y, y = self.y,
ex = self.ex - x + self.x + 1, ex = self.ex - x + self.x + 1,
ey = self.ey }, ey = self.ey },
{ x = x, y = self.y }) { x = x, y = self.y })
screen:blit(device, { self.canvas:blit(device, {
x = self.ex - x + self.x, x = self.ex - x + self.x,
y = self.y, y = self.y,
ex = self.ex + 1, ex = self.ex + 1,
@ -967,6 +1106,26 @@ function UI.TransitionSlideRight:update(device, screen, lastScreen)
return self.pos.x ~= self.ex return self.pos.x ~= self.ex
end 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 --]]-- --[[-- Terminal for computer / advanced computer / monitor --]]--
UI.Device = class(UI.Window) UI.Device = class(UI.Window)
UI.Device.defaults = { UI.Device.defaults = {
@ -994,22 +1153,19 @@ function UI.Device:init(args)
UI.Window.init(self, defaults) 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.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 end
function UI.Device:resize() function UI.Device:resize()
self.width, self.height = self.device.getSize() self.width, self.height = self.device.getSize()
self.lines = { } self.lines = { } -- TODO -- resize canvas
UI.Window.resize(self) UI.Window.resize(self)
end end
@ -1056,30 +1212,31 @@ function UI.Device:addTransition(effect, x, y, width, height)
y = y, y = y,
ex = x + width - 1, ex = x + width - 1,
ey = y + height - 1, ey = y + height - 1,
canvas = self.canvas,
} }
end end
table.insert(self.transitions, effect) table.insert(self.transitions, effect)
end end
function UI.Device:runTransitions(transitions) function UI.Device:runTransitions(transitions, canvas)
for _,t in ipairs(transitions) do for _,t in ipairs(transitions) do
self.blob:punch(t) -- punch out the effect areas canvas:punch(t) -- punch out the effect areas
end end
self.blob:blitClipped(self.device) -- and blit the remainder canvas:blitClipped(self.device) -- and blit the remainder
self.blob:reset() canvas:reset()
while true do while true do
for _,k in ipairs(Util.keys(transitions)) do for _,k in ipairs(Util.keys(transitions)) do
local transition = transitions[k] 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 transitions[k] = nil
end end
end end
if Util.empty(transitions) then if Util.empty(transitions) then
break break
end end
Event.sleep() os.sleep() -- ?
end end
end end
@ -1091,68 +1248,22 @@ function UI.Device:sync()
self.transitions = nil self.transitions = nil
end end
if transitions then if self:getCursorBlink() then
self:runTransitions(transitions) self.device.setCursorBlink(false)
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
end end
self.lastScreen = self.blob:copy() if transitions then
self:runTransitions(transitions, self.canvas)
for y, line in ipairs(self.blob.lines) do else
line.dirty = false self.canvas:render(self.device)
end end
if self:getCursorBlink() then if self:getCursorBlink() then
self.device.setCursorBlink(true)
self.device.setCursorPos(self.cursorX, self.cursorY) self.device.setCursorPos(self.cursorX, self.cursorY)
end end
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 --]]-- --[[-- StringBuffer --]]--
UI.StringBuffer = class() UI.StringBuffer = class()
function UI.StringBuffer:init(bufSize) function UI.StringBuffer:init(bufSize)
@ -1198,9 +1309,16 @@ function UI.Page:init(args)
defaults.parent = UI.defaultDevice defaults.parent = UI.defaultDevice
UI.setProperties(defaults, args) UI.setProperties(defaults, args)
UI.Window.init(self, defaults) 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 end
function UI.Page:enable() function UI.Page:enable()
self.canvas.visible = true
UI.Window.enable(self) UI.Window.enable(self)
if not self.focused or not self.focused.enabled then if not self.focused or not self.focused.enabled then
@ -1208,6 +1326,12 @@ function UI.Page:enable()
end end
end end
function UI.Page:disable()
if self.z then
self.canvas.visible = false
end
end
function UI.Page:getFocused() function UI.Page:getFocused()
return self.focused return self.focused
end end
@ -1305,6 +1429,8 @@ UI.Grid.defaults = {
unfocusedTextSelectedColor = colors.white, unfocusedTextSelectedColor = colors.white,
unfocusedBackgroundSelectedColor = colors.gray, unfocusedBackgroundSelectedColor = colors.gray,
focusIndicator = '>', focusIndicator = '>',
sortIndicator = ' ',
inverseSortIndicator = '^',
values = { }, values = { },
columns = { }, columns = { },
} }
@ -1461,8 +1587,12 @@ function UI.Grid:drawHeadings()
local sb = UI.StringBuffer(self.width) local sb = UI.StringBuffer(self.width)
for _,col in ipairs(self.columns) do for _,col in ipairs(self.columns) do
local ind = ' ' local ind = ' '
if self.inverseSort and col.key == self.sortColumn then if col.key == self.sortColumn then
ind = '^' if self.inverseSort then
ind = self.inverseSortIndicator
else
ind = self.sortIndicator
end
end end
sb:insert(ind .. col.heading, col.width + 1) sb:insert(ind .. col.heading, col.width + 1)
end end
@ -1652,7 +1782,11 @@ end
UI.ScrollingGrid = class(UI.Grid) UI.ScrollingGrid = class(UI.Grid)
UI.ScrollingGrid.defaults = { UI.ScrollingGrid.defaults = {
UIElement = 'ScrollingGrid', UIElement = 'ScrollingGrid',
scrollOffset = 1 scrollOffset = 1,
lineChar = '|',
sliderChar = '#',
upArrowChar = '^',
downArrowChar = 'v',
} }
function UI.ScrollingGrid:init(args) function UI.ScrollingGrid:init(args)
local defaults = UI:getDefaults(UI.ScrollingGrid, args) local defaults = UI:getDefaults(UI.ScrollingGrid, args)
@ -1683,25 +1817,25 @@ function UI.ScrollingGrid:drawScrollbar()
local x = self.width local x = self.width
if self.scrollOffset > 1 then if self.scrollOffset > 1 then
self:write(x, 2, '^') self:write(x, 2, self.upArrowChar)
else else
self:write(x, 2, ' ') self:write(x, 2, ' ')
end end
local row = 0 local row = 0
for i = 1, sp - 1 do for i = 1, sp - 1 do
self:write(x, row+3, '|') self:write(x, row+3, self.lineChar)
row = row + 1 row = row + 1
end end
for i = 1, sa do for i = 1, sa do
self:write(x, row+3, '#') self:write(x, row+3, self.sliderChar)
row = row + 1 row = row + 1
end end
for i = row, sbSize do for i = row, sbSize do
self:write(x, row+3, '|') self:write(x, row+3, self.lineChar)
row = row + 1 row = row + 1
end end
if self.scrollOffset + self.pageSize - 1 < Util.size(self.values) then 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 else
self:write(x, self.pageSize + 1, ' ') self:write(x, self.pageSize + 1, ' ')
end end
@ -1946,19 +2080,23 @@ function UI.MenuBar:init(args)
local x = 1 local x = 1
for k,button in pairs(self.buttons) do for k,button in pairs(self.buttons) do
local buttonProperties = { if button.UIElement then
x = x, table.insert(self.children, button)
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 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
end end
if self.showBackButton then if self.showBackButton then
@ -1997,6 +2135,7 @@ end
UI.DropMenu = class(UI.MenuBar) UI.DropMenu = class(UI.MenuBar)
UI.DropMenu.defaults = { UI.DropMenu.defaults = {
UIElement = 'DropMenu', UIElement = 'DropMenu',
backgroundColor = colors.white,
} }
function UI.DropMenu:init(args) function UI.DropMenu:init(args)
local defaults = UI:getDefaults(UI.DropMenu, args) local defaults = UI:getDefaults(UI.DropMenu, args)
@ -2011,7 +2150,7 @@ function UI.DropMenu:setParent()
for y,child in ipairs(self.children) do for y,child in ipairs(self.children) do
child.x = 1 child.x = 1
child.y = y child.y = y
if #child.text > maxWidth then if #(child.text or '') > maxWidth then
maxWidth = #child.text maxWidth = #child.text
end end
end end
@ -2155,9 +2294,9 @@ function UI.Tabs:activateTab(tab)
child:disable() child:disable()
end end
end end
self.tabBar:selectTab(tab.tabTitle)
tab:enable() tab:enable()
tab:draw() tab:draw()
self.tabBar:selectTab(tab.tabTitle)
self:emit({ type = 'tab_activate', activated = tab, element = self }) self:emit({ type = 'tab_activate', activated = tab, element = self })
end end
@ -2232,33 +2371,21 @@ UI.Notification = class(UI.Window)
UI.Notification.defaults = { UI.Notification.defaults = {
UIElement = 'Notification', UIElement = 'Notification',
backgroundColor = colors.gray, backgroundColor = colors.gray,
height = 1, height = 3,
} }
function UI.Notification:init(args) function UI.Notification:init(args)
local defaults = UI:getDefaults(UI.Notification, args) local defaults = UI:getDefaults(UI.Notification, args)
UI.Window.init(self, defaults) UI.Window.init(self, defaults)
Util.print(self)
end end
function UI.Notification:draw() 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 end
function UI.Notification:enable() function UI.Notification:enable()
self.enabled = false self.enabled = false
end end
function UI.Notification:resize()
self.y = UI.term.height + 1
end
function UI.Notification:error(value, timeout) function UI.Notification:error(value, timeout)
self.backgroundColor = colors.red self.backgroundColor = colors.red
self:display(value, timeout) self:display(value, timeout)
@ -2275,16 +2402,32 @@ function UI.Notification:success(value, timeout)
end end
function UI.Notification:display(value, timeout) function UI.Notification:display(value, timeout)
self.value = value
self.enabled = true 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() Event.addNamedTimer('notificationTimer', timeout or 3, false, function()
-- self.y = UI.term.height + 1
self.enabled = false self.enabled = false
if self.parent.enabled then self.canvas:removeLayer()
self.parent:draw() self:sync()
self:sync()
end
end) end)
end end
@ -2592,7 +2735,7 @@ UI.TextEntry.defaults = {
shadowText = '', shadowText = '',
focused = false, focused = false,
backgroundColor = colors.lightGray, backgroundColor = colors.lightGray,
backgroundFocusColor = colors.green, backgroundFocusColor = colors.lightGray,
height = 1, height = 1,
limit = 6, limit = 6,
pos = 0, pos = 0,
@ -2940,6 +3083,9 @@ function UI.Form:createFields()
if field.limit then if field.limit then
width = field.limit + 2 width = field.limit + 2
end end
if width == 0 then
width = nil
end
local fieldProperties = { local fieldProperties = {
x = self.labelWidth + 2, x = self.labelWidth + 2,
y = k, y = k,
@ -2952,17 +3098,13 @@ function UI.Form:createFields()
end end
function UI.Form:eventHandler(event) function UI.Form:eventHandler(event)
if event.type == 'accept' then if event.type == 'accept' then
for _,child in pairs(self.children) do for _,child in pairs(self.children) do
if child.key then if child.key then
self.values[child.key] = child.value self.values[child.key] = child.value
end end
end end
return false
end end
return false
end end
--[[-- Dialog --]]-- --[[-- Dialog --]]--
@ -3087,8 +3229,9 @@ function UI.NftImage:setImage(image)
self.image = image self.image = image
end 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 end
UI:setDefaultDevice(UI.Device({ device = term.current() })) UI:setDefaultDevice(UI.Device({ device = term.current() }))

View File

@ -57,10 +57,12 @@ function turtle.run(fn, ...)
local e, id, abort = os.pullEventRaw('turtle_ticket') local e, id, abort = os.pullEventRaw('turtle_ticket')
if e == 'terminate' then if e == 'terminate' then
releaseTicket(ticketId) releaseTicket(ticketId)
os.queueEvent('turtle_response')
error('Terminated') error('Terminated')
end end
if abort then if abort then
-- the function was queued, but the queue was cleared -- the function was queued, but the queue was cleared
os.queueEvent('turtle_response')
return false, 'aborted' return false, 'aborted'
end end
if id == ticketId then if id == ticketId then
@ -72,6 +74,7 @@ function turtle.run(fn, ...)
if not s and m then if not s and m then
printError(m) printError(m)
end end
os.queueEvent('turtle_response')
return s, m return s, m
end end
end end

View File

@ -17,6 +17,7 @@ local state = {
moveDig = noop, moveDig = noop,
moveCallback = noop, moveCallback = noop,
locations = {}, locations = {},
coordSystem = 'relative', -- type of coordinate system being used
} }
function turtle.getState() function turtle.getState()
@ -44,6 +45,7 @@ function turtle.reset()
state.moveDig = noop state.moveDig = noop
state.moveCallback = noop state.moveCallback = noop
state.locations = {} state.locations = {}
state.coordSystem = 'relative'
return true return true
end end
@ -231,7 +233,6 @@ turtle.digPolicies = {
if not turtle.isTurtleAtSide(action.side) then if not turtle.isTurtleAtSide(action.side) then
return action.dig() return action.dig()
end end
return Util.tryTimes(6, function() return Util.tryTimes(6, function()
-- if not turtle.isTurtleAtSide(action.side) then -- if not turtle.isTurtleAtSide(action.side) then
-- return true --action.dig() -- return true --action.dig()

View File

@ -117,15 +117,19 @@ process:newThread('discovery_server', function()
end end
end) end)
local info = {
id = os.getComputerID()
}
local function sendInfo() local function sendInfo()
local info = { info.label = os.getComputerLabel()
id = os.getComputerID(), info.uptime = math.floor(os.clock())
label = os.getComputerLabel(),
uptime = math.floor(os.clock()),
}
if turtle then if turtle then
info.fuel = turtle.getFuelLevel() info.fuel = turtle.getFuelLevel()
info.status = turtle.status info.status = turtle.status
info.point = turtle.point
info.inventory = turtle.getInventory()
info.coordSystem = turtle.getState().coordSystem
end end
device.wireless_modem.transmit(999, os.getComputerID(), info) device.wireless_modem.transmit(999, os.getComputerID(), info)
end end
@ -152,13 +156,11 @@ end)
if os.isTurtle() then if os.isTurtle() then
process:newThread('turtle_heartbeat', function() process:newThread('turtle_heartbeat', function()
local lastUpdate = os.clock()
os.sleep(1) os.sleep(1)
while true do while true do
os.pullEvent('turtle_response') os.pullEvent('turtle_response')
if os.clock() - lastUpdate >= 1 then if turtle.status ~= info.status or
lastUpdate = os.clock() turtle.fuel ~= info.fuel then
sendInfo() sendInfo()
end end
end end