diff options
| author | Andrew Lee <alee14498@gmail.com> | 2019-07-19 14:10:39 -0400 |
|---|---|---|
| committer | Andrew Lee <alee14498@gmail.com> | 2019-07-19 14:10:39 -0400 |
| commit | fe08446d84e0aa939780ad013f0545778703da6d (patch) | |
| tree | f45358ae6470dc894c41abcb278c4898c7ef1b18 /.mbs/lib/scroll_window.lua | |
| parent | 21446806b264d8fd6694b1495f0c51bf24d26b35 (diff) | |
| download | bits-UI-fe08446d84e0aa939780ad013f0545778703da6d.tar.gz bits-UI-fe08446d84e0aa939780ad013f0545778703da6d.tar.bz2 bits-UI-fe08446d84e0aa939780ad013f0545778703da6d.zip | |
MBS as default shell
Diffstat (limited to '.mbs/lib/scroll_window.lua')
| -rw-r--r-- | .mbs/lib/scroll_window.lua | 459 |
1 files changed, 459 insertions, 0 deletions
diff --git a/.mbs/lib/scroll_window.lua b/.mbs/lib/scroll_window.lua new file mode 100644 index 0000000..75a6fb1 --- /dev/null +++ b/.mbs/lib/scroll_window.lua @@ -0,0 +1,459 @@ +local type = type + +local colour_lookup = {} +for i = 0, 16 do + colour_lookup[2 ^ i] = string.format("%x", i) +end + +function create(original) + if not original then original = term.current() end + + local text = {} + local text_colour = {} + local back_colour = {} + local palette = {} + + local cursor_x, cursor_y = 1, 1 + + local scroll_offset = 0 + local scroll_cursor_y = cursor_y + + local cursor_blink = false + local cur_text_colour = "0" + local cur_back_colour = "f" + + local sizeX, sizeY = original.getSize() + local color = original.isColor() + + local max_scrollback = 100 + local bubble, delegate = true, nil + + local cursor_threshold = 0 + + local redirect = {} + + if original.getPaletteColour then + for i = 0, 15 do + local c = 2 ^ i + palette[c] = { original.getPaletteColour(c) } + end + end + + local function trim() + if max_scrollback > -1 then + while scroll_offset > max_scrollback do + table.remove(text, 1) + table.remove(text_colour, 1) + table.remove(back_colour, 1) + scroll_offset = scroll_offset - 1 + end + end + scroll_cursor_y = scroll_offset + cursor_y + end + + function redirect.write(writeText) + if delegate then return delegate.write(writeText) end + + writeText = tostring(writeText) + if bubble then original.write(writeText) end + + local pos = cursor_x + + -- If we're off the screen then just emulate a write + if cursor_y > sizeY or cursor_y < 1 then + cursor_x = pos + #writeText + return + end + + if pos + #writeText <= 1 or pos > sizeX then + -- If we're too far off the left then skip. + cursor_x = pos + #writeText + return + elseif pos < 1 then + -- Adjust text to fit on screen starting at one. + writeText = string.sub(writeText, -pos + 2) + pos = 1 + end + + local lineText = text[scroll_cursor_y] + local lineColor = text_colour[scroll_cursor_y] + local lineBack = back_colour[scroll_cursor_y] + local preStop = pos - 1 + local preStart = math.min(1, preStop) + local postStart = pos + string.len(writeText) + local postStop = sizeX + local sub, rep = string.sub, string.rep + + text[scroll_cursor_y] = sub(lineText, preStart, preStop)..writeText..sub(lineText, postStart, postStop) + text_colour[scroll_cursor_y] = sub(lineColor, preStart, preStop)..rep(cur_text_colour, #writeText)..sub(lineColor, postStart, postStop) + back_colour[scroll_cursor_y] = sub(lineBack, preStart, preStop)..rep(cur_back_colour, #writeText)..sub(lineBack, postStart, postStop) + cursor_x = pos + string.len(writeText) + end + + function redirect.blit(writeText, writeFore, writeBack) + if delegate then return delegate.blit(writeText, writeFore, writeBack) end + + if type(writeText) ~= "string" then error("bad argument #1 (expected string, got " .. type(writeText) .. ")", 2) end + if type(writeFore) ~= "string" then error("bad argument #2 (expected string, got " .. type(writeFore) .. ")", 2) end + if type(writeBack) ~= "string" then error("bad argument #3 (expected string, got " .. type(writeBack) .. ")", 2) end + if #writeFore ~= #writeText or #writeBack ~= #writeText then error("Arguments must be the same length", 2) end + + if bubble then original.blit(writeText, writeFore, writeBack) end + + local pos = cursor_x + + -- If we're off the screen then just emulate a write + if cursor_y > sizeY or cursor_y < 1 then + cursor_x = pos + #writeText + return + end + + if pos + #writeText <= 1 then + --skip entirely. + cursor_x = pos + #writeText + return + elseif pos < 1 then + --adjust text to fit on screen starting at one. + writeText = string.sub(writeText, math.abs(cursor_x) + 2) + writeFore = string.sub(writeFore, math.abs(cursor_x) + 2) + writeBack = string.sub(writeBack, math.abs(cursor_x) + 2) + cursor_x = 1 + elseif pos > sizeX then + --if we're off the edge to the right, skip entirely. + cursor_x = pos + #writeText + return + else + writeText = writeText + end + + local lineText = text[scroll_cursor_y] + local lineColor = text_colour[scroll_cursor_y] + local lineBack = back_colour[scroll_cursor_y] + local preStop = cursor_x - 1 + local preStart = math.min(1, preStop) + local postStart = cursor_x + string.len(writeText) + local postStop = sizeX + local sub = string.sub + + text[scroll_cursor_y] = sub(lineText, preStart, preStop)..writeText..sub(lineText, postStart, postStop) + text_colour[scroll_cursor_y] = sub(lineColor, preStart, preStop)..writeFore..sub(lineColor, postStart, postStop) + back_colour[scroll_cursor_y] = sub(lineBack, preStart, preStop)..writeBack..sub(lineBack, postStart, postStop) + cursor_x = pos + #writeText + end + + function redirect.clear() + if delegate then return delegate.clear() end + + if cursor_threshold > 0 then + return redirect.beginPrivateMode().clear() + end + + local text_line = (" "):rep(sizeX) + local fore_line = (cur_text_colour):rep(sizeX) + local back_line = (cur_back_colour):rep(sizeX) + + for i = scroll_offset + 1, sizeY + scroll_offset do + text[i] = text_line + text_colour[i] = fore_line + back_colour[i] = back_line + end + + if bubble then return original.clear() end + end + function redirect.clearLine() + if delegate then return delegate.clearLine() end + + -- If we're off the screen then just emulate a clearLine + if cursor_y > sizeY or cursor_y < 1 then + return + end + + text[scroll_cursor_y] = string.rep(" ", sizeX) + text_colour[scroll_cursor_y] = string.rep(cur_text_colour, sizeX) + back_colour[scroll_cursor_y] = string.rep(cur_back_colour, sizeX) + + if bubble then return original.clearLine() end + end + + function redirect.getCursorPos() + if delegate then return delegate.getCursorPos() end + return cursor_x, cursor_y + end + + function redirect.setCursorPos(x, y) + if delegate then return delegate.setCursorPos(x, y) end + + if type(x) ~= "number" then error("bad argument #1 (expected number, got " .. type(x) .. ")", 2) end + if type(y) ~= "number" then error("bad argument #2 (expected number, got " .. type(y) .. ")", 2) end + + local new_y = math.floor(y) + if new_y >= 1 and new_y < cursor_threshold then + -- If we're writing within a protected region then start a private buffer + return redirect.beginPrivateMode().setCursorPos(x, y) + end + + cursor_x = math.floor(x) + cursor_y = new_y + scroll_cursor_y = new_y + scroll_offset + + if bubble then return original.setCursorPos(x, y) end + end + + function redirect.setCursorBlink(b) + if delegate then return delegate.setCursorBlink(b) end + + if type(b) ~= "boolean" then error("bad argument #1 (expected boolean, got " .. type(b) .. ")", 2) end + + cursor_blink = b + if bubble then return original.setCursorBlink(b) end + end + + function redirect.getSize() + if delegate then return delegate.getSize() end + + return sizeX, sizeY + end + + function redirect.scroll(n) + if delegate then return delegate.scroll(n) end + + if type(n) ~= "number" then error("bad argument #1 (expected number, got " .. type(n) .. ")", 2) end + + if n > 0 then + scroll_offset = scroll_offset + n + for i = sizeY + scroll_offset - n + 1, sizeY + scroll_offset do + text[i] = string.rep(" ", sizeX) + text_colour[i] = string.rep(cur_text_colour, sizeX) + back_colour[i] = string.rep(cur_back_colour, sizeX) + end + + trim() + elseif n < 0 then + for i = sizeY + scroll_cursor_y, math.abs(n) + 1 + scroll_cursor_y, -1 do + if text[i + n] then + text[i] = text[i + n] + text_colour[i] = text_colour[i + n] + back_colour[i] = back_colour[i + n] + end + end + + for i = scroll_cursor_y, math.abs(n) + scroll_cursor_y do + text[i] = string.rep(" ", sizeX) + text_colour[i] = string.rep(cur_text_colour, sizeX) + back_colour[i] = string.rep(cur_back_colour, sizeX) + end + end + + cursor_threshold = cursor_threshold - n + + if bubble then return original.scroll(n) end + end + + function redirect.setTextColour(clr) + if delegate then return delegate.setTextColour(clr) end + + if type(clr) ~= "number" then error("bad argument #1 (expected number, got " .. type(clr) .. ")", 2) end + cur_text_colour = colour_lookup[clr] or error("Invalid colour (got " .. clr .. ")" , 2) + if bubble then return original.setTextColour(clr) end + end + redirect.setTextColor = redirect.setTextColour + + function redirect.setBackgroundColour(clr) + if delegate then return delegate.setBackgroundColour(clr) end + + if type(clr) ~= "number" then error("bad argument #1 (expected number, got " .. type(clr) .. ")", 2) end + cur_back_colour = colour_lookup[clr] or error("Invalid colour (got " .. clr .. ")" , 2) + if bubble then return original.setBackgroundColour(clr) end + end + redirect.setBackgroundColor = redirect.setBackgroundColour + + function redirect.isColour() + if delegate then return delegate.isColour() end + return color == true + end + redirect.isColor = redirect.isColour + + function redirect.getTextColour() + if delegate then return delegate.getTextColour() end + return 2 ^ tonumber(cur_text_colour, 16) + end + redirect.getTextColor = redirect.getTextColour + + function redirect.getBackgroundColour() + if delegate then return delegate.getBackgroundColour() end + return 2 ^ tonumber(cur_back_colour, 16) + end + redirect.getBackgroundColor = redirect.getBackgroundColour + + if original.getPaletteColour then + function redirect.setPaletteColour(colour, r, g, b) + if delegate then return delegate.setPaletteColour(colour, r, g, b) end + + local palcol = palette[colour] + if not palcol then error("Invalid colour (got " .. tostring(colour) .. ")", 2) end + if type(r) == "number" and g == nil and b == nil then + palcol[1], palcol[2], palcol[3] = colours.rgb8(r) + else + if type(r) ~= "number" then error("bad argument #2 (expected number, got " .. type(r) .. ")", 2) end + if type(g) ~= "number" then error("bad argument #3 (expected number, got " .. type(g) .. ")", 2) end + if type(b) ~= "number" then error("bad argument #4 (expected number, got " .. type(b) .. ")", 2) end + + palcol[1], palcol[2], palcol[3] = r, g, b + end + + if bubble then return original.setPaletteColour(colour, r, g, b) end + end + redirect.setPaletteColor = redirect.setPaletteColour + + function redirect.getPaletteColour(colour) + if delegate then return delegate.getPaletteColour(colour) end + + local palcol = palette[colour] + if not palcol then error("Invalid colour (got " .. tostring(colour) .. ")", 2) end + return palcol[1], palcol[2], palcol[3] + end + redirect.getPaletteColor = redirect.getPaletteColour + end + + function redirect.draw(offset, clear) + if delegate then return end + + if original.getPaletteColour then + for colour, pal in pairs(palette) do + original.setPaletteColour(colour, pal[1], pal[2], pal[3]) + end + end + + original.setTextColour(2 ^ tonumber(cur_text_colour, 16)) + original.setBackgroundColor(2 ^ tonumber(cur_back_colour, 16)) + if clear then original.clear() end + + local original = original + local scroll_offset = scroll_offset + (offset or 0) + for i = 1, sizeY do + original.setCursorPos(1,i) + local yOffset = scroll_offset + i + original.blit(text[yOffset], text_colour[yOffset], back_colour[yOffset]) + end + + original.setCursorPos(cursor_x, cursor_y - offset) + original.setCursorBlink(cursor_blink) + end + + function redirect.bubble(b) + bubble = b + end + + function redirect.setCursorThreshold(y) + cursor_threshold = y + end + + function redirect.endPrivateMode(redraw) + if delegate then + local old_delegate = delegate + delegate = nil + redirect.draw(0) + + -- If we should redraw the old buffer then blit it to the canvas + if redraw then + if cursor_threshold > 0 then + redirect.scroll(cursor_threshold) + end + + old_delegate.draw(redirect) + end + end + end + + function redirect.beginPrivateMode() + if not delegate then + delegate = blit_window.create(original) + + for y = 1, sizeY do + delegate.setCursorPos(1, y) + delegate.blit(text[y + scroll_offset], text_colour[y + scroll_offset], back_colour[y + scroll_offset]) + end + + delegate.setCursorPos(cursor_x, cursor_y) + delegate.setCursorBlink(cursor_blink) + delegate.setTextColour(2 ^ tonumber(cur_text_colour, 16)) + delegate.setBackgroundColor(2 ^ tonumber(cur_back_colour, 16)) + + if original.getPaletteColour then + for i = 0, 15 do + local palcol = palette[2 ^ i] + delegate.setPaletteColour(2 ^ i, palcol[1], palcol[2], palcol[3]) + end + end + end + + return delegate + end + + function redirect.isPrivateMode() + return delegate ~= nil + end + + function redirect.getTotalHeight() return scroll_offset end + + function redirect.setMaxScrollback(n) + local old_scrollback = max_scrollback + max_scrollback = n + + if old_scrollback > max_scrollback then trim() end + end + + function redirect.updateSize() + -- Update the delegate window. + if delegate then delegate.updateSize() end + + -- If nothing has changed then just skip. + local new_x, new_y = original.getSize() + if new_x == sizeX and new_y == sizeY then return end + + -- If we have an insufficient number of lines then add some in. + local total_height = #text + + -- For any existing lines, trim them + for y = 1, total_height do + if new_x < sizeX then + text[y] = text[y]:sub(1, new_x) + text_colour[y] = text_colour[y]:sub(1, new_x) + back_colour[y] = back_colour[y]:sub(1, new_x) + elseif new_x > sizeX then + text[y] = text[y] .. (" "):rep(new_x - sizeX) + text_colour[y] = text_colour[y] .. (cur_text_colour):rep(new_x - sizeX) + back_colour[y] = back_colour[y] .. (cur_back_colour):rep(new_x - sizeX) + end + end + + if new_y > sizeY then + -- Append any new lines we might need. + local text_line = (" "):rep(new_x) + local fore_line = (cur_text_colour):rep(new_x) + local back_line = (cur_back_colour):rep(new_x) + for y = total_height + 1, new_y do + text[y] = text_line + text_colour[y] = fore_line + back_colour[y] = back_line + end + elseif new_y < sizeY then + -- Move the cursor "up" the screen, as we're going to scroll the rest of + -- the terminal up. + -- Note, this is a little ugly (we lose the top of the screen even if we) + -- don't need to, but it's the best we can do for now. + cursor_y = cursor_y - sizeY + new_y + end + + sizeX = new_x + sizeY = new_y + + -- Update the scroll offset. For now we just go back to the bottom + scroll_offset = #text - sizeY + scroll_cursor_y = scroll_offset + cursor_y + trim() + end + + redirect.clear() + return redirect +end
\ No newline at end of file |
