Jun
7th
Sun
7th
stdinから一行ずつ読んでそれっぽく表示 on ruby-gnome2
ちょっと前に社内で発表したネタだが仕事中に書いたコードじゃないから公開しておく。どうせRuby会議で使いたい人がいるだろうし。
フォントと色は各自の環境に合わせて弄ること。
#!/bin/ruby -Ku
# Copyright(c) 2009 URABE, Shyouhei.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this code, to deal in the code without restriction, including without
# limitation the rights to use, copy, modify, merge, publish, distribute,
# sublicense, and/or sell copies of the code, and to permit persons to whom the
# code is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the code.
#
# THE CODE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHOR OR COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE CODE OR THE USE OR OTHER DEALINGS IN THE
# CODE.
require 'gnome2'
FPS = 60
Font = Pango::FontDescription.new 'M+ 2p, Ultra-Bold 32'
Line = [0.3, 0.5, 0.7, 0.9] # rgba 0.0 - 1.0
Fill = [0.9, 0.7, 0.5, 0.3] # rgba 0.0 - 1.0
class IO
RS = /[\r\n]+/n
def gets_nonblock siz=4096
@buf1 ||= String.new
@buf2 ||= String.new
read_nonblock siz, @buf2
rescue EOFError
# close
raise
ensure
@buf1 << @buf2
while m = RS.match(@buf1)
str, @buf1 = m.pre_match, m.post_match
yield str unless str.empty?
end
end
end
class Gdk::Drawable
def transparent!
c = create_cairo_context
c.set_source_rgba 1, 1, 1, 0
c.operator = Cairo::OPERATOR_SOURCE
c.paint
c
end
end
class Comment
def initialize w, str
l = Pango::Layout.new Gdk::Pango.context
l.set_text str
l.set_font_description ::Font
@extent = l.get_pixel_extents.last
@x = nil
@y = 0
@map = Gdk::Pixmap.new w.window, @extent.width + w.rect.width / 4, @extent.height, -1
c = @map.transparent!
c.pango_layout_path l
c.set_source_rgba(*::Fill)
c.fill_preserve
c.set_source_rgba(*::Line)
c.set_line_width 1
c.stroke
end
def render r, w, g
# nicovideo player is 512px (+ comment length), 4sec.
i = @extent.width
j = r.width
k = (i + j) / (4.0 * ::FPS)
@x ? @x -= k : @x = j
r = Gdk::Rectangle.new @x, @y, i + k + 1, @extent.height
w.begin_paint r
w.draw_drawable g, @map, 0, 0, *r
w.end_paint
end
attr_reader :x, :extent
attr_accessor :y
def rightmost? w
@x ? @x + @extent.width > w : true
end
def visible?
rightmost? 0
end
end
module Player
def render force = true
return !force unless force or not @comments.empty?
w = window
w.transparent! if force
g = Gdk::GC.new w
@comments.each do |i| i.render @rect, w, g end
@comments.reject! do |i| not i.visible? end
@cache.reject! do |i| not i.visible? end
return !force
end
def push str
c = Comment.new self, str
e = c.extent
n = @rect.height / e.height + 1
i = -1
n.times do |i|
d = @cache[i]
break if d.nil?
next if d.rightmost? @rect.width
break if d.extent.width >= e.width
end
if i == n - 1 # "Curtain Fire" mode
c.y = rand @rect.height - e.height
else
c.y = i * e.height
end
@cache[i] = c
@comments << c
end
def kick fp
@comments = []
@cache = []
trap :INT do
Gtk.main_quit
end
Gtk.timeout_add 1000.0 / ::FPS do
render false
end
tag = Gdk::Input.add fp, Gdk::Input::READ do
begin
fp.gets_nonblock do |str|
str.chomp!
push str
end
rescue EOFError
Gdk::Input.remove tag
end
end
end
end
class Toplevel < Gtk::Window
include Player
attr_reader :rect
def initialize
super
@kicked = false
[[:colormap=, screen.rgba_colormap || screen.rgb_colormap],
[:skip_taskbar_hint=, true],
[:accept_focus=, false],
[:app_paintable=, true],
[:decorated=, false],
[:keep_above=, true],
[:title=, ''],
[:fullscreen],
[:stick],
].each do |a|
send(*a)
end
{
"destroy" => lambda do Gtk.main_quit end,
"expose-event" => lambda do |w, e|
kick ::STDIN unless @kicked
render :force
end,
"configure-event" => lambda do |w, e|
m = Gdk::Pixmap.new nil, e.width, e.height, 1
[m, window].each &:transparent!
input_shape_combine_mask nil, 0, 0
input_shape_combine_mask m, 0, 0
@rect = Gdk::Rectangle.new e.x, e.y, e.width, e.height
false
end,
}.each_pair do |k, v|
signal_connect k, &v
end
show
end
end
Toplevel.new
Gtk.main
# Local Variables:
# mode: ruby
# coding: utf-8
# indent-tabs-mode: t
# tab-width: 3
# ruby-indent-level: 3
# fill-column: 79
# default-justification: full
# End:
# vi: ts=3 sw=3