(2017.11.14 追加記事;

gtk2,gtk3 で図形を描画します。(Gtk::DrawingAreaCairo::ContextGdk::Drawable

図形を描画する方法は主に2つあります。
A.Cairo::Context を使う方法
B.Gdk::Drawable を使う方法(gtk2のみ)

どちらも、最初に Gtk::DrawingArea(描画用ウィジェット)を作成して、そのウィジェット上に図形を描画していきます。
GTK3 は、A. Cairo::Context を使う方法のみになりました。
Use cairo for drawing
In GTK+ 3, the GDK drawing API (...) has been removed. All drawing in GTK+ 3 is done via cairo.
The GdkGC and GdkImage objects, as well as all the functions using them, are gone.

参考)
A.
→・(12) Drawing Area And Cairo - Ruby/GTK2 Tutorial - Ruby-GNOME2 Project Website
→・(12.3) Ruby Cairo Tutorial
 ・(12.3.1) Cairo's Drawing Model
 ・(12.3.2) Drawing with Cairo
→・Painting with Cairo in Ruby GTK - ZetCode
→・PyCairo tutorial  - ZetCode

B.
→・ウインドウへの直接描画(2) - Gdk/Drawable - Ruby-GNOME2 Project Website
→・Drawing Widgets - Ruby/GTK2 Tutorial - Ruby-GNOME2 Project Website
→・gtkmandel.rb - iso.enat.jp さんのブログ
→・[ruby-list/42528] Gdk/GC の foreground - ruby_list
 ・[ruby-list/42529] Re/ Gdk/GC の foreground - ruby_list



1.Gtk::DrawingArea(描画用ウィジェット)の準備(A. B. 共通)
1-1)Gtk::DrawingArea を作る


1-2)DrawingArea の設定
○DrawingArea のサイズを設定する
 幅(width)、高さ(height)をピクセル単位で設定します
 -1;サイズ指定なし

○背景の色を設定する
・gtk2; 
  state(状態);Gtk::STATE_NORMAL(または :normal);通常状態(これ以外は無効?)
  color(色) ;・Gdk::Color.new(red, green, blue)
           red, green, blue はそれぞれ 0 - 65535 の値
         ・Gdk::Color.parse(spec)
            spec は'green'など代表的色名、
          または '#FF0000' のようにRGBそれぞれ 00 - FF の値
・gtk3;
 設定方法が変わりました
 → 2-A-2) を参照
 

1-3)描画の設定
 ・detailed_signal:"expose_event";gtk2
          "draw"    ;gtk3
 ・{ |widget, *args| 処理 }
  widget;シグナルを発行したウィジット(ここでは DrawingArea)
  *args;gtk3では、Cairo::Contextが入ります
     (Gtk::DrawingArea.window.create_cairo_context と同じことになります)
     gtk2では、Gdk::EventExpose が入ります(使い道がない?) 
  処理;描画する処理を書きます

※描画するには、
 Gtk::DrawingArea.signal_connect("draw") do |widget, context|
   (描画処理)
 end
 のブロック内に描画処理を記述します


1-4)Gdk::Drawable を作る
 実際に描画する準備として Drawableクラスが必要なので、DrawingArea から作成します
 ※gtk3 では、signal_connect("draw") { |widget, context| ... } のブロック変数 context に Cairo::Context が代入されるので作らなくてもよい場合もあります

○Gtk::DrawingArea#window
 DrawingArea が持つ Gdk::Window(Gtkではなく、Gdk)を呼び出します
 Gdk::Window は Gdk::Drawableクラスを継承しているので、Drawableの持つ描画メソッドが使えます
 ※ただし、signal_connect のブロック内でないと作れないようです
 

2.描画する
2-A.Cairo::Context を使う方法
画像描画ライブラリcairo を使って図形を描画する方法です

2-A-1)Cairo::Context を作る
Gdk::Drawable#create_cairo_context
 Gtk::DrawingArea#window で Gdk::Drawableが作れるので(ただし、signal_connect のブロック内)、
 Gtk::DrawingArea.window.create_cairo_context で Cairo::Context が作れます

 ※ もしくは、gtk3では
  Gtk::DrawingArea#signal_connect("draw") { |widget, context| ... }
  の contex に Cairo::Context が入ります


2-A-2)図形を描く
上述(1-3))したように
Gtk::DrawingArea#signal_connect のブロック内に記述します

Cairo::Context#set_source_rgb(r, g, b)
Cairo::Context#set_source_rgba(r, g, b, a)
 色の設定をする
 ・red, green, blue (, alpha);0-1.0

○Cairo::Context#set_line_width(width)
 線の太さを設定する(1.0 が標準)

