hexterm/main.rb

353 lines
9.8 KiB
Ruby

require 'gosu'
puts "Hexagonal Terminal Emulator"
puts "---------------------------"
puts "Gosu Version: #{Gosu::VERSION}"
puts "Ruby Version: #{RUBY_VERSION}"
$fg_color = Gosu::Color.new(0, 255, 0)
$bg_color = Gosu::Color.new(0, 0, 0)
$hex_color = Gosu::Color.new(255, 255, 255)
$pallette16 = [
Gosu::Color.new( 0, 0, 0), # Black 000000
Gosu::Color.new(255, 0, 0), # Red FF0000
Gosu::Color.new( 0, 255, 0), # Green 00FF00
Gosu::Color.new(255, 255, 0), # Yellow FFFF00
Gosu::Color.new( 0, 0, 255), # Blue 0000FF
Gosu::Color.new(255, 0, 255), # Magenta FF00FF
Gosu::Color.new( 0, 255, 255), # Cyan 00FFFF
Gosu::Color.new(128, 128, 128), # White 808080
Gosu::Color.new( 64, 64, 64), # Bright Black 404040
Gosu::Color.new(255, 128, 128), # Bright Red 008080
Gosu::Color.new(128, 255, 128), # Bright Green 800080
Gosu::Color.new(255, 255, 128), # Bright Yellow 000080
Gosu::Color.new(128, 128, 255), # Bright Blue 808000
Gosu::Color.new(255, 128, 255), # Bright Magenta 008000
Gosu::Color.new(128, 255, 255), # Bright Cyan 800000
Gosu::Color.new(255, 255, 255), # Bright White FFFFFF
]
#WHITE = Gosu::Color.new(255, 255, 255) # For image drawing purposes
$hex_width = 20
$hex_mid = 40
$hex_tip = 5
HEX_DEBUG_WIDTH = 15
def recalculate_hexagons
# 0
# / \
# 5 1
# | |
# 4 2
# \ /
# 3
$hex_points = [
# A six pointed hexagon
[$hex_width/2, 0 ], # Top center
[$hex_width , $hex_tip ], # Upper right
[$hex_width , $hex_tip+$hex_mid ], # Lower right
[$hex_width/2, ($hex_tip*2)+$hex_mid], # Bottom center
[0 , $hex_tip+$hex_mid ], # Lower left
[0 , $hex_tip ], # Upper left
# Iterate in a clockwise order
]
puts "Recalculated hexagons:"
puts "(#{$hex_points[0][0]},#{$hex_points[0][1]})".center(HEX_DEBUG_WIDTH)
puts "(#{$hex_points[5][0]},#{$hex_points[5][1]})".ljust(HEX_DEBUG_WIDTH/2) \
+ "(#{$hex_points[1][0]},#{$hex_points[1][1]})".rjust(HEX_DEBUG_WIDTH/2)
puts "(#{$hex_points[4][0]},#{$hex_points[4][1]})".ljust(HEX_DEBUG_WIDTH/2) \
+ "(#{$hex_points[2][0]},#{$hex_points[2][1]})".rjust(HEX_DEBUG_WIDTH/2)
puts "(#{$hex_points[3][0]},#{$hex_points[3][1]})".center(HEX_DEBUG_WIDTH)
$hex_x_offset = $hex_width/2
$hex_y_offset = $hex_tip + $hex_mid
$hex_bgs.clear!
end
class HexEditXref_BgGrphStore
def initialize
clear!
end
def clear!
@hex_ids = Hash.new #hexid => color
@hex_refs = Hash.new #color => hexids
@hex_bgs = Hash.new #color => bg
end
def register(hexid,color)
raise TypeError, ("color must be a Gosu::Color, not a '#{color.class}'"
) unless color.is_a? Gosu::Color
argb = color.argb
return if (@hex_ids.has_key?(hexid) &&
@hex_ids[hexid] == argb)
unregister hexid
@hex_ids[hexid] = argb
if @hex_refs.has_key? argb
@hex_refs[argb].add(hexid)
else
@hex_refs[argb] = Set.new([hexid, ])
end
render argb
end
def unregister(hexid)
return unless @hex_ids.has_key? hexid
# ➣ If the key doesn't exist, assume
# the call was made mistakenly.
# Note: may cause leaks later
argb = @hex_ids[hexid]
@hex_ids.delete hexid
if @hex_refs.has_key? argb
@hex_refs[argb].delete hexid
if @hex_refs[argb].size < 1
@hex_refs.delete argb
end
end
if (@hex_bgs.has_key?(argb) &&
!@hex_refs.has_key?(argb))
@hex_bgs.delete argb
end
end
def [](color)
raise TypeError, ("color must be a Gosu::Color, not a '#{color.class}'"
) unless color.is_a? Gosu::Color
argb = color.argb
raise RuntimeError.new(
"Color #{argb} was requested from "+
"a HEX_BGs class, but was not "+
"registered beforehand."
) unless @hex_bgs.has_key? argb
return @hex_bgs[argb]
end
private
def render(argb)
color = Gosu::Color.new(argb)
@hex_bgs[argb] = Gosu::record($hex_width, $hex_mid+($hex_tip*2)) {
# Double Quad Method (0145, 1234):
Gosu::draw_quad( # 0145
$hex_points[0][0], $hex_points[0][1], color, #0
$hex_points[1][0], $hex_points[1][1], color, #1
$hex_points[4][0], $hex_points[4][1], color, #4
$hex_points[5][0], $hex_points[5][1], color, #5
0
)
Gosu.draw_quad( # 1234
$hex_points[1][0], $hex_points[1][1], color, #1
$hex_points[2][0], $hex_points[2][1], color, #2
$hex_points[3][0], $hex_points[3][1], color, #3
$hex_points[4][0], $hex_points[4][1], color, #4
0
)
}
end
end
class TextProps
attr_accessor \
:font, :bg_color, :fg_color,
:hex_border_color,
:bold, :italic, :underline, :overline, :strikethrough
def initialize(font, text_props=nil)
if text_props != nil
@font = font
@fg_color = text_props.fg_color
@bg_color = text_props.bg_color
@hex_border_color = text_props.hex_border_color
@bold = text_props.bold
@italic = text_props.italic
@underline = text_props.underline
@overline = text_props.overline
@strikethrough = text_props.strikethrough
return
end
@font = font
@fg_color = $fg_color
@bg_color = $bg_color
@hex_border_color = $hex_color
@bold = false
@italic = false
@underline = false
@overline = false
@strikethrough = false
return
end
end
class Hex
attr_accessor :x, :y, :char, :text_props
def initialize(x, y, char, text_props)
@x = x
@y = y
@char = char
@text_props = text_props
end
def draw
$hex_bgs[@text_props.bg_color].draw(
@x, @y, 0, 1, 1, Gosu::Color::WHITE
)
@text_props.font.draw_text(@char, @x, @y+$hex_tip, 2, 1.0, 1.0, @text_props.fg_color)
end
end
class HexagonTerminalWindow < Gosu::Window
def initialize(width, height, fullscreen)
# Window
super
self.caption = "Hexagon Terminal"
@width = width
@height = height
# Fonts (load default monospaced font)
@font = Gosu::Font.new(self, "Monaspace Krypton", 30)
$hex_width = @font.text_width("_").round()
$hex_mid = @font.height.round()
recalculate_hexagons
# State
@cursor_pos = [0, 1] # row-1, col-1
@input_buffer = ""
@output_buffer = "."
@active_styling = TextProps.new(@font)
@do_redraw = true
self.text_input = Gosu::TextInput.new
@old_text = [0,'',0]
# Hexes
@xexes = ((width-$hex_x_offset) / $hex_width).floor # xexes = 'x's of hexes
@yexes = ((height-$hex_tip) / $hex_y_offset ).floor # yexes = 'y's of hexes
@next_row_is_shifted = false
@hexes = []
for row in 0..@yexes-1
@hexes << []
for col in 0..@xexes-1
x = (col*$hex_width) + ((row % 2 == 1) ? $hex_x_offset : 0)
y = row*$hex_y_offset
@active_styling.bg_color = Gosu::Color.new(rand(255), rand(255), rand(255))
@hexes[-1] << Hex.new(x, y, "W", TextProps.new(@font, @active_styling))
@next_row_is_shifted = row % 2 == 0
end
end
# Clear the screen
@hexes.each do |rex|
rex.each do |hex|
hex.char = ' '
end
end
end
def needs_redraw?
# Quads are expensive; Avoid redrawing.
return @do_redraw
end
def update
$hex_bgs.clean
if @output_buffer.length > 0
render_output
@do_redraw = true
end
new_text = [
self.text_input.selection_start,
self.text_input.text,
self.text_input.caret_pos
]
if @old_text != new_text
@old_text = [
new_text[0],
new_text[1],
new_text[2]
]
out = ''+new_text[1]
if new_text[0]<new_text[2]
out.insert(new_text[2],"\x1b[m|")
out.insert(new_text[0],"\x1b[7m")
elsif new_text[2]<new_text[0]
out.insert(new_text[0],"\x1b[m")
out.insert(new_text[2],"|\x1b[7m")
else
out.insert(new_text[2],"|")
end
print "\x1b[G\x1b[2K#{out}\x1b[#{new_text[2]+1}G"
end
end
def draw
@do_redraw = false
# Draw the background (clears the screen)
Gosu::draw_rect(
0, 0, @width, @height, $bg_color, 0
)
# Draw the hexes
@hexes.each do |rex| # rex=row of hexes
rex.each do |hex|
hex.draw
end
end
end
def render_output
# Render things from the output buffer
# into the hex grid
@output_buffer.each_char.with_index do |char, i|
if @escape_mode
handle_escape_char(char)
end
case char
when "\n"
@cursor_pos[0] += 1
@cursor_pos[1] = 0
when "\x1b" # ESC
@escape_mode = true
@escape_buffer = ""
next
else
@hexes[@cursor_pos[0]][@cursor_pos[1]].char = char
@cursor_pos[1] += 1
end
if @cursor_pos[1] >= @hexes[0].length
@cursor_pos[0] += 1
@cursor_pos[1] = 0
end
if @cursor_pos[0] >= @hexes.length
@cursor_pos[0] = @hexes.length - 1
scroll_with_new_line
end
end
@do_redraw = true
# @output_buffer = ""
end
def scroll_with_new_line
@active_styling.hex_border_color =\
Gosu::Color.new(rand(255), rand(255), rand(255))
@hexes.each do |rex|
rex.each do |hex|
hex.y -= $hex_y_offset
end
end
@hexes = @hexes[1..-1]
@hexes << []
for i in 0..@xexes-1
@active_styling.bg_color = Gosu::Color.new(rand(255), rand(255), rand(255))
@hexes[-1] << Hex.new(
(i*$hex_width) + (@next_row_is_shifted? $hex_x_offset : 0),
(@yexes-1)*$hex_y_offset,
" ", TextProps.new(@font, @active_styling))
end
@next_row_is_shifted = !@next_row_is_shifted
end
def handle_escape_char(char)
@escape_buffer += @output_buffer[0]
@output_buffer = @output_buffer[1..-1]
end
end
$hex_bgs = HexEditXref_BgGrphStore.new
recalculate_hexagons
window = HexagonTerminalWindow.new(800, 600, false)
window.show