Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- --[[
- multiMon v1.0 - An API which allows multiple monitor objects to act as a single virtual monitor.
- Copyright (C) 2014 Shazz
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
- --]]
- --// Override peripheral.wrap().
- local oWrap = peripheral.wrap
- function peripheral.wrap(side)
- local obj = oWrap(side)
- if type(obj) == 'table' then
- if obj.getName == nil then
- obj.getName = function() return side end
- end
- return obj
- end
- end
- --// Function: create - Creates a virtual monitor out of multiple monitor objects stitched together.
- ---- Argument: name - Name of the virtual monitor.
- ---- Argument: monitors - A table in format: { { object [, ...] } [, ...] } contaning the monitor objects to be stitched.
- ---- Argument: wide - Optional (default: determined by 'monitors' argument). Amount of monitor objects stacked horizontally.
- ---- Argument: tall - Optional (default: determined by 'monitors' argument). Amount of monitor objects stacked vertically.
- ---- Argument: width - Optional (default: the width of the first object in 'monitors' argument). Width of each monitor object.
- ---- Argument: height - Optional (default: the height of the first object in 'monitors' argument). Height of each monitor object.
- ---- Return: handle - A handle to the virtual monitor created. Same as calling peripheral.wrap(name).
- function create(name, monitors, _wide, _tall, _width, _height)
- --// Parameters
- -- Make sure all parameters are of correct type.
- if
- type(name) ~= 'string' or
- type(monitors) ~= 'table' or
- (_wide ~= nil and type(_wide) ~= 'number') or
- (_tall ~= nil and type(_tall) ~= 'number') or
- (_width ~= nil and type(_width) ~= 'number') or
- (_height ~= nil and type(_height) ~= 'number')
- then
- error('Expected string, table [, table [, number [, number [, number [, number ]]]]]', 2)
- end
- -- Make sure 'name' doesn't already exist.
- if peripheral.isPresent(name) then
- error('name already exists', 2)
- end
- -- Make sure the 'monitors' object is in the correct format.
- if
- #monitors < 1 or
- type(monitors[1]) ~= 'table' or
- #monitors[1] < 1
- then
- error('monitors expected in format: { { object [, ...] } [, ...] }', 2)
- end
- -- Make sure that the 'monitors' object doesn't have an empty row and get the smallest width.
- local minWide
- for i = 1, #monitors do
- if not minWide then
- minWide = #monitors[i]
- elseif #monitors[i] < minWide then
- minWide = #monitors[i]
- end
- end
- if minWide < 1 then
- error('monitors has empty row(s)', 2)
- end
- --// Private
- local wide, tall = _wide or minWide, _tall or #monitors
- local monWidth, monHeight
- local width, height
- local isColour = (function()
- -- Only set 'isColour' to true if all monitors are advanced.
- local isColourCount = 0
- for monY = 1, tall do
- for monX = 1, wide do
- if monitors[monY][monX].isColour and monitors[monY][monX].isColour() then
- isColourCount = isColourCount + 1
- end
- end
- end
- if isColourCount < tall * wide then
- return false
- else
- return true
- end
- end)()
- local posX, posY = 1, 1
- local textColour = colours.white
- local backColour = colours.black
- local cursorBlink = false
- local textScale = 1
- local emptyLine
- local buffer
- local log2 = math.log(2)
- local tHex = {['0'] = 1, ['1'] = 2, ['2'] = 4, ['3'] = 8, ['4'] = 16, ['5'] = 32, ['6'] = 64, ['7'] = 128, ['8'] = 256, ['9'] = 512, ['A'] = 1024, ['B'] = 2048, ['C'] = 4096, ['D'] = 8192, ['E'] = 16384, ['F'] = 32768}
- -- Converts from a colour (int) to an uppercase hex character.
- local function colourToHex(colour)
- return string.format('%X', math.floor(math.log(colour) / log2))
- end
- -- Converts an uppercase hex character into a colour (int).
- local function hexToColour(hex)
- return tHex[hex]
- end
- -- Mocks the Java .toString() invoked when calling mon.write with non-strings.
- local function javaToString(val)
- local valType = type(val)
- if valType == 'string' then
- return val
- elseif valType == 'number' then
- if val % 1 == 0 then
- return tostring(val) .. '.0'
- else
- return tostring(val)
- end
- elseif valType == 'boolean' then
- return tostring(val)
- elseif valType == 'table' then
- local function tbl(val, tables)
- tables = tables or {}
- if tables[val] then
- return '(this Map)'
- end
- tables[val] = true
- local str = '{'
- for k, v in pairs(val) do
- local key
- if type(k) == 'table' then
- key = tbl(k, tables)
- else
- key = javaToString(k)
- end
- if key then
- local value
- if type(v) == 'table' then
- value = tbl(v, tables)
- else
- value = javaToString(v)
- end
- if value then
- str = str .. key .. '=' .. value .. ', '
- end
- end
- end
- tables[val] = nil
- if #str > 1 then
- return string.sub(str, 1, -3) .. '}'
- else
- return str .. '}'
- end
- end
- return tbl(val)
- else
- return nil
- end
- end
- -- Calculate width & height variables and clear the buffer.
- local function calculate()
- monWidth = _width or select(1, monitors[1][1].getSize())
- monHeight = _height or select(2, monitors[1][1].getSize())
- width, height = monWidth * wide, monHeight * tall
- emptyLine = string.rep(' ', width)
- buffer = {}
- for y = 1, height do
- buffer[y] = {text = emptyLine, textColour = string.rep(colourToHex(textColour), width), backColour = string.rep(colourToHex(backColour), width)}
- end
- end
- -- Gets the real x from the virtual x.
- local function getRealX(x)
- local monX = math.ceil(x / monWidth)
- if x > monWidth then
- if x % monWidth == 0 then
- x = monWidth
- else
- x = x % monWidth
- end
- end
- return monX, x
- end
- -- Gets the real y from the virtual y.
- local function getRealY(y)
- local monY = math.ceil(y / monHeight)
- if y > monHeight then
- if y % monHeight == 0 then
- y = monHeight
- else
- y = y % monHeight
- end
- end
- return monY, y
- end
- -- Gets the real x, y from the virtual x, y.
- local function getReal(x, y)
- local monX, x = getRealX(x)
- local monY, y = getRealY(y)
- return monitors[monY][monX], x, y
- end
- -- Gets the virtual x, y from the real x, y.
- local function getVirtual(monX, monY, x, y)
- x = x + (monWidth * (monX - 1))
- y = y + (monHeight * (monY - 1))
- return x, y
- end
- -- Updates cursor's colour (to make sure the cursor blinks in the right colour).
- local function updateCursorColour()
- for monY = 1, tall do
- for monX = 1, wide do
- monitors[monY][monX].setTextColour(textColour)
- end
- end
- end
- -- Updates cursor's position (to make sure the cursor blinks in the right position).
- local function updateCursorPos()
- for monY = 1, tall do
- for monX = 1, wide do
- monitors[monY][monX].setCursorPos(0, 0)
- end
- end
- if posX >= 1 and posX <= width and posY >= 1 and posY <= height then
- local mon, x, y = getReal(posX, posY)
- mon.setCursorPos(x, y)
- end
- end
- -- Updates cursor's blink state.
- local function updateCursorBlink()
- for monY = 1, tall do
- for monX = 1, wide do
- monitors[monY][monX].setCursorBlink(cursorBlink)
- end
- end
- end
- -- Updates the text scale.
- local function updateTextScale()
- for monY = 1, tall do
- for monX = 1, wide do
- -- Protected call in case object is not a monitor.
- pcall(function()
- monitors[monY][monX].setTextScale(textScale)
- end)
- end
- end
- end
- -- Writes to the display.
- local clock = os.clock() + 4.5
- local function rawWrite(text)
- -- Hackish way to prevent 'too long without yielding' on huge displays.
- if os.clock() >= clock then
- clock = os.clock() + 4.5
- os.queueEvent('')
- coroutine.yield()
- end
- local monY, y = getRealY(posY)
- local x_ = posX
- local spacesLeft = 0
- repeat
- text = string.sub(text, spacesLeft + 1)
- local monX, x = getRealX(x_)
- spacesLeft = monWidth - x + 1
- x_ = x_ + spacesLeft
- if not monitors[monY][monX] then
- break
- end
- monitors[monY][monX].setTextColour(textColour)
- monitors[monY][monX].setBackgroundColour(backColour)
- monitors[monY][monX].setCursorPos(x, y)
- monitors[monY][monX].write(text)
- until #text <= spacesLeft
- updateCursorColour()
- updateCursorPos()
- end
- -- Clears a line on the display.
- local function rawClearLine()
- local monY, y = getRealY(posY)
- for monX = 1, wide do
- monitors[monY][monX].setBackgroundColour(backColour)
- monitors[monY][monX].setCursorPos(0, y)
- monitors[monY][monX].clearLine()
- end
- updateCursorPos()
- end
- -- Clears the whole display.
- local function rawClear()
- for monY = 1, tall do
- for monX = 1, wide do
- monitors[monY][monX].setBackgroundColour(backColour)
- monitors[monY][monX].clear()
- end
- end
- end
- --// Public
- local public = {}
- -- Mocks mon.write().
- function public.write(text)
- text = javaToString(text)
- if not text or #text < 1 then
- return
- end
- local endX = (posX + #text - 1)
- local textLength = #text
- if posY >= 1 and posY <= height and posX <= width and endX >= 1 then
- local textStart, textEnd = 1, #text
- if posX < 1 then
- textStart = math.abs(posX) + 2
- end
- if endX > width then
- textEnd = width - endX - 1
- end
- text = string.sub(text, textStart, textEnd)
- rawWrite(text)
- buffer[posY].text = string.sub(buffer[posY].text, 1, posX - 1) .. text .. string.sub(buffer[posY].text, endX + 1)
- buffer[posY].textColour = string.sub(buffer[posY].textColour, 1, posX - 1) .. string.rep(colourToHex(textColour), #text) .. string.sub(buffer[posY].textColour, endX + 1)
- buffer[posY].backColour = string.sub(buffer[posY].backColour, 1, posX - 1) .. string.rep(colourToHex(backColour), #text) .. string.sub(buffer[posY].backColour, endX + 1)
- end
- posX = posX + textLength
- end
- -- Mocks mon.clearLine().
- function public.clearLine()
- if posY >= 1 and posY <= height then
- rawClearLine()
- buffer[posY].text = emptyLine
- buffer[posY].backColour = string.rep(colourToHex(backColour), width)
- end
- end
- -- Mocks mon.clear().
- function public.clear()
- rawClear()
- for y = 1, height do
- buffer[y].text = emptyLine
- buffer[y].backColour = string.rep(colourToHex(backColour), width)
- end
- end
- -- Mocks mon.scroll().
- function public.scroll(n)
- local function _write(y, text, sTextColour, sBackColour)
- local _posX, _posY = posX, posY
- local _textColour, _backColour = textColour, backColour
- posY = y
- local cPosX = 1
- local cText = ''
- local cTextColour, cBackColour = string.sub(sTextColour, 1, 1), string.sub(sBackColour, 1, 1)
- for x = 1, width + 1 do
- local _cTextColour = string.sub(sTextColour, x, x)
- local _cBackColour = string.sub(sBackColour, x, x)
- local _cText = string.sub(text, x, x)
- if _cTextColour == cTextColour and _cBackColour == cBackColour then
- cText = cText .. _cText
- else
- posX = cPosX
- textColour = hexToColour(cTextColour)
- backColour = hexToColour(cBackColour)
- public.write(cText)
- cPosX = x
- cText = _cText
- cTextColour, cBackColour = _cTextColour, _cBackColour
- end
- end
- posX, posY = _posX, _posY
- textColour, backColour = _textColour, _backColour
- end
- local function _clearLine(y)
- local _posY = posY
- posY = y
- public.clearLine()
- posY = _posY
- end
- if n ~= 0 then
- local startY, endY, step
- if n < 0 then
- startY, endY, step = height, 1, -1
- elseif n > 0 then
- startY, endY, step = 1, height, 1
- end
- for y = startY, endY, step do
- if buffer[y + n] then
- _write(y, buffer[y + n].text, buffer[y + n].textColour, buffer[y + n].backColour)
- else
- _clearLine(y)
- end
- end
- end
- end
- -- Mocks mon.isColour().
- function public.isColour()
- return isColour
- end
- public.isColor = public.isColour
- -- Always returns true, used to identify if monitor object is a virtual monitor.
- function public.isMulti()
- return true
- end
- -- Returns the name of the virtual monitor.
- function public.getName()
- return name
- end
- -- Mocks mon.getSize().
- function public.getSize()
- return width, height
- end
- -- Mocks mon.getCursorPos().
- function public.getCursorPos()
- return posX, posY
- end
- -- Mocks mon.setTextScale().
- function public.setTextScale(scale)
- if type(scale) ~= 'number' then
- error('Expected number', 2)
- end
- scale = math.floor(scale * 2) / 2
- if scale < 0.5 or scale > 5 then
- error('Expected number in range 0.5-5', 2)
- end
- textScale = scale
- updateTextScale()
- public.clear()
- calculate()
- end
- -- Mocks mon.setCursorBlink().
- function public.setCursorBlink(bool)
- if type(bool) ~= 'boolean' then
- error('Expected boolean', 2)
- end
- cursorBlink = bool
- updateCursorBlink()
- end
- -- Mocks mon.setCursorPos().
- function public.setCursorPos(x, y)
- if type(x) ~= 'number' or type(y) ~= 'number' then
- error('Expected number, number', 2)
- end
- x, y = math.floor(x), math.floor(y)
- posX, posY = x, y
- updateCursorPos()
- end
- -- Mocks mon.setTextColour().
- function public.setTextColour(colour)
- if type(colour) ~= 'number' then
- error('Expected number', 2)
- end
- if colour <= 0 then
- error('Colour out of range', 2)
- end
- _colour = tonumber(colourToHex(colour), 16)
- if _colour < 0 or _colour > 15 then
- error('Colour out of range', 2)
- end
- if not isColour and _colour ~= 0 and _colour ~= 15 then
- error('Colour not supported', 2)
- end
- textColour = colour
- updateCursorColour()
- end
- public.setTextColor = public.setTextColour
- -- Mocks mon.setBackgroundColour().
- function public.setBackgroundColour(colour)
- if type(colour) ~= 'number' then
- error('Expected number', 2)
- end
- if colour <= 0 then
- error('Colour out of range', 2)
- end
- _colour = tonumber(colourToHex(colour), 16)
- if _colour < 0 or _colour > 15 then
- error('Colour out of range', 2)
- end
- if not isColour and _colour ~= 0 and _colour ~= 15 then
- error('Colour not supported', 2)
- end
- backColour = colour
- end
- public.setBackgroundColor = public.setBackgroundColour
- --// Init
- -- Called upon object instantiation.
- local function constructor()
- calculate()
- -- peripheral.getNames() override.
- local oGetNames = peripheral.getNames
- function peripheral.getNames()
- local tbl = oGetNames()
- table.insert(tbl, name)
- return tbl
- end
- -- peripheral.isPresent() override.
- local oIsPresent = peripheral.isPresent
- function peripheral.isPresent(side)
- if side == name then
- return true
- else
- return oIsPresent(side)
- end
- end
- -- peripheral.getType() override.
- local oGetType = peripheral.getType
- function peripheral.getType(side)
- if side == name then
- return 'monitor'
- else
- return oGetType(side)
- end
- end
- -- peripheral.getMethods() override.
- local oGetMethods = peripheral.getMethods
- function peripheral.getMethods(side)
- if side == name then
- local retTbl = {}
- for k, v in pairs(public) do
- table.insert(retTbl, k)
- end
- return retTbl
- else
- return oGetMethods(side)
- end
- end
- -- peripheral.call() override.
- local oCall = peripheral.call
- function peripheral.call(side, method, ...)
- if side == name and type(method) == 'string' then
- if type(public[method]) ~= 'function' then
- error('No such method ' .. method, 2)
- else
- return public[method](...)
- end
- else
- return oCall(side, method, ...)
- end
- end
- -- os.pullEventRaw() override.
- local oPullEventRaw = os.pullEventRaw
- function os.pullEventRaw(...)
- local ev = {oPullEventRaw(...)}
- if ev[1] == 'monitor_touch' then
- for monY = 1, tall do
- local toBreak = false
- for monX = 1, wide do
- if monitors[monY][monX].getName and ev[2] == monitors[monY][monX].getName() then
- os.queueEvent('monitor_touch', name, getVirtual(monX, monY, ev[3], ev[4]))
- oPullEventRaw(...)
- toBreak = true
- break
- end
- end
- if toBreak then
- break
- end
- end
- end
- return unpack(ev)
- end
- end
- constructor()
- return peripheral.wrap(name)
- end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement