From 5b2e41a5228f7cadb4c1bcd21e973dcdc8807ac6 Mon Sep 17 00:00:00 2001
From: Jeffrey Phillips Freeman <jeffrey.freeman@syncleus.com>
Date: Sat, 20 Apr 2019 19:32:41 -0400
Subject: [PATCH] Refactored Ncurses windows into their own class.

---
 lib/aethyr/core/commands/settings.rb |   2 +-
 lib/aethyr/core/objects/area.rb      |  40 +-
 lib/aethyr/core/render/display.rb    | 658 ++++++++++++---------------
 lib/aethyr/core/render/format.rb     |   4 +-
 4 files changed, 323 insertions(+), 381 deletions(-)

diff --git a/lib/aethyr/core/commands/settings.rb b/lib/aethyr/core/commands/settings.rb
index e0901f3..f59cc91 100644
--- a/lib/aethyr/core/commands/settings.rb
+++ b/lib/aethyr/core/commands/settings.rb
@@ -83,7 +83,7 @@ module Settings
         player.io.to_default
         player.output "Colors set to defaults."
       else
-        player.output player.io.set_fg_color(event[:option], event[:color])
+        player.output player.io.set_color(event[:option], event[:color])
       end
     end
 
diff --git a/lib/aethyr/core/objects/area.rb b/lib/aethyr/core/objects/area.rb
index 5cf6e45..b162047 100644
--- a/lib/aethyr/core/objects/area.rb
+++ b/lib/aethyr/core/objects/area.rb
@@ -10,17 +10,17 @@ require 'aethyr/core/objects/traits/location'
 # info.terrain.area_type = :urban
 class Area < GridContainer
   include Location
-  
+
   attr_accessor :map_type
 
   def initialize(*args)
     super
-    
+
     @article = "an"
     @generic = "area"
     @map_type = :rooms
   end
-  
+
   def render_map(player, position, map_rows = 10, map_columns = 10)
     if @map_type == :rooms
       return render_rooms(player, position, map_rows, map_columns)
@@ -32,7 +32,7 @@ class Area < GridContainer
       raise "Invalid map type for area!"
     end
   end
-  
+
   private
   def render_world(player, position, map_rows, map_columns)
     player_room = self.inventory.find_by_id(player.container)
@@ -44,7 +44,7 @@ class Area < GridContainer
       (0..width).step(1) do |screen_column|
         column = screen_column + (position[0] - (map_columns / 2))
         room = self.find_by_position([column , row])
-        
+
         if room.nil?
           rendered += " "
         elsif room.eql? player_room
@@ -57,7 +57,7 @@ class Area < GridContainer
     end
     rendered
   end
-  
+
   def render_rooms(player, position, map_rows, map_columns)
     player_room = self.inventory.find_by_id(player.container)
     return "The current location doesn't appear on any maps." if position.nil?
@@ -72,7 +72,7 @@ class Area < GridContainer
       until column >= width
         border_column = (column % 4 == 0);
         room_column = (column / 4) + (position[0] - (map_rows / 2))
-        
+
         room = self.find_by_position([room_column, room_row])
         here_room = (room != nil && row < height - 1 && column < width - 1)
         west = self.find_by_position([room_column - 1, room_row])
@@ -80,7 +80,7 @@ class Area < GridContainer
         north =  self.find_by_position([room_column , room_row + 1 ])
         north_room = (column >= width - 1 ? false : north != nil)
         north_west_room = (row >= height - 1 || column >= width - 1 ? false : self.find_by_position([room_column - 1, room_row + 1]) != nil)
-        
+
         if border_row
           if border_column
             if (here_room and north_west_room) or (west_room and north_room)
@@ -157,15 +157,15 @@ class Area < GridContainer
             column += 2
           end
         end
-        
+
         column += 1
       end
       rendered += "\r\n"
     end
-    
+
     rendered
   end
-  
+
   def room_has_nonstandard_exits(room)
     exits = room.exits.map() { |e| e.alt_names[0] }
     exits.each do |exit|
@@ -173,21 +173,21 @@ class Area < GridContainer
     end
     false
   end
-  
+
   def render_room(room, player)
     return "   " if room.nil?
     player_room = self.inventory.find_by_id(player.container)
     has_player = (player_room.eql? room)
-    
+
     me_here = has_player
     other_player_here = (!room.players(true, player).nil?) && (room.players(true, player).length > 0)
     merchants_here = false
     zone_change_here = room_has_nonstandard_exits(room)
-    
+
     up_here = room.exits.map{ |e| e.alt_names[0]}.include?("up")
     down_here = room.exits.map{ |e| e.alt_names[0]}.include?("down")
     mobs_here = (!room.mobs.nil?) && (room.mobs.length > 0)
