卜部昌平のあまりreblogしないtumblr RSS

Archive

Jun
7th
Sun
permalink

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
  1. thata reblogged this from shyouhei
  2. shyouhei posted this