· 2 years ago · Feb 13, 2023, 03:00 AM
1--------------------------------------------------------------------------------------------------------
2-- Wojbies API 4.3 - CanvasTerm - Api for drawing window-like term object on Plethora overlay glasses --
3--------------------------------------------------------------------------------------------------------
4-- Copyright (c) 2015-2021 Wojbie (wojbie@wojbie.net)
5-- Redistribution and use in source and binary forms, with or without modification, are permitted (subject to the limitations in the disclaimer below) provided that the following conditions are met:
6-- 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
7-- 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
8-- 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
9-- 4. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
10-- 5. The origin of this software must not be misrepresented; you must not claim that you wrote the original software.
11-- NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. YOU ACKNOWLEDGE THAT THIS SOFTWARE IS NOT DESIGNED, LICENSED OR INTENDED FOR USE IN THE DESIGN, CONSTRUCTION, OPERATION OR MAINTENANCE OF ANY NUCLEAR FACILITY.
12
13--### Basic utility functions
14local function copyTable(...)
15 local tArgs = {...}
16 local copy = {}
17 for _, piece in pairs(tArgs) do
18 if piece and type(piece) == "table" then
19 for key, val in pairs(piece) do
20 if type(val) == "table" then copy[key] = copyTable( copy[key] or {}, val)
21 else copy[key] = val end
22 end
23 end
24 end
25 return copy
26end
27
28--### Initializing
29local c = shell and {} or (_ENV or getfenv())
30c.versionName = "CanvasTerm By Wojbie"
31c.versionNum = 4.338 --2020-01-15
32
33local expect = require "cc.expect".expect
34
35--### Internal palette related tables.
36local tDefaultPallette = {[ colors.white ] = 0xF0F0F0, [ colors.orange ] = 0xF2B233, [ colors.magenta ] = 0xE57FD8, [ colors.lightBlue ] = 0x99B2F2, [ colors.yellow ] = 0xDEDE6C, [ colors.lime ] = 0x7FCC19, [ colors.pink ] = 0xF2B2CC, [ colors.gray ] = 0x4C4C4C, [ colors.lightGray ] = 0x999999, [ colors.cyan ] = 0x4C99B2, [ colors.purple ] = 0xB266E5, [ colors.blue ] = 0x3366CC, [ colors.brown ] = 0x7F664C, [ colors.green ] = 0x57A64E, [ colors.red ] = 0xCC4C4C, [ colors.black ] = 0x111111}
37
38local tRGBA = {[ "0" ] = 0xF0F0F0FF, [ "1" ] = 0xF2B233FF, [ "2" ] = 0xE57FD8FF, [ "3" ] = 0x99B2F2FF, [ "4" ] = 0xDEDE6CFF, [ "5" ] = 0x7FCC19FF, [ "6" ] = 0xF2B2CCFF, [ "7" ] = 0x4C4C4CFF, [ "8" ] = 0x999999FF, [ "9" ] = 0x4C99B2FF, [ "a" ] = 0xB266E5FF, [ "b" ] = 0x3366CCFF, [ "c" ] = 0x7F664CFF, [ "d" ] = 0x57A64EFF, [ "e" ] = 0xCC4C4CFF, [ "f" ] = 0x111111FF}
39
40local tHex = {[ colors.white ] = "0", [ colors.orange ] = "1", [ colors.magenta ] = "2", [ colors.lightBlue ] = "3", [ colors.yellow ] = "4", [ colors.lime ] = "5", [ colors.pink ] = "6", [ colors.gray ] = "7", [ colors.lightGray ] = "8", [ colors.cyan ] = "9", [ colors.purple ] = "a", [ colors.blue ] = "b", [ colors.brown ] = "c", [ colors.green ] = "d", [ colors.red ] = "e", [ colors.black ] = "f"}
41
42c.dummyTerm = function(nWidth, nHeight, bColor)
43
44 local isColor = bColor and true or false
45 local tDefaultPallette = copyTable(tDefaultPallette)
46
47 local dummy = {}
48 dummy.getPaletteColour = function( c ) return colors.unpackRGB(tDefaultPallette[c]) end
49 dummy.isColor = function() return isColor end
50
51 local buffer = window.create( dummy, 1, 1, nWidth, nHeight, false )
52 local bufferResize = buffer.reposition
53
54 buffer.setVisible = nil
55 buffer.redraw = nil
56 buffer.restoreCursor = nil
57 buffer.getPosition = nil
58 buffer.reposition = nil
59 buffer.resize = function(nWidth, nHeight) return bufferResize(1, 1, nWidth, nHeight) end
60
61 return buffer
62end local dummyTerm = c.dummyTerm
63
64local textOffsetX = { --How much it goes to right on each symbol
65 ["!"] = 2, ['"'] = 1, ["'"] = 2, ['('] = 1, [')'] = 1, ['*'] = 1, [","] = 2, ["."] = 2, [":"] = 2, [";"] = 2, ["I"] = 1, ["["] = 1, ["]"] = 1, ["`"] = 2, ["f"] = 1, ["i"] = 2, ["k"] = 1, ["l"] = 2, ["t"] = 1, ["{"] = 1, ["|"] = 2, ["}"] = 1,
66 ["\161"] = 2, ["\162"] = 1, ["\164"] = 1, ["\165"] = 1, ["\166"] = 3, ["\167"] = 1, ["\168"] = 1, ["\169"] = 1, ["\176"] = -1, ["\181"] = 1, ["\182"] = 1, ["\184"] = 2, ["\195"] = 1, ["\204"] = 1, ["\205"] = 1, ["\206"] = 1, ["\207"] = 1, ["\208"] = 1, ["\210"] = 1, ["\215"] = 1, ["\217"] = 1, ["\219"] = 1, ["\221"] = 1, ["\222"] = 1, ["\236"] = 1, ["\237"] = 2, ["\239"] = 1, ["\240"] = 1, ["\253"] = 1, ["\254"] = 1,
67}
68for i = 1, 8 do textOffsetX[string.char(i)] = -1 end
69for i = 11, 12 do textOffsetX[string.char(i)] = -1 end
70for i = 14, 31 do textOffsetX[string.char(i)] = -1 end
71for i = 127, 159 do textOffsetX[string.char(i)] = -1 end
72textOffsetX["\173"] = -1
73
74--### Main function
75c.create = function( parent, nX, nY, nWidth, nHeight, nScale, bVisible, bFrameCursor, bDisableAutoRedraw )
76
77 expect(1, parent, "table")
78 if not parent.addGroup then
79 error( "Invalid parent object, please provide 2d canvas. try canvas() or canvas3d().create().addFrame()", 2 )
80 end
81 expect(2, nX, "number")
82 expect(3, nY, "number")
83 expect(4, nWidth, "number")
84 expect(5, nHeight, "number")
85 expect(6, nScale, "number", "nil")
86 expect(7, bVisible, "boolean", "nil")
87 expect(8, bFrameCursor, "boolean", "nil")
88 expect(9, bDisableAutoRedraw, "boolean", "nil")
89
90 bVisible = bVisible ~= false
91 nScale = nScale or 1
92
93 local group = parent.addGroup({nX, nY})
94 local buffer = dummyTerm(nWidth, nHeight, true)
95 local bufferResize = buffer.resize
96 buffer.resize = nil
97 local tRGBA = copyTable(tRGBA)
98 local tHex = copyTable(tHex)
99 local textOffsetX = copyTable(textOffsetX)
100 local tFrame, tTexts, tBacks, tCursor
101 local tText, tFront, tBack, tCursorMeta
102 local bBlink, bOnScreen = true, true
103 local nTextScaler = 4 / 6
104
105 local function build()
106 tTexts = {}
107 tBacks = {}
108 local xSize = 4 * nScale
109 local ySize = 6 * nScale
110 tFrame = group.addRectangle( 0 , 0 , xSize * nWidth + 2, ySize * nHeight + 2, 0x111111FF)
111 for y = 1, nHeight do
112 tBacks[y] = {}
113 tTexts[y] = {}
114 for x = 1, nWidth do
115 tBacks[y][x] = group.addRectangle( (x - 1) * xSize + 1 , (y - 1) * ySize + 1 , xSize, ySize, 0x7F664CFF)
116 end
117 for x = 1, nWidth do
118 tTexts[y][x] = group.addText({(x - 1) * xSize + 1, (y - 1) * ySize + 1}, "A", 0x57A64EFF, nScale * nTextScaler)
119 tTexts[y][x].x = (x - 1) * xSize + 1
120 tTexts[y][x].y = (y - 1) * ySize + 1
121 end
122 end
123 tCursor = group.addGroup({0, 0})
124 tCursor.cursor = tCursor.addText({1, 1}, "_", 0xF0F0F0FF, nScale * nTextScaler)
125 if bFrameCursor then
126 tCursor.lines = tCursor.addLines({1, 1}, {1, ySize + 1}, {xSize + 1, ySize + 1}, {xSize + 1, 1}, 0xF0F0F0FF, 1)
127 end
128 bBlink = true
129 tText = {}
130 tFront = {}
131 tBack = {}
132 tCursorMeta = {}
133 for i in pairs(tHex) do
134 tRGBA[tHex[i]] = bit.bor(bit.blshift(colors.packRGB(buffer.getPaletteColour(i)), 8), 0xFF)
135 end
136 end
137
138 local function destroy()
139 for y = 1, nHeight do
140 for x = 1, nWidth do tBacks[y][x].remove() end
141 for x = 1, nWidth do tTexts[y][x].remove() end
142 end
143 tFrame.remove()
144 tCursor.remove()
145 end
146
147 local function redrawLine(nLine, bForce)
148 local text, front, back = buffer.getLine(nLine)
149 if bForce or tBack[nLine] ~= back then --dirty back
150 local rowBack = tBacks[nLine]
151 back:gsub("()(.)", function(x, c)
152 if rowBack[x].color ~= tRGBA[c] then
153 rowBack[x].setColor(tRGBA[c])
154 rowBack[x].color = tRGBA[c]
155 end
156 end)
157 tBack[nLine] = back
158 end
159 if bForce or tText[nLine] ~= text or tFront[nLine] ~= front then --dirty text
160 local rowText = tTexts[nLine]
161 front:gsub("()(.)", function(x, c)
162 if rowText[x].color ~= tRGBA[c] then
163 rowText[x].setColor(tRGBA[c])
164 rowText[x].color = tRGBA[c]
165 end
166 end)
167 text:gsub("()(.)", function(x, c)
168 if rowText[x].text ~= c then
169 rowText[x].setText(c)
170 rowText[x].setPosition(rowText[x].x + (textOffsetX[c] or 0) * nTextScaler * nScale, rowText[x].y)
171 rowText[x].text = c
172 end
173 end)
174 tText[nLine] = text
175 tFront[nLine] = front
176 end
177 end
178
179 local function redraw(bForce)
180 for y = 1, nHeight do
181 redrawLine(y, bForce)
182 end
183 end
184
185 local function updateCursor()
186 local cx, cy = buffer.getCursorPos()
187 if tCursorMeta.x ~= cx or tCursorMeta.y ~= cy then
188 bOnScreen = cx > 0 and cx <= nWidth and cy > 0 and cy <= nHeight
189 tCursor.setPosition((cx - 1) * 4 * nScale, (cy - 1) * 6 * nScale)
190 tCursorMeta.x, tCursorMeta.y = cx, cy
191 end
192 local cColor = tRGBA[tHex[buffer.getTextColor()]]
193 if tCursorMeta.color ~= cColor then
194 tCursor.cursor.setColor(cColor)
195 tCursorMeta.color = cColor
196 end
197 local cBlink = buffer.getCursorBlink()
198 if tCursorMeta.blink ~= cBlink or tCursorMeta.bOnScreen ~= bOnScreen then
199 tCursor.cursor.setAlpha(bBlink and bOnScreen and cBlink and 0xFF or 0)
200 tCursorMeta.blink = cBlink
201 tCursorMeta.bOnScreen = bOnScreen
202 end
203 end
204
205 local function updatePaletteColor(c)
206 if tHex[c] then
207 local col = bit.bor(bit.blshift(colors.packRGB(buffer.getPaletteColour(c)), 8), 0xFF)
208 if tRGBA[tHex[c]] ~= col then
209 tRGBA[tHex[c]] = col
210 return true
211 end
212 end
213 return false
214 end
215
216 local function updatePalette()
217 local changed = false
218 for i in pairs(tHex) do
219 local col = bit.bor(bit.blshift(colors.packRGB(buffer.getPaletteColour(i)), 8), 0xFF)
220 if tRGBA[tHex[i]] ~= col then
221 tRGBA[tHex[i]] = col
222 changed = true
223 end
224 end
225 return changed
226 end
227
228 local function blinker()
229 if math.floor(os.epoch("utc") / 400) % 2 == 0 ~= bBlink then
230 bBlink = not bBlink
231 tCursor.cursor.setAlpha(bBlink and bOnScreen and buffer.getCursorBlink() and 0xFF or 0)
232 end
233 end
234
235
236 local redrawDirtiers = {["clear"] = true, ["scroll"] = true }
237 local redrawLineDirtiers = {["write"] = true, ["blit"] = true, ["clearLine"] = true }
238 local cursorDirters = {["setCursorPos"] = true, ["setCursorBlink"] = true, ["setTextColor"] = true, ["setTextColour"] = true}
239 local paletteDirters = {["setPaletteColor"] = true, ["setPaletteColour"] = true}
240
241 local canvasTerm = {}
242
243 for i, k in pairs(buffer) do
244 if bDisableAutoRedraw then
245 canvasTerm[i] = k
246 elseif redrawDirtiers[i] then
247 canvasTerm[i] = function(...)
248 local returns = table.pack(k(...))
249 if bVisible then redraw() updateCursor() end
250 return table.unpack(returns, 1, returns.n)
251 end
252 elseif redrawLineDirtiers[i] then
253 canvasTerm[i] = function(...)
254 local returns = table.pack(k(...))
255 if bVisible then redrawLine(select(2, buffer.getCursorPos())) updateCursor() end
256 return table.unpack(returns, 1, returns.n)
257 end
258 elseif cursorDirters[i] then
259 canvasTerm[i] = function(...)
260 local returns = table.pack(k(...))
261 if bVisible then updateCursor() end
262 return table.unpack(returns, 1, returns.n)
263 end
264 elseif paletteDirters[i] then
265 canvasTerm[i] = function(c, ...)
266 local returns = table.pack(k(c, ...))
267 local changed = updatePaletteColor(c)
268 if bVisible then redraw(changed) updateCursor() end
269 return table.unpack(returns, 1, returns.n)
270 end
271 else
272 canvasTerm[i] = k
273 end
274 end
275
276 canvasTerm.setVisible = function( b )
277 expect(1, b, "boolean")
278 if bVisible ~= b then
279 bVisible = b
280 if bVisible then
281 build()
282 redraw()
283 updateCursor()
284 else
285 destroy()
286 end
287 end
288 end
289
290 if bDisableAutoRedraw then
291
292 canvasTerm.flush = function()
293 if bVisible then
294 local changed = updatePalette()
295 redraw(changed)
296 updateCursor()
297 end
298 end
299
300 end
301
302 canvasTerm.loadLines = function(tWindow)
303 expect(1, tWindow, "table")
304 assert(tWindow.getLine, "Error. loadLones requires windows table with getLine supported")
305 local _, inY = tWindow.getSize()
306 local cX, xY = buffer.getCursorPos()
307 for i in pairs(tHex) do
308 buffer.setPaletteColour(i, tWindow.getPaletteColour(i))
309 end
310 for y = 1, math.min(nHeight, inY) do
311 buffer.setCursorPos(1, y)
312 buffer.blit(tWindow.getLine(y))
313 end
314 buffer.setCursorPos(cX, xY)
315 if bVisible then
316 local changed = updatePalette()
317 redraw(changed)
318 updateCursor()
319 end
320 end
321
322 canvasTerm.blinker = function()
323 if bVisible then
324 blinker()
325 end
326 end
327
328 canvasTerm.reposition = function( nNewX, nNewY, nNewWidth, nNewHeight, nNewScale, newParent )
329 if nNewX ~= nil or nNewY ~= nil then
330 expect(1, nNewX, "number")
331 expect(2, nNewY, "number")
332 end
333 if nNewWidth ~= nil or nNewHeight ~= nil then
334 expect(3, nNewWidth, "number")
335 expect(4, nNewHeight, "number")
336 end
337 expect(5, nNewScale, "number", "nil")
338 expect(6, newParent, "table", "nil")
339
340 if bVisible then
341 destroy()
342 end
343
344 nX = nNewX or nX
345 nY = nNewY or nY
346 nWidth = nNewWidth or nWidth
347 nHeight = nNewHeight or nHeight
348 nScale = nNewScale or nScale
349 parent = newParent or parent
350
351 group.remove()
352 group = parent.addGroup({nX, nY})
353 bufferResize(nWidth, nHeight)
354
355 if bVisible then
356 build()
357 redraw()
358 updateCursor()
359 end
360 end
361
362 if bVisible then
363 build()
364 redraw()
365 updateCursor()
366 end
367
368 return canvasTerm
369end
370
371--### Finalizing
372return c