-    
+
     left_char = " "
     if zone_change_here
       left_char = "<exit>☼</exit>"
@@ -196,7 +196,7 @@ class Area < GridContainer
     elsif down_here
       left_char = "<exit>↓</exit>"
     end
-    
+
     right_char = " "
     if mobs_here
       right_char = "<mob>*</mob>"
@@ -205,12 +205,12 @@ class Area < GridContainer
     elsif other_player_here
       right_char = "<player>☺</player>"
     end
-    
+
     middle_char = " "
     if me_here
       middle_char = "<me>☺</me>"
     end
-    
+
     if (left_char.eql? " ") and (not right_char.eql? " ")
       if mobs_here and merchants_here
         left_char = "<merchant>☻</merchant>"
@@ -222,7 +222,7 @@ class Area < GridContainer
         right_char = "<exit>↓</exit>"
       end
     end
-    
+
     left_char + middle_char + right_char
   end
-end
\ No newline at end of file
+end
diff --git a/lib/aethyr/core/render/display.rb b/lib/aethyr/core/render/display.rb
index d1cb5b9..40ae2f3 100644
--- a/lib/aethyr/core/render/display.rb
+++ b/lib/aethyr/core/render/display.rb
@@ -4,191 +4,124 @@ require 'aethyr/core/connection/telnet_codes'
 require 'aethyr/core/connection/telnet'
 require 'aethyr/core/components/manager'
 
-class Display
-  attr_accessor :color_settings, :use_color
-
-  DEFAULT_HEIGHT = 43
-  DEFAULT_WIDTH = 80
-  BUFFER_SIZE = 10000
-
-  def initialize(socket, new_color_settings = nil)
-    @height = DEFAULT_HEIGHT
-    @width = DEFAULT_WIDTH
+class Window
+  attr_reader :window_border, :window_text, :buffer, :buffer_lines, :text_height, :text_width, :x, :y, :height, :width, :buffered, :use_color, :buffer_size, :color_settings, :buffer_pos
+  attr_accessor :selected
+
+  def initialize( color_settings, buffered: false, buffer_size: 10000 )
+    @buffer = [] if buffered
+    @buffer_lines = [] if buffered
+    @buffer_pos = 0 if buffered
+    @buffer_size = buffer_size if buffered
+    @buffered = buffered
+    @selected = false
     @use_color = false
-    @layout_type = :basic
-    @buffer = Hash.new
-    @buffer_lines = Hash.new
-    @buffer_pos = 0
-
+    @color_settings = color_settings
     @color_stack = []
-    @color_settings = new_color_settings || to_default_colors
-
-    @socket = socket #StringIO.new
-    @scanner = TelnetScanner.new(socket, self)
-    @scanner.send_preamble
-
-    @selected = :input
-    @screen = Ncurses.newterm("xterm-256color", @socket, @socket)
-
-    Ncurses.set_term(@screen)
-    Ncurses.resizeterm(@height, @width)
-    Ncurses.cbreak           # provide unbuffered input
-    Ncurses.noecho           # turn off input echoing
-    Ncurses.nonl             # turn off newline translation
-    Ncurses.curs_set(2) #high visibility cursor
-
-    Ncurses.stdscr.intrflush(false) # turn off flush-on-interrupt
-    Ncurses.stdscr.keypad(true)     # turn on keypad mode
-
-    #Ncurses.scrollok(Ncurses.stdscr, true)
-    Ncurses.stdscr.clear
-
-    layout
-  end
+    @exists = false
+  end
+
+  def exists?
+    return @exists
+  end
+
+  def create(width: 0, height: 0, x: 0, y: 0)
+    raise "width out of range" unless width >= 0
+    raise "height out of range" unless height >= 0
+    raise "x out of range" unless x >= 0
+    raise "y out of range" unless y >= 0
+    @height = height
+    @width = width
+    @x = x
+    @y = y
+
+    destroy
+    @exists = true
+    @window_border = Ncurses::WINDOW.new(@height, @width, @y, @x)
+    @window_border_height = @window_border.getmaxy - 2
+    @window_border_width = @window_border.getmaxx - 2
+    @window_text = @window_border.derwin(@window_border_height, @window_border_width, 1, 1)
+    @text_height = @window_text.getmaxy - 2
+    @text_width = @window_text.getmaxx - 2
+    Ncurses.scrollok(@window_text, true)
+    @window_text.clear
+    @window_text.move(@text_height, 1)
+
+    if buffered
+      @buffer_pos = 0 if @buffered
+      parse_buffer
+      buffer_from = [@buffer_lines.length * -1, -1 * (@height + @buffer_pos + 1)].max
+      buffer_to = [@buffer_lines.length * -1, (@buffer_pos + 1) * -1].max
 
