Created
January 12, 2018 16:55
-
-
Save jrunning/b2fb863555c9e35c426d2fec25d9ac27 to your computer and use it in GitHub Desktop.
terminal-eyes.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
at_exit { stop! } | |
require "io/console" | |
$last_click_pos = nil | |
LEFT_EYE_OFFSET = -3 | |
RIGHT_EYE_OFFSET = 3 | |
EYE_CENTER_OFFSET = 2 | |
# Xterm control sequences | |
CTL_START_TRACKING = "\e[?1003h".freeze | |
CTL_STOP_TRACKING = "\e[?1003l".freeze | |
CTL_CLEAR_CANVAS = "\e[2J".freeze | |
CTL_MOVE_CURSOR = "\e[%d;%dH".freeze | |
# CTL_HIDE_CURSOR = "\e[?25l".freeze | |
# CTL_SHOW_CURSOR = "\e[?25h".freeze | |
def set_cursor_position(x, y) | |
printf CTL_MOVE_CURSOR, y, x | |
end | |
def start! | |
# print CTL_HIDE_CURSOR | |
print CTL_CLEAR_CANVAS | |
print CTL_START_TRACKING | |
end | |
def stop! | |
print CTL_STOP_TRACKING | |
# print CTL_SHOW_CURSOR | |
end | |
# Parse Xterm control sequences | |
MATCH_SEQUENCE = /\e\[M(?<event>[C#])(?<x>.)(?<y>.)/ | |
EVENT_CODE_RELEASE = "#".freeze | |
def decode_sequence(str) | |
return unless MATCH_SEQUENCE =~ str | |
{ event_code: $~["event"], | |
x: $~["x"].ord - 32, | |
y: $~["y"].ord - 32, | |
} | |
end | |
# \e[5D = move cursor 5 left | |
# \e[1B = move cursor 1 down | |
EYE = | |
".---.\e[5D\e[1B" \ | |
"|cde|\e[5D\e[1B" \ | |
"|bf7|\e[5D\e[1B" \ | |
"|a98|\e[5D\e[1B" \ | |
"'---'" | |
def eye(target_x, target_y) | |
# Calculate angle w/ x-axis | |
hyp = Math.sqrt(target_y**2 + target_x**2) | |
theta = Math.asin(target_y / hyp) | |
# Convert to eighths of a turn | |
eighths = 4 * theta / Math::PI | |
octant_num = | |
if target_x < 0 | |
4 - eighths | |
elsif target_y < 0 | |
8 + eighths | |
else | |
eighths | |
end | |
# Convert octant 0–7 to 7–e (avoids conflict w/ nums in control seqs), or f for center | |
octant_code = octant_num.nan? ? 15 : octant_num.round % 8 + 7 | |
EYE.tr(octant_code.to_s(16), "0").tr("789a-f", " ") | |
end | |
def draw_eyes(mouse_x, mouse_y) | |
base_x, y = $last_click_pos | |
[ base_x - LEFT_EYE_OFFSET, base_x - RIGHT_EYE_OFFSET ].each do |x| | |
set_cursor_position(x - EYE_CENTER_OFFSET, y - EYE_CENTER_OFFSET) | |
print eye(mouse_x - x, mouse_y - y) | |
end | |
end | |
def handle_event(event_code:, x:, y:) | |
print CTL_CLEAR_CANVAS | |
$last_click_pos = [ x, y ] if event_code == EVENT_CODE_RELEASE | |
draw_eyes(x, y) unless $last_click_pos.nil? | |
end | |
start! | |
input = "" | |
while char = STDIN.getch | |
exit 130 if char == "\003" # Ctrl+C | |
input << char | |
if info = decode_sequence(input) | |
handle_event(info) | |
input = "" | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment