(Latest update) This discussion relates to the first attempts to develop code to redirect print()
output from the output pane to the viewer. The most recent version of the code has grown and has been decamped to a new discussion here.
Very cool. I bet this is much faster than the built in output window, too.
I am going to edit my initial post, because I believe that pushStyle()
preserves more than is currently documented in the Codea in-app reference, based on the definition of GraphicsStyle()
in the Codea Runtime Library, including text and font-related matters.
@mpilgrem that sounds like a documentation bug. I should have documented that pushStyle() preserves font and text settings, I overlooked it.
I may start using this as it allows me to test in full screen and still get my debug text that I print out. Thanks for sharing.
Thank you! I’ve updated the code in my original comment above to allow the font to be set, allow wrapping, tolerate changes of orientation (text already redirected does not re-wrap on changed orientation) and change slightly the appearance of the scroll bar.
(Update) A more elaborate example of its use has been moved to this discussion here.
This is really awesome @mpilgrem. I’ve been debugging a plist parser and printing out the parsed output to the console was an annoying wait at best, and caused Codea to crash at the worst. Thanks to this I was able to print out the output from really long plists to debug them without issue.
I have further updated the code in the original comment to correct an error - I forgot to apply the clean
function to string l
before processing it. Apologies. (Update) In addition, I have now corrected clear
and process
to deal with empty lines and added a visual cue to the region captured by the scroll bar.
This is really great @mpilgrem. I love this functionality, because I want to see my logs in fullscreen.
I have made two changes to it for my usage which might be interesting to others, so I post them:
-
Draw the text first in black then in white with 1 pixel shift in x and y → Better readabilty on light backgrounds.
-
Added a close button to hide the log. When the next log is added, it is shown again.
I have marked all changes with -->, see below.
Kilam.
...
local scrollBarTop = 0
-- New variable to define if the log should be shown:
--> local showOutput = false
local output = {}
...
l = clean(l).."\
" -- Enhanced
process(l)
resetFirstLine()
-- Whenever the print function is called, set the log to be shown:
--> showOutput = true
end
local olddraw = draw
draw = function ()
olddraw()
-- Jump out of the draw function, if it should not be shown currently:
--> if not showOutput then
--> return
--> end
pushStyle()
...
for i = fli, lli do
local l = output[i]
h = h - lineHeight
-- Draw the text black and 1 pixel shifted before drawing it in white. Gives better contrast on different colors.
--> fill(0, 0, 0, 113)
--> text(l, 1, h - 1)
fill(255, 255, 255, 255)
text(l, 0, h)
end
...
if scrollBarVisible then
local l = scrollBarLength
fill(127, 127, 127, 255)
noStroke()
local t = scrollBarTop
rectMode(CORNER)
rect(WIDTH - 9, t - l, 8, l)
ellipseMode(CENTER)
ellipse(WIDTH - 5, t, 8, 8)
ellipse(WIDTH - 5, t - l, 8, 8)
end
-- Draw the close button:
--> fill(127, 127, 127, 127)
--> rectMode(CORNER)
--> rect(WIDTH - 30, HEIGHT - 20, 20, 20)
--> stroke(229, 27, 27, 127)
--> strokeWidth(5)
--> lineCapMode(SQUARE)
--> smooth()
--> line(WIDTH - 30, HEIGHT, WIDTH - 10, HEIGHT - 20)
--> line(WIDTH - 30, HEIGHT - 20, WIDTH - 10, HEIGHT)
popStyle()
end
...
touched = function (touch)
--> local state = touch.state -- moved up two lines
if #output > heightInLines then
if state == ENDED and scrollBarVisible then
scrollBarVisible = false
return
end
...
end
-- Set the show flag to false if the close button is touched:
--> if math.abs((WIDTH - 20) - touch.x) < 10 and
--> math.abs((HEIGHT - 10) - touch.y) < 10 then
--> showOutput = false
--> return
--> end
if oldtouched then oldtouched(touch) end
end
end
...
Thank you, @KilamMalik. I have updated the code in my first post for your excellent extensions, with some minor variations. (Update) The updated code is available at the start of this discussion here.
Cool, I like that close button. Thanks. Maybe I will have another addition to set the position and size of the logging. Then I can combine it with my watcher view.
Hi @mpilgrem, I have made another addition to your code. As I want to view your logging overlay next to another overlay from me, I need to pass the position and size of the logging view. It works as far as I can see, but I did not fully understand the scrollbar calculations, so I had to trial and error a bit.
Also I have added a transparent rectangle as background and a title bar. Maybe you like to add these additions. I will post my watch overlay soon, so people could use both at the same time.
Sample call:
redirect.setLogRect(10, 35, 500, 450)
redirect.setLogAlpha(60)
Hmmm, I cant post the full Source, it says body 2300 characters too long. How did you add your code then, because I did not add that much lines
Splitting into two comments:
--
-- Redirect, version 2012.07.09.23.00
--
-- Use redirect.on() to turn redirection on, and
-- redirect.off() to turn it off. Use redirect.font(name, size) to
-- set the font used, before first use.
--
-- These variables hold the font used. For the tab function to
-- make sense, a fixed-width font is best.
local outputFont = "Inconsolata"
local outputFontSize = 17
-- This variable determines how much of the right of the
-- viewer will take over the scroll bar touches.
local scrollBarTolerance = 10
local initialised = false
local stream = false
local isWrap = false
local firstLine = 0
local heightInLines = 1
local lineHeight = 0
local scrollBarVisible = false
local scrollBarLength = 0
local scrollBarTop = 0
local showOutput = false
local logRect = {x=0, y=0, w=WIDTH, h=HEIGHT} -- Viewing rectangle.
local logAlpha = 127 -- Transparency of viewing rectangle
local output = {}
-- A private function to assist with setting the line height
local setLineHeight = function ()
pushStyle()
font(outputFont)
fontSize(outputFontSize)
dummy, lineHeight = textSize("text")
popStyle()
heightInLines = math.floor(logRect.h/lineHeight)-1 -- -1 because of title.
end
-- A private function to assist with setting the first line on screen
local resetFirstLine = function ()
if #output > heightInLines then
firstLine = #output - heightInLines
scrollBarLength = (logRect.h - lineHeight) *
heightInLines/#output
scrollBarTop = logRect.y + logRect.h - lineHeight - (logRect.h - lineHeight) * firstLine/#output
end
end
-- A private function to clean up a string 's'
local clean = function (s)
-- Remove all control characters other than carriage returns (\\r),
-- new lines (\
) and tabs (\\t)
s = string.gsub(s, "[^%C\\r\
\\t]", "")
-- Replace \\r\
by \
, and eliminate any leading spaces
s = string.gsub(s, "[% \\t]*\\r\
", "\
")
-- Replace \
\\r by \
, and eliminate any leading spaces
s = string.gsub(s, "[% \\t]*\
\\r", "\
")
-- Replace remaining \\r by \
s = string.gsub(s, "\\r", "\
")
-- Eliminate any leading spaces before \
s = string.gsub(s, "[% \\t]*\
", "\
")
return s
end
-- A private function to check if a string 's' will fit if rendered to
-- the screen
local tooWide = function (s)
pushStyle()
font(outputFont)
fontSize(outputFontSize)
local tooWide = textSize(s) > logRect.w
popStyle()
return tooWide
end
-- A private function to write out a string 'l' with wrapping. It
-- returns any tail that does not fit.
local write = function (l)
local p = #l
while (tooWide(string.sub(l, 1, p))) do
p = p - 1
end
output[#output + 1] = string.sub(l, 1, p)
return string.sub(l, p + 1)
end
-- A private function to process a string 's' that may contain new
-- line (\
) or tab (\\t) characters
local process = function (s)
-- Separate s into lines 'l' ending in \
for l in string.gmatch(s, "([^\
]*)\
") do
local b = ""
local p1 = 1
while p1 <= #l do
if string.sub(l, p1, p1)=="\\t" then
local temp = b..string.rep(" ", 8 - #b % 8)
if tooWide(temp) then
write(b)
else
b = temp
end
p1 = p1 + 1
else
local p2
p1, p2 = string.find(l, "[^\\t]+", p1)
b = b..string.sub(l, p1, p2)
while tooWide(b) do
b = write(b)
end
p1 = p2 + 1
end
end
write(b)
end
end
local init = function ()
local dummy
initialised = true
setLineHeight()
local oldprint = print
print = function (...)
if not stream then oldprint(unpack(arg)) return end
local l = ""
for i, v in ipairs(arg) do
if i > 1 then
l = l.."\\t"
end
l = l .. tostring(v)
end
l = clean(l).."\
"
process(l)
resetFirstLine()
showOutput = true
end
Part two:
local olddraw = draw
draw = function ()
olddraw()
if not showOutput then return end
pushStyle()
rectMode(CORNER)
ellipseMode(CENTER)
noStroke()
-- Draw background
fill(0, logAlpha)
rect(logRect.x, logRect.y, logRect.w, logRect.h)
-- Draw title
rect(logRect.x, logRect.y + logRect.h - lineHeight, logRect.w, lineHeight)
font(outputFont)
fontSize(outputFontSize)
textMode(CORNER)
textAlign(LEFT)
textWrapWidth(0)
fill(255)
text("Logging", logRect.x, logRect.y + logRect.h - lineHeight)
if #output > heightInLines then
fill(127, 127)
rect(logRect.x + logRect.w - scrollBarTolerance, logRect.y,
scrollBarTolerance, logRect.h - lineHeight)
end
local h = logRect.y + logRect.h - lineHeight
local fli = firstLine + 1
local lli = math.min(#output, firstLine + heightInLines)
for i = fli, lli do
local l = output[i]
h = h - lineHeight
fill(0, 127)
text(l, logRect.x + 1, h - 1)
fill(255)
text(l, logRect.x, h)
end
if scrollBarVisible then
local l = scrollBarLength
fill(127, 255)
local t = scrollBarTop + logRect.y
rect(logRect.x + logRect.w - 9, t - l, 8, l)
ellipse(logRect.x + logRect.w - 5, t, 8, 8)
ellipse(logRect.x + logRect.w - 5, t - l, 8, 8)
end
-- Draw an OSX-like red close button
local ex = logRect.x + logRect.w
local ey = logRect.y + logRect.h
fill(0)
ellipse(ex - 8, ey - 8, 16, 16)
fill(255, 0, 0)
ellipse(ex - 8, ey - 8, 14, 14)
fill(255)
ellipse(ex - 8, ey - 4, 7, 4)
fill(255, 127)
ellipse(ex - 8, ey - 12, 9, 6)
popStyle()
end
local oldorientationchanged = orientationChanged
orientationChanged = function(orientation)
if oldorientationchanged then
oldorientationchanged(orientation)
end
heightInLines = math.floor(logRect.h/lineHeight)-1
resetFirstLine()
end
local oldtouched = touched
touched = function (touch)
local state = touch.state
if #output > heightInLines then
if state == ENDED and scrollBarVisible then
scrollBarVisible = false
return
end
if state == BEGAN then
local sbxCenter = (logRect.x + logRect.w - scrollBarTolerance / 2)
if touch.x < sbxCenter + 5 and touch.x > sbxCenter - 5 and
touch.y < (logRect.y + logRect.h - lineHeight) and
touch.y > logRect.y then
scrollBarLength = (logRect.h - 26) *
heightInLines/#output
scrollBarTop = logRect.h - 21 - (logRect.h - 26) *
firstLine/#output
scrollBarVisible = true
return
end
end
if state == MOVING and scrollBarVisible then
local t = scrollBarTop + touch.deltaY
t = math.min(t, logRect.h - 21)
t = math.max(t, scrollBarLength + 5)
scrollBarTop = t
firstLine = #output - math.floor(#output * (t - 5)
/(logRect.h - 26))
return
end
end
-- This also has == ENDED because if there is a dbg.print in the main touch, then
-- this stops the reopen of the dialog because of the return:
if (state == BEGAN or state == ENDED) then
-- Touch area is larger than the small button to touch it easier (24x24):
local ex = logRect.x + logRect.w - 8
local ey = logRect.y + logRect.h - 8
if math.abs(ex - touch.x) <= 12 and math.abs(ey - touch.y) <= 12 then
showOutput = false
return
end
end
if oldtouched then oldtouched(touch) end
end
end
local on = function (w)
if not initialised then init() end
stream = true
isWrap = w or false
end
local off = function ()
if not initialised then init() end
stream = false
end
local font = function (fName, fSize)
if not fName then return outputFont, outputFontSize end
if not fSize then fSize = 17 end
if not initialised then
outputFont = fName
outputFontSize = fSize
init()
end
end
local setLogRect = function(x, y, w, h)
logRect.x = x
logRect.y = y
logRect.w = w
logRect.h = h
end
local setLogAlpha = function(a)
logAlpha = a
end
redirect = {
on = on,
off = off,
font = font,
setLogRect = setLogRect,
setLogAlpha = setLogAlpha
}
Sorry, had a bug in the changes. Just fixed the two comments above ( last line was not printed).
Hello @KilamMalik. I also hit the limit for comments - so my commentary became shorter as the code grew longer. I am going to try to fix that by decamping to a new discussion here. What I have in mind will take more than one step, so please bear with me; I’ve not yet incorporated your most recent suggestions.