-  def init_colors
-    Ncurses.start_color
-    @use_color = true
-    puts "There are #{Ncurses.COLORS} colors on this client"
-    Ncurses.assume_default_colors(Color::Foreground.attribute(:white), Color::Background.attribute(:black));
-    Ncurses.COLORS.times do |fg|
-      Ncurses.COLORS.times do |bg|
-        Ncurses.init_pair(fg + bg * Ncurses.COLORS, fg, bg)
+      @buffer_lines[buffer_from..buffer_to].each do | message|
+        render(message)
       end
     end
-    update
   end
 
-  def activate_color(window, fg, bg)
-    return if not @use_color
-    #window.attron(fg + bg * Ncurses.COLORS)
-    if Ncurses.respond_to?(:color_set)
-      window.color_set(fg + bg * Ncurses.COLORS, nil)
-    else
-      window.attrset(Ncurses.COLOR_PAIR(fg + bg * Ncurses.COLORS))
-    end
+  def destroy
+    @exists = false
+    Ncurses.delwin(@window_border) unless @window_border.nil?
+    @window_border = nil
+    @window_text = nil
+    @selected = false
   end
 
-  def layout
-    case @layout_type
-    when :basic
-      Ncurses.delwin(@window_main_border) unless @window_main_border.nil?
-      @window_main_border = Ncurses::WINDOW.new(@height - 3, 0, 0, 0)
-      @window_main_border_height = @window_main_border.getmaxy - 2
-      @window_main_border_width = @window_main_border.getmaxx - 2
-      @window_main = @window_main_border.derwin(@window_main_border.getmaxy - 2, @window_main_border.getmaxx - 2, 1, 1)
-      @window_main_height = @window_main.getmaxy - 2
-      @window_main_width = @window_main.getmaxx - 2
-      Ncurses.scrollok(@window_main, true)
-      @window_main.clear
-      @window_main.move(@window_main.getmaxy - 2,1)
-      @buffer[:main] = [] if @buffer[:main].nil?
-      @buffer_pos = 0
-      parse_buffer
-      buffer_from = [@buffer_lines[:main].length * -1, -1 * (@height - 3 + @buffer_pos + 1)].max
-      buffer_to = [@buffer_lines[:main].length * -1, (@buffer_pos + 1) * -1].max
-      log "rendering #{buffer_from} #{buffer_to}"
-      @buffer_lines[:main][buffer_from..buffer_to].each do | message|
-        render(message, @window_main)
-      end
-
-      Ncurses.delwin(@window_input_border) unless @window_input_border.nil?
-      @window_input_border = Ncurses::WINDOW.new(3, 0, @height - 3, 0)
-      @window_input = @window_input_border.derwin(@window_input_border.getmaxy - 2, @window_input_border.getmaxx - 2, 1, 1)
-      Ncurses.scrollok(@window_input, false)
-      @window_input.clear
-      @window_input.move(@window_input.getmaxy - 2,1)
-    when :full
-      Ncurses.delwin(@window_map_border) unless @window_map_border.nil?
-      @window_map_border = Ncurses::WINDOW.new(@height/2, 0, 0, 0)
-      @window_map = @window_map_border.derwin(@window_map_border.getmaxy - 2, @window_map_border.getmaxx - 2, 1, 1)
-      Ncurses.scrollok(@window_map, true)
-      @window_map.clear
-      @window_map.move(@window_map.getmaxy - 2,1)
-
-      Ncurses.delwin(@window_look_border) unless @window_look_border.nil?
-      @window_look_border = Ncurses::WINDOW.new(@height/2 - 3, 83, @height/2, 0)
-      @window_look = @window_look_border.derwin(@window_look_border.getmaxy - 2, @window_look_border.getmaxx - 2, 1, 1)
-      Ncurses.scrollok(@window_look, true)
-      @window_look.clear
-      @window_look.move(@window_look.getmaxy - 2,1)
-
-      Ncurses.delwin(@window_main_border) unless @window_main_border.nil?
-      @window_main_border = Ncurses::WINDOW.new(@height/2 - 3, 0, @height/2, 83)
-      @window_main_border_height = @window_main_border.getmaxy - 2
-      @window_main_border_width = @window_main_border.getmaxx - 2
-      @window_main = @window_main_border.derwin(@window_main_border.getmaxy - 2, @window_main_border.getmaxx - 2, 1, 1)
-      @window_main_height = @window_main.getmaxy - 2
-      @window_main_width = @window_main.getmaxx - 2
-      Ncurses.scrollok(@window_main, true)
-      @window_main.clear
-      @window_main.move(@window_main.getmaxy - 2,1)
-      @buffer[:main] = [] if @buffer[:main].nil?
-      @buffer_pos = 0
-      parse_buffer
-      buffer_from = [@buffer_lines[:main].length * -1, -1 * (33 + @buffer_pos + 1)].max
-      buffer_to = [@buffer_lines[:main].length * -1, (@buffer_pos + 1) * -1].max
-      log "rendering #{buffer_from} #{buffer_to}"
-      @buffer_lines[:main][buffer_from..buffer_to].each do | message|
-        render(message, @window_main)
-      end
+  def update
+    white_fg = Color::Foreground.attribute(:white)
+    grey_fg = Color::Foreground.attribute(:grey)
+    black_bg = Color::Background.attribute(:black)
 
