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

[Custom Read] Weird setCursorPos

Started by Goof, 07 September 2014 - 06:49 PM
Goof #1
Posted 07 September 2014 - 08:49 PM
Hello

I've recently tried to make a custom Object API, which includes a customized Read()

Code:
Spoiler

Object = {}

Object.InputBox = function( _, Tbl )
  Tbl = Tbl or {
	Position = { X = 2, Width = 10, Y = 2, },
	Color = { Background = colors.black, Foreground = colors.yellow, Foreground_After = colors.white, Background_After = colors.black },
	Replace = nil,
  }
  Tbl.Position.Width = Tbl.Position.Width + 1
  Tbl.Current = Tbl.Current or {}
  Tbl.Current.Text = Tbl.Current.Text or ''
  Tbl.Current.TextPos = Tbl.Current.TextPos or 0

  local Width, Height = term.getSize()
  term.setCursorPos( Tbl.Position.X, Tbl.Position.Y )
  term.setCursorBlink( true )

  term.setTextColor( Tbl.Color.Foreground )
  term.setBackgroundColor( Tbl.Color.Background )
  -- Draw to the screen
  local Draw = function( ReplaceChar )
	local Scroll = 0
	if Tbl.Position.X + Tbl.Current.TextPos >= Tbl.Position.X + Tbl.Position.Width then
	  Scroll = ( Tbl.Position.X + Tbl.Current.TextPos ) - Tbl.Position.Width
	elseif Tbl.Position.X + Tbl.Current.TextPos >= Width then
	  Scroll = ( Tbl.Position.X + Tbl.Current.TextPos ) - Width
	end

	local _, CursorY = term.getCursorPos()
	term.setCursorPos( Tbl.Position.X, CursorY )
	local ReplaceChar = ReplaceChar or Tbl.Replace
	if ReplaceChar then
	  term.write( string.rep( ReplaceChar, math.max( string.len( Tbl.Current.Text ) - Scroll, 0 ) ) )
	else
	  term.write( string.sub( Tbl.Current.Text, Scroll ) )
	end

	local ExtraScroll = 0
	if Scroll > 0 then
	  ExtraScroll = 1
	end
	term.setCursorPos( Tbl.Position.X + Tbl.Current.TextPos - Scroll + ExtraScroll, CursorY )
  end

  -- Event handling
  while true do
	local Event, Param = coroutine.yield( )
	if Event == 'Object:Pause' then
	  -- Paused
	  term.setCursorBlink( false )
	  coroutine.yield( 'Object:Resume' )
	  term.setCursorBlink( true )
	elseif Event == 'char' then
	  -- Typed key
		Tbl.Current.Text = string.sub( Tbl.Current.Text, 1, Tbl.Current.TextPos ) .. Param .. string.sub( Tbl.Current.Text, Tbl.Current.TextPos + 1 )
		Tbl.Current.TextPos = Tbl.Current.TextPos + 1
		Draw()
	elseif Event == 'key' then
	  if Param == keys[ 'enter' ] then
		-- Enter
		break
	  elseif Param == keys[ 'left' ] then
		-- Left
		if Tbl.Current.TextPos > 0 then
		  Tbl.Current.TextPos = Tbl.Current.TextPos - 1
		  Draw()
		end
	  elseif Param == keys[ 'right' ] then
		-- Right				
		if Tbl.Current.TextPos < string.len( Tbl.Current.Text ) then
		  Draw( ' ' )
		  Tbl.Current.TextPos = Tbl.Current.TextPos + 1
		  Draw()
		end
	  elseif Param == keys[ 'backspace' ] then
		-- Backspace
		if Tbl.Current.TextPos > 0 then
		  Draw( ' ' )
		  Tbl.Current.Text = string.sub( Tbl.Current.Text, 1, Tbl.Current.TextPos - 1 ) .. string.sub( Tbl.Current.Text, Tbl.Current.TextPos + 1 )
		  Tbl.Current.TextPos = Tbl.Current.TextPos - 1					
		  Draw()
		end
	  elseif Param == keys[ 'home' ] then
		-- Home
		Draw( ' ' )
		Tbl.Current.TextPos = 0
		Draw()		
	  elseif Param == keys[ 'delete' ] then
		-- Delete
		if Tbl.Current.TextPos < string.len( Tbl.Current.Text ) then
		  Draw( ' ' )
		  Tbl.Current.Text = string.sub( Tbl.Current.Text, 1, Tbl.Current.TextPos ) .. string.sub( Tbl.Current.Text, Tbl.Current.TextPos + 2 )				
		  Draw()
		end
	  elseif Param == keys[ 'end' ] then
		-- End
		Draw( ' ' )
		Tbl.Current.TextPos = string.len(Tbl.Current.Text)
		Draw()
	  end
	elseif Event == 'term_resize' then
	  -- Terminal resized
	  Width = term.getSize()
	  Draw()
	end
  end

  term.setTextColor( Tbl.Color.Foreground_After )
  term.setBackgroundColor( Tbl.Color.Background_After )
  term.setCursorBlink( false )
  return Tbl.Current.Text
