# RubyText RubyText is a curses wrapper that is in the early experimental stages. The code has bugs. Running it may cause general bad luck as well as demon infestation. Install the `rubytext` gem and then run the demo or the slides or both. ``` $ gem install rubytext $ rubytext demo # 2-minute demo $ rubytext slides # longer "slideshow" ``` There is also `examples/ide.rb` ("world's simplest Ruby IDE"). It is _very_ dumb. :) ### Getting started Here's the obligatory hello-world program. ```ruby require 'rubytext' RubyText.start puts "Hello, world!" getch ``` You invoke `RubyText.start` to initialize the curses environment with default settings. If you then use `puts`, it will write to the standard screen (called `STDSCR` in this library). The `getch` (get character) simply waits for keyboard input before proceeding. Without it, the program might terminate too fast for you to see anything. ## The "slideshow" Here are some of the programs from the `rubytext slides` set of demos. See the `examples` directory and the `showme.rb` script. A simple window: ```ruby require 'rubytext' win = RubyText.window(6, 25, r: 2, c: 34, # 6 rows, 25 cols; upper left at 2,4 fg: Blue, bg: White) # foreground, background win.puts "This is a window..." ``` How `output` works. This name may change. ```ruby require 'rubytext' win = RubyText.window(9, 36, r: 2, c: 6, fg: White, bg: Red) win.output do puts "Because this code uses #output," puts "it doesn't have to specify the" puts "window as a receiver each time." end ``` Windows are bordered by default. ```ruby require 'rubytext' win = RubyText.window(9, 35, r: 3, c: 7, border: false, fg: Black, bg: Green) win.puts "A window doesn't have to" win.puts "have a border." ``` Using `puts` will wrap around the window automagically. ```ruby require 'rubytext' win = RubyText.window(8, 39, r: 4, c: 9, fg: Black, bg: Blue) win.puts "If your text is longer than " + "the width of the window, by default it will " + "wrap around." win.puts "Scrolling is not yet supported." ``` Scrolling is not yet implemented. ```ruby require 'rubytext' win = RubyText.window(10, 70, r: 2, c: 14, fg: Yellow, bg: Black) win.output do puts "Without scrolling, this is what happens when your window fills up..." puts "This behavior will probably change later." sleep 1 puts "Let's print 10 more lines now:" sleep 1 10.times {|i| puts "Printing line #{i}..."; sleep 0.2 } end ``` You can use `print` and `p` as well as `puts`. ```ruby require 'rubytext' win = RubyText.window(10, 60, r: 2, c: 14, fg: Blue, bg: Black) win.output do puts "The #print and #p methods also act as you expect." print "This will all " print "go on a single " puts "line." puts array = [1, 2, 3] p array end ``` You can still use `puts` (etc.) with files, but watch for `STDOUT` and `STDERR`. ```ruby require 'rubytext' win = RubyText.window(10, 50, r: 0, c: 5, fg: Yellow, bg: Blue) win.output do puts "Of course, #puts and #print are unaffected \nfor other receivers." out = File.new("/tmp/junk", "w") out.puts "Nothing to see here." sleep 2 print "\nHowever, if you print to STDOUT or STDERR \nwithout redirection, " STDOUT.print "you will have some " STDERR.print "unexpected/undefined results " puts " in more ways than one." end ``` Use `[]=` to stuff single characters into a window (like an array). ```ruby require 'rubytext' win = RubyText.window(11, 50, r: 0, c: 5, fg: Yellow, bg: Blue) win.puts "We can use the []= method (0-based)" win.puts "to address individual window locations" win.puts "and place characters there.\n " sleep 2 win[4,22] = "X"; win.refresh sleep 2 win[8,20] = "y"; win.refresh sleep 2 win[6,38] = "Z"; win.refresh ``` Likewise use `[]` to retrieve characters from a window. ```ruby require 'rubytext' win = RubyText.window(12, 60, r: 2, c: 5, fg: Yellow, bg: Blue) win.puts "ABCDE Method [] can retrieve characters " win.puts "FGHIJ from a window." win.puts "KLMNO\nPQRST\nUVWZYZ" win.puts sleep 2 win.puts "(2,2) => '#{win[2,2]}' (0,4) => '#{win[0,4]}'" win.puts "(6,7) => '#{win[6,7]}' (0,15) => '#{win[0,15]}'" ``` You can write to `STDSCR` or to a subwindow. ```ruby require 'rubytext' win = RubyText.window(6, 30, r: 2, c: 5, fg: Yellow, bg: Blue) win.puts "You can write to a window..." sleep 2 9.times { STDSCR.puts } STDSCR.puts "...or you can write to STDSCR (standard screen)" sleep 1 puts "STDSCR is the default receiver." sleep 2 STDSCR.go 5, 0 puts "Nothing stops you from overwriting a window." ``` You can retrieve cursor position and window size. ```ruby require 'rubytext' win = RubyText.window(12, 65, r: 1, c: 5, fg: Yellow, bg: Blue) win.output do puts "You can detect the size and cursor position of any window." puts "\nSTDSCR is #{STDSCR.rows} rows by #{STDSCR.cols} columns" puts "win is #{win.rows} rows by #{win.cols} columns" puts "\nSlightly Heisenbergian report of cursor position:" puts " STDSCR.rc = #{STDSCR.rc.inspect}\n win.rc = #{win.rc.inspect}" puts "\nFor fun, I'll print \"ABC\" to STDSCR..." sleep 2 STDSCR.print "ABC" end ``` Move the cursor with `go` (and use a block to jump/return). ```ruby require 'rubytext' win = RubyText.window(11, 65, r: 0, c: 15, fg: Blue, bg: Black) win.puts "The #go method will move the cursor to a specific location." win.go 2, 5 win.puts "x <-- The x is at 2,5" win.puts "\nWith a block, it will execute the block and then" win.puts "return to its previous location." win.print "\n ABC..." sleep 2 win.go(8, 20) { win.print "XYZ" } sleep 2 win.print "DEF" ``` Use `rcprint` to print at specific coordinates. ```ruby require 'rubytext' win = RubyText.window(13, 65, r: 0, c: 6, fg: Blue, bg: White) win.puts "The #rcprint method will print at the specified" win.puts "row/column, like go(r,c) followed by a print," win.puts "except that it does NOT move the cursor." win.rcprint 4,8, "Simplify," win.rcprint 6,12, "simplify," win.rcprint 8,16, "simplify!" win.rcprint 10,0, "Later there will be other ways to do this kind of thing." ``` Window navigation: `home`, `up`, `down`, `left`, `right` ```ruby require 'rubytext' win = RubyText.window(11, 65, r: 0, c: 6, fg: Blue, bg: White) win.go 2,0 win.puts " Method #home will home the cursor..." win.puts " and #putch will put a character at the current location." sleep 2 win.home; win.putch "H"; sleep 2 win.rcprint 4,3, "We can also move up/down/left/right..."; sleep 2 win.go 7, 29; win.putch("+"); sleep 1 win.go 7, 29; win.up; win.putch("U"); sleep 1 win.go 7, 29; win.down; win.putch("D"); sleep 1 win.go 7, 29; win.left; win.putch("L"); sleep 1 win.go 7, 29; win.right; win.putch("R"); sleep 1 ``` More navigation: `up`, `down`, `left`, `right` with a parameter. ```ruby require 'rubytext' win = RubyText.window(11, 65, r: 1, c: 6, fg: Blue, bg: White) win.puts "Methods up/down/left/right can also take an integer..." win.go 4, 29; win.putch("+"); sleep 1 win.go 4, 29; win.up(2); win.putch("2"); sleep 1 win.go 4, 29; win.down(3); win.putch("3"); sleep 1 win.go 4, 29; win.left(4); win.putch("4"); sleep 1 win.go 4, 29; win.right(5); win.putch("5"); sleep 1 ``` Still more navigation: `up!`, `down!`, `left!`, `right!` ```ruby require 'rubytext' win = RubyText.window(11, 65, r: 1, c: 6, fg: Blue, bg: White) win.go 2,0 win.puts "We also have: up!, down!, left!, and right! which can" win.puts "Take us to the edges of the window." win.go 5, 21; win.putch("+"); sleep 1 win.go 5, 21; win.up!; win.putch("U"); sleep 1 win.go 5, 21; win.down!; win.putch("D"); sleep 1 win.go 5, 21; win.left!; win.putch("L"); sleep 1 win.go 5, 21; win.right!; win.putch("R"); sleep 1 ``` And finally, `top` and `bottom`. ```ruby require 'rubytext' win = RubyText.window(11, 65, r: 1, c: 6, fg: Blue, bg: White) win.go 2,0 win.puts "#top and #bottom are the same as #up! and #down!" win.go 5, 21; win.putch("+"); sleep 1 win.go 5, 21; win.top; win.putch("T"); sleep 1 win.go 5, 21; win.bottom; win.putch("B"); sleep 1 ``` Somewhat useless, but there is a `center` method. ```ruby require 'rubytext' win = RubyText.window(15, 65, r: 1, c: 6, fg: Green, bg: Blue) win.puts "#center will print text centered on the current row" win.puts "and do an implicit CRLF at the end.\n " stuff = ["I","can","never","imagine","good sets","of real words", "which can somehow", "produce tree shapes", "|", "|"] stuff.each {|str| win.center(str) } ``` Changing colors during printing. This syntax will change. ```ruby require 'rubytext' win = RubyText.window(12, 65, r: 0, c: 6, fg: Green, bg: Blue) win.puts "This is EXPERIMENTAL." win.puts "Use a color symbol to change text color temporarily:\n " win.puts "This is", :yellow, " another color", :white, " and yet another." win.puts "And this is normal again.\n " win.puts "This does mean that you can't print a symbol that is" win.puts "also a color name... you'd need a workaround.\n " sym = :red win.puts "The symbol is ", sym.inspect, " which works", sym, " just fine." ``` A simple menu (incomplete, still experimental). ```ruby require 'rubytext' puts "This very crude menu is also EXPERIMENTAL." puts "At the moment, it only works inside STDSCR!\n" puts "It knows up, down, Enter, and Escape.\n " puts "Press any key to display the menu..." getch days = %w[Monday Tuesday Wednesday Thursday Friday] num, day = STDSCR.menu(c: 30, items: days) puts if day.nil? puts "You picked nothing!" else puts "You picked item #{num} which is #{day.inspect}" end ``` DESC ```ruby require 'rubytext' ``` A marquee or ticker example. This uses threads and is kind of cool. ```ruby require 'rubytext' msg = <<~EOS A "ticker" example that actually uses threads. Maybe Curses is not as slow as you thought? PRESS ANY KEY TO EXIT... EOS w, h = STDSCR.cols, STDSCR.rows - 1 threads = [] r = RubyText t1 = -> { r.ticker(text: msg, row: h-8, col: 20, width: w-40, delay: 0.02, fg: Red, bg: Black) } t2 = -> { r.ticker(text: msg, row: h-6, col: 15, width: w-30, delay: 0.04) } t3 = -> { r.ticker(text: msg, row: h-4, col: 10, width: w-20, delay: 0.06, fg: Black, bg: Green) } t4 = -> { r.ticker(text: msg, row: h-2, col: 5, width: w-10, delay: 0.08, bg: Black) } t5 = -> { r.ticker(text: msg) } # All defaults -- goes at bottom threads << Thread.new { t1.call } << Thread.new { t2.call } << Thread.new { t3.call } << Thread.new { t4.call } << Thread.new { t5.call } threads << Thread.new { getch; exit } # quitter thread... threads.each {|t| t.join } ``` *More later...*