○Cairo::Context#scale(x_scale, y_scale)
 座標の拡大率を設定する(1.0で等倍)

Cairo::Context#move_to(x, y)
 点を打つ座標を指定する

Cairo::Context#line_to(x, y)
 前の座標から(x, y)までの直線を引く(経路を)指定をする

Cairo::Context#stroke
 指定された経路を線で描く

○Cairo::Context#rectangle(x1, y1, x2, y2)
 四角を描く

Cairo::Context#arc(x, y, radius, starting_point, ending_point)
 円弧を描く
 ・中心;x, y
 ・半径;radius
 ・描画開始点;starting_point(角度;ラジアン 0 〜 2*Math::PI)
 ・描画終了点;ending_point

Cairo::Context#fill
 図形内部に色を塗る

Cairo::Context#paint
 Gtk::DrawingArea 全体に色を塗る
  gtk3では、Gtk::DrawingArea の背景色の設定はこれを使います(→ 前述 1-2) 参照)

○Cairo::Context#destroy
 Cairo::Context を廃棄します
 ※ gtk2では、macOSの場合;
  Gtk::DrawingArea.signal_connect("expose_event") do |widget, event|
    (描画処理)
  end
  の描画処理のブロックの最後に Cairo::Context#destroy をしないと、
  終了時に毎回クラッシュレポートが表示されます
→・[gtk2] "DrawingArea + Cairo" on macOS; crash reports emerge ・ Issue #1081 ・ ruby-gnome2/ruby-gnome2 - GitHub


※ 注意)
・gtk3:mac の Retina Display だと、すべてのウィジェットが2倍に拡大表示されてしまいます
→・[gtk3] Gtk/DrawingArea shows the 2 * scaling on Mac Retina display ・ Issue #1079 ・ ruby-gnome2/ruby - GitHub 
→・HiDPI/Retina display support and gtk3-port branch? (2014) — GIMP Development — gimpusers.com


(license; public domain)
・gtk_drawing_1_gtk2.rb
require 'gtk2'

window = Gtk::Window.new
window.set_size_request(400, 300)

cyan   = Gdk::Color.new(0, 65535, 65535)  # RGB; 0-65535
yellow = Gdk::Color.parse('#FFFF00')      # RGB; 00-FF (parseを使った作り方)

drawing_area = Gtk::DrawingArea.new
drawing_area.set_size_request(290,160)
drawing_area.modify_bg(:normal, cyan)

drawing_area.signal_connect("expose_event") do |widget, event|
  context = widget.window.create_cairo_context
  context.set_source_rgb(1.0, 0, 0)  # RGB; 0-1.0
  context.set_line_width(1)

  context.move_to(  0,  10)
  context.line_to( 25,  10)
  context.line_to( 37,   0)
  context.line_to( 49,  10)
  context.line_to(289,  10)
  context.line_to(289, 159)
  context.line_to(  0, 159)
  context.line_to(  0,  10)
  context.stroke

  context.set_source_rgb(1.0, 0, 1.0)
  context.arc(20, 40, 10, 0, 2 * Math::PI)
  context.fill
  context.destroy    # macOSの場合、終了時にクラッシュレポートが出るので必要
end

text_area = Gtk::TextView.new
text_area.set_size_request(150, 50)
text_area.buffer.text = 'Hello World!'
text_area.modify_base(:normal, yellow)

fixed = Gtk::Fixed.new
fixed.put(drawing_area, 20, 20)
fixed.put(text_area, 80, 80)
window.add(fixed)

window.show_all
window.signal_connect("destroy") { Gtk.main_quit }
Gtk.main

gtk_drawing_1-3_gtk2



・gtk_drawing_1_gtk3.rb
# macOS Retina Display だと拡大表示される
require 'gtk3'

window = Gtk::Window.new
window.set_size_request(400, 300)

yellow = Gdk::RGBA::new(1.0, 1.0,   0, 1.0)  # RGBA; 0-1.0

drawing_area = Gtk::DrawingArea.new
drawing_area.set_size_request(290,160)

drawing_area.signal_connect("draw") do |widget, context|
  context.set_source_rgb(0, 1.0, 1.0)  # RGB; 0-1.0
  context.paint                        # 背景色を塗る

  context.set_source_rgb(1.0, 0, 0)
  context.set_line_width(1)

  context.move_to(  0,  10)
  context.line_to( 25,  10)
  context.line_to( 37,   0)
  context.line_to( 49,  10)
  context.line_to(289,  10)
  context.line_to(289, 159)
  context.line_to(  0, 159)
  context.line_to(  0,  10)
  context.stroke

  context.set_source_rgb(1.0, 0, 1.0)
  context.arc(20, 40, 10, 0, 2 * Math::PI)
  context.fill
end

