Vertical Scrolling Utility (now also an API…)


In answering a question, I decided to write a utility to allow for the vertical scrolling of text to display on a monitor, computer or turtle (thanks to xbsktball10x for the idea). I dusted off a few routines I had developed for some user interfaces in other programs, and added the refreshDisplay() function and scrollPrint() function to finish it all up. The code is in the spoiler below. The same code (as an API) is in the second spoiler.

Vertical Scroll Program

--[[
Vertical Scrolling Text
by Surferpup
]]
--[[
	TO DO
		Finish Color Aspects
		More Error Checking
		Convert to API	
]]
function setOutput(_device)
	local result,width,height = pcall(function () return _device.getSize() end)
	if not result then
		print "setOutput:  The output device specified is not a monitor or terminal"
		error()
	end
	return _device,width,height
end
function setMaxWidth(_textArray)
	local maxWidth = 0
	for i = 1,#_textArray do
		if (maxWidth < #(_textArray[i])) then
			maxWidth = #(_textArray[i])
		end
	end
	return maxWidth
end
function setScale(_maxWidth,_device)
	if not (pcall(function () _device.setTextScale(5) end)) then
		return false
	else
		local scale = 5
		local function checkScale()
			local thisWidth,temp
			thisWidth,temp = _device.getSize()
			return (_maxWidth > thisWidth)
		end
		while checkScale() and scale >= 1 do
			scale = scale - 0.5
			_device.setTextScale(scale)
		end
		return true,_device.getSize()
	end
end	  

function refreshDisplay(_startY,_startIndex,_text,_device,_width,_height)
	if not (_width and _height) then
		_width, _height = _device.getSize()
	end
	for y = _startY,1,-1 do
		centerPrint(_text[_startIndex],_width,y,_device)
		_startIndex = _startIndex-1
		if _startIndex < 1 then break end
	end
end

function centerPrint(_text,_width,_yPosition,_output,_highlight,_highlightBackGroundColor,_highlightTextColor,_defaultBackgroundColor,_defaultTextColor)
	_text = _text or ""
	_output = _output or term
	if not _width then
		_width,_ = _output.getSize()
	end
	_yPosition = _yPosition or 1
	_highlight = _highlight or 0

	if not _output.isColor() then
		_highlightBackgroundColor = colors.white
		_highlightTextColor = colors.black
		_defaultBackgroundColor = colors.black
		_defaultTextColor= colors.white
	else
		_highlightBackgroundColor = _highlightBackgroundColor or colors.white
		_highlightTextColor = _highlightTextColor or colors.black
		_defaultBackgroundColor = _defaultBackgroundColor or colors.black
		_defaultTextColor= _defaultTextColor or colors.white
	end
	local xPosition = math.floor(_width/2) - (#_text/2)
	if xPosition < 1  then
		xPosition=1
	end
	_output.setCursorPos(xPosition,_yPosition)
	if _highlight == true then
		_output.setBackgroundColor(_highlightBackgroundColor)
		_output.setTextColor(_highlightTextColor)
	else
		_output.setBackgroundColor(_defaultBackgroundColor)
		_output.setTextColor(_defaultTextColor)
	end
	_output.write(_text)
	_output.setBackgroundColor(_defaultBackgroundColor)
	_output.setTextColor(_defaultTextColor)
end

function scrollPrint(_textArray,  -- the text table of strings to print
					_startingVerticalPosition, -- the y-position to begin on the monitor
					_maximumVerticalPosition, -- the maximum y-position to go to
					_sleepInterval, -- delay between printed lines
					_outputDevice) -- the output device (term or monitor peripheral)


	_textArray = _textArray or {" ";}
	_outputDevice = _outputDevice or term
	_outputDevice,_,_ = setOutput(_outputDevice)
	_sleepInterval = _sleepInterval or 1
	local maxWidth = setMaxWidth(_textArray)
	local result,width,height = setScale(maxWidth,_outputDevice)
	if not result then
		width,height = _outputDevice.getSize()
	end
	_startingVerticalPostition = _startingVerticalPostition or 1
	_maximumVerticalPosition = _maximumVerticalPosition or height
	if _startingVerticalPosition > height then
		_startingVerticalPosition = math.floor(height/2)
	end
	if _maximumVerticalPosition &amp;gt; height then
		_maximumVerticalPosition = height
	end
	for i = 1,#_textArray do

		if _startingVerticalPosition >=_maximumVerticalPosition then
			_startingVerticalPosition = _maximumVerticalPosition
		else
			_startingVerticalPosition=_startingVerticalPosition+1
		end
		for j = 1,_startingVerticalPosition do
			_outputDevice.setCursorPos(1,j)
			_outputDevice.write(string.rep(" ",width))
		end
		refreshDisplay(_startingVerticalPosition,i,_textArray,_outputDevice,2)
		sleep(_sleepInterval)
	end
end

--Main Program Code Block  begins here

-- HERE IS THE LONG TABLE OF STRINGS:

local myDemoTable = {
"This is a vertical scrolling example.";
"";
"The text that is printing is taken from the table of strings";
"the name of witch is passed as the first argument to the";
"scrollPrint() function.";
" ";
"The fourth argument to the scroll print function is the";
"sleep interval, which determines how long the program will pause";
"between printed lines of text.";
"";
"The final argument to the scrollPrint function is the output device.";
"This can be an attached or wired monitor, or the term of the";
"computer or turtle.  The program will test to make sure an";
"allowed device is given for this argument.";
"";
"There are defaults for all arguments.";
"";
"The text defaults to a single entry table of one blank string.";
"";
"The starting vertical position defaults to 1 if not supplied,";
"or 1/2 the height of the output device if a value greater than";
"the output device height is given.  Also, if the starting vertical";
"position is greater than the maximum vertical position,";
"the starting vertical position will be set to equal the  maximum";
"vertical position.";
"";
"The maximum vertical position defaults to the output device";
"height if not supplied,  or the output device height if the";
"maximum vertical position is greater than the height.";
" ";
"The sleep interval defaults to 1 second.";
"";
"The output device defaults to term if not supplied.";
" ";
"There is error checking which will stop the program if";
"a device other than a computer, terminal or monitor";
"is specified.";
"";
"Only the portions of the monitor or terminal that require";
"updating are updated.  This means that any text or graphics";
"you place below the maximum vertical position will be left alone";
"by this program.";
"";
"The utility auto scales monitors to the best of its ability (obviously";
"auto-scaling does not apply to term devices on computers or turtles).";
"When auto-scaling, the utility will search all of the text strings in the";
"supplied table, and it will determine the maximum length required";
"to print the table strings.  It will select the smallest scale";
"from 0.5-5.0 which will allow the longest string to print.";
"If there is a string that is too long to print even at the";
"smallest scale, it will left-justify the string and print what it can.";
"";
"All text is centered horizontally.  A later update may give options on this.";
"";
"For now, the program does not handle color, although if you inspect";
"the code, you will note that I am contemplating adding a color and";
"a highlighting feature.";
"";
"Play around with it, see if it is useful for you.";
" ";
"---";
"";
"Best regards,";
" ";
"Surferpup";
}

scrollPrint(myDemoTable,5,20,1.6,peripheral.wrap("top"))

print ("Done")

API


--[[
Vertical Scrolling Text
by Surferpup
]]
--[[
	TO DO
		Finish Color Aspects
		More Error Checking
]]
local function setOutput(_device)
	local result,width,height = pcall(function () return _device.getSize() end)
	if not result then
		print "setOutput:  The output device specified is not a monitor or terminal"
		error()
	end
	return _device,width,height
end
local function setMaxWidth(_textArray)
	local maxWidth = 0
	for i = 1,#_textArray do
		if (maxWidth < #(_textArray[i])) then
			maxWidth = #(_textArray[i])
		end
	end
	return maxWidth
end
local function setScale(_maxWidth,_device)
	if not (pcall(function () _device.setTextScale(5) end)) then
		return false
	else
		local scale = 5
		local function checkScale()
			local thisWidth,temp
			thisWidth,temp = _device.getSize()
			return (_maxWidth > thisWidth)
		end
		while checkScale() and scale >= 1 do
			scale = scale - 0.5
			_device.setTextScale(scale)
		end
		return true,_device.getSize()
	end
end	  

local function centerPrint(_text,_width,_yPosition,_output,_highlight,_highlightBackGroundColor,_highlightTextColor,_defaultBackgroundColor,_defaultTextColor)
	_text = _text or ""
	_output = _output or term
	if not _width then
		_width,_ = _output.getSize()
	end
	_yPosition = _yPosition or 1
	_highlight = _highlight or false

	if not _output.isColor() then
		_highlightBackgroundColor = colors.white
		_highlightTextColor = colors.black
		_defaultBackgroundColor = colors.black
		_defaultTextColor= colors.white
	else
		_highlightBackgroundColor = _highlightBackgroundColor or colors.white
		_highlightTextColor = _highlightTextColor or colors.black
		_defaultBackgroundColor = _defaultBackgroundColor or colors.black
		_defaultTextColor= _defaultTextColor or colors.white
	end
	local xPosition = math.floor(_width/2) - (#_text/2)
	if xPosition < 1  then
		xPosition=1
	end
	_output.setCursorPos(xPosition,_yPosition)
	if _highlight == true then
		_output.setBackgroundColor(_highlightBackgroundColor)
		_output.setTextColor(_highlightTextColor)
	else
		_output.setBackgroundColor(_defaultBackgroundColor)
		_output.setTextColor(_defaultTextColor)
	end
	_output.write(_text)
end
 
local function refreshDisplay(_startY,_startIndex,_text,_device,_width,_height)
	if not (_width and _height) then
		_width,_heigh = _device.getSize()
	end
	for y = _startY,1,-1 do
		centerPrint(_text[_startIndex],_width,y,_device)
		_startIndex = _startIndex-1
		if _startIndex < 1 then break end
	end
end

-- User-accessible Function

function scrollPrint(_textArray,  -- the text table of strings to print
					_startingVerticalPosition, -- the y-position to begin on the monitor
					_maximumVerticalPosition, -- the maximum y-position to go to
					_sleepInterval, -- delay between printed lines
					_outputDevice) -- the output device (term or monitor peripheral)


	_textArray = _textArray or {" ";}
	_outputDevice = _outputDevice or term
	_outputDevice,_,_ = setOutput(_outputDevice)
	_sleepInterval = _sleepInterval or 1
	local maxWidth = setMaxWidth(_textArray)
	local result,width,height = setScale(maxWidth,_outputDevice)
	if not result then
		width,height = _outputDevice.getSize()
	end
	_startingVerticalPostition = _startingVerticalPostition or 1
	_maximumVerticalPosition = _maximumVerticalPosition or height
	if _startingVerticalPosition > height then
		_startingVerticalPosition = math.floor(height/2)
	end
	if _maximumVerticalPosition > height then
		_maximumVerticalPosition = height
	end
	for i = 1,#_textArray do
		if _startingVerticalPosition>=_maximumVerticalPosition then
			_startingVerticalPosition = _maximumVerticalPosition
		else
			_startingVerticalPosition=_startingVerticalPosition+1
		end
		for j = 1,_startingVerticalPosition do
			_outputDevice.setCursorPos(1,j)
			_outputDevice.write(string.rep(" ",width))
		end
		refreshDisplay(_startingVerticalPosition,i,_textArray,_outputDevice)
		sleep(_sleepInterval)
	end
end

The code is about 125 lines long, and I included myDemoTable table of strings which adds about 70 lines. The main function that drives the utility is the scrollPrint() function. It takes the following arguments:
  • _textArray, – the table of strings to print
  • _startingVerticalPosition – the y-position to begin on the monitor
  • _maximumVerticalPosition, – the maximum y-position to go to
  • _sleepInterval, – delay between printed lines
  • _outputDevice) – the output device (term or monitor peripheral)
I have some built in error checking, although I would like to do more. Also, I started on color support and highlighted text, however, that is not complete in this version. Filally, I think this would make a good API, so I will try and finish this up as well. I tried to make all of the variable names and function names make sense. If a variable is an argument to a function, it begins with an underscore (ie. _argument). If a variable does not begin with an underscore, it is defined as a local variable. There are no global variables in the code.

To use it, insert the functions into your code, create your own table of strings and fill in the main function call. In the example, that call is:


scrollPrint(myDemoTable,5,20,1.7,peripheral.wrap("top"))

In the example, the strings in myDemoTable are vertically scrolled on a 4 wide X 3 high monitor I directly attached to the top of a computer. The first lines starts centered on line 5 of the monitor. It then continues adding lines until it hits line 20 of the monitor. From then on, it prints each new line on line 20 and scrolls the older lines up and out of view (much like a normal terminal display would do). If you want it to start on line 20 and scroll up, change the _startingVerticalPosition to 20. If you want it to use the whole screen, set the _startingVerticalPosition argument to 1 and _maximumVerticalPosition argument to _ (underscore). Ex.


scrollPrint(myDemoTable,1,_,1,peripheral.wrap("top"))

If you want to use the code as an API, simply save off the API version of the code, and call it in your program as follows:


os.loadAPI("VerticalScroll") -- or whatever you saved it as

--Here is a test Text Table

text = {"This is","a test","of the","Vertical Scrolling API."}

-- Here is the call to the driving function

VerticalScroll.scrollPrint(text,7,7,1,term) -- will default to term

-- At end of your program, unload API
os.unloadAPI("VerticalScroll")


I also don't clear the monitor. Instead, I only write over used lines with spaces. I think this allows for a smother display. It also allows for you to have other text (or graphics) on say the bottom half of the monitor while the text scrolls in the top half.

I auto-scale to the largest scale that will fit the width of all of the text strings. If you are using a terminal, autoscale is ignored. If it can't fit text on a line, it left justifies and fits as much as it can.


Let me know if you need help sorting out any of the functions. I sincerely appreciate any feedback you may have. I am always looking for ways to improve. Thanks.

EDIT
  • Cleaned up variables in refreshDisplay() function
  • Created API version of code
  • Turned centerPrint into a local function (thanks Bomb Bloke for answering my question on that)