-      Ncurses.delwin(@window_input_border) unless @window_input_border.nil?
-      @window_input_border = Ncurses::WINDOW.new(3, 0, @height - 3, 0)
-      @window_input = @window_input_border.derwin(@window_input_border.getmaxy - 2, @window_input_border.getmaxx - 2, 1, 1)
-      Ncurses.scrollok(@window_input, false)
-      @window_input.clear
-      @window_input.move(@window_input.getmaxy - 2,1)
+    if @use_color
+      activate_color_window(@window_border, grey_fg, black_bg) unless @window_border.nil?
     end
 
-    @echo = true
-    update
-  end
+    default_border = 32
+    @window_border.border(*([default_border]*8)) unless @window_border.nil?
 
-  def resolution
-    [@width, @height]
-  end
+    if @use_color
+      activate_color_window(@window_border, white_fg, black_bg) if @selected && @window_border.nil? == false
+    end
 
-  def resolution=(resolution)
-    @width = resolution[0]
-    @height = resolution[1]
-    Ncurses.resizeterm(@height, @width)
-    @layout_type = :full if @height > 100 && @width > 165
-    layout
-  end
+    @window_border.border(*([0]*8)) if @selected && @window_border.nil? == false
 
-  def read_rdy?
-    ready, _, _ = IO.select([@socket])
-    ready.any?
+    @window_border.noutrefresh() unless @window_border.nil?
+    @window_text.noutrefresh() unless @window_text.nil?
   end
 
-  def echo?
-    @echo
+  def enable_color
+    @use_color = true
   end
 
-  def echo_on
-    @echo = true
+  def activate_color(fg, bg)
+    activate_color_window(@window_text, fg, bg)
   end
 
-  def echo_off
-    @echo = false
+  def clear
+    @window_text.clear
   end
 
-  def recv
-    return nil unless read_rdy?
-
-    set_term
-    recvd = read_line(0, 0)
+  def send (message, word_wrap = true, add_newline: true)
+    unless @buffer.nil?
+      @buffer << message.dup
+      @buffer << "" if add_newline
+      @buffer.drop(@buffer.length - @buffer_size) if @buffer.length > @buffer_size
+    end
 
-    puts "read returned: #{recvd}"
-    recvd + "\n"
+    message = message.tr("\r", '')
+    if buffered
+      render_buffer
+    else
+      render(message, add_newline: add_newline)
+    end
   end
 
-  def send_raw message
-    @socket.puts message
+  def buffer_pos= new_pos
+    @buffer_pos = new_pos if new_pos <= @buffer_size && new_pos <= @buffer_lines.length - @text_height && new_pos >= 0
+    render_buffer
   end
 
-  def split_message(message, cols = @window_main_width)
+  def self.split_message(message, cols = @text_width)
     new_message = message.gsub(/\t/, '     ')
     new_message.tr!("\r", '')
 
@@ -210,19 +143,7 @@ class Display
     return buffer_lines
   end
 
-  def parse_buffer(channel = :main, cols = @window_main_width)
-    @buffer_lines[channel] = nil
-    buffer = @buffer[channel]
-    raise "channel #{channel} has no buffer" if buffer.nil?
-    buffer_lines = []
-    @buffer_lines[channel]  = buffer_lines
-
-    buffer.each do |message|
-      buffer_lines.concat(split_message(message, cols))
-    end
-  end
-
-  def word_wrap(line, cols = @window_main_width)
+  def self.word_wrap(line, cols = @text_width)
     lines = []
     new_line = ""
     new_line_length = 0
@@ -275,81 +196,38 @@ class Display
     return lines
   end
 
