2017-07-21 02:48:04 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
require 'thread'
|
|
|
|
|
2017-07-21 11:23:43 -04:00
|
|
|
require 'faker'
|
|
|
|
|
2017-07-24 08:48:22 -04:00
|
|
|
require 'screen'
|
2017-07-21 10:00:01 -04:00
|
|
|
|
2017-07-21 02:48:04 -04:00
|
|
|
class Main
|
|
|
|
def self.inherited(_base)
|
|
|
|
raise "#{self} is final"
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.mutex
|
|
|
|
(@mutex ||= Mutex.new).tap { freeze }
|
|
|
|
end
|
|
|
|
|
|
|
|
def initialize
|
|
|
|
raise "#{self.class} is singleton" unless self.class.mutex.try_lock
|
|
|
|
call
|
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
def call
|
|
|
|
before_loop
|
|
|
|
loop do
|
|
|
|
before_iteration
|
|
|
|
sleep
|
|
|
|
after_iteration
|
|
|
|
end
|
|
|
|
after_loop
|
|
|
|
end
|
|
|
|
|
|
|
|
def sleep
|
|
|
|
super 0.01
|
|
|
|
end
|
|
|
|
|
|
|
|
def before_loop
|
2017-07-24 08:48:22 -04:00
|
|
|
@screen = Screen.new
|
2017-07-22 05:46:15 -04:00
|
|
|
Style.default = Style.new
|
2017-07-21 02:48:04 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def after_loop
|
2017-07-24 08:48:22 -04:00
|
|
|
@screen.close
|
2017-07-21 02:48:04 -04:00
|
|
|
end
|
|
|
|
|
2017-07-21 07:41:05 -04:00
|
|
|
def before_iteration
|
2017-07-24 18:38:23 -04:00
|
|
|
@screen.props = state
|
2017-07-24 08:48:22 -04:00
|
|
|
@screen.render
|
2017-07-21 07:41:05 -04:00
|
|
|
end
|
2017-07-21 02:48:04 -04:00
|
|
|
|
|
|
|
def after_iteration
|
2017-07-24 08:48:22 -04:00
|
|
|
@screen.poll
|
2017-07-21 08:03:53 -04:00
|
|
|
end
|
2017-07-24 18:38:23 -04:00
|
|
|
|
|
|
|
def state
|
|
|
|
@state ||= {
|
2017-07-25 09:46:31 -04:00
|
|
|
x: 0,
|
|
|
|
y: 0,
|
|
|
|
width: Curses.stdscr.maxx,
|
|
|
|
height: Curses.stdscr.maxy,
|
2017-07-24 18:38:23 -04:00
|
|
|
focus: :sidebar,
|
2017-07-25 09:46:31 -04:00
|
|
|
focused: true,
|
|
|
|
|
2017-07-25 12:22:38 -04:00
|
|
|
on_window_left: method(:on_window_left),
|
2017-07-25 12:03:32 -04:00
|
|
|
on_window_right: method(:on_window_right),
|
|
|
|
|
2017-07-25 12:22:38 -04:00
|
|
|
on_menu_up: method(:on_menu_up),
|
|
|
|
on_menu_down: method(:on_menu_down),
|
|
|
|
|
2017-07-24 18:38:23 -04:00
|
|
|
sidebar: {
|
2017-07-25 09:46:31 -04:00
|
|
|
x: 0,
|
|
|
|
y: 0,
|
|
|
|
width: Widgets::Logo::WIDTH,
|
|
|
|
height: Curses.stdscr.maxy,
|
2017-07-24 18:38:23 -04:00
|
|
|
focus: :menu,
|
2017-07-25 09:46:31 -04:00
|
|
|
focused: true,
|
|
|
|
|
2017-07-25 10:29:01 -04:00
|
|
|
logo: {
|
2017-07-25 09:46:31 -04:00
|
|
|
x: 0,
|
|
|
|
y: 0,
|
|
|
|
width: Widgets::Logo::WIDTH,
|
2017-07-25 10:29:01 -04:00
|
|
|
height: Widgets::Logo::HEIGHT,
|
|
|
|
}.freeze,
|
|
|
|
|
|
|
|
menu: {
|
|
|
|
x: 0,
|
|
|
|
y: Widgets::Logo::HEIGHT,
|
|
|
|
width: Widgets::Logo::WIDTH,
|
2017-07-25 09:46:31 -04:00
|
|
|
height: Curses.stdscr.maxy - Widgets::Logo::HEIGHT,
|
2017-07-24 18:38:23 -04:00
|
|
|
focused: true,
|
2017-07-25 09:46:31 -04:00
|
|
|
|
2017-07-24 18:38:23 -04:00
|
|
|
active: 0,
|
|
|
|
top: 0,
|
|
|
|
items: 1.upto(Curses.stdscr.maxy).map do
|
|
|
|
{
|
|
|
|
name: Faker::Name.name,
|
|
|
|
online: [false, true].sample,
|
|
|
|
}
|
|
|
|
end,
|
|
|
|
}.freeze,
|
|
|
|
}.freeze,
|
2017-07-25 09:46:31 -04:00
|
|
|
|
2017-07-24 18:38:23 -04:00
|
|
|
chat: {
|
2017-07-25 10:29:01 -04:00
|
|
|
x: Widgets::Logo::WIDTH,
|
2017-07-25 09:46:31 -04:00
|
|
|
y: 0,
|
|
|
|
width: Curses.stdscr.maxx - Widgets::Logo::WIDTH,
|
|
|
|
height: Curses.stdscr.maxy,
|
2017-07-24 18:38:23 -04:00
|
|
|
focus: :new_message,
|
2017-07-25 09:46:31 -04:00
|
|
|
focused: false,
|
|
|
|
|
2017-07-24 18:38:23 -04:00
|
|
|
info: {
|
2017-07-25 10:29:01 -04:00
|
|
|
x: Widgets::Logo::WIDTH,
|
2017-07-25 09:46:31 -04:00
|
|
|
y: 0,
|
|
|
|
width: Curses.stdscr.maxx - Widgets::Logo::WIDTH,
|
|
|
|
height: 2,
|
|
|
|
focused: false,
|
|
|
|
|
2017-07-24 18:38:23 -04:00
|
|
|
name: Faker::Name.name,
|
|
|
|
public_key: SecureRandom.hex(32),
|
|
|
|
}.freeze,
|
2017-07-25 09:46:31 -04:00
|
|
|
|
2017-07-24 18:38:23 -04:00
|
|
|
new_message: {
|
2017-07-25 10:29:01 -04:00
|
|
|
x: Widgets::Logo::WIDTH,
|
|
|
|
y: Curses.stdscr.maxy - 1,
|
2017-07-25 09:46:31 -04:00
|
|
|
width: Curses.stdscr.maxx - Widgets::Logo::WIDTH,
|
2017-07-25 10:29:01 -04:00
|
|
|
height: 1,
|
2017-07-25 09:46:31 -04:00
|
|
|
focused: false,
|
|
|
|
|
2017-07-24 18:38:23 -04:00
|
|
|
text: '',
|
|
|
|
cursor_pos: 0,
|
|
|
|
}.freeze,
|
2017-07-25 09:46:31 -04:00
|
|
|
|
2017-07-24 18:38:23 -04:00
|
|
|
history: {
|
2017-07-25 10:29:01 -04:00
|
|
|
x: Widgets::Logo::WIDTH,
|
|
|
|
y: 2,
|
2017-07-25 09:46:31 -04:00
|
|
|
width: Curses.stdscr.maxx - Widgets::Logo::WIDTH,
|
2017-07-25 10:29:01 -04:00
|
|
|
height: Curses.stdscr.maxy - 3,
|
2017-07-25 09:46:31 -04:00
|
|
|
focused: true,
|
|
|
|
|
2017-07-24 18:38:23 -04:00
|
|
|
messages: 1.upto(100).map do
|
|
|
|
{
|
|
|
|
out: rand <= 0.2,
|
|
|
|
time: Faker::Time.forward,
|
|
|
|
name: Faker::Name.name,
|
|
|
|
text: Faker::Lorem.sentence,
|
|
|
|
}
|
|
|
|
end,
|
|
|
|
}.freeze,
|
|
|
|
}.freeze,
|
|
|
|
}.freeze
|
|
|
|
end
|
2017-07-25 12:03:32 -04:00
|
|
|
|
|
|
|
def on_window_left
|
|
|
|
@state = state.merge(
|
|
|
|
focus: :sidebar,
|
|
|
|
|
|
|
|
sidebar: state[:sidebar].merge(
|
|
|
|
focused: true,
|
|
|
|
|
|
|
|
logo: state[:sidebar][:logo].merge(focused: state[:sidebar][:focus] == :logo).freeze,
|
|
|
|
menu: state[:sidebar][:menu].merge(focused: state[:sidebar][:focus] == :menu).freeze,
|
|
|
|
).freeze,
|
|
|
|
|
|
|
|
chat: state[:chat].merge(
|
|
|
|
focused: false,
|
|
|
|
|
|
|
|
info: state[:chat][:info].merge(focused: false).freeze,
|
|
|
|
new_message: state[:chat][:new_message].merge(focused: false).freeze,
|
|
|
|
history: state[:chat][:history].merge(focused: false).freeze,
|
|
|
|
).freeze,
|
|
|
|
).freeze
|
|
|
|
end
|
|
|
|
|
|
|
|
def on_window_right
|
|
|
|
@state = state.merge(
|
|
|
|
focus: :sidebar,
|
|
|
|
|
|
|
|
sidebar: state[:sidebar].merge(
|
|
|
|
focused: true,
|
|
|
|
|
|
|
|
logo: state[:sidebar][:logo].merge(focused: false).freeze,
|
|
|
|
menu: state[:sidebar][:menu].merge(focused: false).freeze,
|
|
|
|
).freeze,
|
|
|
|
|
|
|
|
chat: state[:chat].merge(
|
|
|
|
focused: false,
|
|
|
|
|
|
|
|
info: state[:chat][:info].merge(focused: state[:chat][:focus] == :info).freeze,
|
|
|
|
new_message: state[:chat][:new_message].merge(focused: state[:chat][:focus] == :new_message).freeze,
|
|
|
|
history: state[:chat][:history].merge(focused: state[:chat][:focus] == :history).freeze,
|
|
|
|
).freeze,
|
|
|
|
).freeze
|
|
|
|
end
|
2017-07-25 12:22:38 -04:00
|
|
|
|
|
|
|
def on_menu_up
|
|
|
|
active = state[:sidebar][:menu][:active]
|
|
|
|
top = state[:sidebar][:menu][:top]
|
|
|
|
items = state[:sidebar][:menu][:items]
|
|
|
|
height = state[:sidebar][:menu][:height]
|
|
|
|
|
|
|
|
active -= 1
|
|
|
|
|
|
|
|
active = items.count - 1 if active.negative?
|
|
|
|
|
|
|
|
if active < top
|
|
|
|
top = active
|
|
|
|
elsif active >= top + height
|
|
|
|
top = active - height + 1
|
|
|
|
end
|
|
|
|
|
|
|
|
@state = state.merge(
|
|
|
|
sidebar: state[:sidebar].merge(
|
|
|
|
menu: state[:sidebar][:menu].merge(
|
|
|
|
active: active,
|
|
|
|
top: top,
|
|
|
|
).freeze,
|
|
|
|
).freeze,
|
|
|
|
).freeze
|
|
|
|
end
|
|
|
|
|
|
|
|
def on_menu_down
|
|
|
|
active = state[:sidebar][:menu][:active]
|
|
|
|
top = state[:sidebar][:menu][:top]
|
|
|
|
items = state[:sidebar][:menu][:items]
|
|
|
|
height = state[:sidebar][:menu][:height]
|
|
|
|
|
|
|
|
active += 1
|
|
|
|
|
|
|
|
active = 0 if active >= items.count
|
|
|
|
|
|
|
|
if active < top
|
|
|
|
top = active
|
|
|
|
elsif active >= top + height
|
|
|
|
top = active - height + 1
|
|
|
|
end
|
|
|
|
|
|
|
|
@state = state.merge(
|
|
|
|
sidebar: state[:sidebar].merge(
|
|
|
|
menu: state[:sidebar][:menu].merge(
|
|
|
|
active: active,
|
|
|
|
top: top,
|
|
|
|
).freeze,
|
|
|
|
).freeze,
|
|
|
|
).freeze
|
|
|
|
end
|
2017-07-21 08:38:11 -04:00
|
|
|
end
|