Skip to content

Commit 464073d

Browse files
authored
Merge pull request #3169 from RetiredWizard/trackpad
FruitJam PyPaint and Memory: Add support for Composite HID devices
2 parents 247bb30 + 11b38ec commit 464073d

File tree

2 files changed

+117
-80
lines changed
  • Fruit_Jam/Fruit_Jam_PyPaint
  • Metro/Metro_RP2350_Memory/memory_game

2 files changed

+117
-80
lines changed

Fruit_Jam/Fruit_Jam_PyPaint/code.py

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,17 @@
1717
"""
1818

1919
import gc
20+
import sys
2021
import time
22+
import atexit
2123
import supervisor
2224
import board
2325
import displayio
26+
import terminalio
27+
from adafruit_display_text import label
2428

2529
try:
26-
from adafruit_usb_host_mouse import find_and_init_boot_mouse
30+
from adafruit_usb_host_mouse import find_and_init_boot_mouse, find_and_init_report_mouse
2731
usb_available = True
2832
except ImportError:
2933
usb_available = False
@@ -208,13 +212,17 @@ def __init__(self, splash, cursor_bmp, screen_width, screen_height, sensitivity=
208212
def find_mouse(self): # pylint: disable=too-many-statements, too-many-locals
209213
"""Find and initialize the USB mouse."""
210214
self.mouse = find_and_init_boot_mouse()
215+
if self.mouse is None:
216+
self.mouse = find_and_init_report_mouse()
217+
self.sensitivity = 1
211218
if self.mouse is None:
212219
print("No mouse found.")
213220
return False
214221

215222
# Change the mouse resolution so it's not too sensitive
223+
fontHeight = terminalio.FONT.get_bounding_box()[1]
216224
self.mouse.display_size = (supervisor.runtime.display.width*self.sensitivity,
217-
supervisor.runtime.display.height*self.sensitivity)
225+
(supervisor.runtime.display.height - fontHeight)*self.sensitivity)
218226
return True
219227

220228
def poll(self):
@@ -324,12 +332,24 @@ def _cursor_bitmap_3():
324332

325333
self._display = display
326334
self._w = self._display.width
327-
self._h = self._display.height
335+
self._h = self._display.height - terminalio.FONT.get_bounding_box()[1]
328336
self._x = self._w // 2
329337
self._y = self._h // 2
330338

331339
self._splash = displayio.Group()
332340

341+
self._info_label = label.Label(
342+
terminalio.FONT,
343+
text = "Right Click->Palette:Exit Right Click->Canvas:Fill"[:self._w],
344+
color = 0xFFFFFF,
345+
x = 0,
346+
y = self._h
347+
)
348+
self._info_label.anchor_point = (0.0, 1.0)
349+
self._info_label.anchored_position = (2, display.height - 2)
350+
351+
self._splash.append(self._info_label)
352+
333353
self._bg_bitmap = displayio.Bitmap(self._w, self._h, 1)
334354
self._bg_palette = displayio.Palette(1)
335355
self._bg_palette[0] = Color.BLACK
@@ -377,6 +397,7 @@ def _cursor_bitmap_3():
377397

378398
self._brush = 0
379399
self._cursor_bitmaps = [_cursor_bitmap_1(), _cursor_bitmap_3()]
400+
self.mouse = None
380401
if hasattr(board, "TOUCH_XL"):
381402
self._poller = TouchscreenPoller(self._splash, self._cursor_bitmaps[0])
382403
elif hasattr(board, "BUTTON_CLOCK"):
@@ -385,8 +406,9 @@ def _cursor_bitmap_3():
385406
self._poller = MousePoller(self._splash, self._cursor_bitmaps[0], self._w, self._h)
386407
if not self._poller.mouse:
387408
raise RuntimeError("No mouse found. Please connect a USB mouse.")
409+
self.mouse = self._poller.mouse
388410
else:
389-
raise AttributeError("PyPaint requires a touchscreen or cursor.")
411+
raise AttributeError("PyPaint requires a mouse, touchscreen or cursor.")
390412

391413
self._a_pressed = False
392414
self._last_a_pressed = False
@@ -603,6 +625,8 @@ def _handle_b_release(self, location):
603625
if location[0] >= self._w // 10: # not in color picker
604626
self._fill(location[0], location[1], self._pencolor)
605627
self._poller.poke()
628+
else:
629+
supervisor.reload()
606630

607631
@property
608632
def _was_a_just_pressed(self):
@@ -646,6 +670,23 @@ def run(self):
646670
self._handle_motion(self._last_location, self._location)
647671
time.sleep(0.1)
648672

649-
650673
painter = Paint()
674+
675+
def atexit_callback():
676+
"""
677+
re-attach USB devices to kernel if needed.
678+
:return:
679+
"""
680+
print("inside atexit callback")
681+
if painter.mouse is not None:
682+
mouse = painter.mouse
683+
mouse.release()
684+
if mouse.was_attached:
685+
# The keyboard buffer seems to have data left over from when it was detached
686+
# This clears it before the next process starts
687+
while supervisor.runtime.serial_bytes_available:
688+
sys.stdin.read(1)
689+
690+
atexit.register(atexit_callback)
691+
651692
painter.run()

Metro/Metro_RP2350_Memory/memory_game/code.py

Lines changed: 71 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,19 @@
77
88
Players trade off using the USB mouse to play their turns.
99
"""
10-
import array
10+
import sys
1111
import random
1212
import time
1313
import atexit
14-
from displayio import Group, OnDiskBitmap, TileGrid
14+
from displayio import Group, OnDiskBitmap, TileGrid, CIRCUITPYTHON_TERMINAL
1515
from adafruit_display_text.bitmap_label import Label
1616
from adafruit_display_text.text_box import TextBox
1717
from adafruit_displayio_layout.layouts.grid_layout import GridLayout
1818
from adafruit_ticks import ticks_ms
1919
import supervisor
2020
import terminalio
21-
import usb.core
2221
from adafruit_fruitjam.peripherals import request_display_config
23-
import adafruit_usb_host_descriptors
22+
from adafruit_usb_host_mouse import find_and_init_boot_mouse, find_and_init_report_mouse
2423
from adafruit_pathlib import Path
2524

