(2022.11.15. 公開)
マウスを使った画面の拡大・縮小をやってみます。また、表示位置を平行移動させる機能と、拡大・縮小をリセットする機能も同時に実装しましょう。
マウスイベントには多数の種類がありますが、ここでは以下のものを使います。
wxEVT_LEFT_DOWN
: 左ボタンが押されたwxEVT_LEFT_UP
: 左ボタンが離されたwxEVT_MOUSE
: マウスポインタが移動した
frame.panel
上でこれらのイベントが発生したら、つかまえるようにします。それぞれにハンドラを書くこともできますが、同じハンドラを呼び出して、ハンドラ内で処理を分岐しても構いません。
-- イベントハンドラを接続
frame.button:Connect(wx.wxEVT_BUTTON, OnClick)
frame.panel:Connect(wx.wxEVT_PAINT, OnPaint)
frame.panel:Connect(wx.wxEVT_LEFT_DOWN, OnMouse)
frame.panel:Connect(wx.wxEVT_LEFT_UP, OnMouse)
frame.panel:Connect(wx.wxEVT_MOTION, OnMouse)
マウスイベントハンドラの実装には、決まった型があります。最初に、マウスイベントから「イベントが起きた時のマウスの位置」を取得します。
function OnMouse(event)
-- マウスポインタの位置
local pt = event:GetPosition()
今回は、「画面内のドラッグ」を実装しようとしています。この場合、「最初にマウスボタンが押された位置」を記録しておきます。「最初」かどうかは、event:LeftDown()
(左マウスボタンが押された)か、または event:Dragging()
(ドラッグされた)でかつ「まだマウスボタンの位置が記録されていない」場合として判断します。(wxWidgets が正しく動作していれば event:LeftDown()
だけでよいはずですが、念のために event:Dragging()
のロジックも書いておく、というスタンスです。)
if event:LeftDown() or (event:Dragging() and frame.save == nil) then
-- 現在の状態を保存
frame.save = { x0 = frame.x0, y0 = frame.y0, scale = frame.scale, pt = wx.wxPoint(pt) }
その後、マウスボタンが押された状態で wxEVT_MOTION
が来た時に、ドラッグの処理をします。変数 active_id
は、「拡大縮小」か、「平行移動」か、どちらかを選ぶものです。後から、これを切り替えるためのボタンも実装します。
elseif event:Dragging() then
-- ドラッグ
if active_id == EXPAND_ID then
local height = frame.panel:GetClientSize():GetHeight()
-- 開始点と現在の点の (1, 1) 方向への射影を求める
local r1 = (frame.save.pt.x - frame.ax_width) + frame.save.pt.y
local r2 = (pt.x - frame.ax_width) + pt.y
r = math.max(r1, 2.0) / math.max(r2, 2.0)
-- 左上を起点に r 倍に拡大(縮小)
frame.scale = frame.save.scale * r
frame.y0 = frame.save.y0 + (height - frame.ax_height) * (frame.save.scale - frame.scale)
elseif active_id == MOVE_ID then
local x0 = frame.save.x0 - (pt.x - frame.save.pt.x) * frame.scale
local y0 = frame.save.y0 + (pt.y - frame.save.pt.y) * frame.scale
frame.x0 = x0
frame.y0 = y0
end
frame:Refresh()
マウスボタンが離された時に、「最初にマウスボタンが押された位置」の情報をクリアしておきます。この情報があるかどうかで「ドラッグが始まったかどうか」を判断しているので、この処理を忘れるとおかしな動作をします。
elseif event:LeftUp() then
-- ドラッグ終了
frame.save = nil
end
拡大、平行移動の切り替えをボタンでできるようにします。拡大をリセットするホームボタンも実装します。
ボタンのアイコンは PNG か何かで用意すればよいのですが、別ファイルになるのも面倒なので、テキストで記述する XPM 形式にしてしまいました。
local expand_xpm = {
-- width height ncolors cpp
"16 16 6 1",
-- colors
"A c #000000",
"B c #131313",
"C c #383838",
"D c #676767",
"E c #C0C0C0",
". c None",
-- pixels
"................",
".AAAAAD.........",
".ACE............",
".AEBE...........",
".A.EBE..........",
".A..EBE.........",
".D...EBE........",
"......EBE.......",
".......EBE......",
"........EBE...D.",
".........EBE..A.",
"..........EBE.A.",
"...........EBEA.",
"............ECA.",
".........DAAAAA.",
"................"
}
ボタンは次のように作ります。
-- 「移動」「拡大縮小」ボタンを作成
local move_bitmap = wx.wxBitmap(cross_xpm)
frame.move_button = wx.wxBitmapToggleButton(frame.panel, MOVE_ID, move_bitmap, wx.wxPoint(0, 0), wx.wxSize(22, 22), TOGGLEBUTTON_STYLE)
local expand_bitmap = wx.wxBitmap(expand_xpm)
frame.expand_button = wx.wxBitmapToggleButton(frame.panel, EXPAND_ID, expand_bitmap, wx.wxPoint(0, 0), wx.wxSize(22, 22), TOGGLEBUTTON_STYLE)
wxBitmapToggleButton
のスタイル指定は、Mac と Windows で変えたほうがよいようです。TOGGLEBUTTON_STYLE
という値を下のように初期化しておきます。wxWidgets は、各プラットフォームのネイティブな GUI 部品を使うことがメリットなのですが、特定のプラットフォーム上で GUI を作り込むと、他のプラットフォームで実行した時に画面が崩れることがよくあります。プラットフォーム依存だけじゃなくて、OS のバージョンにも依存する場合がありますね。このあたりは、わりときめ細かく対応していかないといけません。
-- wxBitmapButton のスタイル(プラットフォーム依存)
if string.find(wx.wxPlatformInfo.Get():GetOperatingSystemFamilyName(), "Mac") then
TOGGLEBUTTON_STYLE = wx.wxBORDER_SIMPLE
else
TOGGLEBUTTON_STYLE = 0
end
「拡大」と「平行移動」は状態を選択するトグルボタンですが、「拡大のリセット」は動作を指定するプッシュボタンです。プッシュボタンには wxBitmapButton
を使います。
local home_bitmap = wx.wxBitmap(home_xpm)
frame.home_button = wx.wxBitmapButton(frame.panel, HOME_ID, home_bitmap, wx.wxPoint(0, 0), wx.wxSize(22, 22), TOGGLEBUTTON_STYLE) -- これはトグルボタンではなくプッシュボタン
イベントハンドラはボタンごとに書いたほうが単純ですが、同じようなコードが続くのも格好がよくないので、トグルボタン2つは共通にしてみました。
frame.move_button:Connect(wx.wxEVT_TOGGLEBUTTON, OnClickIconButton)
frame.expand_button:Connect(wx.wxEVT_TOGGLEBUTTON, OnClickIconButton)
frame.home_button:Connect(wx.wxEVT_BUTTON, OnClickHomeButton)
実装はこんな感じです。OnClickIconButton
の方は、ちょっと無駄なコードが多かったかも。
-- 「拡大縮小」または「移動」ボタンがクリックされた
function OnClickIconButton(event)
local button = event:GetEventObject():DynamicCast("wxToggleButton")
local id = button:GetId()
active_id = id -- MOVE_ID または EXPAND_ID
for i = 1, LAST_ID do
local b = frame.panel:FindWindow(i):DynamicCast("wxToggleButton")
b:SetValue(i == id)
end
end
-- ホームボタンがクリックされた
function OnClickHomeButton(event)
frame.x0 = -2
frame.y0 = -2
frame.scale = 0.01
frame:Refresh()
end
今回は一応これで完成とします。エラー処理とか、単体で動くアプリにするとか、積み残したことがいろいろありますが、必要があれば続編を書くことにしたいと思います。
本章のプログラム: [graphcalc06.wx.lua]
目次