end

However, when I call it with a position of > 2 it kinda does something weird, when the position is at 10.

Code calling:
Spoiler

	Objects[ 1 ]:InputBox(
	  {
		Position = {
		  X = 3,
		  Width = 10,
		  Y = 2,
		},
		Color = {
		  Background_After = colors.black,
		  Background = colors.lightGray,
		  Foreground = colors.black,
		  Foreground_After = colors.white,
		},
	  }
	)

Screenies
http://imgur.com/a/Vew6X/embed#0


Pastebin'ed codes
API: 2eHAejyD
CallCode: i4HvSXDW

If you need more explanation, please tell me

Thanks in advance
Edited on 07 September 2014 - 06:53 PM
Bomb Bloke #2
Posted 08 September 2014 - 02:31 AM
In your "Draw" function, when determining "Scroll", you're erroneously referring to Tbl.Position.X in a few places.

I reckon replacing:

    local Scroll = 0
    if Tbl.Position.X + Tbl.Current.TextPos >= Tbl.Position.X + Tbl.Position.Width then
      Scroll = ( Tbl.Position.X + Tbl.Current.TextPos ) - Tbl.Position.Width
    elseif Tbl.Position.X + Tbl.Current.TextPos >= Width then
      Scroll = ( Tbl.Position.X + Tbl.Current.TextPos ) - Width
    end

… with something like:

local Scroll = (Tbl.Current.TextPos >= Tbl.Position.Width) and (Tbl.Current.TextPos - Tbl.Position.Width + 1) or 0

… should do it.

Whether or not you pay attention to the terminal width doesn't really matter.
Goof #3
Posted 08 September 2014 - 08:51 AM
In your "Draw" function, when determining "Scroll", you're erroneously referring to Tbl.Position.X in a few places.

I reckon replacing:

    local Scroll = 0
    if Tbl.Position.X + Tbl.Current.TextPos >= Tbl.Position.X + Tbl.Position.Width then
      Scroll = ( Tbl.Position.X + Tbl.Current.TextPos ) - Tbl.Position.Width
    elseif Tbl.Position.X + Tbl.Current.TextPos >= Width then
      Scroll = ( Tbl.Position.X + Tbl.Current.TextPos ) - Width
    end

… with something like:

local Scroll = (Tbl.Current.TextPos >= Tbl.Position.Width) and (Tbl.Current.TextPos - Tbl.Position.Width + 1) or 0

… should do it.

Whether or not you pay attention to the terminal width doesn't really matter.

Oh. im gonna look further into the code when i get Home,
but, what if the starting X is at 45, and the width set to 20?
wouldnt it then overlap the sreen width, and throw an error?


Thanks
Bomb Bloke #4
Posted 08 September 2014 - 09:23 AM
It'll be written outside of the screen view, but it won't throw an error. You simply won't see it.

There may be a slight difference in speed, but I've no idea whether it's faster to crop the string yourself or to let term.write() do it for you. Your code isn't executing often enough that I'd feel it's important.

Bear in mind that functions such as write() or print() act differently to term.write(), in that they apply word-wrap (which WOULD be a problem).
Goof #5
Posted 08 September 2014 - 10:47 AM
It'll be written outside of the screen view, but it won't throw an error. You simply won't see it.

There may be a slight difference in speed, but I've no idea whether it's faster to crop the string yourself or to let term.write() do it for you. Your code isn't executing often enough that I'd feel it's important.

