3 # An `Nfoiled::Window` is a "box" in the terminal to which output can be
4 # printed and from which input can be received. A basic Nfoiled application
5 # will utilize only one of these, a single `Window` covering the entirety
6 # of the `Terminal`'s available area.
10 # This is simply an accessor for all the windows on the current Terminal.
12 def windows; Terminal.current.windows; end
19 # The Y co-ordinate of the top left corner of this `Window`'s bounding box
21 # The X co-ordinate of the top left corner of this `Window`'s bounding box
23 # The height in lines of this `Window`'s bounding box
25 # The width in columns of this `Window`'s bounding box
28 # The actual window object as returned by Ncurses
31 # The `Terminal` that this `Window` pertains to
34 # The status of the global 'input lock' on this window. While locked, all
35 # `#getk` calls will return nil.
37 # If you plan to use `#getb`, always check for a lock first - otherwise
38 # you could grab a Unicode byte right from the waiting hands of a Unicode
40 attr_accessor :input_locked
41 alias_method :input_locked?, :input_locked
42 def lock_input!; self.input_locked = true; end
43 def unlock_input!; self.input_locked = false; end
45 # The proc to be run when a character is received
48 # ====================
49 # = Setup / Teardown =
50 # ====================
53 # Responsible for creating a new `Window`, this will also take care of
54 # initializing Ncurses if necessary. See `newwin(3X)`.
55 def initialize opts = Hash.new
58 @wrapee = ::Ncurses.newwin(
59 opts[:height] ? @height = opts[:height] : ::Ncurses.LINES,
60 opts[:width] ? @width = opts[:width] : ::Ncurses.COLS,
61 opts[:top] ? @top = opts[:top] : 0,
62 opts[:left] ? @left = opts[:left] : 0)
64 ::Ncurses.wtimeout(wrapee, 0) # Prevents ncurses from blocking for input
66 (@owner = Terminal.current).windows << self
70 # Destroys the `wrapee` of this `Window`, and removes this `Window`
71 # from its owning `Terminal`'s `#windows`. See `delwin(3X)`.
73 ::Ncurses.delwin(wrapee)
75 owner.windows.delete self
83 # Gets a single byte from the input buffer for this window. Returns nil if
84 # there are no new characters in the buffer. See `wgetch(3X)`.
86 byte = ::Ncurses.wgetch(wrapee)
87 byte == -1 ? nil : byte
91 # Gets a single `Key` from the input buffer for this window.
93 # This will asynchronously yield the `Key` to a block you provide, if such
94 # a block is given - in this mode, this method will return `true` if a
95 # `getk` is currently possible (input may become temporarily locked under
96 # certain circumstances), and `nil` if there is no `Key` to get.
98 # For the most part, you can expect blocks to be yielded extremely
99 # quickly; however, don't count on this (a sudden, large paste of long-
100 # byte UTF-8 characters could cause each subsequent `getk` to take longer
103 # This method can also be employed in a synchronous manner if no block is
104 # given; in this mode it acts as no more than a wrapper for `Window#getb`
105 # that automatically processes the ASCII char into a `Key` object. Higher-
106 # byte sequences such as Unicode UTF-8 will be treated as errors in this
107 # mode, returning `false`. Otherwise the `Key`-wrapped ASCII is returned.
109 return nil if self.input_locked?
111 return byte unless byte
116 when 128..191, 192..193, 254..255
118 when 194..223, 224..239, 240..244, 245..247, 248..251, 252..253
119 handle_unicode byte, &Proc.new
122 else # We'll synchronously return the ASCII value wrapped in a `Key`.
125 return Key.ascii byte
134 # Handles a UTF-8 sequence from `getk`.
135 def handle_unicode byte, &handler
137 when 194..223; then bytes = 2 # 2 byte sequence
138 when 224..239; then bytes = 3 # 3 byte sequence
139 when 240..244; then bytes = 4 # 4 byte sequence below 10FFFF
140 when 245..247; then bytes = 4 # 4 byte sequence above 10FFFF
141 when 248..251; then bytes = 5 # 5 byte sequence
142 when 252..253; then bytes = 6 # 6 byte sequence
145 Thread.start(self, byte, bytes, handler) do |window, first_byte, bytes, handler|
148 Thread.pass while window.input_locked?
151 until uba.length >= bytes
155 when 0..127, 192..193, 254..255, 194..223, 224..239, 240..244, 245..247, 248..251, 252..253
156 Thread.start(handler) {|handler| handler[Key.new "�"] }
167 Thread.start(handler) {|handler| handler[Key.new uba.pack('C*')] }
176 # This sets this `Window` as the current `Terminal.acceptor`.
178 owner.acceptor = self
183 # Defines a block that controls how the global input loop from
184 # `Nfoiled::read!` handles input when this window has focus.
186 # This acts as both a getter & setter, depending on whether a block is
189 block_given? ? @on_key = Proc.new : @on_key
197 # Prints a string to the window
199 wrapee.printw stringish.to_s
204 # Updates the virtual screen associated with this window. See `wnoutrefresh(3X)`.
210 # Prints a string, followed by a newline, to the window
212 self.print stringish.to_s + "\n"