290 lines
7.9 KiB
Python
290 lines
7.9 KiB
Python
from talon import Context, Module, ui
|
|
from talon.canvas import Canvas, MouseEvent
|
|
from talon.screen import Screen
|
|
from talon.skia.canvas import Canvas as SkiaCanvas
|
|
from talon.types import Point2d, Rect
|
|
|
|
mod = Module()
|
|
ctx = Context()
|
|
canvas: Canvas | None = None
|
|
last_mouse_pos: Point2d | None = None
|
|
|
|
mod.tag("gamepad_tester", "Gamepad tester gui is showing")
|
|
|
|
buttons = {
|
|
"dpad_up": False,
|
|
"dpad_right": False,
|
|
"dpad_down": False,
|
|
"dpad_left": False,
|
|
"north": False,
|
|
"east": False,
|
|
"south": False,
|
|
"west": False,
|
|
"select": False,
|
|
"start": False,
|
|
"l1": False,
|
|
"r1": False,
|
|
"l3": False,
|
|
"r3": False,
|
|
}
|
|
|
|
triggers = {
|
|
"l2": 0.0,
|
|
"r2": 0.0,
|
|
}
|
|
|
|
sticks = {
|
|
"left": (0.0, 0.0),
|
|
"right": (0.0, 0.0),
|
|
}
|
|
|
|
BACKGROUND_COLOR = "fffafa" # Snow
|
|
BORDER_COLOR = "000000" # Black
|
|
FONT_SIZE = 16
|
|
WIDTH = 900
|
|
HEIGHT = 800
|
|
CIRCLE_RADIUS = 100
|
|
BUTTON_OFFSET = CIRCLE_RADIUS / 2
|
|
BUTTON_RADIUS = BUTTON_OFFSET / 2
|
|
ROW_OFFSET = CIRCLE_RADIUS * 1.25
|
|
BUTTON_FLAT_WIDTH = BUTTON_RADIUS * 3
|
|
BUTTON_FLAT_HEIGHT = BUTTON_RADIUS
|
|
TRIGGER_HEIGHT = CIRCLE_RADIUS
|
|
BUTTON_OFFSETS = [(0, -1), (1, 0), (0, 1), (-1, 0)]
|
|
|
|
|
|
def render_round_button(c: SkiaCanvas, x: float, y: float, is_pressed: bool):
|
|
c.paint.style = c.paint.Style.FILL if is_pressed else c.paint.Style.STROKE
|
|
c.draw_circle(x, y, BUTTON_RADIUS)
|
|
|
|
|
|
def render_square_button(c: SkiaCanvas, x: float, y: float, is_pressed: bool):
|
|
c.paint.style = c.paint.Style.FILL if is_pressed else c.paint.Style.STROKE
|
|
c.draw_rect(
|
|
Rect(
|
|
x - BUTTON_RADIUS,
|
|
y - BUTTON_RADIUS,
|
|
BUTTON_RADIUS * 2,
|
|
BUTTON_RADIUS * 2,
|
|
)
|
|
)
|
|
|
|
|
|
def render_flat_button(c: SkiaCanvas, x: float, y: float, is_pressed: bool):
|
|
c.paint.style = c.paint.Style.FILL if is_pressed else c.paint.Style.STROKE
|
|
c.draw_rect(
|
|
Rect(
|
|
x - BUTTON_FLAT_WIDTH / 2,
|
|
y - BUTTON_FLAT_HEIGHT / 2,
|
|
BUTTON_FLAT_WIDTH,
|
|
BUTTON_FLAT_HEIGHT,
|
|
)
|
|
)
|
|
|
|
|
|
def render_buttons(
|
|
c: SkiaCanvas, x: float, y: float, useCircle: bool, buttons_ids: list[str]
|
|
):
|
|
c.paint.style = c.paint.Style.STROKE
|
|
c.draw_circle(x, y, CIRCLE_RADIUS)
|
|
for i, button_id in enumerate(buttons_ids):
|
|
(offset_x, offset_y) = BUTTON_OFFSETS[i]
|
|
button_x = x + offset_x * BUTTON_OFFSET
|
|
button_y = y + offset_y * BUTTON_OFFSET
|
|
is_pressed = buttons[button_id]
|
|
if useCircle:
|
|
render_round_button(c, button_x, button_y, is_pressed)
|
|
else:
|
|
render_square_button(c, button_x, button_y, is_pressed)
|
|
|
|
|
|
def render_trigger(c: SkiaCanvas, x: float, y: float, value: float):
|
|
# Render button outline
|
|
c.paint.style = c.paint.Style.STROKE
|
|
c.draw_rect(
|
|
Rect(
|
|
x - BUTTON_FLAT_WIDTH / 2,
|
|
y - TRIGGER_HEIGHT,
|
|
BUTTON_FLAT_WIDTH,
|
|
TRIGGER_HEIGHT,
|
|
)
|
|
)
|
|
# Render button value
|
|
c.paint.style = c.paint.Style.FILL
|
|
height = value * TRIGGER_HEIGHT
|
|
c.draw_rect(
|
|
Rect(
|
|
x - BUTTON_FLAT_WIDTH / 2,
|
|
y - height,
|
|
BUTTON_FLAT_WIDTH,
|
|
height,
|
|
)
|
|
)
|
|
# Render value text
|
|
text = str(round(value * 100))
|
|
text_rect = c.paint.measure_text(text)[1]
|
|
c.draw_text(
|
|
text,
|
|
x - text_rect.x - text_rect.width / 2,
|
|
y - TRIGGER_HEIGHT - text_rect.height,
|
|
)
|
|
|
|
|
|
def render_stick(
|
|
c: SkiaCanvas, x: float, y: float, is_pressed: bool, value_x: float, value_y: float
|
|
):
|
|
# Stick click
|
|
if is_pressed:
|
|
c.paint.style = c.paint.Style.FILL
|
|
c.draw_circle(x, y, CIRCLE_RADIUS)
|
|
return
|
|
|
|
c.paint.style = c.paint.Style.STROKE
|
|
# Draw outer circle
|
|
c.draw_circle(x, y, CIRCLE_RADIUS)
|
|
# Draw cross
|
|
c.draw_line(x - CIRCLE_RADIUS, y, x + CIRCLE_RADIUS, y)
|
|
c.draw_line(x, y - CIRCLE_RADIUS, x, y + CIRCLE_RADIUS)
|
|
dot_x = x + value_x * CIRCLE_RADIUS
|
|
dot_y = y + value_y * CIRCLE_RADIUS
|
|
# Draw line to dot
|
|
c.draw_line(x, y, dot_x, dot_y)
|
|
# Draw center dot
|
|
c.paint.style = c.paint.Style.FILL
|
|
c.draw_circle(dot_x, dot_y, 5)
|
|
# Render value texts
|
|
text = f"{round(value_x * 100)}, {round(value_y * 100)}"
|
|
text_rect = c.paint.measure_text(text)[1]
|
|
c.draw_text(
|
|
text,
|
|
x - text_rect.x - text_rect.width / 2,
|
|
y - CIRCLE_RADIUS - text_rect.height,
|
|
)
|
|
|
|
|
|
def render_close_text(c: SkiaCanvas, x: float, y: float):
|
|
text = 'Say "gamepad tester" to close'
|
|
text_rect = c.paint.measure_text(text)[1]
|
|
c.draw_text(
|
|
text,
|
|
x - text_rect.x - text_rect.width / 2,
|
|
y - 2 * text_rect.height,
|
|
)
|
|
|
|
|
|
def on_draw(c: SkiaCanvas):
|
|
c.paint.textsize = FONT_SIZE
|
|
|
|
# Render background
|
|
c.paint.style = c.paint.Style.FILL
|
|
c.paint.color = BACKGROUND_COLOR
|
|
c.draw_rect(c.rect)
|
|
|
|
c.paint.color = BORDER_COLOR
|
|
y_center = c.rect.center.y + ROW_OFFSET * 0.75
|
|
|
|
offset = CIRCLE_RADIUS * 2.5
|
|
|
|
# Draw trigger buttons
|
|
y = y_center - ROW_OFFSET * 2.5
|
|
render_trigger(c, c.rect.center.x - offset, y, triggers["l2"])
|
|
render_trigger(c, c.rect.center.x + offset, y, triggers["r2"])
|
|
|
|
# Draw bumper buttons
|
|
y = y_center - ROW_OFFSET * 2.15
|
|
render_flat_button(c, c.rect.center.x - offset, y, buttons["l1"])
|
|
render_flat_button(c, c.rect.center.x + offset, y, buttons["r1"])
|
|
|
|
y = y_center - ROW_OFFSET
|
|
|
|
# Draw D-pad buttons
|
|
render_buttons(
|
|
c,
|
|
c.rect.center.x - offset,
|
|
y,
|
|
False,
|
|
["dpad_up", "dpad_right", "dpad_down", "dpad_left"],
|
|
)
|
|
|
|
# Draw compass buttons
|
|
render_buttons(
|
|
c,
|
|
c.rect.center.x + offset,
|
|
y,
|
|
True,
|
|
["north", "east", "south", "west"],
|
|
)
|
|
|
|
# Draw select/start buttons
|
|
offset = CIRCLE_RADIUS * 0.75
|
|
y = y_center - ROW_OFFSET / 3
|
|
render_round_button(c, c.rect.center.x - offset, y, buttons["select"])
|
|
render_round_button(c, c.rect.center.x + offset, y, buttons["start"])
|
|
|
|
# Draw sticks
|
|
offset = CIRCLE_RADIUS * 1.5
|
|
y = y_center + ROW_OFFSET
|
|
render_stick(c, c.rect.center.x - offset, y, buttons["l3"], *sticks["left"])
|
|
render_stick(c, c.rect.center.x + offset, y, buttons["r3"], *sticks["right"])
|
|
|
|
# Draw close text
|
|
render_close_text(c, c.rect.center.x, c.rect.bot)
|
|
|
|
|
|
def on_mouse(e: MouseEvent):
|
|
global last_mouse_pos
|
|
if e.event == "mousedown" and e.button == 0:
|
|
last_mouse_pos = e.gpos
|
|
elif e.event == "mousemove" and last_mouse_pos:
|
|
dx = e.gpos.x - last_mouse_pos.x
|
|
dy = e.gpos.y - last_mouse_pos.y
|
|
last_mouse_pos = e.gpos
|
|
if canvas is not None:
|
|
canvas.move(canvas.rect.x + dx, canvas.rect.y + dy)
|
|
elif e.event == "mouseup" and e.button == 0:
|
|
last_mouse_pos = None
|
|
|
|
|
|
def show():
|
|
global canvas
|
|
screen: Screen = ui.main_screen()
|
|
x = screen.rect.center.x
|
|
y = screen.rect.center.y
|
|
canvas = Canvas.from_rect(Rect(x - WIDTH / 2, y - HEIGHT / 2, WIDTH, HEIGHT))
|
|
canvas.draggable = True
|
|
canvas.blocks_mouse = True
|
|
canvas.register("draw", on_draw)
|
|
canvas.register("mouse", on_mouse)
|
|
ctx.tags = ["user.gamepad_tester"]
|
|
|
|
|
|
def hide():
|
|
global canvas
|
|
if canvas is not None:
|
|
canvas.unregister("draw", on_draw)
|
|
canvas.unregister("mouse", on_mouse)
|
|
canvas.close()
|
|
canvas = None
|
|
ctx.tags = []
|
|
|
|
|
|
@mod.action_class
|
|
class Actions:
|
|
def gamepad_tester_toggle():
|
|
"""Toggle visibility of gamepad tester gui"""
|
|
if canvas is None:
|
|
show()
|
|
else:
|
|
hide()
|
|
|
|
def gamepad_tester_button(id: str, is_pressed: bool):
|
|
"""Indicates that a gamepad button has changed state"""
|
|
buttons[id] = is_pressed
|
|
|
|
def gamepad_tester_trigger(id: str, value: float):
|
|
"""Indicates that a gamepad trigger has changed state"""
|
|
triggers[id] = value
|
|
|
|
def gamepad_tester_stick(id: str, x: float, y: float):
|
|
"""Indicates that a gamepad stick has changed state"""
|
|
sticks[id] = (x, y)
|