- audio panel file browser can be used to assign file paths to sound effects

- sound effect assignments are automatically saved to config file
- Vector getattr raises AttributeError like Python default class method
This commit is contained in:
Frank DeMarco 2020-02-20 02:43:37 -05:00
parent a30464edf5
commit 697eb22633
3 changed files with 182 additions and 29 deletions

View File

@ -1,4 +1,4 @@
import os, re, pygame
import os, re, shutil, pygame
from .GameChild import *
from .Sprite import *
@ -51,15 +51,15 @@ class Audio(GameChild):
# - repository paths are not loaded at init but can replace loaded paths
# and get written to config file
#
def load_sfx(self, path=None):
def load_sfx(self, sfx_location=None):
for name, path in self.get_configuration("sfx").items():
self.load_sfx_file(sfx, path, name, True)
if path is None:
path = self.get_configuration("audio", "sfx-project-path") + \
self.load_sfx_file(path, name, True)
if sfx_location is None:
sfx_location = self.get_configuration("audio", "sfx-project-path") + \
self.get_configuration("audio", "sfx-default-path")
if isinstance(path, str):
path = [path]
for root in path:
if isinstance(sfx_location, str):
sfx_location = [sfx_location]
for root in sfx_location:
prefix = ""
root = self.get_resource(root)
print("checking {} for sound effects".format(root))
@ -80,11 +80,13 @@ class Audio(GameChild):
if path and path.split(".")[-1] in self.get_configuration("audio", "sfx-extensions"):
if name is None:
name = prefix + re.sub("\.[^.]*$", "", os.path.basename(path))
if replace and name in self.sfx:
print("skipping existing sound effect for %s => %s" % (path, name))
if not replace and name in self.sfx:
print("skipping existing sound effect for %s: %s" % (name, path))
else:
print("loading sound effect %s into %s" % (path, name))
self.sfx[name] = SoundEffect(self, path)
return True
return False
def update(self):
for ii in range(pygame.mixer.get_num_channels()):
@ -128,25 +130,26 @@ class AudioPanel(Animation):
def __init__(self, parent):
Animation.__init__(self, parent)
self.file_browser = AudioPanelFileBrowser(self)
self.subscribe(self.respond)
self.subscribe(self.respond, pygame.MOUSEBUTTONDOWN)
self.reset()
def reset(self):
self.selected = None
self.row_offset = 0
self.deactivate()
def activate(self):
pygame.mouse.set_visible(True)
self.active = True
self.set_rows()
self.build()
def deactivate(self):
pygame.mouse.set_visible(self.get_configuration("mouse", "visible"))
self.active = False
# button 5 means fingers slide up
# button 4 means finders slide down
def respond(self, event):
print("audio panel received %s" % event)
if self.get_delegate().compare(event, "toggle-audio-panel") and \
pygame.key.get_mods() & KMOD_CTRL and pygame.key.get_mods() & KMOD_SHIFT:
if self.active:
@ -154,14 +157,25 @@ class AudioPanel(Animation):
else:
self.activate()
elif self.active:
if event.type == pygame.MOUSEBUTTONDOWN:
print("audio panel received mouse down event %s" % event)
if event.button == 5:
if event.type == pygame.MOUSEBUTTONDOWN and self.file_browser.is_hidden():
if event.button == 1:
for row in self.rows:
if row.location.collidepoint(event.pos):
if row != self.selected:
self.file_browser.visit(self.file_browser.HOME)
self.selected = row
self.selected.start_blinking()
self.file_browser.unhide()
else:
if row == self.selected:
self.selected = None
row.stop_blinking()
elif event.button == 5:
self.row_offset += 1
elif event.button == 4:
self.row_offset -= 1
def set_rows(self):
def build(self):
self.rows = []
for key in sorted(self.parent.sfx):
self.rows.append(AudioPanelRow(self, key))
@ -179,21 +193,24 @@ class AudioPanel(Animation):
row.update()
corner.y += row.location.height + self.MARGIN
index += 1
self.file_browser.update()
class AudioPanelRow(Sprite):
class AudioPanelRow(BlinkingSprite):
BACKGROUND = pygame.Color(255, 255, 255)
FOREGROUND = pygame.Color(0, 0, 0)
BACKGROUND = pygame.Color(255, 255, 255, 180)
FOREGROUND = pygame.Color(0, 0, 0, 180)
WIDTH = .6
FONT_SIZE = 18
HEIGHT = 30
INDENT = 4
MAX_NAME_WIDTH = .7
def __init__(self, parent, key):
Sprite.__init__(self, parent)
BlinkingSprite.__init__(self, parent, 500)
self.key = key
self.build()
self.halt()
def build(self):
font = pygame.font.Font(
@ -206,6 +223,10 @@ class AudioPanelRow(Sprite):
self.add_frame(surface)
name_sprite = Sprite(self)
name = font.render(self.key + ":", True, self.FOREGROUND)
if name.get_width() > int(self.location.w * self.MAX_NAME_WIDTH):
crop = pygame.Rect(0, 0, int(self.location.w * self.MAX_NAME_WIDTH), name.get_height())
crop.right = name.get_rect().right
name = name.subsurface(crop)
name_sprite.add_frame(name)
name_sprite.display_surface = surface
name_sprite.location.midleft = self.INDENT, self.location.centery
@ -218,9 +239,6 @@ class AudioPanelRow(Sprite):
file_sprite.add_frame(box)
file_sprite.location.midright = self.location.right - self.INDENT, self.location.centery
file_sprite.display_surface = surface
# file_text = render_box(
# font, self.get_sound_effect().path, color=self.FOREGROUND,
# border=self.FOREGROUND, padding=2)
file_name_sprite = Sprite(self)
file_name_text = font.render(self.get_sound_effect().path, True, self.FOREGROUND)
file_name_sprite.add_frame(file_name_text)
@ -231,3 +249,124 @@ class AudioPanelRow(Sprite):
def get_sound_effect(self):
return self.get_game().get_audio().sfx[self.key]
class AudioPanelFileBrowser(Sprite):
WIDTH = .75
HEIGHT = .75
BACKGROUND = pygame.Color(255, 255, 255)
FOREGROUND = pygame.Color(0, 0, 0)
HOME, UP = "[HOME]", "[UP]"
FONT_SIZE = 16
def __init__(self, parent):
Sprite.__init__(self, parent)
ds = self.get_display_surface()
dsr = ds.get_rect()
surface = pygame.Surface((dsr.w * self.WIDTH, dsr.h * self.HEIGHT), SRCALPHA)
surface.fill(self.BACKGROUND)
self.add_frame(surface)
self.location.center = dsr.center
self.reset()
self.subscribe(self.respond, pygame.MOUSEBUTTONDOWN)
def reset(self):
self.visit(self.HOME)
self.hide()
def respond(self, event):
if not self.is_hidden():
if event.button == 1:
if self.location.collidepoint(event.pos):
for row in self.rows:
if row.collide(Vector(*event.pos).get_moved(-self.location.left, -self.location.top)):
full_path = os.path.join(os.path.sep.join(self.trail), row.path)
if row.path == self.HOME or row.path == self.UP or \
os.path.isdir(full_path) and os.access(full_path, os.R_OK):
self.visit(row.path)
elif os.path.isfile(full_path) and os.access(full_path, os.R_OK):
key = self.parent.selected.key
if self.get_audio().load_sfx_file(full_path, key, True):
if not self.get_configuration().has_section("sfx"):
self.get_configuration().add_section("sfx")
self.get_configuration().set("sfx", key, full_path)
config_path = self.get_configuration().locate_project_config_file()
backup_path = config_path + ".backup"
shutil.copyfile(config_path, backup_path)
self.get_configuration().write(open(config_path, "w"))
self.hide()
self.get_delegate().cancel_propagation()
self.parent.build()
else:
self.hide()
self.get_delegate().cancel_propagation()
elif event.button == 4:
self.row_offset -= 1
elif event.button == 5:
self.row_offset += 1
def visit(self, path):
if path == self.UP and len(self.trail) > 1:
path = self.trail[-2]
self.trail = self.trail[:-2]
self.visit(path)
elif path != self.UP:
self.row_offset = 0
if path == self.HOME:
self.trail = []
self.paths = ["/"]
for option in "sfx-repository-path", "sfx-default-path", "sfx-project-path":
for sfx_location in self.get_configuration("audio", option):
if self.get_resource(sfx_location):
self.paths.append(self.get_resource(sfx_location))
else:
self.paths = [self.HOME]
self.trail.append(path)
if len(self.trail) > 1:
self.paths.append(self.UP)
self.paths.extend(sorted(os.listdir(os.path.sep.join(self.trail))))
self.build()
def build(self):
self.rows = []
for path in self.paths:
row = Sprite(self)
font = pygame.font.Font(
self.get_resource(self.get_configuration("audio", "panel-font")),
self.FONT_SIZE)
row.path = path
text = font.render(path, True, self.FOREGROUND)
surface = pygame.Surface((self.location.w, text.get_height()), SRCALPHA)
surface.blit(text, (0, 0))
row.add_frame(surface)
row.display_surface = self.get_current_frame()
row.location.bottom = 0
self.rows.append(row)
def update(self):
self.get_current_frame().fill(self.BACKGROUND)
if not self.is_hidden():
# ds = self.get_display_surface()
# dsr = ds.get_rect()
# corner = Vector(self.MARGIN, self.MARGIN)
corner = Vector(0, 0)
index = self.row_offset
# while corner.y < dsr.height - self.MARGIN:
for row in self.rows:
row.remove_locations()
row.location.bottom = 0
# row.hide()
while corner.y < self.location.h:
row = self.rows[index % len(self.rows)]
# row.unhide()
if index - self.row_offset >= len(self.rows):
row.add_location(corner.copy())
else:
row.location.topleft = corner.copy()
# corner.y += row.location.height + self.MARGIN
corner.y += row.location.height
index += 1
for row in self.rows:
row.update()
Sprite.update(self)

View File

@ -125,17 +125,21 @@ class Sprite(Animation):
def collide(self, other):
if not isinstance(other, Rect):
other = other.rect
if hasattr(other, "rect"):
other = other.rect
else:
other = Rect(other[0], other[1], 1, 1)
for location in self.locations:
if location.colliderect(other):
return location
def collide_mask(self, other):
if self.collide(other):
location = self.collide(other)
if location:
current_mask = mask.from_surface(self.get_current_frame())
other_mask = mask.from_surface(other.get_current_frame())
offset = other.location.left - self.location.left, \
other.location.top - self.location.top
offset = other.location.left - location.left, \
other.location.top - location.top
return current_mask.overlap(other_mask, offset) is not None
def mirror(self):
@ -479,6 +483,13 @@ class BlinkingSprite(Sprite):
def blink(self):
self.toggle_hidden()
def start_blinking(self):
self.play(self.blink)
def stop_blinking(self):
self.halt(self.blink)
self.unhide()
class RainbowSprite(Sprite):

View File

@ -16,6 +16,8 @@ class Vector(list):
return self[0]
elif name == "y":
return self[1]
else:
raise AttributeError
def __setattr__(self, name, value):
if name == "x":
@ -107,6 +109,7 @@ class EVector(Vector):
def __init__(self, x=0, y=0, dx=0, dy=0, magnitude=None, angle=0):
Vector.__init__(self, x, y)
self.angle = None
self.set_step(dx, dy, magnitude, angle)
def set_step(self, dx=0, dy=0, magnitude=None, angle=0):