-  def send (message, word_wrap = true, message_type: :main, internal_clear: false, add_newline: true)
-    window = nil
-
-    unless @buffer[message_type].nil?
-      @buffer[message_type] << message.dup
-      @buffer[message_type] << "" if add_newline
-      @buffer[message_type].drop(@buffer[message_type].length - BUFFER_SIZE) if @buffer[message_type].length > BUFFER_SIZE
-    end
-
-    case message_type
-    when :main
-      window = @window_main
-    when :look
-      unless @window_look.nil?
-        window = @window_look
-        #window.clear
-      else
-        window = @window_main
-      end
-    when :map
-      unless @window_map.nil?
-        window = @window_map
-        #window.clear
-      else
-        window = @window_main
-      end
-    end
-    raise "window_type not recognized" if window.nil?
-
-    window.clear if internal_clear and not message_type.eql? :main
-
-    if message_type == :main && @buffer[:main].nil? == false
-      render_buffer(channel: :main)
+  private
+  def activate_color_window(window, fg, bg)
+    return if not @use_color
+    #window.attron(fg + bg * Ncurses.COLORS)
+    if Ncurses.respond_to?(:color_set)
+      window.color_set(fg + bg * Ncurses.COLORS, nil)
     else
-      render(message, window, add_newline: add_newline)
+      window.attrset(Ncurses.COLOR_PAIR(fg + bg * Ncurses.COLORS))
     end
   end
 
-  def render_buffer(channel: :main)
-    raise "only handle main channel for now" if channel != :main
+  def render_buffer
     parse_buffer
-    #window.clear
-    buffer_from = [@buffer_lines[:main].length * -1, -1 * (@window_main_height + @buffer_pos + 1)].max
-    buffer_to = [@buffer_lines[:main].length * -1, (@buffer_pos + 1) * -1].max
-    log "rendering pos #{@buffer_pos} #{buffer_from} to #{buffer_to} height #{@height}"
-    @window_main.move(0,0)
-    @buffer_lines[:main][buffer_from..buffer_to].each do | message|
-      colored_send(@window_main, message + "\n")
+    buffer_from = [@buffer_lines.length * -1, -1 * (@text_height + @buffer_pos + 1)].max
+    buffer_to = [@buffer_lines.length * -1, (@buffer_pos + 1) * -1].max
+
+    @window_text.move(0,0)
+    @buffer_lines[buffer_from..buffer_to].each do | message|
+      colored_send(message + "\n")
     end
   end
 
-  def render(message, window = @window_main, add_newline: true)
-
-    message = message.tr("\r", '')
-    # lines = message.split("\n");
-    # return if lines.empty?
-    # if lines.length > 1
-    #   lines.each do |line|
-    #     render line, window, add_newline: add_newline
-    #   end
-    #   return
-    # end
-    # message = lines[0]
-
+  def render(message, add_newline: true)
     message += "\n" if add_newline
-    colored_send(window, message)
+    colored_send(message)
   end
 
-  def colored_send(window, message)
-    set_term
-
+  def colored_send(message)
     regular_format = nil
     if @use_color
       regular_format = FormatState.new(@color_settings["regular"], self.method(:activate_color))
-      regular_format.apply(window)
+      regular_format.apply(@window_text)
     end
 
     colors = @color_settings.keys.dup
@@ -361,72 +239,194 @@ class Display
         if @use_color
           part.match(/<([\/]{0,1})([^>]*)>/i) do
             if ($1.nil?) || ($1.length <= 0)
-              color_encode(window, $2)
+              color_encode($2)
             else
-              color_decode(window, $2)
+              color_decode($2)
             end
           end
         end
       else
-        window.addstr("#{part}")
+        @window_text.addstr("#{part}")
       end
     end
 
     if @use_color
-      regular_format.revert(window)
+      regular_format.revert(@window_text)
     end
 
     update
   end
 
-  def paginate message
-    if @player.nil?
-      return line_wrap(message)
-    elsif not @player.page_height
-      return line_wrap(message)
-    #elsif not @word_wrap
-      #return message.gsub(/([^\r]?)\n/, '\1' + "\r\n")
+  def color_encode(code)
+    parent = @color_stack[-1]
+    code = code.downcase
+    code = "regular" if code.nil? || code.empty? || @color_settings[code].nil?
+    unless code.start_with? "raw "
+      result = FormatState.new(@color_settings[code], self.method(:activate_color), parent)
+    else
+      /raw (?<code>.*)/ =~ code
+      result = FormatState.new(code, self.method(:activate_color), parent)
+    end
+    @color_stack << result
+    result.apply(@window_text)
+  end
+
+  def color_decode(code)
+    @color_stack.pop.revert(@window_text)
+  end
+
+  def parse_buffer(cols = @text_width)
+    raise "channel has no buffer" if buffer.nil?
+    @buffer_lines  = []
+
+    @buffer.each do |message|
+      @buffer_lines.concat(Window.split_message(message, cols))
     end