text_area = Gtk::TextView.new
text_area.set_size_request(150, 50)
text_area.buffer.text = 'Hello World!'
text_area.override_background_color(:normal, yellow)

fixed = Gtk::Fixed.new
fixed.put(drawing_area, 20, 20)
fixed.put(text_area, 80, 80)
window.add(fixed)

window.show_all
window.signal_connect("destroy") { Gtk.main_quit }
Gtk.main
※ macOS で Retina Display だと、2倍に拡大表示されてしまう

gtk_drawing_1-3_gtk3_retina
(MacBook Pro 2016; Retina Display)

gtk_drawing_1-3_gtk3_nonRetina
(MacBook Air 2016; non Retina Display)



2-B.Gdk::Drawable を使う方法(gtk2のみ)
2-B-1)Gdk::GC(graphics context)を作る
 Gdk::Drawable の描画メソッドを使うには Gdk::GCが必要になります
Gdk::GC.new(drawable)
 ・drawable; Gdk::Drawable
  Gdk::Drawable を作るには、Gtk::DrawingArea.window(上述 A.B.4)または、
  Gtk::DrawingArea.style.fg_gc(Gtk::DrawingArea.state) で作れます
  ※ただし、signal_connect のブロック内でないと作れないようです 

 まとめると、signal_connect のブロック内で
 Gdk::GC.new(Gtk::DrawingArea.window) または、
 Gtk::DrawingArea.style.fg_gc(Gtk::DrawingArea.state)  
 で作ります


2-B-2)Gdk::GC(graphics context)の設定をする
Gdk::GC#set_rgb_fg_color(color)
 色の設定をする
 ・color;・Gdk::Color.new(red, green, blue)
       red, green, blue はそれぞれ 0 - 65535 の値
     ・Gdk::Color.parse(spec)
        spec は'green'など代表的色名、
      または '#FF0000' のようにRGBそれぞれ 00 - FF の値

Gdk::GC#set_line_attributes(line_width, line_style, cap_style, join_style)
 ・line_width;線の太さ(整数)
 ・line_style;実線か点線か;GdkLineStyle 
  (LINE_SOLID,LINE_ON_OFF_DASH,LINE_DOUBLE_DASH)
 ・cap_style;線の端の設定;GdkCapStyle
  (CAP_NOT_LAST,CAP_BUTT,CAP_ROUND,CAP_PROJECTING)
 ・join_style: 複数の線の設定;GdkJoinStyle
  (JOIN_MITER,JOIN_ROUND,JOIN_BEVEL)



2-B-3)図形を描く
描画処理メソッドは、Gdk::Drawableのものなので、
Gtk::DrawingArea.window
で Gdk::Drawableを作って(上述 1-4))から使います

さらに、上述(1-3))の通り
Gtk::DrawingArea.signal_connect("expose_event") do |widget, event|
  (描画処理)
end
のブロック内に描画処理を記述します

→・Drawing Widgets - Ruby/GTK2 Tutorial - Ruby-GNOME2 Project Website
 ・gc;Gdk::GC (graphics context)



・gtk_drawing_2.rb
require 'gtk2'

window = Gtk::Window.new
window.set_size_request(400, 300)

cyan    = Gdk::Color.new(    0, 65535, 65535)  # RGB; 0-65535
red     = Gdk::Color.new(65535,     0,     0)
magenta = Gdk::Color.new(65535,    0, 65535)
yellow  = Gdk::Color.parse('#FFFF00')          # RGB; 00-FF (parseを使った作り方)

drawing_area = Gtk::DrawingArea.new
drawing_area.set_size_request(290,160)
drawing_area.modify_bg(:normal, cyan)

drawing_area.signal_connect("expose_event") do |widget, event|
  gc = Gdk::GC.new(widget.window)         # signal_connectのループ内でないとnilになる
  #gc = widget.style.fg_gc(widget.state)  # これでも可
  gc.set_rgb_fg_color(red) 
  widget.window.draw_lines(gc,
      [[  0,  10],
       [ 25,  10],
       [ 37,   0],
       [ 49,  10],
       [289,  10],
       [289, 159],
       [  0, 159],
       [  0,  10]])
  gc.rgb_fg_color = magenta
  widget.window.draw_arc(gc, true, 10, 30, 20, 20, 0, 64 * 360)
end

text_area = Gtk::TextView.new
text_area.set_size_request(150, 50)
text_area.buffer.text = 'Hello World!'
text_area.modify_base(:normal, yellow)

fixed = Gtk::Fixed.new
fixed.put(drawing_area, 20, 20)
fixed.put(text_area, 80, 80)
window.add(fixed)

window.show_all
window.signal_connect("destroy") { Gtk.main_quit }
Gtk.main

gtk_drawing_2