2625

@@ -223,6 +222,20 @@ def update_score_text():
223222
# add the game over group to the main group
224223
main_group.append(game_over_group)
225224

225+
# add the Exit Game button to game screen
226+
exit_game = TextBox(
227+
terminalio.FONT,
228+
text="Exit",
229+
color=0xFFFFFF,
230+
background_color=0xFF0000,
231+
width=30,
232+
height=15,
233+
align=TextBox.ALIGN_CENTER,
234+
)
235+
exit_game.x = display.width - 30
236+
exit_game.y = display.height - 15
237+
main_group.append(exit_game)
238+
226239
# create score label for each player
227240
for i in range(2):
228241
# create a new label to hold score
@@ -268,78 +281,52 @@ def update_score_text():
268281
# add it to the main group
269282
main_group.append(title_screen_tg)
270283

271-
# load the mouse bitmap
272-
mouse_bmp = OnDiskBitmap("mouse_cursor.bmp")
273-
274-
# make the background pink pixels transparent
275-
mouse_bmp.pixel_shader.make_transparent(0)
276-
277-
# create a TileGrid for the mouse
278-
mouse_tg = TileGrid(mouse_bmp, pixel_shader=mouse_bmp.pixel_shader)
279-
280-
# place it in the center of the display
281-
mouse_tg.x = display.width // 2
282-
mouse_tg.y = display.height // 2
283-
284-
# add the mouse to the main group
285-
main_group.append(mouse_tg)
286-
287284
# variable for the mouse USB device instance
288285
mouse = None
289286

290287
# wait a second for USB devices to be ready
291288
time.sleep(1)
292289

293-
mouse_interface_index, mouse_endpoint_address = None, None
294-
mouse = None
295-
296290
# scan for connected USB devices
297-
for device in usb.core.find(find_all=True):
298-
# print information about the found devices
299-
print(f"{device.idVendor:04x}:{device.idProduct:04x}")
300-
print(device.manufacturer, device.product)
301-
print(device.serial_number)
302-
mouse_interface_index, mouse_endpoint_address = (
303-
adafruit_usb_host_descriptors.find_boot_mouse_endpoint(device))
304-
305-
if mouse_interface_index is not None and mouse_endpoint_address is not None:
306-
mouse = device
307-
print(
308-
f"mouse interface: {mouse_interface_index} "
309-
+ f"endpoint_address: {hex(mouse_endpoint_address)}"
310-
)
311-
312-
break
291+
mouse_ptr = find_and_init_boot_mouse("mouse_cursor.bmp")
292+
if mouse_ptr is None:
293+
mouse_ptr = find_and_init_report_mouse("mouse_cursor.bmp")
294+
if mouse_ptr is None:
295+
display.root_group = CIRCUITPYTHON_TERMINAL
296+
print("\nNo mouse found")
297+
print("Memory requires a mouse to run")
298+
print("please attach a mouse and try again.")
299+
time.sleep(7)
300+
# restart back to code.py
301+
supervisor.reload()
302+
303+
mouse = mouse_ptr.device
304+
305+
mouse_tg = mouse_ptr.tilegrid
313306

307+
# place it in the center of the display
308+
mouse_tg.x = display.width // 2
309+
mouse_tg.y = display.height // 2
314310

315-
mouse_was_attached = None
316-
if mouse is not None:
317-
# detach the kernel driver if needed
318-
if mouse.is_kernel_driver_active(0):
319-
mouse_was_attached = True
320-
mouse.detach_kernel_driver(0)
321-
else:
322-
mouse_was_attached = False
323-
324-
# set configuration on the mouse so we can use it
325-
mouse.set_configuration()
311+
# add the mouse to the main group
312+
main_group.append(mouse_tg)
326313

