はじめに
この記事はPython アドベントカレンダー2日目の記事です。
PythonのTkinterでマウスで操作することができる仮想ジョイスティックを作りました。
ゲームパッド本体が手元にない場合でもこれを使用することでロボットの操作などをすることができます。
環境
以下の環境で動作を確認しています。
項目 | バージョン |
---|---|
Windows | 11 |
WSL | 2 |
Ubuntu | 22.04 |
Python | 3.10.12 |
実装
まずソースコード全体を以下に貼ります。
import tkinter
import dataclasses
@dataclasses.dataclass
class WindowSize:
width: int
height: int
@dataclasses.dataclass
class Position:
x: float
y: float
_WINDOS_SIZE = WindowSize(200, 300)
_CANVAS_SIZE = WindowSize(200, 200)
_CIRCLE_SIZE = 20
class MousePosition(tkinter.Frame):
def __init__(self, master):
super().__init__(master, height=_WINDOS_SIZE.height, width=_WINDOS_SIZE.width)
canvas = tkinter.Canvas(
master,
background="white",
height=_CANVAS_SIZE.height,
width=_CANVAS_SIZE.width,
)
self._canvas = canvas
self._virtual_stick_circle = canvas.create_oval(
_CANVAS_SIZE.width / 2 - _CIRCLE_SIZE,
_CANVAS_SIZE.height / 2 - _CIRCLE_SIZE,
_CANVAS_SIZE.width / 2 + _CIRCLE_SIZE,
_CANVAS_SIZE.height / 2 + _CIRCLE_SIZE,
tag="oval",
)
canvas.create_line(
_CANVAS_SIZE.width / 2, 0, _CANVAS_SIZE.width / 2, _CANVAS_SIZE.height
)
canvas.create_line(
0, _CANVAS_SIZE.height / 2, _CANVAS_SIZE.width, _CANVAS_SIZE.height / 2
)
canvas.grid(row=0, column=0)
canvas.bind("<Motion>", self._mouse_callback)
canvas.bind("<Button-1>", self._click_callback)
self._position_label = tkinter.Label(master)
self._position_label.grid(row=1, column=0)
self._last_position = Position(0.0, 0.0)
self._is_clicked = False
def _mouse_callback(self, event):
position_from_center = Position(
event.x - 1 / 2 * _CANVAS_SIZE.width,
1 / 2 * _CANVAS_SIZE.height - event.y,
)
self._position_label["text"] = (
str(position_from_center.x) + ", " + str(position_from_center.y)
)
if self._is_clicked:
self._canvas.move(
self._virtual_stick_circle,
-1 * (self._last_position.x - position_from_center.x),
self._last_position.y - position_from_center.y,
)
self._last_position.x = position_from_center.x
self._last_position.y = position_from_center.y
def _click_callback(self, event):
if self._is_clicked:
self._is_clicked = False
else:
self._is_clicked = True
if __name__ == "__main__":
root = tkinter.Tk()
MousePosition(root)
root.mainloop()
GUIの作成
図形描画用のCanvasと数値表示用のLabelを用意します。
canvas = tkinter.Canvas(
master,
background="white",
height=_CANVAS_SIZE.height,
width=_CANVAS_SIZE.width,
)
self._position_label = tkinter.Label(master)
TkinterのCanvas上にcreate_oval
を使用して円を描画します。
create_oval
の返り値を、canvas.move
の引数に入れることでその丸を移動させることができます。
self._virtual_stick_circle = canvas.create_oval(
_CANVAS_SIZE.width / 2 - _CIRCLE_SIZE,
_CANVAS_SIZE.height / 2 - _CIRCLE_SIZE,
_CANVAS_SIZE.width / 2 + _CIRCLE_SIZE,
_CANVAS_SIZE.height / 2 + _CIRCLE_SIZE,
tag="oval",
)
self._canvas.move(
self._virtual_stick_circle,
-1 * (self._last_position.x - position_from_center.x),
self._last_position.y - position_from_center.y,
)
動きをつける
bindでCanvas上の上をマウスカーソルが動いた時のイベントを取得してcallback関数を呼び出します。
canvas.bind("<Motion>", self._mouse_callback)
def _mouse_callback(self, event):
position_from_center = Position(
event.x - 1 / 2 * _CANVAS_SIZE.width,
1 / 2 * _CANVAS_SIZE.height - event.y,
)
self._position_label["text"] = (
str(position_from_center.x) + ", " + str(position_from_center.y)
)
if self._is_clicked:
self._canvas.move(
self._virtual_stick_circle,
-1 * (self._last_position.x - position_from_center.x),
self._last_position.y - position_from_center.y,
)
self._last_position.x = position_from_center.x
self._last_position.y = position_from_center.y
また、このままだとON/OFFのような機能がないので、マウスクリックのイベントを取得してON/OFFのフラグを設定するようにします。
canvas.bind("<Button-1>", self._click_callback)
def _click_callback(self, event):
if self._is_clicked:
self._is_clicked = False
else:
self._is_clicked = True
データクラスを使う
widthとheight、xとyを扱う必要があり、ややこしくなると思ったためdataclassを使用しています。
C言語などの構造体と同様に使用することができただのタプルやリストに比べて意味のあるデータとすることができるので可読性や拡張性が広がります。
@dataclasses.dataclass
class WindowSize:
width: int
height: int
@dataclasses.dataclass
class Position:
x: float
y: float
おわりに
今回は簡単なGUIを作成し、その中の図形をマウスカーソルの動きに沿って動かしてみました。
これで取得できたX,Y座標を使用してロボットなどの操作に使っていきたいです。