Advertisement
Shazz

multiMon v1.0

Apr 20th, 2014
360
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 16.70 KB | None | 0 0
  1. --[[
  2.     multiMon v1.0 - An API which allows multiple monitor objects to act as a single virtual monitor.
  3.     Copyright (C) 2014  Shazz
  4.  
  5.     This program is free software: you can redistribute it and/or modify
  6.     it under the terms of the GNU General Public License as published by
  7.     the Free Software Foundation, either version 3 of the License, or
  8.     (at your option) any later version.
  9.  
  10.     This program is distributed in the hope that it will be useful,
  11.     but WITHOUT ANY WARRANTY; without even the implied warranty of
  12.     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13.     GNU General Public License for more details.
  14.  
  15.     You should have received a copy of the GNU General Public License
  16.     along with this program.  If not, see <http://www.gnu.org/licenses/>.
  17. --]]
  18.  
  19. --// Override peripheral.wrap().
  20. local oWrap = peripheral.wrap
  21. function peripheral.wrap(side)
  22.     local obj = oWrap(side)
  23.     if type(obj) == 'table' then
  24.         if obj.getName == nil then
  25.             obj.getName = function() return side end
  26.         end
  27.         return obj
  28.     end
  29. end
  30.  
  31. --// Function:  create      - Creates a virtual monitor out of multiple monitor objects stitched together.
  32. ---- Argument:  name        - Name of the virtual monitor.
  33. ---- Argument:  monitors    - A table in format: { { object [, ...] } [, ...] } contaning the monitor objects to be stitched.
  34. ---- Argument:  wide        - Optional (default: determined by 'monitors' argument). Amount of monitor objects stacked horizontally.
  35. ---- Argument:  tall        - Optional (default: determined by 'monitors' argument). Amount of monitor objects stacked vertically.
  36. ---- Argument:  width       - Optional (default: the width of the first object in 'monitors' argument). Width of each monitor object.
  37. ---- Argument:  height      - Optional (default: the height of the first object in 'monitors' argument). Height of each monitor object.
  38. ---- Return:    handle      - A handle to the virtual monitor created. Same as calling peripheral.wrap(name).
  39. function create(name, monitors, _wide, _tall, _width, _height)
  40.     --// Parameters
  41.     -- Make sure all parameters are of correct type.
  42.     if
  43.         type(name) ~= 'string' or
  44.         type(monitors) ~= 'table' or
  45.         (_wide ~= nil and type(_wide) ~= 'number') or
  46.         (_tall ~= nil and type(_tall) ~= 'number') or
  47.         (_width ~= nil and type(_width) ~= 'number') or
  48.         (_height ~= nil and type(_height) ~= 'number')
  49.     then
  50.         error('Expected string, table [, table [, number [, number [, number [, number ]]]]]', 2)
  51.     end
  52.  
  53.     -- Make sure 'name' doesn't already exist.
  54.     if peripheral.isPresent(name) then
  55.         error('name already exists', 2)
  56.     end
  57.  
  58.     -- Make sure the 'monitors' object is in the correct format.
  59.     if
  60.         #monitors < 1 or
  61.         type(monitors[1]) ~= 'table' or
  62.         #monitors[1] < 1
  63.     then
  64.         error('monitors expected in format: { { object [, ...] } [, ...] }', 2)
  65.     end
  66.  
  67.     -- Make sure that the 'monitors' object doesn't have an empty row and get the smallest width.
  68.     local minWide
  69.     for i = 1, #monitors do
  70.         if not minWide then
  71.             minWide = #monitors[i]
  72.         elseif #monitors[i] < minWide then
  73.             minWide = #monitors[i]
  74.         end
  75.     end
  76.     if minWide < 1 then
  77.         error('monitors has empty row(s)', 2)
  78.     end
  79.  
  80.  
  81.     --// Private
  82.     local wide, tall = _wide or minWide, _tall or #monitors
  83.     local monWidth, monHeight
  84.     local width, height
  85.     local isColour = (function()
  86.         -- Only set 'isColour' to true if all monitors are advanced.
  87.         local isColourCount = 0
  88.         for monY = 1, tall do
  89.             for monX = 1, wide do
  90.                 if monitors[monY][monX].isColour and monitors[monY][monX].isColour() then
  91.                     isColourCount = isColourCount + 1
  92.                 end
  93.             end
  94.         end
  95.         if isColourCount < tall * wide then
  96.             return false
  97.         else
  98.             return true
  99.         end
  100.     end)()
  101.  
  102.  
  103.     local posX, posY = 1, 1
  104.     local textColour = colours.white
  105.     local backColour = colours.black
  106.     local cursorBlink = false
  107.     local textScale = 1
  108.  
  109.     local emptyLine
  110.     local buffer
  111.  
  112.     local log2 = math.log(2)
  113.     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}
  114.  
  115.     -- Converts from a colour (int) to an uppercase hex character.
  116.     local function colourToHex(colour)
  117.         return string.format('%X', math.floor(math.log(colour) / log2))
  118.     end
  119.  
  120.     -- Converts an uppercase hex character into a colour (int).
  121.     local function hexToColour(hex)
  122.         return tHex[hex]
  123.     end
  124.  
  125.     -- Mocks the Java .toString() invoked when calling mon.write with non-strings.
  126.     local function javaToString(val)
  127.         local valType = type(val)
  128.         if valType == 'string' then
  129.             return val
  130.         elseif valType == 'number' then
  131.             if val % 1 == 0 then
  132.                 return tostring(val) .. '.0'
  133.             else
  134.                 return tostring(val)
  135.             end
  136.         elseif valType == 'boolean' then
  137.             return tostring(val)
  138.         elseif valType == 'table' then
  139.             local function tbl(val, tables)
  140.                 tables = tables or {}
  141.                 if tables[val] then
  142.                     return '(this Map)'
  143.                 end
  144.                 tables[val] = true
  145.  
  146.                 local str = '{'
  147.                 for k, v in pairs(val) do
  148.                     local key
  149.                     if type(k) == 'table' then
  150.                         key = tbl(k, tables)
  151.                     else
  152.                         key = javaToString(k)
  153.                     end
  154.  
  155.                     if key then
  156.                         local value
  157.                         if type(v) == 'table' then
  158.                             value = tbl(v, tables)
  159.                         else
  160.                             value = javaToString(v)
  161.                         end
  162.  
  163.                         if value then
  164.                             str = str .. key .. '=' .. value .. ', '
  165.                         end
  166.                     end
  167.                 end
  168.  
  169.                 tables[val] = nil
  170.  
  171.                 if #str > 1 then
  172.                     return string.sub(str, 1, -3) .. '}'
  173.                 else
  174.                     return str .. '}'
  175.                 end
  176.             end
  177.  
  178.             return tbl(val)
  179.         else
  180.             return nil
  181.         end
  182.     end
  183.  
  184.     -- Calculate width & height variables and clear the buffer.
  185.     local function calculate()
  186.         monWidth = _width or select(1, monitors[1][1].getSize())
  187.         monHeight = _height or select(2, monitors[1][1].getSize())
  188.         width, height = monWidth * wide, monHeight * tall
  189.  
  190.         emptyLine = string.rep(' ', width)
  191.         buffer = {}
  192.         for y = 1, height do
  193.             buffer[y] = {text = emptyLine, textColour = string.rep(colourToHex(textColour), width), backColour = string.rep(colourToHex(backColour), width)}
  194.         end
  195.     end
  196.  
  197.     -- Gets the real x from the virtual x.
  198.     local function getRealX(x)
  199.         local monX = math.ceil(x / monWidth)
  200.  
  201.         if x > monWidth then
  202.             if x % monWidth == 0 then
  203.                 x = monWidth
  204.             else
  205.                 x = x % monWidth
  206.             end
  207.         end
  208.  
  209.         return monX, x
  210.     end
  211.  
  212.     -- Gets the real y from the virtual y.
  213.     local function getRealY(y)
  214.         local monY = math.ceil(y / monHeight)
  215.  
  216.         if y > monHeight then
  217.             if y % monHeight == 0 then
  218.                 y = monHeight
  219.             else
  220.                 y = y % monHeight
  221.             end
  222.         end
  223.  
  224.         return monY, y
  225.     end
  226.  
  227.     -- Gets the real x, y from the virtual x, y.
  228.     local function getReal(x, y)
  229.         local monX, x = getRealX(x)
  230.         local monY, y = getRealY(y)
  231.  
  232.         return monitors[monY][monX], x, y
  233.     end
  234.  
  235.     -- Gets the virtual x, y from the real x, y.
  236.     local function getVirtual(monX, monY, x, y)
  237.         x = x + (monWidth * (monX - 1))
  238.         y = y + (monHeight * (monY - 1))
  239.         return x, y
  240.     end
  241.  
  242.     -- Updates cursor's colour (to make sure the cursor blinks in the right colour).
  243.     local function updateCursorColour()
  244.         for monY = 1, tall do
  245.             for monX = 1, wide do
  246.                 monitors[monY][monX].setTextColour(textColour)
  247.             end
  248.         end
  249.     end
  250.  
  251.     -- Updates cursor's position (to make sure the cursor blinks in the right position).
  252.     local function updateCursorPos()
  253.         for monY = 1, tall do
  254.             for monX = 1, wide do
  255.                 monitors[monY][monX].setCursorPos(0, 0)
  256.             end
  257.         end
  258.  
  259.         if posX >= 1 and posX <= width and posY >= 1 and posY <= height then
  260.             local mon, x, y = getReal(posX, posY)
  261.             mon.setCursorPos(x, y)
  262.         end
  263.     end
  264.  
  265.     -- Updates cursor's blink state.
  266.     local function updateCursorBlink()
  267.         for monY = 1, tall do
  268.             for monX = 1, wide do
  269.                 monitors[monY][monX].setCursorBlink(cursorBlink)
  270.             end
  271.         end
  272.     end
  273.  
  274.     -- Updates the text scale.
  275.     local function updateTextScale()
  276.         for monY = 1, tall do
  277.             for monX = 1, wide do
  278.                 -- Protected call in case object is not a monitor.
  279.                 pcall(function()
  280.                     monitors[monY][monX].setTextScale(textScale)
  281.                 end)
  282.             end
  283.         end
  284.     end
  285.  
  286.     -- Writes to the display.
  287.     local clock = os.clock() + 4.5
  288.     local function rawWrite(text)
  289.         -- Hackish way to prevent 'too long without yielding' on huge displays.
  290.         if os.clock() >= clock then
  291.             clock = os.clock() + 4.5
  292.             os.queueEvent('')
  293.             coroutine.yield()
  294.         end
  295.  
  296.         local monY, y = getRealY(posY)
  297.         local x_ = posX
  298.         local spacesLeft = 0
  299.         repeat
  300.             text = string.sub(text, spacesLeft + 1)
  301.             local monX, x = getRealX(x_)
  302.             spacesLeft = monWidth - x + 1
  303.             x_ = x_ + spacesLeft
  304.  
  305.             if not monitors[monY][monX] then
  306.                 break
  307.             end
  308.  
  309.             monitors[monY][monX].setTextColour(textColour)
  310.             monitors[monY][monX].setBackgroundColour(backColour)
  311.  
  312.             monitors[monY][monX].setCursorPos(x, y)
  313.             monitors[monY][monX].write(text)
  314.         until #text <= spacesLeft
  315.  
  316.         updateCursorColour()
  317.         updateCursorPos()
  318.     end
  319.  
  320.     -- Clears a line on the display.
  321.     local function rawClearLine()
  322.         local monY, y = getRealY(posY)
  323.         for monX = 1, wide do
  324.             monitors[monY][monX].setBackgroundColour(backColour)
  325.             monitors[monY][monX].setCursorPos(0, y)
  326.             monitors[monY][monX].clearLine()
  327.         end
  328.  
  329.         updateCursorPos()
  330.     end
  331.  
  332.     -- Clears the whole display.
  333.     local function rawClear()
  334.         for monY = 1, tall do
  335.             for monX = 1, wide do
  336.                 monitors[monY][monX].setBackgroundColour(backColour)
  337.                 monitors[monY][monX].clear()
  338.             end
  339.         end
  340.     end
  341.  
  342.     --// Public
  343.     local public = {}
  344.  
  345.     -- Mocks mon.write().
  346.     function public.write(text)
  347.         text = javaToString(text)
  348.         if not text or #text < 1 then
  349.             return
  350.         end
  351.  
  352.         local endX = (posX + #text - 1)
  353.         local textLength = #text
  354.         if posY >= 1 and posY <= height and posX <= width and endX >= 1 then
  355.             local textStart, textEnd = 1, #text
  356.             if posX < 1 then
  357.                 textStart = math.abs(posX) + 2
  358.             end
  359.             if endX > width then
  360.                 textEnd = width - endX - 1
  361.             end
  362.             text = string.sub(text, textStart, textEnd)
  363.  
  364.             rawWrite(text)
  365.  
  366.             buffer[posY].text = string.sub(buffer[posY].text, 1, posX - 1) .. text .. string.sub(buffer[posY].text, endX + 1)
  367.             buffer[posY].textColour = string.sub(buffer[posY].textColour, 1, posX - 1) .. string.rep(colourToHex(textColour), #text) .. string.sub(buffer[posY].textColour, endX + 1)
  368.             buffer[posY].backColour = string.sub(buffer[posY].backColour, 1, posX - 1) .. string.rep(colourToHex(backColour), #text) .. string.sub(buffer[posY].backColour, endX + 1)
  369.         end
  370.  
  371.         posX = posX + textLength
  372.     end
  373.  
  374.     -- Mocks mon.clearLine().
  375.     function public.clearLine()
  376.         if posY >= 1 and posY <= height then
  377.             rawClearLine()
  378.  
  379.             buffer[posY].text = emptyLine
  380.             buffer[posY].backColour = string.rep(colourToHex(backColour), width)
  381.         end
  382.     end
  383.  
  384.     -- Mocks mon.clear().
  385.     function public.clear()
  386.         rawClear()
  387.  
  388.         for y = 1, height do
  389.             buffer[y].text = emptyLine
  390.             buffer[y].backColour = string.rep(colourToHex(backColour), width)
  391.         end
  392.     end
  393.  
  394.     -- Mocks mon.scroll().
  395.     function public.scroll(n)
  396.         local function _write(y, text, sTextColour, sBackColour)
  397.             local _posX, _posY = posX, posY
  398.             local _textColour, _backColour = textColour, backColour
  399.             posY = y
  400.  
  401.             local cPosX = 1
  402.             local cText = ''
  403.             local cTextColour, cBackColour = string.sub(sTextColour, 1, 1), string.sub(sBackColour, 1, 1)
  404.  
  405.             for x = 1, width + 1 do
  406.                 local _cTextColour = string.sub(sTextColour, x, x)
  407.                 local _cBackColour = string.sub(sBackColour, x, x)
  408.                 local _cText = string.sub(text, x, x)
  409.                 if _cTextColour == cTextColour and _cBackColour == cBackColour then
  410.                     cText = cText .. _cText
  411.                 else
  412.                     posX = cPosX
  413.                     textColour = hexToColour(cTextColour)
  414.                     backColour = hexToColour(cBackColour)
  415.                     public.write(cText)
  416.  
  417.                     cPosX = x
  418.                     cText = _cText
  419.                     cTextColour, cBackColour = _cTextColour, _cBackColour
  420.                 end
  421.             end
  422.  
  423.             posX, posY = _posX, _posY
  424.             textColour, backColour = _textColour, _backColour
  425.         end
  426.  
  427.         local function _clearLine(y)
  428.             local _posY = posY
  429.  
  430.             posY = y
  431.             public.clearLine()
  432.  
  433.             posY = _posY
  434.         end
  435.  
  436.         if n ~= 0 then
  437.             local startY, endY, step
  438.             if n < 0 then
  439.                 startY, endY, step = height, 1, -1
  440.             elseif n > 0 then
  441.                 startY, endY, step = 1, height, 1
  442.             end
  443.  
  444.             for y = startY, endY, step do
  445.                 if buffer[y + n] then
  446.                     _write(y, buffer[y + n].text, buffer[y + n].textColour, buffer[y + n].backColour)
  447.                 else
  448.                     _clearLine(y)
  449.                 end
  450.             end
  451.         end
  452.     end
  453.  
  454.     -- Mocks mon.isColour().
  455.     function public.isColour()
  456.         return isColour
  457.     end
  458.  
  459.     public.isColor = public.isColour
  460.  
  461.     -- Always returns true, used to identify if monitor object is a virtual monitor.
  462.     function public.isMulti()
  463.         return true
  464.     end
  465.  
  466.     -- Returns the name of the virtual monitor.
  467.     function public.getName()
  468.         return name
  469.     end
  470.  
  471.     -- Mocks mon.getSize().
  472.     function public.getSize()
  473.         return width, height
  474.     end
  475.  
  476.     -- Mocks mon.getCursorPos().
  477.     function public.getCursorPos()
  478.         return posX, posY
  479.     end
  480.  
  481.     -- Mocks mon.setTextScale().
  482.     function public.setTextScale(scale)
  483.         if type(scale) ~= 'number' then
  484.             error('Expected number', 2)
  485.         end
  486.  
  487.         scale = math.floor(scale * 2) / 2
  488.  
  489.         if scale < 0.5 or scale > 5 then
  490.             error('Expected number in range 0.5-5', 2)
  491.         end
  492.  
  493.         textScale = scale
  494.         updateTextScale()
  495.  
  496.         public.clear()
  497.  
  498.         calculate()
  499.     end
  500.  
  501.     -- Mocks mon.setCursorBlink().
  502.     function public.setCursorBlink(bool)
  503.         if type(bool) ~= 'boolean' then
  504.             error('Expected boolean', 2)
  505.         end
  506.  
  507.         cursorBlink = bool
  508.         updateCursorBlink()
  509.     end
  510.  
  511.     -- Mocks mon.setCursorPos().
  512.     function public.setCursorPos(x, y)
  513.         if type(x) ~= 'number' or type(y) ~= 'number' then
  514.             error('Expected number, number', 2)
  515.         end
  516.  
  517.         x, y = math.floor(x), math.floor(y)
  518.  
  519.         posX, posY = x, y
  520.         updateCursorPos()
  521.     end
  522.  
  523.     -- Mocks mon.setTextColour().
  524.     function public.setTextColour(colour)
  525.         if type(colour) ~= 'number' then
  526.             error('Expected number', 2)
  527.         end
  528.  
  529.         if colour <= 0 then
  530.             error('Colour out of range', 2)
  531.         end
  532.  
  533.         _colour = tonumber(colourToHex(colour), 16)
  534.  
  535.         if _colour < 0 or _colour > 15 then
  536.             error('Colour out of range', 2)
  537.         end
  538.  
  539.         if not isColour and _colour ~= 0 and _colour ~= 15 then
  540.             error('Colour not supported', 2)
  541.         end
  542.  
  543.         textColour = colour
  544.         updateCursorColour()
  545.     end
  546.  
  547.     public.setTextColor = public.setTextColour
  548.  
  549.     -- Mocks mon.setBackgroundColour().
  550.     function public.setBackgroundColour(colour)
  551.         if type(colour) ~= 'number' then
  552.             error('Expected number', 2)
  553.         end
  554.  
  555.         if colour <= 0 then
  556.             error('Colour out of range', 2)
  557.         end
  558.  
  559.         _colour = tonumber(colourToHex(colour), 16)
  560.  
  561.         if _colour < 0 or _colour > 15 then
  562.             error('Colour out of range', 2)
  563.         end
  564.  
  565.         if not isColour and _colour ~= 0 and _colour ~= 15 then
  566.             error('Colour not supported', 2)
  567.         end
  568.  
  569.         backColour = colour
  570.     end
  571.  
  572.     public.setBackgroundColor = public.setBackgroundColour
  573.  
  574.  
  575.     --// Init
  576.     -- Called upon object instantiation.
  577.     local function constructor()
  578.         calculate()
  579.  
  580.         -- peripheral.getNames() override.
  581.         local oGetNames = peripheral.getNames
  582.         function peripheral.getNames()
  583.             local tbl = oGetNames()
  584.             table.insert(tbl, name)
  585.             return tbl
  586.         end
  587.  
  588.         -- peripheral.isPresent() override.
  589.         local oIsPresent = peripheral.isPresent
  590.         function peripheral.isPresent(side)
  591.             if side == name then
  592.                 return true
  593.             else
  594.                 return oIsPresent(side)
  595.             end
  596.         end
  597.  
  598.         -- peripheral.getType() override.
  599.         local oGetType = peripheral.getType
  600.         function peripheral.getType(side)
  601.             if side == name then
  602.                 return 'monitor'
  603.             else
  604.                 return oGetType(side)
  605.             end
  606.         end
  607.  
  608.         -- peripheral.getMethods() override.
  609.         local oGetMethods = peripheral.getMethods
  610.         function peripheral.getMethods(side)
  611.             if side == name then
  612.                 local retTbl = {}
  613.                 for k, v in pairs(public) do
  614.                     table.insert(retTbl, k)
  615.                 end
  616.                 return retTbl
  617.             else
  618.                 return oGetMethods(side)
  619.             end
  620.         end
  621.  
  622.         -- peripheral.call() override.
  623.         local oCall = peripheral.call
  624.         function peripheral.call(side, method, ...)
  625.             if side == name and type(method) == 'string' then
  626.                 if type(public[method]) ~= 'function' then
  627.                     error('No such method ' .. method, 2)
  628.                 else
  629.                     return public[method](...)
  630.                 end
  631.             else
  632.                 return oCall(side, method, ...)
  633.             end
  634.         end
  635.  
  636.         -- os.pullEventRaw() override.
  637.         local oPullEventRaw = os.pullEventRaw
  638.         function os.pullEventRaw(...)
  639.             local ev = {oPullEventRaw(...)}
  640.  
  641.             if ev[1] == 'monitor_touch' then
  642.                 for monY = 1, tall do
  643.                     local toBreak = false
  644.                     for monX = 1, wide do
  645.                         if monitors[monY][monX].getName and ev[2] == monitors[monY][monX].getName() then
  646.                             os.queueEvent('monitor_touch', name, getVirtual(monX, monY, ev[3], ev[4]))
  647.                             oPullEventRaw(...)
  648.  
  649.                             toBreak = true
  650.                             break
  651.                         end
  652.                     end
  653.                     if toBreak then
  654.                         break
  655.                     end
  656.                 end
  657.             end
  658.  
  659.             return unpack(ev)
  660.         end
  661.     end
  662.    
  663.     constructor()
  664.     return peripheral.wrap(name)
  665. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement