« 時計をつくる4 | トップページ | 時計をつくる6 »

2009年5月17日 (日)

時計をつくる5

再び表示系の処理を追加します。

今度はThrought Clockの特徴的な機能、針や文字以外は透けるようにします。
これを実現するためにどうやるか結構悩みました。

TopLevelWindow#set_transparentとかWindow#set_window_style_flagなどイロイロいじってみましたがどうもよろしくない。もう一旦ビットマップに落としてWx::SplashScreenでSPLASH_NO_TIMEOUTにしてタイマーイベントででペイントしていけばよいかな、と考えていました。

ん?ビットマップといえばサンプルにRubyロゴの形になったウィンドウのデモがあったことを思い出してどうやっているのか調べてみました。結果、TopLevelWindow#set_shapeを使えば実現できそう、ということで今回はこれを使って表示部分を書き換えてみます。

今回はハマったポイントが結構あります。

ビットマップに描画するにはWx::Bitmap#drawを使います。これを使うとMemoryDCが渡されるので、DCのdraw系のメソッドで線などを描いていくことが出来ます。

当初クラスリファレンスでBitmap#drawのリンクをクリックしてもに飛ばないので、リネームしたのかといろいろ調べても情報が出てこなくて、結局よく見てみるとリンクの先がちゃんと設定されていないだけで、メソッドについてちゃんと書かれていました。メソッド一覧から抜け落ちているので注意。

DCのdraw系メソッドはOKだったので、次にWx::GraphicsContextのメソッドを試したのですが、描画でエラーは出ないのですが表示が全くされずにココでも悩みました。
いろいろ試した結果、Rubyロゴのpng画像をWx::Imageで読み込んだときは描画がうまくいったので、Wx::Imageのメソッドを試行錯誤してみました。
パレットのようなモノが設定されていないのかなとWx::Paletteを調べてみたが関連メソッドは未実装だったりして、最終的に辿り着いたのが、Wx::Image#init_alpha。
どうやらアルファチャネルの初期化が必要なようだ。Googleで引っかかってきた情報と比較すると、init_alphaせずに書いてある例もあったのでなんとなくプラットフォーム依存な気もするのですが描画できるようになったのでよしとします。

Wx::GraphicsContextの描画もうまくいったので、続いて試しているとフォントの色が設定を反映していないことに気付きました。何故かFrameのバックグラウンドカラーと同じになってしまう。これはDCのdraw_textもちゃんと表示されずに悩んでしまった。
あきらめてバックグラウンドカラーをいじってしまおうと設定を変えてみるとBLACKにしたらフォントの色が反映されるようになりました。BLACKは透過する設定になっているのでバックグラウンドカラーを透過してその後ろにある、フォントが表示されたということなのでしょうか。

とすると、レイヤー的には上から

  • GraphicsContext
  • DC
  • Frameのバックグラウンド
  • GraphicsContextフォント

のような形になっているようにおもえる。うーむ謎。