327314
def atexit_callback():
328315
"""
329316
re-attach USB devices to kernel if needed.
330317
:return:
331318
"""
332319
print("inside atexit callback")
333-
if mouse_was_attached and not mouse.is_kernel_driver_active(0):
334-
mouse.attach_kernel_driver(0)
335-
320+
if mouse_ptr.device is not None:
321+
mouse_ptr.release()
322+
if mouse_ptr.was_attached:
323+
# The keyboard buffer seems to have data left over from when it was detached
324+
# This clears it before the next process starts
325+
while supervisor.runtime.serial_bytes_available:
326+
sys.stdin.read(1)
336327

337328
atexit.register(atexit_callback)
338329

339-
# Buffer to hold data read from the mouse
340-
# Boot mice have 4 byte reports
341-
buf = array.array("b", [0] * 4)
342-
343330
# timestamp in the future to wait until before
344331
# awarding points for a pair, or flipping cards
345332
# back over and changing turns
@@ -352,36 +339,40 @@ def atexit_callback():
352339
waiting_to_reset = False
353340

354341
# main loop
342+
last_left_button_state = None
343+
left_button_pressed = False
355344
while True:
356345
# timestamp of the current time
357346
now = ticks_ms()
358347

359348
# attempt mouse read
360-
try:
361-
# try to read data from the mouse, small timeout so the code will move on
362-
# quickly if there is no data
363-
data_len = mouse.read(mouse_endpoint_address, buf, timeout=20)
349+
buttons = mouse_ptr.update()
350+
351+
# Extract button states
352+
if buttons is None or last_left_button_state is None:
353+
current_left_button_state = 0
354+
else:
355+
current_left_button_state = 1 if 'left' in buttons else 0
364356

365-
# if there was data, then update the mouse cursor on the display
366-
# using min and max to keep it within the bounds of the display
367-
mouse_tg.x = max(0, min(display.width - 1, mouse_tg.x + buf[1] // 2))
368-
mouse_tg.y = max(0, min(display.height - 1, mouse_tg.y + buf[2] // 2))
357+
# Detect button presses
358+
if current_left_button_state == 1 and last_left_button_state == 0:
359+
left_button_pressed = True
360+
elif current_left_button_state == 0 and last_left_button_state == 1:
361+
left_button_pressed = False
369362

370-
# timeout error is raised if no data was read within the allotted timeout
371-
except usb.core.USBTimeoutError:
372-
# no problem, just go on
373-
pass
363+
# Update button states
364+
last_left_button_state = current_left_button_state
374365

375366
# if the current state is title screen
376367
if CUR_STATE == STATE_TITLE:
377368
# if the left mouse button was clicked
378-
if buf[0] & (1 << 0) != 0:
369+
if left_button_pressed:
379370
# change the current state to playing
380371
CUR_STATE = STATE_PLAYING
381372
# hide the title screen
382373
title_screen_tg.hidden = True
383374
# change the mouse cursor color to match the current player
384-
mouse_bmp.pixel_shader[2] = colors[current_turn_index]
375+
mouse_tg.pixel_shader[2] = colors[current_turn_index]
385376

386377
# if the current state is playing
387378
elif CUR_STATE == STATE_PLAYING:
@@ -455,15 +446,20 @@ def atexit_callback():
455446
current_player_lbl.color = colors[current_turn_index]
456447

457448
# update the color of the mouse cursor
458-
mouse_bmp.pixel_shader[2] = colors[current_turn_index]
449+
mouse_tg.pixel_shader[2] = colors[current_turn_index]
459450

460451
# empty out the cards flipped this turn list
461452
cards_flipped_this_turn = []
462453

463454
# ignore any clicks while the code is waiting to take reset cards
464455
if now >= WAIT_UNTIL:
465456
# left btn pressed
466-
if buf[0] & (1 << 0) != 0:
457+
if left_button_pressed:
458+
# if the mouse point is within the exit button
459+
if (mouse_tg.x >= display.width - 30 and
460+
mouse_tg.y >= display.height - 20):
461+
# restart back to code.py
462+
supervisor.reload()
467463

468464
# loop over all cards
469465
for card_index, card in enumerate(card_tgs):
@@ -491,7 +487,7 @@ def atexit_callback():
491487
# if the current state is gameover
492488
elif CUR_STATE == STATE_GAMEOVER:
493489
# left btn pressed
494-
if buf[0] & (1 << 0) != 0:
490+
if left_button_pressed:
495491
# get the coordinates of the mouse cursor point
496492
coords = (mouse_tg.x, mouse_tg.y, 0)
497493

@@ -500,7 +496,7 @@ def atexit_callback():
500496
if play_again_btn.contains(coords):
501497
# set next code file to this one
502498
supervisor.set_next_code_file(__file__,
503-
working_directory=Path(__file__).parent.absolute())
499+
working_directory=Path(__file__).parent.absolute())
504500
# reload
505501
supervisor.reload()
506502

0 commit comments

Comments
 (0)