カスタムウィジェットで立方体を回しやすくしてみた
陰線消去(というか陰線を点線に)して立方体を表示するプログラムがあります。ところが立方体を回すためには、矢印ボタンをカチカチ何度も押さなければいけなくて、ぎこちなかった。
そこで操作の労力が減るように、カスタムウィジェットを作った。みためはオーディオミキサーのスライダーをイメージ。
つまみをマウスでドラッグすると、[-1, +1] の範囲で値が変化し、マウスを離すと一瞬で真ん中の 0 位置に戻る。ゲームコントローラのアナログスティックのようなものだ。1つにつき1つの軸しか操作できないけど。
Slider クラス。読み飛ばそう。
# -*- coding: utf-8 -*- # Self-centering slider for direction input class Slider < Gtk::DrawingArea include Gtk include Cairo type_register signal_new('changed', GLib::Signal::ACTION, nil, nil) attr_reader :value def initialize super() set_size_request(180, 40) @value = 0.0 signal_connect('expose-event') do |w, event| draw true end signal_connect('configure-event') do |_, e| true end set_event_mask signal_connect('button-press-event', &method(:on_button_press)) signal_connect('motion-notify-event', &method(:on_motion_notify)) signal_connect('button-release-event', &method(:on_button_release)) signal_connect('realize') do window.cursor = Gdk::Cursor.new(Gdk::Cursor::Type::HAND1) end end def on_button_press(_this, e) case e.button when 1 self.value = coords_to_value(e.x, e.y) @control_state = :dragging true else false end end def on_motion_notify(_this, e) return false unless (e.state & Gdk::Window::BUTTON1_MASK) != 0 self.value = coords_to_value(e.x, e.y) true end def on_button_release(_this, e) return false unless e.button == 1 self.value = 0.0 true end def coords_to_value(x, y) result = (x - knob_width / 2).fdiv(slit_length) * 2 - 1.0 [[-1.0, result].max, 1.0].min end def set_event_mask self.events |= Gdk::Event::BUTTON_PRESS_MASK self.events |= Gdk::Event::BUTTON_RELEASE_MASK self.events |= Gdk::Event::POINTER_MOTION_MASK end def invalidate return if window.nil? window.invalidate(window.clip_region, true) window.process_updates(true) end def value=(new_value) @value = new_value invalidate signal_emit('changed') end def draw cr = window.create_cairo_context cr.scale(1, 1) # dot by dot draw_bg(cr) draw_markings(cr) draw_knob(cr) end def draw_bg(cr) cr.save ensure cr.restore end # 溝 def draw_markings(cr) cr.save # 0 marking cr.set_source_color [0.1, 0.1, 0.1] cr.set_line_width(1) cr.move_to(allocation.width / 2, allocation.height / 2 - knob_height / 2) cr.rel_line_to(0, knob_height) cr.stroke cr.antialias = :none cr.set_font_size(7) extents = cr.text_extents('0') cr.move_to(allocation.width / 2 - extents.width / 2, allocation.height / 2 - knob_height / 2 - 2) cr.show_text('0') # slit cr.antialias = :best cr.set_source_color [0.0, 0.0, 0.0] cr.set_line_width(5) cr.set_line_cap(LINE_CAP_ROUND) cr.move_to(knob_width / 2, allocation.height/2) cr.rel_line_to(allocation.width - knob_width, 0) cr.stroke ensure cr.restore end def draw_knob(cr) cr.save cr.set_source_color [0.2, 0.2, 0.2] cr.rounded_rectangle(hposition - knob_width/2, vposition - knob_height/2, knob_width, knob_height, 5, 5) cr.fill # knob center line cr.set_source_color [1, 1, 1] cr.move_to(hposition, vposition - knob_height/2 + 3) cr.line_to(hposition, vposition + knob_height/2 - 3) cr.stroke ensure cr.restore end def hposition knob_width / 2 + slit_length * (value / 2 + 0.5) end def slit_length allocation.width - knob_width end def vposition vcenter end def knob_width 40 end def knob_height 20 end def hcenter allocation.width/2 end def vcenter allocation.height/2 end end
つまみの位置が変化すると changed シグナルが送出される。今回は値が変化しなくても、連続的に回転しなければならないので、changed は使わずタイマーで一定時間ごとに値を読み取って、変換行列に反映させるようにした。
カチカチしなくてもグルグル回せるようになった。いえい。