Bear in mind that functions such as write() or print() act differently to term.write(), in that they apply word-wrap (which WOULD be a problem).
Well, whenever i can get Home to test, im gonna try Without manually cropping( with the screen width )
if its continious visually weird i might add the screen width to it

Thanks
Goof #6
Posted 08 September 2014 - 02:34 PM
It'll be written outside of the screen view, but it won't throw an error. You simply won't see it.

There may be a slight difference in speed, but I've no idea whether it's faster to crop the string yourself or to let term.write() do it for you. Your code isn't executing often enough that I'd feel it's important.

Bear in mind that functions such as write() or print() act differently to term.write(), in that they apply word-wrap (which WOULD be a problem).

After some testing it seems theres a weird problem, when using backspace until the scroll is 0.

Screenies:





Code:
2eHAejyD – Object API

Im calling the inputbox the same way as in the OP


Thanks in Advance
Edited on 08 September 2014 - 12:36 PM
Bomb Bloke #7
Posted 08 September 2014 - 04:48 PM
Off-by-one errors can be fun.

Let's see now, Tbl.Current.TextPos starts at 0, Tbl.Position.Width at 10. After typing 10 characters it should therefore be 10.

At that point, Scroll will be 1 instead of 0. This still looks ok when the text is written because string.sub()'ing from 0 is the same as doing so from 1. This does not look ok when a replacement character is set because your string.rep() is affected.

So I guess something like this:

local Scroll = (Tbl.Current.TextPos >= Tbl.Position.Width) and (Tbl.Current.TextPos - Tbl.Position.Width + 1) or 1

term.write( string.rep( ReplaceChar, math.max( string.len( Tbl.Current.Text ) - Scroll + 1, 0 ) ) )
Goof #8
Posted 08 September 2014 - 06:33 PM
Off-by-one errors can be fun.

Let's see now, Tbl.Current.TextPos starts at 0, Tbl.Position.Width at 10. After typing 10 characters it should therefore be 10.

At that point, Scroll will be 1 instead of 0. This still looks ok when the text is written because string.sub()'ing from 0 is the same as doing so from 1. This does not look ok when a replacement character is set because your string.rep() is affected.

So I guess something like this:

local Scroll = (Tbl.Current.TextPos >= Tbl.Position.Width) and (Tbl.Current.TextPos - Tbl.Position.Width + 1) or 1

term.write( string.rep( ReplaceChar, math.max( string.len( Tbl.Current.Text ) - Scroll + 1, 0 ) ) )

That worked very well ;)/>

Thank you! :rolleyes:/>
Goof #9
Posted 08 September 2014 - 08:39 PM
Off-by-one errors can be fun.

Let's see now, Tbl.Current.TextPos starts at 0, Tbl.Position.Width at 10. After typing 10 characters it should therefore be 10.

At that point, Scroll will be 1 instead of 0. This still looks ok when the text is written because string.sub()'ing from 0 is the same as doing so from 1. This does not look ok when a replacement character is set because your string.rep() is affected.

So I guess something like this:

local Scroll = (Tbl.Current.TextPos >= Tbl.Position.Width) and (Tbl.Current.TextPos - Tbl.Position.Width + 1) or 1

term.write( string.rep( ReplaceChar, math.max( string.len( Tbl.Current.Text ) - Scroll + 1, 0 ) ) )

That worked very well ;)/>

Thank you! :rolleyes:/>

Well. I ended up with the manually Scroll ( with the width ), however it doesnt seem to quite work as suspected.

Inside the Draw function

local Scroll = 0
    if Tbl.Current.TextPos + Tbl.Position.X < Width then
      Scroll = ( Tbl.Current.TextPos >= Tbl.Position.Width ) and ( Tbl.Current.TextPos - Tbl.Position.Width + 1 ) or 1
    else
      -- I tried with a couple ways, but none worked.
    end

As i commented out, i couldnt find a solution to that.

Do you have any ideas?

Thanks
Bomb Bloke #10
Posted 09 September 2014 - 03:48 AM
So you're saying you want automatic scrolling at the edge of the screen?

local boxWidth = math.min(Tbl.Position.X + Tbl.Position.Width - 1, width) - Tbl.Position.X + 1
local Scroll = (Tbl.Current.TextPos >= boxWidth) and (Tbl.Current.TextPos - boxWidth + 1) or 1