+  end
+end
 
-    ph = @player.page_height
 
-    out = []
-    #message = message.gsub(/((\e\[\d+[\;]{0,1}\d*[\;]{0,1}\d*m|[^\r\n\n\s\Z]){#@word_wrap})/, "\\1 ") if @word_wrap
-    message = wrap(message, @word_wrap).join("\r\n") if @word_wrap
-    message.scan(/((((\e\[\d+[\;]{0,1}\d*[\;]{0,1}\d*m)|.){1,#{@word_wrap}})(\r\n|\n|\s+|\Z))|(\r\n|\n)/) do |m|
-      if $2
-        out << $2
-      else
-        out << ""
+
+class Display
+  attr_accessor :color_settings, :use_color
+
+  DEFAULT_HEIGHT = 43
+  DEFAULT_WIDTH = 80
+
+  def initialize(socket, new_color_settings = nil)
+    @height = DEFAULT_HEIGHT
+    @width = DEFAULT_WIDTH
+    @use_color = false
+    @layout_type = :basic
+
+    @color_settings = new_color_settings || to_default_colors
+
+    @socket = socket #StringIO.new
+    @scanner = TelnetScanner.new(socket, self)
+    @scanner.send_preamble
+
+    @screen = Ncurses.newterm("xterm-256color", @socket, @socket)
+
+    Ncurses.set_term(@screen)
+    Ncurses.resizeterm(@height, @width)
+    Ncurses.cbreak           # provide unbuffered input
+    Ncurses.noecho           # turn off input echoing
+    Ncurses.nonl             # turn off newline translation
+    Ncurses.curs_set(2) #high visibility cursor
+
+    Ncurses.stdscr.intrflush(false) # turn off flush-on-interrupt
+    Ncurses.stdscr.keypad(true)     # turn on keypad mode
+
+    Ncurses.stdscr.clear
+
+    @windows = Hash.new
+    @windows[:main] = Window.new(@color_settings, buffered: true)
+    @windows[:input] = Window.new(@color_settings)
+    @windows[:map] = Window.new(@color_settings)
+    @windows[:look] = Window.new(@color_settings)
+    self.selected = :input
+    layout
+  end
+
+  def init_colors
+    Ncurses.start_color
+    @use_color = true
+    @windows[:main].enable_color
+    @windows[:input].enable_color
+    @windows[:map].enable_color
+    @windows[:look].enable_color
+    puts "There are #{Ncurses.COLORS} colors on this client"
+    Ncurses.assume_default_colors(Color::Foreground.attribute(:white), Color::Background.attribute(:black));
+    Ncurses.COLORS.times do |fg|
+      Ncurses.COLORS.times do |bg|
+        Ncurses.init_pair(fg + bg * Ncurses.COLORS, fg, bg)
       end
     end
+    update
+  end
 
-    if out.length < ph
-      return out.join("\r\n")
+  def selected= channel
+    @windows.each do |channel, window|
+      window.selected = false
     end
-
-    @paginator = KPaginator.new(self, out)
-    @paginator.more
+    @windows[channel].selected = true
   end
 
-  #Only use if there is no line height
-  def line_wrap message
-    message = wrap(message, @word_wrap).join("\n") if @word_wrap
-    #message = message.gsub(/((\e\[\d+[\;]{0,1}\d*[\;]{0,1}\d*m|[^\r\n\n\s\Z]){#{@word_wrap}})/, "\\1 ") if @word_wrap
-    message.gsub(/(((\e\[\d+[\;]{0,1}\d*[\;]{0,1}\d*m)|.){1,#{@word_wrap}})(\r\n|\n|\s+|\Z)/, "\\1\n")
+  def selected
+    @windows.each do |channel, window|
+      return channel if window.selected
+    end
   end
 
-  #Next page of paginated output
-  def more
-    if @paginator and @paginator.more?
-      self.print(@paginator.more, false)
-      if not @paginator.more?
-        @paginator = nil
-      end
-    else
-      @paginator = nil
-      self.puts "There is no more."
+  def layout
+    case @layout_type
+    when :basic
+      @windows[:map].destroy
+      @windows[:look].destroy
+      @windows[:main].create(height: @height - 2)
+      @windows[:input].create(height: 3, y: @height - 3)
+    when :full
+      @windows[:map].create(height: @height/2)
+      @windows[:look].create(height: @height/2 - 3, width: 83, y: @height/2)
+      @windows[:main].create(height: @height/2 - 3, x: 83, y: @height/2)
+      @windows[:input].create(height: 3, y: @height - 3)
     end
+
+    @echo = true
+    update
+  end
+
+  def resolution
+    [@width, @height]
+  end
+
+  def resolution=(resolution)
+    @width = resolution[0]
+    @height = resolution[1]
+    Ncurses.resizeterm(@height, @width)
+    @layout_type = :full if @height > 100 && @width > 165
+    layout
+  end
+
+  def read_rdy?
+    ready, _, _ = IO.select([@socket])
+    ready.any?
+  end
+
+  def echo?
+    @echo
+  end
+
+  def echo_on
+    @echo = true
+  end
+
+  def echo_off
+    @echo = false
+  end
+
+  def recv
+    return nil unless read_rdy?
+
+    set_term
+    recvd = read_line(0, 0)
+
+    puts "read returned: #{recvd}"
+    recvd + "\n"
+  end
+
+  def send_raw message
+    @socket.puts message
+  end
+
+  def send (message, word_wrap = true, message_type: :main, internal_clear: false, add_newline: true)
+    window = nil
+
+    raise "window_type not recognized" if @windows[message_type].nil?
+
+    @windows[message_type].clear if internal_clear
+    @windows[message_type].send(message, word_wrap, add_newline: add_newline)
+
   end
 
   def close
@@ -469,26 +469,8 @@ class Display
     }
   end
 
-  def color_encode(window, code)
-    parent = @color_stack[-1]
-    code = code.downcase
-    code = "regular" if code.nil? || code.empty? || @color_settings[code].nil?
-    unless code.start_with? "raw "
-      result = FormatState.new(@color_settings[code], self.method(:activate_color), parent)
-    else
-      /raw (?<code>.*)/ =~ code
-      result = FormatState.new(code, self.method(:activate_color), parent)
-    end
-    @color_stack << result
-    result.apply(window)
-  end
-
-  def color_decode(window, code)
-    @color_stack.pop.revert(window)
-  end
-
   #Sets the foreground color for a given setting.
-  def set_fg_color(code, color)
+  def set_color(code, color)
     code.downcase! unless code.nil?
     color.downcase! unless color.nil?
 
@@ -543,7 +525,7 @@ CONF
   end
 
   def refresh_watch_windows(player)
-    unless @window_look.nil?
+    if @windows[:look].exists?
       if player.blind?
         send( "You cannot see while you are blind.", message_type: :look, internal_clear: true)
       else
@@ -551,7 +533,7 @@ CONF
         if not room.nil?
           look_text = room.look(player)
           cleared = false
-          split_message(look_text, 79).each do |msg|
+          Window.split_message(look_text, 79).each do |msg|
             send(msg, message_type: :look, internal_clear: !cleared, add_newline: true)
             cleared = true
           end
@@ -561,7 +543,7 @@ CONF
       end
     end
 
-    unless @window_map.nil?
+    if @windows[:map].exists?
       room = $manager.get_object(player.container)
       if not room.nil?
         send(room.area.render_map(player, room.area.position(room)), message_type: :map, internal_clear: true)
@@ -574,45 +556,9 @@ CONF
   private
 
   def update
-
-    white_fg = Color::Foreground.attribute(:white)
-    grey_fg = Color::Foreground.attribute(:grey)
-    black_bg = Color::Background.attribute(:black)
-
-    if @use_color
-      activate_color(@window_main_border, grey_fg, black_bg) unless @window_main_border.nil?
-      activate_color(@window_input_border, grey_fg, black_bg) unless @window_input_border.nil?
-      activate_color(@window_map_border, grey_fg, black_bg) unless @window_map_border.nil?
-      activate_color(@window_look_border, grey_fg, black_bg) unless @window_look_border.nil?
-    end
-
-    default_border = 0 if @color_enable
-    default_border = 32 unless @color_enable
-    @window_main_border.border(*([default_border]*8)) unless @window_main_border.nil?
-    @window_input_border.border(*([default_border]*8)) unless @window_input_border.nil?
-    @window_map_border.border(*([default_border]*8)) unless @window_map_border.nil?
-    @window_look_border.border(*([default_border]*8)) unless @window_look_border.nil?
-
-    if @use_color
-      activate_color(@window_main_border, white_fg, black_bg) if @selected.eql? :main
-      activate_color(@window_input_border, white_fg, black_bg) if @selected.eql? :input
-      activate_color(@window_map_border, white_fg, black_bg) if @selected.eql? :map
-      activate_color(@window_look_border, white_fg, black_bg) if @selected.eql? :look
+    @windows.each do |channel, window|
+      window.update
     end
-
-    @window_main_border.border(*([0]*8)) if @selected.eql? :main
-    @window_input_border.border(*([0]*8)) if @selected.eql? :input
-    @window_map_border.border(*([0]*8)) if @selected.eql? :map
-    @window_look_border.border(*([0]*8)) if @selected.eql? :look
-
-    @window_main_border.noutrefresh() unless @window_main_border.nil?
-    @window_main.noutrefresh() unless @window_main.nil?
-    @window_map_border.noutrefresh() unless @window_map_border.nil?
-    @window_map.noutrefresh() unless @window_map.nil?
-    @window_look_border.noutrefresh() unless @window_look_border.nil?
-    @window_look.noutrefresh() unless @window_look.nil?
-    @window_input_border.refresh() unless @window_input_border.nil?
-    @window_input.refresh() unless @window_input.nil?
     Ncurses.doupdate()
   end
 
@@ -622,19 +568,18 @@ CONF
 
 
   def read_line(y, x,
-                window: @window_input,
-                max_len: (window.getmaxx - x - 1),
+                max_len: (@windows[:input].window_text.getmaxx - x - 1),
                 string: "",
                 cursor_pos: 0)
     escape = nil
     loop do
-      window.clear
-      window.mvaddstr(y,x,string) if echo?
-      window.move(y,x+cursor_pos) if echo?
+      @windows[:input].clear
+      @windows[:input].window_text.mvaddstr(y,x,string) if echo?
+      @windows[:input].window_text.move(y,x+cursor_pos) if echo?
       update
 
       next if not @scanner.process_iac
-      ch = window.getch
+      ch = @windows[:input].window_text.getch
       puts ch
 
       unless escape.nil?
@@ -672,8 +617,7 @@ CONF
         when [27, 91, 53]
           case ch
           when 126 #page up
-            @buffer_pos += 1 if @buffer_pos < BUFFER_SIZE && @buffer_pos < @buffer_lines[:main].length - 33
-            render_buffer(channel: :main)
+            @windows[:main].buffer_pos += 1
             escape = nil
             next
           else
@@ -684,8 +628,7 @@ CONF
         when [27, 91, 54]
           case ch
           when 126 #page down
-            @buffer_pos -= 1 if @buffer_pos > 0
-            render_buffer(channel: :main)
+            @windows[:main].buffer_pos -= 1
             escape = nil
             next
           else
@@ -710,11 +653,10 @@ CONF
   #      when Ncurses::KEY_ENTER, ?\n, ?\r
   #        return string, cursor_pos, ch # Which return key has been used?
         when 13 # return
-          window.clear
-          send("≫≫≫≫≫ #{string}") if echo?
-          @selected = :input
-          @buffer_pos = 0
-          render_buffer(channel: :main)
+          @windows[:input].clear
+          self.selected = :input
+          @windows[:main].send("≫≫≫≫≫ #{string}") if echo?
+          @windows[:main].buffer_pos = 0
           update
           return string#, cursor_pos, ch # Which return key has been used?
         #when Ncurses::KEY_BACKSPACE
@@ -735,27 +677,27 @@ CONF
           end
           @selected = :input
         when 9 # tab
-          case @selected
+          case self.selected
           when :input
-            @selected = :main
+            self.selected = :main
           when :main
-            if not @window_map.nil?
-              @selected = :map
-            elsif not @window_look.nil?
-              @selected = :look
+            if @windows[:map].exists?
+              self.selected = :map
+            elsif @windows[:look].exists?
+              self.selected = :look
             else
-              @selected = :input
+              self.selected = :input
             end
           when :map
-            if not @window_look.nil?
-              @selected = :look
+            if @windows[:look].exists?
+              self.selected = :look
             else
-              @selected = :input
+              self.selected = :input
             end
           when :look
-            @selected = :input
+            self.selected = :input
           else
-            @selected = :input
+            self.selected = :input
           end
           update
         else
diff --git a/lib/aethyr/core/render/format.rb b/lib/aethyr/core/render/format.rb
index 7dff2b0..352ceb4 100644
--- a/lib/aethyr/core/render/format.rb
+++ b/lib/aethyr/core/render/format.rb
@@ -744,7 +744,7 @@ class FormatState
   end
 
   def apply(window)
-    @activate_color.call(window, self.fg, self.bg)
+    @activate_color.call(self.fg, self.bg)
 
     if blink?
       window.attron(Ncurses::A_BLINK)
@@ -786,7 +786,7 @@ class FormatState
   def revert(window)
     return @parent.apply(window) unless @parent.nil?
 
-    @activate_color.call(window, Color::Foreground.attribute(:white), Color::Background.attribute(:black))
+    @activate_color.call(Color::Foreground.attribute(:white), Color::Background.attribute(:black))
     window.attrset(Ncurses::A_NORMAL)
   end
 end
-- 
GitLab