ということで、ソースです。TopLevelWindow#set_shapeを使うためにXRCでwxFRAME_SHAPEDをオンにしています。コレは省略します。

  1. begin
  2.   require 'rubygems'
  3. rescue LoadError
  4. end
  5. require 'wx'
  6. require 'RTC.rb'
  7.  
  8. class RubyThroughClock < RTC
  9.   def initialize(parent = nil)
  10.     super()
  11.  
  12.     @tbicon = RTC_TaskBarIcon.new(self)
  13.     # structure for testing
  14.     @time = Struct.new('Time', :hour, :min).new
  15.     @time.hour = 0
  16.     @time.min = 0
  17.     # Create Buffer Bitmap
  18.     createBuffer()
  19.  
  20.     # Event handler
  21.     evt_paint(){|event| drawClock()}
  22.     evt_size(){|event| createBuffer(); drawClock()}
  23.     evt_close(){|event| onClose(event)}
  24.     m_spinctrl1.evt_spinctrl(m_spinctrl1.get_id()){|event| updateHour(event)}
  25.     m_spinctrl2.evt_spinctrl(m_spinctrl2.get_id()){|event| updateMin(event)}
  26.  
  27.     # Frame size
  28.     set_size(Wx::Size.new(250,250))
  29.  
  30.   end
  31.  
  32.   def createBuffer()
  33.     size = get_client_size()
  34.     @img = Wx::Image.new(size.get_width, size.get_height)
  35.     @img.init_alpha
  36.     @buffer = @img.convert_to_bitmap
  37.     set_background_colour(Wx::BLACK)
  38.     refresh
  39.   end
  40.  
  41.   def onClose(event)
  42.     @tbicon.remove_icon
  43.     @tbicon.destroy
  44.     event.skip
  45.   end
  46.  
  47.   def updateHour(event)
  48.     @time.hour = @m_spinctrl1.get_value
  49.     drawClock()
  50.   end
  51.  
  52.   def updateMin(event)
  53.     @time.min = @m_spinctrl2.get_value
  54.     drawClock()
  55.   end
  56.  
  57.   def drawClock()
  58.     @buffer.draw do |dc|
  59.       # Create Graphics Context from Memory DC
  60.       gc = Wx::GraphicsContext.create(dc)
  61.       dc.set_background(Wx::TRANSPARENT_BRUSH)
  62.       # Clear DC
  63.       dc.clear
  64.       # Get current time
  65.       time = Time.now
  66.       #time = @time
  67.  
  68.       # Draw the current time
  69.       time_str = "%02d" % time.hour.to_s + ':' + "%02d" % time.min.to_s
  70.       dc.set_text_foreground(Wx::GREEN)
  71.       dc.draw_text(time_str, 5, dc.size.height - 30)
  72.       # Offset X Y position
  73.       gc.translate(dc.size.width / 2, dc.size.height / 2)
  74.       # Draw Circle
  75.       drawCircle(gc, dc.size.height / 2, Wx::Pen.new(Wx::RED, 3))
  76.       # Draw Hour hand
  77.       hand = [ [0, dc.size.height / 30], [-5, 0], [0, -dc.size.height / 4], [5, 0] ]
  78.       drawHand(gc, hand, Wx::CYAN_BRUSH, Math::PI * ( 30 * (time.hour % 12)) / 180)
  79.       # Draw Min hand
  80.       hand = [ [0, dc.size.height / 30], [-5, 0], [0, -dc.size.height * 2 / 5], [5, 0] ]
  81.       drawHand(gc, hand, Wx::BLUE_BRUSH, Math::PI * ( 6 * time.min ) / 180)
  82.     end
  83.     paint do |dc|
  84.       r = Wx::Region.new(@buffer)
  85.       set_shape(r)
  86.       dc.draw_bitmap(@buffer, 0, 0, true)
  87.     end
  88.   end
  89.  
  90.   def drawHand(gc, hand, brush, radians)
  91.     path = gc.create_path
  92.     mt   = gc.get_transform
  93.     # Set Hand Polygon
  94.     hand.each do |xy|
  95.       path.add_line_to_point(xy[0], xy[1])
  96.     end
  97.     path.close_subpath
  98.  
  99.     # Move Hand
  100.     gc.rotate(radians)
  101.     # Pen Setting
  102.     gc.set_pen(Wx::Pen.new(Wx::BLACK, 1))
  103.     # Brush Setting
  104.     gc.set_brush(brush)
  105.  
  106.     gc.fill_path(path)
  107.     #gc.fill_path(path, Wx::WINDING_RULE)
  108.     #gc.stroke_path(path)
  109.     #gc.draw_path(path)
  110.  
  111.     gc.set_transform(mt)
  112.   end
  113.  
  114.   def drawCircle(gc, radius, pen)
  115.     path = gc.create_path
  116.     mt   = gc.get_transform
  117.     # Font setting
  118.     font = Wx::NORMAL_FONT
  119.     font.set_point_size(14)
  120.     gc.set_font(font,Wx::Colour.new(255,0,255,255))
  121.     #gc.set_font(font,Wx::BLACK)
  122.  
  123.     # Set Path
  124.     path.add_circle(0, 0, radius * 4 / 5 - font.get_point_size)
  125.     # Pen Setting
  126.     gc.set_pen(pen)
  127.     # Draw Circle
  128.     gc.draw_path(path)
  129.     # Offset X Y position
  130.     gc.translate(-font.get_point_size / 2, -font.get_point_size / 2 - 5)
  131.     # Set the number of hour
  132.     gc.rotate(Math::PI)
  133.     for hour in 1..12
  134.       gc.rotate(Math::PI * 30  / 180)
  135.       gc.draw_text(hour.to_s, 0, radius * 4 / 5, Math::PI * (30 * hour + 180)  / 180)
  136.     end
  137.  
  138.     gc.set_transform(mt)
  139.   end
  140.  
  141.   def showOption(event)
  142.   end
  143.  
  144.   def onStay_on_top(event)
  145.     flag = get_window_style_flag
  146.     if event.is_checked
  147.       flag = flag | Wx::STAY_ON_TOP
  148.     else
  149.       flag = flag ^ Wx::STAY_ON_TOP
  150.     end
  151.     set_window_style_flag(flag)
  152.     refresh
  153.   end
  154.  
  155.   def moveRU(event)
  156.     rect = Wx::Display.new.get_client_area
  157.     width = get_screen_rect.get_width
  158.     point = rect.get_top_right
  159.     move(point.x - width, point.y)
  160.   end
  161.  
  162.   def moveRB(event)
  163.     rect = Wx::Display.new.get_client_area
  164.     width = get_screen_rect.get_width
  165.     height = get_screen_rect.get_height
  166.     point = rect.get_bottom_right
  167.     move(point.x - width, point.y - height)
  168.   end
  169.  
  170.   def moveLU(event)
  171.     rect = Wx::Display.new.get_client_area
  172.     move(rect.get_top_left)
  173.   end
  174.  
  175.   def moveLB(event)
  176.     rect = Wx::Display.new.get_client_area
  177.     height = get_screen_rect.get_height
  178.     point = rect.get_bottom_left
  179.     move(point.x, point.y - height)
  180.   end
  181.  
  182. end
  183.  
  184. class RTC_TaskBarIcon < Wx::TaskBarIcon
  185.   # Event ID
  186.   TBMENU_OPTION      = 6000
  187.   TBMENU_STAY_ON_TOP = 6001
  188.   TBMENU_MOVE_RU     = 6002
  189.   TBMENU_MOVE_RB     = 6003
  190.   TBMENU_MOVE_LU     = 6004
  191.   TBMENU_MOVE_LB     = 6005
  192.   TBMENU_EXIT        = 6006
  193.  
  194.   def initialize(frame)
  195.     super()
  196.     @frame = frame
  197.     @stay_on_top = true
  198.     # starting image
  199.     icon = make_icon('ruby.png')
  200.     set_icon(icon, 'RubyThroughClock')
  201.     # Menu Event
  202.     evt_menu(TBMENU_OPTION)      {|event| @frame.showOption(event) }
  203.     evt_menu(TBMENU_STAY_ON_TOP) {|event| onTB_stay_on_top(event) }
  204.     evt_menu(TBMENU_MOVE_RU)     {|event| @frame.moveRU(event) }
  205.     evt_menu(TBMENU_MOVE_RB)     {|event| @frame.moveRB(event) }
  206.     evt_menu(TBMENU_MOVE_LU)     {|event| @frame.moveLU(event) }
  207.     evt_menu(TBMENU_MOVE_LB)     {|event| @frame.moveLB(event) }
  208.     evt_menu(TBMENU_EXIT)        {|event| @frame.close }
  209.   end
  210.  
  211.   def onTB_stay_on_top(event)
  212.     @stay_on_top = event.is_checked
  213.     @frame.onStay_on_top(event)
  214.   end
  215.  
  216.   def make_icon(imgname)
  217.     # Different platforms have different requirements for the
  218.     #  taskbar icon size
  219.     filename = File.join( File.dirname(__FILE__), imgname )
  220.     img = Wx::Image.new(filename, Wx::BITMAP_TYPE_PNG)
  221.     img.set_mask
  222.     if Wx::PLATFORM == "WXMSW"
  223.       img = img.scale(16, 16)
  224.     elsif Wx::PLATFORM == "WXGTK"
  225.       img = img.scale(22, 22)
  226.     end
  227.     # WXMAC can be any size up to 128x128, so don't scale
  228.     icon = Wx::Icon.new
  229.     icon.copy_from_bitmap(Wx::Bitmap.new(img))
  230.     return icon
  231.   end
  232.  
  233.   def create_popup_menu
  234.     # Called by the base class when it needs to popup the menu
  235.     #  (the default evt_right_down event).  Create and return
  236.     #  the menu to display.
  237.     menu = Wx::Menu.new
  238.     menu.append(TBMENU_OPTION,     "Option")
  239.     menu.append_check_item(TBMENU_STAY_ON_TOP,"Stay On Top")
  240.     menu.check(TBMENU_STAY_ON_TOP, @stay_on_top)
  241.     submenu = Wx::Menu.new
  242.     submenu.append(TBMENU_MOVE_RU, "Right Upper")
  243.     submenu.append(TBMENU_MOVE_RB, "Right Bottom")
  244.     submenu.append(TBMENU_MOVE_LU, "Left Upper")
  245.     submenu.append(TBMENU_MOVE_LB, "Left Bottom")
  246.     menu.append_menu(Wx::ID_ANY,   "Move",submenu)
  247.     menu.append(TBMENU_EXIT,       "Exit")
  248.     return menu
  249.   end
  250. end
  251.  
  252. class App < Wx::App
  253.   def on_init
  254.     f =  ::RubyThroughClock.new
  255.     f.show
  256.   end
  257. end
  258. App.new.main_loop
  259.  

実行結果。まずはshapeオフ。スピンボックス等はまだそのままです。

01_2

次にshapeオン。ちゃんと透過しています。

02_2

|

« 時計をつくる4 | トップページ | 時計をつくる6 »

時計をつくる」カテゴリの記事

コメント

コメントを書く



(ウェブ上には掲載しません)




トラックバック

この記事のトラックバックURL:
http://app.f.cocolog-nifty.com/t/trackback/1201593/29655402

この記事へのトラックバック一覧です: 時計をつくる5:

« 時計をつくる4 | トップページ | 時計をつくる6 »