Give or take a one here or there.
Goof #11
Posted 09 September 2014 - 06:48 AM
So you're saying you want automatic scrolling at the edge of the screen?

local boxWidth = math.min(Tbl.Position.X + Tbl.Position.Width - 1, width) - Tbl.Position.X + 1
local Scroll = (Tbl.Current.TextPos >= boxWidth) and (Tbl.Current.TextPos - boxWidth + 1) or 1

Give or take a one here or there.

Ohhhh i didnt know i needed to use math.min in that case.

However it works! ;)/>

Thank you! :rolleyes:/>
Bomb Bloke #12
Posted 09 September 2014 - 07:23 AM
You didn't "need" to. It just provides a shorter way of doing things than an "if" statement in this case.

Here's code to the same effect using the structure you had:

local Scroll

if width > Tbl.Position.X + Tbl.Position.Width - 1 then
  Scroll = (Tbl.Current.TextPos >= Tbl.Position.Width) and (Tbl.Current.TextPos - Tbl.Position.Width + 1) or 1
else
  Scroll = (Tbl.Current.TextPos >= width - Tbl.Position.X + 1) and (Tbl.Current.TextPos - width - Tbl.Position.X + 2) or 1
end
Goof #13
Posted 09 September 2014 - 07:57 PM
You didn't "need" to. It just provides a shorter way of doing things than an "if" statement in this case.

Here's code to the same effect using the structure you had:

local Scroll

if width > Tbl.Position.X + Tbl.Position.Width - 1 then
  Scroll = (Tbl.Current.TextPos >= Tbl.Position.Width) and (Tbl.Current.TextPos - Tbl.Position.Width + 1) or 1
else
  Scroll = (Tbl.Current.TextPos >= width - Tbl.Position.X + 1) and (Tbl.Current.TextPos - width - Tbl.Position.X + 2) or 1
end

Oh. Well both examples works currently very well..

However after running this, i've ran into a quite "advanced" question about how im calling the InputBox…

Is it possible to make the API ( InputBox ) store its function in the background, running / showing the box, so the code would look like this?



    Objects[ 2 ]:InputBox() -- When calling this, it just runs the function, as it were a term.setCursorPos().
    -- which in the most explanaiable way, would instantly run, then continue.


    Objects[ 2 ]:InputBox() -- Instant call... No sleep or anything.
    -- Directly after calling the function above, it continues with other functions or types of code.

    local SomeTable = {
      'This code is running.',
      'However the InputBox is still active and listening for events'
    }
    for k, v in pairs( SomeTable ) do
      print( v )
    end

If you know what i mean, is it then possible to tell, if that kinds of multi-running APIS in returns of multitasking?


Thanks in Advance :P/>
Bomb Bloke #14
Posted 10 September 2014 - 03:05 AM
There are a number of ways. The one which involves the least re-writing would be to run the boxes in parallel:

parallel.waitForAny(Objects[ 1 ]:InputBox, Objects[ 2 ]:InputBox)

You would need to make sure that each input box moves the cursor into itself before writing any characters.
Goof #15
Posted 10 September 2014 - 11:22 AM
There are a number of ways. The one which involves the least re-writing would be to run the boxes in parallel:

parallel.waitForAny(Objects[ 1 ]:InputBox, Objects[ 2 ]:InputBox)

You would need to make sure that each input box moves the cursor into itself before writing any characters.

Well.. I know the parallel method, however is it possible to make the API return some kindof coroutine.create(self, Object)
so i wont have to recode the main program?

Thanks in advance :D/>
Bomb Bloke #16
Posted 10 September 2014 - 01:04 PM
Well, you could handle the co-routines yourself, but… really that's the sort of thing that the parallel API is there for. :mellow:/>

Personally I'd either rig your main script so it can run alongside the boxes, eg:

parallel.waitForAny(mainfunction,box1,box2,etc)

… or, rig the boxes so that they work like eg the buttons in the touchpoint API (take a look at the "handleEvents" function there specifically).

It is possible to rig things up to work the way you're wanting, but I'd say it's more work than either of the above two methods (and more complex to boot). The idea is that you have a box-manager function constantly running in parallel with the rest of your script (whether you need a box or not), and to make it do stuff (or to get information from it) you send custom events back and forth. This is how I configured this music playing API to work without blocking execution of the scripts you use it with.