Skip to content
Permalink
master
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time
local unpack = unpack or table.unpack
local standard = {
clean = {
ul = {"+", "0", "f"},
ur = {"+", "0", "f"},
ll = {"+", "0", "f"},
lr = {"+", "0", "f"},
top = {"-", "0", "f"},
bottom = {"-", "0", "f"},
left = {"|", "0", "f"},
right = {"|", "0", "f"},
min = {"-", "0", "f"},
max = {"-", "0", "f"},
restore = {"-", "0", "f"},
close = {"-", "e", "f"},
caption = {"", "0", "f"},
minPos = 0,
maxPos = 0,
closePos = 0,
captionSize = {3, -3},
},
window = {
ul = {"+", "0", "f"},
ur = {"+", "0", "f"},
ll = {"+", "0", "f"},
lr = {"*", "0", "f"},
top = {"-", "0", "f"},
bottom = {"-", "0", "f"},
left = {"|", "0", "f"},
right = {"|", "0", "f"},
min = {"_", "0", "f"},
max = {"M", "0", "f"},
restore = {"R", "0", "f"},
close = {"X", "e", "f"},
caption = {"", "0", "f"},
minPos = -5,
maxPos = -4,
closePos = -3,
captionSize = {3, -7},
},
menu = {
ul = {"/", "0", "f"},
ur = {"\\", "0", "f"},
ll = {"\\", "0", "f"},
lr = {"/", "0", "f"},
top = {"-", "0", "f"},
bottom = {"-", "0", "f"},
left = {"|", "0", "f"},
right = {"|", "0", "f"},
min = {"-", "0", "f"},
max = {"-", "0", "f"},
restore = {"-", "0", "f"},
close = {"-", "e", "f"},
caption = {"", "0", "f"},
minPos = 0,
maxPos = 0,
closePos = 0,
captionSize = {3, -3},
},
modal = {
ul = {"+", "0", "f"},
ur = {"+", "0", "f"},
ll = {"+", "0", "f"},
lr = {"+", "0", "f"},
top = {"-", "0", "f"},
bottom = {"-", "0", "f"},
left = {"|", "0", "f"},
right = {"|", "0", "f"},
min = {"-", "0", "f"},
max = {"-", "0", "f"},
restore = {"-", "0", "f"},
close = {"X", "e", "f"},
caption = {"", "0", "f"},
minPos = 0,
maxPos = 0,
closePos = -3,
captionSize = {3, -5},
},
}
local pretty = {
clean = {
ul = {" ", "0", "8"},
ur = {" ", "0", "8"},
ll = {" ", "0", "8"},
lr = {" ", "0", "8"},
top = {" ", "0", "8"},
bottom = {" ", "0", "8"},
left = {" ", "0", "8"},
right = {" ", "0", "8"},
min = {" ", "0", "8"},
max = {" ", "0", "8"},
restore = {" ", "0", "8"},
close = {" ", "0", "8"},
caption = {"", "f", "8"},
minPos = 0,
maxPos = 0,
closePos = 0,
captionSize = {3, -3},
},
window = {
ul = {" ", "0", "8"},
ur = {" ", "0", "8"},
ll = {" ", "0", "8"},
lr = {"/", "7", "8"},
top = {" ", "0", "8"},
bottom = {" ", "0", "8"},
left = {" ", "0", "8"},
right = {" ", "0", "8"},
min = {"-", "0", "9"},
max = {"M", "0", "9"},
restore = {"R", "0", "9"},
close = {"X", "0", "e"},
caption = {"", "f", "8"},
minPos = -5,
maxPos = -4,
closePos = -3,
captionSize = {3, -7},
},
menu = {
ul = {" ", "0", "8"},
ur = {" ", "0", "8"},
ll = {" ", "0", "8"},
lr = {" ", "0", "8"},
top = {" ", "0", "8"},
bottom = {" ", "0", "8"},
left = {" ", "0", "8"},
right = {" ", "0", "8"},
min = {" ", "0", "8"},
max = {" ", "0", "8"},
restore = {" ", "0", "8"},
close = {" ", "0", "8"},
caption = {"", "f", "8"},
minPos = 0,
maxPos = 0,
closePos = 0,
captionSize = {3, -3},
},
modal = {
ul = {" ", "0", "8"},
ur = {" ", "0", "8"},
ll = {" ", "0", "8"},
lr = {" ", "0", "8"},
top = {" ", "0", "8"},
bottom = {" ", "0", "8"},
left = {" ", "0", "8"},
right = {" ", "0", "8"},
min = {" ", "0", "8"},
max = {" ", "0", "8"},
restore = {" ", "0", "8"},
close = {"X", "0", "e"},
caption = {"", "f", "8"},
minPos = 0,
maxPos = 0,
closePos = -3,
captionSize = {3, -5},
},
}
local themes = theme.newSet("window", "standard", standard)
if themes then
themes:add("pretty", pretty)
else
themes = theme.getSet("window")
if not themes:get("standard") then themes:add("standard", standard) end
if not themes:get("pretty") then themes:add("pretty", pretty) end
end
local Window = {
draw = function(self, mode)
--render window to table
local maxX, maxY = self.target.getSize()
local redirect = framebuffer.new(maxX, maxY, self.target.isColor())
local outputBuffer = redirect.buffer
local function findBoundaries(position)
local first, second
if position < 0 then
first = maxX - (maxX - (self.x + self.w)) - (math.abs(position) - 1) - 2
second = maxX - (maxX - (self.x + self.w)) - (math.abs(position) - 1)
elseif position > 0 then
first = self.x + position - 2
second = self.x + position
end
return first, second
end
local function drawDecoration(decoration, position, line)
local len, start = findBoundaries(position)
if len and start then
outputBuffer.text[line] = string.sub(outputBuffer.text[line], 1, len)..decoration[1]..string.sub(outputBuffer.text[line], start)
outputBuffer.textColor[line] = string.sub(outputBuffer.textColor[line], 1, len)..decoration[2]..string.sub(outputBuffer.textColor[line], start)
outputBuffer.backColor[line] = string.sub(outputBuffer.backColor[line], 1, len)..decoration[3]..string.sub(outputBuffer.backColor[line], start)
return true
else
return false
end
end
if mode ~= "cursor" then
local prelineBuffer = self.x - 1
local postlineBuffer = maxX - (self.x + self.w - 1)
for i=1, maxY do
if self.borderless and i >= self.y and i <= self.y + self.h - 1 then
outputBuffer.text[i] = string.rep(" ", prelineBuffer)..string.sub(self.redirect.buffer.text[i - self.y + 1], 1, self.w)..string.rep(" ", postlineBuffer)
outputBuffer.textColor[i] = string.rep("0", prelineBuffer)..string.sub(self.redirect.buffer.textColor[i - self.y + 1], 1, self.w)..string.rep("0", postlineBuffer)
outputBuffer.backColor[i] = string.rep("f", prelineBuffer)..string.sub(self.redirect.buffer.backColor[i - self.y + 1], 1, self.w)..string.rep("f", postlineBuffer)
elseif i == self.y then
outputBuffer.text[i] = string.rep(" ", prelineBuffer)..self.decorations.ul[1]..string.rep(self.decorations.top[1], self.w - 2)..self.decorations.ur[1]..string.rep(" ", postlineBuffer)
outputBuffer.textColor[i] = string.rep("0", prelineBuffer)..self.decorations.ul[2]..string.rep(self.decorations.top[2], self.w - 2)..self.decorations.ur[2]..string.rep("0", postlineBuffer)
outputBuffer.backColor[i] = string.rep("f", prelineBuffer)..self.decorations.ul[3]..string.rep(self.decorations.top[3], self.w - 2)..self.decorations.ur[3]..string.rep("f", postlineBuffer)
if self.caption then
local captionLength = math.max(self.w - (self.decorations.captionSize[1] - 1) - (math.abs(self.decorations.captionSize[2]) - 1), 0)
local caption = string.sub(self.caption, 1, captionLength)
local preCapSize = self.x + self.decorations.captionSize[1] - 2
local postCapStart = maxX - (maxX - (self.x + self.w)) - (math.abs(self.decorations.captionSize[2]) - 1)
if captionLength > 0 and preCapSize + 1 < postCapStart then
outputBuffer.text[i] = string.sub(outputBuffer.text[i], 1, preCapSize)..caption..string.rep(self.decorations.top[1], captionLength - #caption)..string.sub(outputBuffer.text[i], postCapStart)
outputBuffer.textColor[i] = string.sub(outputBuffer.textColor[i], 1, preCapSize)..string.rep(self.decorations.caption[2], #caption)..string.rep(self.decorations.top[2], captionLength - #caption)..string.sub(outputBuffer.textColor[i], postCapStart)
outputBuffer.backColor[i] = string.sub(outputBuffer.backColor[i], 1, preCapSize)..string.rep(self.decorations.caption[3], #caption)..string.rep(self.decorations.top[3], captionLength - #caption)..string.sub(outputBuffer.backColor[i], postCapStart)
end
end
if self.windowType == "modal" or self.windowType == "window" then
if not drawDecoration(self.decorations.close, self.decorations.closePos, i) then
--invalid decoration, set theme and exit
self:setTheme("standard")
return self:draw()
end
if self.windowType == "window" then
if not drawDecoration(self.decorations.min, self.decorations.minPos, i) then
--invalid decoration, set theme and exit
self:setTheme("standard")
return self:draw()
end
if not drawDecoration(self.maximized and self.decorations.restore or self.decorations.max, self.decorations.maxPos, i) then
--invalid decoration, set theme and exit
self:setTheme("standard")
return self:draw()
end
end
end
elseif i == self.y + self.h - 1 then
outputBuffer.text[i] = string.rep(" ", prelineBuffer)..self.decorations.ll[1]..string.rep(self.decorations.bottom[1], self.w - 2)..self.decorations.lr[1]..string.rep(" ", postlineBuffer)
outputBuffer.textColor[i] = string.rep("0", prelineBuffer)..self.decorations.ll[2]..string.rep(self.decorations.bottom[2], self.w - 2)..self.decorations.lr[2]..string.rep("0", postlineBuffer)
outputBuffer.backColor[i] = string.rep("f", prelineBuffer)..self.decorations.ll[3]..string.rep(self.decorations.bottom[3], self.w - 2)..self.decorations.lr[3]..string.rep("f", postlineBuffer)
elseif i >= self.y and i <= self.y + self.h - 1 then
outputBuffer.text[i] = string.rep(" ", prelineBuffer)..self.decorations.left[1]..string.sub(self.redirect.buffer.text[i - self.y], 1, self.w - 2)..self.decorations.right[1]..string.rep(" ", postlineBuffer)
outputBuffer.textColor[i] = string.rep("0", prelineBuffer)..self.decorations.left[2]..string.sub(self.redirect.buffer.textColor[i - self.y], 1, self.w - 2)..self.decorations.right[2]..string.rep("0", postlineBuffer)
outputBuffer.backColor[i] = string.rep("f", prelineBuffer)..self.decorations.left[3]..string.sub(self.redirect.buffer.backColor[i - self.y], 1, self.w - 2)..self.decorations.right[3]..string.rep("f", postlineBuffer)
else
outputBuffer.text[i] = string.rep(" ", maxX)
outputBuffer.textColor[i] = string.rep("0", maxX)
outputBuffer.backColor[i] = string.rep("f", maxX)
end
end
local _old = term.redirect(self.target)
local maxX, maxY = term.getSize()
if not self.minimized then
local isColor = term.isColor()
outputBuffer.sizeX, outputBuffer.sizeY = maxX, maxY
outputBuffer.color = isColor
outputBuffer.cursorX, outputBuffer.cursorY = self.x + self.redirect.buffer.cursorX, self.y + self.redirect.buffer.cursorY
if self.borderless then outputBuffer.cursorX, outputBuffer.cursorY = outputBuffer.cursorX - 1, outputBuffer.cursorY - 1 end
outputBuffer.cursorBlink = self.redirect.buffer.cursorBlink
outputBuffer.curTextColor = self.redirect.buffer.curTextColor
outputBuffer.curBackColor = self.redirect.buffer.curTextColor
if self.target.setBounds then self.target.setBounds(self.x, self.y, self.x + self.w - 1, self.y + self.h - 1) end
else
for i=1, maxY do
outputBuffer.text[i] = string.rep(" ", maxX)
outputBuffer.textColor[i] = string.rep("0", maxX)
outputBuffer.backColor[i] = string.rep("f", maxX)
end
if self.target.setBounds then self.target.setBounds(0, 0, 0, 0) end
end
if self.compatibility then
redirect.setBounds(self.x, self.y, self.x + self.w - 1, self.y + self.h - 1)
framebuffer.draw(outputBuffer)
elseif not self.target.render then
framebuffer.draw(outputBuffer)
else
self.target.render(outputBuffer)
term.setCursorPos(outputBuffer.cursorX, outputBuffer.cursorY)
term.setCursorBlink(outputBuffer.cursorBlink)
term.setTextColor(2 ^ tonumber(outputBuffer.curTextColor, 16))
end
if _old then
term.redirect(_old)
else
term.restore()
end
else
local _old = term.redirect(self.target)
outputBuffer.cursorX, outputBuffer.cursorY = self.x + self.redirect.buffer.cursorX, self.y + self.redirect.buffer.cursorY
if self.borderless then outputBuffer.cursorX, outputBuffer.cursorY = outputBuffer.cursorX - 1, outputBuffer.cursorY - 1 end
outputBuffer.cursorBlink = self.redirect.buffer.cursorBlink
outputBuffer.curTextColor = self.redirect.buffer.curTextColor
term.setCursorPos(outputBuffer.cursorX, outputBuffer.cursorY)
term.setCursorBlink(outputBuffer.cursorBlink)
term.setTextColor(2 ^ tonumber(outputBuffer.curTextColor, 16))
if _old then
term.redirect(_old)
else
term.restore()
end
end
end,
move = function(self, x, y)
if not self.movable then return nil, "not movable" end
if x and y then
local xlim, ylim = self.target.getSize()
if (x < 1 or x + self.w - 1 > xlim) and (y < 1 or y + self.h - 1 > ylim) then
return nil, "location out of bounds"
end
if x >= 1 and x + self.w - 1 <= xlim then
self.x = x
end
if y >= 1 and y + self.h - 1 <= ylim then
self.y = y
end
if not self.deferDraw then self:draw() end
return true
else
return nil, "must specify x and y"
end
end,
resize = function(self, w, h)
if not self.resizable then return nil, "not resizable" end
if w and h then
local w = tonumber(w)
local h = tonumber(h)
local xlim, ylim = self.target.getSize()
if self.x + w - 1 > xlim or self.y + h - 1 > ylim then
return nil, "window sized out of bounds"
end
local buf = self.redirect.buffer
if self.borderless then
buf.sizeX = w
buf.sizeY = h
else
buf.sizeX = w - 2
buf.sizeY = h - 2
end
if buf.sizeX >= 3 and buf.sizeY >= 1 then
--adjust buffer vertical size
if #buf.text > buf.sizeY then
--remove entries to trim buffer
repeat
table.remove(buf.text)
table.remove(buf.textColor)
table.remove(buf.backColor)
until #buf.text == buf.sizeY
elseif #buf.text < buf.sizeY then
repeat
table.insert(buf.text, string.rep(" ", buf.sizeX))
table.insert(buf.textColor, string.rep(buf.curTextColor, buf.sizeX))
table.insert(buf.backColor, string.rep(buf.curBackColor, buf.sizeX))
until #buf.text == buf.sizeY
end
self.h = h
--adjust buffer horizontal size.
if #buf.text[1] > buf.sizeX then
--make less wide.
for i = 1, #buf.text do
buf.text[i] = string.sub(buf.text[i], 1, buf.sizeX)
buf.textColor[i] = string.sub(buf.textColor[i], 1, buf.sizeX)
buf.backColor[i] = string.sub(buf.backColor[i], 1, buf.sizeX)
end
elseif #buf.text[1] < buf.sizeX then
--make wider.
local expand = buf.sizeX - #buf.text[1]
for i = 1, #buf.text do
buf.text[i] = buf.text[i]..string.rep(" ", expand)
buf.textColor[i] = buf.textColor[i]..string.rep(buf.curTextColor, expand)
buf.backColor[i] = buf.backColor[i]..string.rep(buf.curBackColor, expand)
end
end
self.w = w
if not self.deferDraw then self:draw() end
return true
else
return nil, "size too small"
end
else
return nil, "must specify width and height"
end
end,
destroy = function(self)
if process and process.compositor then
process.compositor:removeBuffer(self.target.buffer)
end
end,
setTheme = function(self, themeName)
if not themeName then
return nil, "Invalid theme"
elseif type(themeName) == "string" then
if themes:get(themeName) then
self.theme = themeName
for k,v in pairs((themes:get(themeName))[self.windowType]) do
self.decorations[k] = v
end
else
return nil, "Invalid theme"
end
end
if not self.deferDraw then self:draw() end
end,
setDecorations = function(self, decoration)
if not decoration then
return nil, "Invalid decoration"
elseif type(decoration) == "table" then
for k,v in pairs(themes.themes.standard.window) do
--check for completeness.
if type(decoration[k]) ~= "table" or #decoration[k] < 3 then
return nil, "Invalid decoration"
end
end
for k,v in pairs(decoration) do
self.decorations[k] = v
end
else
return nil, "Invalid decoration"
end
if not self.deferDraw then self:draw() end
end,
maximize = function(self)
if not self.resizable then return nil, "not resizable" end
self.normalSize = {x = self.x, y = self.y, w = self.w, h = self.h}
self:move(1, 1)
self.maximized = true
self:resize(self.target.getSize())
end,
minimize = function(self)
if not self.minimizable then return nil, "not minimizable" end
self.minimized = true
if not self.deferDraw then self:draw() end
end,
fullscreen = function(self)
if not self.resizable then return nil, "not resizable" end
self:maximize()
self:setBorderless(true)
end,
unMinimize = function(self)
self.minimized = false
if not self.deferDraw then self:draw() end
end,
restore = function(self)
if not self.resizable then return nil, "not resizable" end
self.maximized = false
if self.borderless then
self.borderless = false
end
self:resize(self.normalSize.w, self.normalSize.h)
self:move(self.normalSize.x, self.normalSize.y)
self.normalSize = nil
end,
setType = function(self, winType)
self.windowType = winType
self:setTheme(self.theme)
--setTheme draws the window if able
end,
setMode = function(self, mode)
self.mode = mode
end,
setCaption = function(self, text)
self.caption = text
if not self.deferDraw then self:draw() end
end,
setBorderless = function(self, mode)
self.borderless = mode
local resizable = self.resizable
self.resizable = true
self:resize(self.w, self.h)
self.resizable = resizable
end,
}
local wmetatable = {
__index = Window,
}
function new(w, h, x, y, caption, target)
local win = {
target = target or (type(term.native) == "function" and term.current() or term.native),
w = tonumber(w) or 19,
h = tonumber(h) or 8,
x = tonumber(x) or 1,
y = tonumber(y) or 1,
caption = caption or nil,
--windowType: clean, window, modal
--clean is normal window, window includes _MX controls, modal includes X control only.
windowType = "clean",
theme = "standard",
--mode is normal, ephemeral or modal
mode = "normal",
decorations = {
ul = {"+", "0", "f"},
ur = {"+", "0", "f"},
ll = {"+", "0", "f"},
lr = {"+", "0", "f"},
top = {"-", "0", "f"},
bottom = {"-", "0", "f"},
left = {"|", "0", "f"},
right = {"|", "0", "f"},
min = {"-", "0", "f"},
max = {"-", "0", "f"},
restore = {"-", "0", "f"},
close = {"-", "e", "f"},
caption = {"", "0", "f"},
minPos = 0,
maxPos = 0,
closePos = 0,
captionSize = {3, -3},
},
maximized = false,
minimized = false,
borderless = false,
minimizable = true,
resizable = true,
movable = true,
closable = true,
compatibility = false,
deferDraw = false,
}
if win.target.setBounds then win.target.setBounds(win.x, win.y, win.x + win.w - 1, win.y + win.h - 1) end
local xlim, ylim = win.target.getSize()
--x and y bounding for initial window.
if win.x < 1 then
win.x = 1
elseif win.x + win.w - 1 > xlim then
win.x = xlim - win.w + 1
end
if win.y < 1 then
win.y = 1
elseif win.y + win.h - 1 > ylim then
win.y = ylim - win.h + 1
end
local _redirect = framebuffer.new(w - 2, h - 2, target.isColor())
win.redirect = {}
for k, v in pairs(_redirect) do
if type(k) == "string" and type(v) == "function" then
win.redirect[k] = function(...)
local result = {_redirect[k](...)}
if not win.deferDraw then win:draw() end
return unpack(result)
end
else
win.redirect[k] = _redirect[k]
end
end
setmetatable( win, wmetatable )
return win
end
function active()
return process and process.id() and process.getWindow()
end
--compatibility with CC 1.6 window API.
function create(parent, x, y, width, height, visible)
local win = window.new(width, height, x, y, "", parent)
win.compatibility = true
win:setBorderless(true)
if not visible then
win:minimize()
end
win.redirect.setVisible = function(b)
if b then
win:unMinimize()
else
win:minimize()
end
end
win.redirect.redraw = function()
win:draw()
end
win.redirect.restoreCursor = function()
win:draw("cursor")
end
win.redirect.getPosition = function()
return win.x, win.y
end
win.redirect.reposition = function(x, y, w, h)
local min = win.minimized
win:minimize()
win:resize(w, h)
win:move(x, y)
if not min then
win:unMinimize()
end
end
return win.redirect
end