This is a read-only snapshot of the ComputerCraft forums, taken in April 2020.
Himself12794's profile picture

Help with a scrolling multiple choice selection

Started by Himself12794, 12 February 2014 - 04:35 PM
Himself12794 #1
Posted 12 February 2014 - 05:35 PM
I'm writing a function to allow selection from a list of options, returning the index of the selected choice. At first, it only worked if the length of the items fit onto the page, then I modified it, so now it can do a list if the items fit on a second page, at the cost of losing the header. However, anything larger than two pages just does weird things. Anyone know how I can fix it?
Here's the code:
Spoiler

local function header(banner)
    term.clear()
    term.setCursorPos(1,1)
    print("HimCo Industries' Experimental OS")
    --print(loadFromFile('session','./config'),' logged into Computer ID #',os.getComputerID())
    print()
    printCentered(banner)
    print()
end
 
local function lineClear()
    local _,y=term.getCursorPos()
    term.clearLine()
    term.setCursorPos(1,y)
end
 
function printCentered(str)
    local termX, _termY=term.getSize()
    local _cursX, cursY=term.getCursorPos()
    local newX = math.ceil(termX/2)-math.ceil(string.len(str)/2)
    term.setCursorPos(newX,cursY)
    print(str)
end

local function multipleChoice(choices, sel,escape,delete)
    local shiftList=false
    local escape=escape or 'startup'
    local size = #choices
    local sel = sel or 1 --initial selection
    local xsize,ysize=term.getSize()
    local xcoord,ycoord=term.getCursorPos()
    
    local diff=ysize-ycoord
    if diff>#choices then
        shiftList=true
    end
    
    while true do
        if sel<=diff then
            term.setCursorPos(xcoord,ycoord)
            for i,v in ipairs(choices) do
                lineClear()
                if i == sel then
                    printCentered('[ '..v..' ]')
                elseif i <= diff then
                    printCentered('  '..v..'  ')
                end
            end
        else
            for i,v in ipairs(choices) do
                printCentered('')
            end
            local over=diff-sel
            term.setCursorPos(xcoord,ycoord)
            for i,v in ipairs(choices) do
                lineClear()
                if i == sel then
                    printCentered('[ '..v..' ]')
                elseif i >= sel+over then
                    printCentered('  '..v..'  ')
                end
            end
        end
            
        --user input
        local event,key=os.pullEvent('key')
        if key == 208 --down arrow
        or key == 31 then -- S
            sel = sel+1
            if sel > size then
                sel = 1 --bottom reached, moving back to top
            end
        elseif key == 200 -- up arrow
        or key == 17 then -- W
            sel = sel-1
            if sel < 1 then
                sel = size -- top reached, moving to bottom
            end
        elseif key == 28 -- enter
        or key == 57 then --space
            return sel --input complete
        elseif key == 14 then -- backspace
            shell.run(escape)
            shell.exit()
            --os.reboot()
        elseif key == 211 and delete then -- Returns true as well as selection when delete is pressed
            return sel,true
        end
    end
end
header('What?')
choices={'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','aa','ab','ac','ad','ae','af'}
choice=multipleChoice(choices)
Help is appreciated.
Bomb Bloke #2
Posted 12 February 2014 - 06:09 PM
I haven't tested either your code nor mine, but best I can make out, rather then this:

Spoiler
        if sel<=diff then
            term.setCursorPos(xcoord,ycoord)
            for i,v in ipairs(choices) do
                lineClear()
                if i == sel then
                    printCentered('[ '..v..' ]')
                elseif i <= diff then
                    printCentered('  '..v..'  ')
                end
            end
        else
            for i,v in ipairs(choices) do
                printCentered('')
            end
            local over=diff-sel
            term.setCursorPos(xcoord,ycoord)
            for i,v in ipairs(choices) do
                lineClear()
                if i == sel then
                    printCentered('[ '..v..' ]')
                elseif i >= sel+over then
                    printCentered('  '..v..'  ')
                end
            end
        end

… you may want something like this:

Spoiler
	term.setCursorPos(xcoord,ycoord)
	local start = math.floor(sel / diff) * diff
	for i=start+1,start+diff do
		lineClear()
		if i <= size then
			if i == sel then
				printCentered('[ '..choices[i]..' ]')
			elseif i <= diff then
				printCentered('  '..choices[i]..'  ')
			end
		end
	end

Also, the keys API is worth a look. It allows you to rewrite stuff like this:

        if key == 208 --down arrow
        or key == 31 then -- S

… like this:

        if key == keys.down or key == keys.s then

Using these pre-defined constant variables stored in that API means you don't need to work out the numeric codes yourself.
Himself12794 #3
Posted 12 February 2014 - 06:35 PM
I haven't tested either your code nor mine, but best I can make out, rather then this:

Spoiler
		if sel<=diff then
			term.setCursorPos(xcoord,ycoord)
			for i,v in ipairs(choices) do
				lineClear()
				if i == sel then
					printCentered('[ '..v..' ]')
				elseif i <= diff then
					printCentered('  '..v..'  ')
				end
			end
		else
			for i,v in ipairs(choices) do
				printCentered('')
			end
			local over=diff-sel
			term.setCursorPos(xcoord,ycoord)
			for i,v in ipairs(choices) do
				lineClear()
				if i == sel then
					printCentered('[ '..v..' ]')
				elseif i >= sel+over then
					printCentered('  '..v..'  ')
				end
			end
		end

… you may want something like this:

Spoiler
	term.setCursorPos(xcoord,ycoord)
	local start = math.floor(sel / diff) * diff
	for i=start+1,start+diff do
		lineClear()
		if i <= size then
			if i == sel then
				printCentered('[ '..choices[i]..' ]')
			elseif i <= diff then
				printCentered('  '..choices[i]..'  ')
			end
		end
	end

Also, the keys API is worth a look. It allows you to rewrite stuff like this:

		if key == 208 --down arrow
		or key == 31 then -- S

… like this:

		if key == keys.down or key == keys.s then

Using these pre-defined constant variables stored in that API means you don't need to work out the numeric codes yourself.
That does help maintain the header, but there's still weirdness with printing the options.
Himself12794 #4
Posted 12 February 2014 - 11:38 PM
So I made something that works, but not exactly as I wish for it too. I don't wish for the values to scroll until selection hits the bottom, but this code scrolls through the rest of values until they no longer fill the page. It's a fair compromise for what I want, but I will work until I get the results.
Here's the code for this:
Spoiler

local function multipleChoice(choices, sel,escape,delete)
    local escape=escape or 'startup'
    local size = #choices
    local sel = sel or 1 --initial selection
    local xsize,ysize=term.getSize()
    local xcoord,ycoord=term.getCursorPos()
    
    local diff=ysize-ycoord
    
    while true do
        term.setCursorPos(xcoord,ycoord)
        -- if sel<=diff then
            -- term.setCursorPos(xcoord,ycoord)
        if sel>=diff then
            
        for i=sel, sel+diff-1 do
            lineClear()
            if i == sel then
                printCentered('[ '..choices[i]..' ]')
            elseif i<=size then
                printCentered('  '..choices[i]..'  ')
            else
                printCentered('')
            end
        end        
        --user input
        local event,key=os.pullEvent('key')
        if key == 208 --down arrow
        or key == 31 then -- S
            sel = sel+1
            if sel > size then
                sel = 1 --bottom reached, moving back to top
            end
        elseif key == 200 -- up arrow
        or key == 17 then -- W
            sel = sel-1
            if sel < 1 then
                sel = size -- top reached, moving to bottom
            end
        elseif key == 28 -- enter
        or key == 57 then --space
            return sel --input complete
        elseif key == 14 then -- backspace
            shell.run(escape)
            shell.exit()
            --os.reboot()
        elseif key == 211 and delete then -- Returns true as well as selection when delete is pressed
            return sel,true
        end
    end
end
Himself12794 #5
Posted 12 February 2014 - 11:58 PM
I got it! Runs just as I wanted!
Here's the code:
Spoiler

local function header(banner)
	term.clear()
	term.setCursorPos(1,1)
	print("HimCo Industries' Experimental OS")
	--print(loadFromFile('session','./config'),' logged into Computer ID #',os.getComputerID())
	print()
	printCentered(banner)
	print()
end

local function lineClear()
	local _,y=term.getCursorPos()
	term.clearLine()
	term.setCursorPos(1,y)
end

function printCentered(str)
	local termX, _termY=term.getSize()
	local _cursX, cursY=term.getCursorPos()
	local newX = math.ceil(termX/2)-math.ceil(string.len(str)/2)
	term.setCursorPos(newX,cursY)
	print(str)
end

local function multipleChoice(choices, sel,escape,delete)
	local escape=escape or 'startup'
	local size = #choices
	local sel = sel or 1 --initial selection
	local xsize,ysize=term.getSize()
	local xcoord,ycoord=term.getCursorPos()
	
	local diff=ysize-ycoord
	local interval=1
	while true do
		term.setCursorPos(xcoord,ycoord)
		
		if sel>diff then
			interval=sel-diff+1
		else
			interval=1
		end
		for i=interval, interval+diff-1 do			
			lineClear()
			if i == sel then
				printCentered('[ '..choices[i]..' ]')
			elseif i<=size then
				printCentered('  '..choices[i]..'  ')
			end
		end		
		--user input
		local event,key=os.pullEvent('key')
		if key == 208 --down arrow
		or key == 31 then -- S
			sel = sel+1
			if sel > size then
				sel = 1 --bottom reached, moving back to top
			end
		elseif key == 200 -- up arrow
		or key == 17 then -- W
			sel = sel-1
			if sel < 1 then
				sel = size -- top reached, moving to bottom
			end
		elseif key == 28 -- enter
		or key == 57 then --space
			return sel --input complete
		elseif key == 14 then -- backspace
			shell.run(escape)
			shell.exit()
			--os.reboot()
		elseif key == 211 and delete then -- Returns true as well as selection when delete is pressed
			return sel,true
		end
	end
end
Edited on 12 February 2014 - 11:56 PM