diff options
author | B Stack <bgstack15@gmail.com> | 2022-07-03 23:06:09 -0400 |
---|---|---|
committer | B Stack <bgstack15@gmail.com> | 2022-07-03 23:06:09 -0400 |
commit | b72841e3ff7220c7db44ae523c173321d5750497 (patch) | |
tree | 7535d0bf912e4a4e87399a719f56e5fe1755f1b7 /pyjstest.py | |
download | pyjstest-b72841e3ff7220c7db44ae523c173321d5750497.tar.gz pyjstest-b72841e3ff7220c7db44ae523c173321d5750497.tar.bz2 pyjstest-b72841e3ff7220c7db44ae523c173321d5750497.zip |
initial commit
Diffstat (limited to 'pyjstest.py')
-rwxr-xr-x | pyjstest.py | 429 |
1 files changed, 429 insertions, 0 deletions
diff --git a/pyjstest.py b/pyjstest.py new file mode 100755 index 0000000..dc7e08d --- /dev/null +++ b/pyjstest.py @@ -0,0 +1,429 @@ +#!/usr/bin/env python3 +# File: pyjstest.py +# Location: https://bgstack15.ddns.net/git/pyjstest/ +# Author: bgstack15 +# SPDX-License-Identifier: GPL-3.0 +# Startdate: 2022-06-29 +# Title: Pyjstest +# Package: pyjstest +# Purpose: Display inputs of one connected gamepad +# History: +# Usage: +# Adjust config.py and then run! +# Reference: +# https://stackoverflow.com/a/50572903 +# https://pysdl2.readthedocs.io/en/0.9.11/tutorial/pong.html +# Improve: +# display text of axes values? https://github.com/caerus706/Python3-pySDL2-examples/blob/master/text.py +# Documentation: See README.md +import ctypes, time, random +from sdl2 import * +import sdl2.ext, sys + +from config import colors, CONFIG + +class SoftwareRenderer(sdl2.ext.SoftwareSpriteRenderSystem): + def __init__(self, window): + super(SoftwareRenderer, self).__init__(window) + def render(self, components): + sdl2.ext.fill(self.surface, colors["BKGD"]) + super(SoftwareRenderer, self).render(components) + +class MovementSystem(sdl2.ext.Applicator): + def __init__(self, minx, miny, maxx, maxy): + super(MovementSystem, self).__init__() + self.componenttypes = Velocity, sdl2.ext.Sprite + self.minx = minx + self.miny = miny + self.maxx = maxx + self.maxy = maxy + def process(self, world, componentsets): + for velocity, sprite in componentsets: + swidth, sheight = sprite.size + sprite.x += velocity.vx + sprite.y += velocity.vy + sprite.x = max(self.minx, sprite.x) + sprite.y = max(self.miny, sprite.y) + pmaxx = sprite.x + swidth + pmaxy = sprite.y + sheight + if pmaxx > self.maxx: + sprite.x = self.maxx - swidth + if pmaxy > self.maxy: + sprite.y = self.maxy - sheight + +class Velocity(object): + def __init__(self): + super(Velocity, self).__init__() + self.vx = 0 + self.vy = 0 + +class Rectangle(sdl2.ext.Entity): + def __init__(self, world, sprite, posx=0, posy=0): + self.world = world + self.sprite = sprite + self.sprite.position = posx, posy + self.velocity = Velocity() + +class TwoAxisBackground(sdl2.ext.Entity): + def __init__(self, world, factory, color, posx=0, posy=0, sizex=100, sizey=100): + self.world = world + self.sprite = factory.from_color(color, size=(sizex, sizey)) + self.sprite.position = posx, posy + +class Button(object): + def __init__(self, world, factory, color_on, color_off, posx, posy, size, button, relx=0, rely=0, owner = None): + self.world = world + self.factory = factory + self.color_on = color_on + self.color_off = color_off + self.posx = posx + self.posy = posy + self.size = size + self.object = Rectangle(self.world, self.factory.from_color(self.color_off, size=(self.size, self.size)), self.posx, self.posy) + self.status = False + self.button = button + self.type = "button" + self.relx = relx + self.rely = rely + self.owner = owner # either None, or an Analog + def on(self): + if not self.status: + self.object.sprite = self.factory.from_color(self.color_on, size=(self.size,self.size)) + if self.owner is None: + self.object.sprite.position = self.posx+self.relx, self.posy+self.rely + self.status = True + def off(self): + if self.status: + self.object.sprite = self.factory.from_color(self.color_off, size=(self.size,self.size)) + if self.owner is None: + self.object.sprite.position = self.posx+self.relx, self.posy+self.rely + self.status = False + +class Dpad(object): + """ Represent simple UDLR position of an analog stick. One Dpad object per direction per axis excluding off. """ + def __init__(self, world, factory, color_on, color_off, posx, posy, size, axis, direction): + self.world = world + self.factory = factory + self.color_on = color_on + self.color_off = color_off + self.posx = posx + self.posy = posy + self.size = size + self.type = "dpad" + self.axis = axis + self.direction = direction + self.object = Rectangle(self.world, self.factory.from_color(self.color_off, size=(self.size,self.size)), self.posx, self.posy) + self.status = False + def positive(self, axis): + #print(f"Dpad axis {axis} moved positive.") + self.on() if "positive" == self.direction else self.off() + def negative(self, axis): + #print(f"Dpad axis {axis} moved negative.") + self.on() if "negative" == self.direction else self.off() + def middle(self, axis): + self.off() + def on(self): + if not self.status: + #self.object = Rectangle(self.world, self.factory.from_color(self.color_on, size=(self.size,self.size)), self.posx, self.posy) + self.object.sprite = self.factory.from_color(self.color_on, size=(self.size,self.size)) + self.object.sprite.position = self.posx, self.posy + self.status = True + def off(self): + if self.status: + #self.object = Rectangle(self.world, self.factory.from_color(self.color_off, size=(self.size,self.size)), self.posx, self.posy) + self.object.sprite = self.factory.from_color(self.color_off, size=(self.size,self.size)) + self.object.sprite.position = self.posx, self.posy + self.status = False + def react(self, config, axis, value): + #print(f"Dpad axis {axis} value {value}") + if value >= config["dpad_axes"]["min"][axis] and value < config["dpad_axes"]["mid_min"][axis]: + self.negative(axis) + elif value >= config["dpad_axes"]["mid_min"][axis] and value <= config["dpad_axes"]["mid_max"][axis]: + self.middle(axis) + elif value > config["dpad_axes"]["mid_max"][axis] and value <= config["dpad_axes"]["max"][axis]: + self.positive(axis) + else: + print(f"Warning! For Dpad {self} axis {axis}, value {value} is undefined behavior.") + +class Analog(object): + def __init__(self, world, factory, color_on, color_off, centerx, centery, indicator_size, size, axes, orientations, buttons): + self.world = world + self.factory = factory + self.color_on = color_on + self.color_off = color_off + self.posx = centerx + self.posy = centery + self.size = size + self.type = "analog" + self.axes = axes + self.indicator_size = indicator_size + self.orientations = orientations + self.buttons_in = buttons # a list of ["color_on", "color_off", button_id] + self.ulx = int(self.posx-(size/2)) + self.uly = int(self.posy-(size/2)) + self.lrx = int(self.posx+(size/2)) + self.lry = int(self.posy+(size/2)) + self.background = TwoAxisBackground(self.world, self.factory, self.color_off, self.ulx, self.uly, size, size) + self.indicator = Rectangle(self.world, self.factory.from_color(self.color_on, size=(self.indicator_size,self.indicator_size)), self.posx, self.posy) + #self.movement = MovementSystem(self.ulx, self.uly, self.lrx, self.lry) + #self.world.add_system(self.movement) + self.buttons = [] + for b in buttons: + self.buttons.append(Button(self.world,self.factory,colors[b[0]],colors[b[1]],self.posx,self.posy,b[4],b[5],b[2],b[3],self)) + def react(self, config, axis, value): + #print(f"Updating with {axis}, {value}") + _max = config["dpad_axes"]["max"][axis] + _min = config["dpad_axes"]["min"][axis] + percent = (value - _min) / (_max - _min) * 100 + current_position = self.indicator.sprite.position # (x, y) + newx = current_position[0] + newy = current_position[1] + # X is axes[0] + if axis == self.axes[0]: + newx = int((self.ulx+(self.size*percent/100))-(self.indicator_size/2)) + elif axis == self.axes[1]: + newy = int((self.uly+(self.size*percent/100))-(self.indicator_size/2)) + self.indicator.sprite.position = newx, newy + for b in self.buttons: + b.object.sprite.position = (newx+b.relx), (newy+b.rely) + return newx, newy + +class Hat(object): + def __init__(self, world, factory, color_on, color_off, posx, posy, size, hat, off, up, right, down, left): + self.world = world + self.factory = factory + self.color_on = color_on + self.color_off = color_off + self.hat = hat + self.basex = posx + self.basey = posy + self.size = size + self.type = "hat" + # center, up, right, down, left + self.posx = [0, 0, 20, 0, -20] + self.posy = [0, -20, 0, 20, 0] + self.box = [] + for i in range(0,5): + self.box.append(Rectangle(self.world, self.factory.from_color(self.color_off, size=(self.size,self.size)), self.basex+self.posx[i], self.basey+self.posy[i])) + # array of binary values that will describe the current hat position + self.binary = [off, up, right, down, left] + def react(self, value): + print(f"Hat {self.hat} value {value}") + possible = [0,1,2,3,4] + on = [] + if value == self.binary[0]: + on.append(0) + for r in range(1,5): # 1 to 4 + if value & self.binary[r]: + on.append(r) + # evaluate all the options + off = possible + for option in on: + off.remove(option) + #print(f"Hat options on: {on}") + #print(f"Hat options off: {off}") + x = 0 + while x < 5: + self.box = [] + if x in on: + self.box.append(Rectangle(self.world, self.factory.from_color(self.color_on, size=(self.size,self.size)), self.basex+self.posx[x], self.basey+self.posy[x])) + else: + self.box.append(Rectangle(self.world, self.factory.from_color(self.color_off, size=(self.size,self.size)), self.basex+self.posx[x], self.basey+self.posy[x])) + x = x + 1 + +# color_on, color_off, x,y, size, axis, direction +#["GRAY", "DARK_GRAY", 60, 40, 20, 2, 1], +class Trigger(object): + """ Direction is not actually implemented. """ + def __init__(self, world, factory, renderer, color_on, color_off, color_bkgd, x1, y1, x2, y2, size, axis, direction): + self.world = world + self.factory = factory + self.color_on = color_on + self.color_off = color_off + self.color_bkgd = color_bkgd + self.axis = axis + self.x1 = x1 + self.y1 = y1 + self.x2 = x2 + self.y2 = y2 + self.boxx1 = min(x1,x2) + self.boxy1 = min(y1,y2) + self.boxx2 = max(x1,x2) + self.boxy2 = max(y1,y2) + self.linex1 = x1 - self.boxx1 + self.liney1 = y1 - self.boxy1 + self.linex2 = x2 - self.boxx1 + self.liney2 = y2 - self.boxy1 + self.type = "trigger" + self.indicator_size = size + # This line makes rectangle the minimum size so the drawn line actually displays. + self.rectangle_size = max((self.boxx2-self.boxx1),1),max((self.boxy2-self.boxy1),1) + print(f"Drawing rectangle, {self.color_bkgd}, size ({self.rectangle_size}), starting ({self.boxx1},{self.boxy1})") + self.background = Rectangle(self.world, self.factory.from_color(self.color_bkgd, size=self.rectangle_size), self.boxx1, self.boxy1) + + print(f"Drawing line color {self.color_off}, from ({self.linex1}, {self.liney1}) to ({self.linex2},{self.liney2})") + sdl2.ext.draw.line(self.background.sprite, self.color_off, (self.linex1, self.liney1, self.linex2, self.liney2)) + + self.indicator = Rectangle(self.world, self.factory.from_color(self.color_on, size=(self.indicator_size,self.indicator_size)), self.x1, self.y1) + def react(self, config, axis, value): + """ This one-axis trigger will move in X,Y dimensions based on the line drawn. """ + print(f"Axis {axis} value {value}") + _max = config["dpad_axes"]["max"][axis] + _min = config["dpad_axes"]["min"][axis] + percent = (value - _min) / (_max - _min) * 100 + current_position = self.indicator.sprite.position # (x, y) + newx = int((self.x2 - self.x1)*percent/100-(self.indicator_size/2) + self.x1) +1 + newy = int((self.y2 - self.y1)*percent/100-(self.indicator_size/2) + self.y1) +1 + self.indicator.sprite.position = newx, newy + +def find_joystick_config_by_alias(config, name): + """ + Given CONFIG and a joystick name, see if that joystick name shows up as either a ruleset or alias for a ruleset, and then return the proper ruleset name. + """ + if name in config: + print(f"DEBUG (fjcba): Found name {name} in config.") + return name + else: + for c in config: + if "aliases" in config[c]: + if name in config[c]["aliases"]: + print(f"DEBUG (fjcba): Found name {c} that matches alias {name}.") + return c + print(f"DEBUG (fjcba): oops! Found nothing that matches {name}.") + return None + +def run(): + world = sdl2.ext.World() + gamepads = [] + #joystick = Joystick() + SDL_Init(SDL_INIT_JOYSTICK) + sdl2.ext.init() + joystick = "" + # load resources/ directory + #RESOURCES = sdl2.ext.Resources(__file__,"resources") + factory = sdl2.ext.SpriteFactory(sdl2.ext.SOFTWARE) + #sprite = factory.from_image(RESOURCES.get_path("gamepad.png")) + + input_array = [] + #if "buttons" in CONFIG[joystick]: + # for b in CONFIG[joystick]["buttons"]: + # input_array.append(button(world,factory,colors[b[0]],colors[b[1]],b[2],b[3],b[4])) + #if "dpads" in CONFIG[joystick]: + # for b in CONFIG[joystick]["dpads"]: + # input_array.append(Dpad(world,factory,colors[b[0]],colors[b[1]],b[2],b[3],b[4],b[5])) + #if "hats" in CONFIG[joystick]: + # for b in CONFIG[joystick]["hats"]: + # input_array.append(Hat(world, factory, colors[b[0]], colors[b[1]], b[2], b[3], b[4], b[5],b[6],b[7],b[8],b[9])) + #window = sdl2.ext.Window("Pyjoy", size=CONFIG[joystick]["window"]) + #window = sdl2.ext.Window("Pyjoy", size=(100,100), flags = SDL_WINDOW_RESIZABLE) + window = sdl2.ext.Window("Pyjoy", size=(100,100)) + window.show() + + spriterenderer = SoftwareRenderer(window) + world.add_system(spriterenderer) + running = True + status = {} + while running: + event = SDL_Event() + while SDL_PollEvent(ctypes.byref(event)) != 0: + if event.type == SDL_JOYDEVICEADDED: + #self.device = SDL_JoystickOpen(event.jdevice.which) + joystick = SDL_JoystickName(SDL_JoystickOpen(event.jdevice.which)).decode('UTF-8') + print(f"Device: {joystick}") + config_name = find_joystick_config_by_alias(CONFIG,joystick) + window.title = config_name + if config_name: + joystick = config_name + print(f"Working with controller type {config_name}") + #window.size = CONFIG[joystick]["window"] + SDL_SetWindowSize(window.window,CONFIG[joystick]["window"][0],CONFIG[joystick]["window"][1]) + window.refresh() + #window.get_surface() + # Use a temporary list, to avoid the "Set size changed during iteration." + temp_list = [] + for i in world.entities: + temp_list.append(i) + for i in temp_list: + i.delete() + world.remove_system(spriterenderer) + spriterenderer = SoftwareRenderer(window) + world.add_system(spriterenderer) + input_array = [] + if "buttons" in CONFIG[joystick]: + for b in CONFIG[joystick]["buttons"]: + input_array.append(Button(world,factory,colors[b[0]],colors[b[1]],b[2],b[3],b[4],b[5])) + if "dpads" in CONFIG[joystick]: + for b in CONFIG[joystick]["dpads"]: + input_array.append(Dpad(world,factory,colors[b[0]],colors[b[1]],b[2],b[3],b[4],b[5],b[6])) + if "hats" in CONFIG[joystick]: + for b in CONFIG[joystick]["hats"]: + input_array.append(Hat(world, factory, colors[b[0]], colors[b[1]], b[2], b[3], b[4], b[5],b[6],b[7],b[8],b[9],b[10])) + if "analogs" in CONFIG[joystick]: + for b in CONFIG[joystick]["analogs"]: + a = Analog(world, factory, colors[b[0]], colors[b[1]], b[2], b[3], b[4], b[5], b[6], b[7], b[8]) + input_array.append(a) + for b in a.buttons: + input_array.append(b) + if "triggers" in CONFIG[joystick]: + for b in CONFIG[joystick]["triggers"]: + input_array.append(Trigger(world, factory, spriterenderer, colors[b[0]], colors[b[1]], colors[b[2]], b[3], b[4], b[5], b[6], b[7], b[8], b[9])) + #spriterenderer = SoftwareRenderer(window) + #world.add_system(spriterenderer) + #return 0 + elif event.type == SDL_JOYAXISMOTION: + #self.axis[event.jaxis.axis] = event.jaxis.value + #print(event.jaxis.axis,event.jaxis.value) + status[event.jaxis.axis] = event.jaxis.value + axis = event.jaxis.axis + value = event.jaxis.value + for q in input_array: + if "dpad" == q.type and q.axis == axis: + q.react(CONFIG[joystick], axis, value) + if "analog" == q.type and axis in q.axes: + response = q.react(CONFIG[joystick], axis, value) + #print(f"Coord of indicator: {response}") + if "trigger" == q.type and axis == q.axis: + q.react(CONFIG[joystick], axis, value) + elif event.type == SDL_JOYHATMOTION: + #print(f"hat {event.jhat.hat},{event.jhat.value}") + for q in input_array: + if "hat" == q.type and q.hat == event.jhat.hat: + q.react(event.jhat.value) + elif event.type == SDL_JOYBUTTONDOWN: + #self.button[event.jbutton.button] = True + print(f"{event.jbutton.button} down") + b = event.jbutton.button + for q in input_array: + if "button" == q.type and b == q.button: + q.on() + elif event.type == SDL_JOYBUTTONUP: + #self.button[event.jbutton.button] = False + print(f"{event.jbutton.button} up") + b = event.jbutton.button + for q in input_array: + if "button" == q.type and b == q.button: + q.off() + elif event.type == sdl2.SDL_QUIT: + window.close() + print("quitting...") + running = False + break + else: + # file /usr/lib/python3/dist-packages/sdl2/events.py + # 1536 = SDL_JOYAXISMOTION but gets thrown separately too? + # 1024 = SDL_MOUSEMOTION + #if event.type not in [1536, 1538, 1024]: + print(f"Unknown event {event.type}") + result = "\r" + for s in sorted(status): + result += f"{s}: {status[s]:06} " + print(result, end=" ") + world.process() + return 0 + +if __name__ == "__main__": + running_main = True + while running_main: + if -1 == run(): + running_main = False + sys.exit(0) |