diff --git a/pgfw/Animation.py b/pgfw/Animation.py index 7fd817b..f0a1c78 100644 --- a/pgfw/Animation.py +++ b/pgfw/Animation.py @@ -58,7 +58,7 @@ class Animation(GameChild): include_delay) def is_account_playing(self, account, include_delay): - return account.playing and (not include_delay or not account.delay) + return account.playing and (include_delay or not account.delay) def reset_timer(self, method=None): if not method: diff --git a/pgfw/Audio.py b/pgfw/Audio.py index 62cdecd..98e0ea9 100644 --- a/pgfw/Audio.py +++ b/pgfw/Audio.py @@ -8,19 +8,20 @@ from .Input import * from .Animation import * from .extension import * -class Audio(GameChild): +class Audio(Animation): UP, DOWN = .1, -.1 BASE_VOLUME = 1.0 CONFIG_SEPARATOR = "," def __init__(self, game): - GameChild.__init__(self, game) + Animation.__init__(self, game) # self.original_volumes = {} self.current_bgm = None self.volume = self.BASE_VOLUME if self.check_command_line("-mute"): self.volume = 0 + self.register(self.play_sfx) self.audio_panel = AudioPanel(self) self.subscribe(self.respond) self.sfx = {} @@ -106,6 +107,9 @@ class Audio(GameChild): return True return False + def play_sfx(self, name): + self.sfx[name].play() + # # Loading BGM procedure # @@ -180,13 +184,7 @@ class Audio(GameChild): return True def update(self): - # for ii in range(pygame.mixer.get_num_channels()): - # channel = pygame.mixer.Channel(ii) - # sound = channel.get_sound() - # if sound is not None: - # if sound not in self.original_volumes.keys(): - # self.original_volumes[sound] = sound.get_volume() - # sound.set_volume(self.original_volumes[sound] * self.volume) + Animation.update(self) self.audio_panel.update() @@ -209,6 +207,9 @@ class BGM(GameChild): def get_volume(self): return self.volume + def __eq__(self, other): + return self.path == other.path + class SoundEffect(GameChild, pygame.mixer.Sound): @@ -329,6 +330,7 @@ class AudioPanel(Animation): self.active = False if self.bgm_elapsed is not None: self.get_audio().play_bgm(start=self.bgm_elapsed) + self.file_browser.hide() def respond(self, event): if self.get_delegate().compare(event, "toggle-audio-panel") and self.get_audio().sfx: @@ -444,7 +446,7 @@ class AudioPanelRow(BlinkingSprite): self.subscribe(self.respond, pygame.MOUSEBUTTONDOWN) def respond(self, event): - if event.button == 1: + if self.parent.active and event.button == 1: if self.parent.file_browser.is_hidden() and self.location.collidepoint(event.pos): if not self.selected: self.parent.file_browser.visit(self.parent.file_browser.HOME) @@ -530,6 +532,15 @@ class AudioPanelRow(BlinkingSprite): shutil.copyfile(config_path, backup_path) self.get_configuration().write(open(config_path, "w")) + def set_clickable(self, clickable=True): + self.play_button.set_clickable(clickable) + self.stop_button.set_clickable(clickable) + self.volume_spinner.set_clickable(clickable) + if not self.is_bgm: + self.fade_out_spinner.set_clickable(clickable) + self.loops_spinner.set_clickable(clickable) + self.maxtime_spinner.set_clickable(clickable) + def update(self): self.play_button.location.midleft = self.location.move(5, 0).midright self.stop_button.location.midleft = self.play_button.location.midright @@ -612,8 +623,7 @@ class AudioPanelFileBrowser(Sprite): for row in self.parent.rows: row.selected = False row.stop_blinking() - row.play_button.set_clickable() - row.stop_button.set_clickable() + row.set_clickable(True) for row in self.rows: if row.has_child("button"): row.get_child("button").set_clickable(False) @@ -623,8 +633,7 @@ class AudioPanelFileBrowser(Sprite): def unhide(self): for row in self.parent.rows: - row.play_button.set_clickable(False) - row.stop_button.set_clickable(False) + row.set_clickable(False) for row in self.rows: if row.has_child("button"): row.get_child("button").set_clickable() @@ -762,6 +771,7 @@ class AudioPanelSpinner(Sprite): self.down_button.location.topleft = self.display.location.right - 1, \ self.up_button.location.bottom - 1 self.down_button.display_surface = self.get_current_frame() + self.set_clickable() self.subscribe(self.respond, pygame.MOUSEBUTTONDOWN) def unsubscribe(self, callback=None, kind=None): @@ -788,7 +798,7 @@ class AudioPanelSpinner(Sprite): self.update_display() def respond(self, event): - if event.button == 1: + if self.clickable and event.button == 1: relative_position = Vector(*event.pos).get_moved( -self.location.left, -self.location.top) up_collides = self.up_button.collide(relative_position) @@ -800,6 +810,9 @@ class AudioPanelSpinner(Sprite): self.increment(False) self.parent.update_config() + def set_clickable(self, clickable=True): + self.clickable = clickable + def update(self): self.get_current_frame().fill(self.background) self.label.update() @@ -816,8 +829,8 @@ class AudioPanelButton(Sprite): self.callback = callback self.callback_kwargs = callback_kwargs self.containers = containers - self.clickable = True self.pass_mods = pass_mods + self.set_clickable() self.subscribe(self.respond, pygame.MOUSEBUTTONDOWN) def unsubscribe(self, callback=None, kind=None): @@ -827,7 +840,7 @@ class AudioPanelButton(Sprite): Sprite.unsubscribe(self, callback, kind) def respond(self, event): - if self.clickable and event.button == 1: + if self.get_audio().audio_panel.active and self.clickable and event.button == 1: pos = Vector(*event.pos) for container in self.containers: pos.move(-container.location.left, -container.location.top) diff --git a/pgfw/Configuration.py b/pgfw/Configuration.py index 65a7d51..610e69c 100644 --- a/pgfw/Configuration.py +++ b/pgfw/Configuration.py @@ -67,7 +67,7 @@ class Configuration(RawConfigParser): set_option(section, "whitespace-placeholder", "-", False) set_option(section, "windows-dist-path", "dist/win/", False) set_option(section, "windows-icon-path", "", False) - set_option(section, "lowercase-boolean-true", "yes", False) + set_option(section, "boolean-true-lowercase", "yes, true, t, 1", False) set_option(section, "osx-includes", "", False) section = "display" add_section(section) @@ -275,9 +275,7 @@ class Configuration(RawConfigParser): # if type(value) == str or type(value) == unicode: if type(value) == str: if pair in types["bool"]: - if value.lower() == self.get("setup", "lowercase-boolean-true"): - return True - return False + return value.lower() in self.get("setup", "boolean-true-lowercase") elif pair in types["int"]: return int(value) elif pair in types["float"]: @@ -422,7 +420,7 @@ class TypeDeclarations(dict): "setup": {"list": ["classifiers", "resource-search-path", "requirements", "data-exclude", - "additional-packages", "osx-includes"], + "additional-packages", "osx-includes", "boolean-true-lowercase"], "path": ["installation-dir", "changelog", "description-file", "main-object", "icon-path", "windows-dist-path", diff --git a/pgfw/Sprite.py b/pgfw/Sprite.py index 2f9ebba..87e28e9 100644 --- a/pgfw/Sprite.py +++ b/pgfw/Sprite.py @@ -1,6 +1,6 @@ +import collections from os import listdir from os.path import isfile, join -from sys import exc_info, stdout from glob import glob from pygame import Color, Rect, Surface, PixelArray, mask @@ -10,11 +10,11 @@ from pygame.locals import * from .Animation import Animation from .Vector import Vector, EVector -from .extension import get_hue_shifted_surface, get_step +from .extension import get_hue_shifted_surface, get_step, load_frames, get_blinds_rects class Sprite(Animation): - def __init__(self, parent, framerate=None, neighbors=[], mass=None): + def __init__(self, parent, framerate=None, draw_children_on_frame=True): Animation.__init__(self, parent, self.shift_frame, framerate) self.frames = [] self.mirrored = False @@ -22,15 +22,20 @@ class Sprite(Animation): self.locations = [] self.framesets = [Frameset(self, framerate=framerate)] self.frameset_index = 0 - self.neighbors = neighbors - self.mass = mass self._step = EVector() - self.children = {} + self.children = collections.OrderedDict() + self.draw_children_on_frame = draw_children_on_frame self.set_frameset(0) self.locations.append(Location(self)) self.motion_overflow = Vector() self.display_surface = self.get_display_surface() - self.register(self.toggle_hidden) + self.wipe_blinds = [] + self.current_wipe_index = 0 + self.register(self.toggle_hidden, self.wipe_out) + + def reset(self): + self.halt(self.wipe_out) + self.current_wipe_index = 0 def __getattr__(self, name): if name in ("location", "rect"): @@ -61,6 +66,7 @@ class Sprite(Animation): if self.get_current_frameset().length() > 1: self.play() self.get_current_frameset().reset() + return self.get_current_frameset() def register_interval(self): self.register(self.shift_frame, @@ -81,25 +87,10 @@ class Sprite(Animation): def load_from_path(self, path, transparency=False, ppa=True, key=None, query=None, omit=False): - if isfile(path): - paths = [path] - else: - if query: - paths = sorted(glob(join(path, query))) - else: - paths = [join(path, name) for name in sorted(listdir(path))] - for path in paths: - img = load(path) - if transparency: - if ppa: - frame = img.convert_alpha() - else: - frame = self.fill_colorkey(img, key) - else: - frame = img.convert() + for frame in load_frames(self.get_resource(path), transparency, ppa, key, query): self.add_frame(frame, omit) - def fill_colorkey(self, img, key=None): + def fill_colorkey(img, key=None): if not key: key = (255, 0, 255) img = img.convert_alpha() @@ -116,6 +107,8 @@ class Sprite(Animation): frameset = self.get_current_frameset() frameset.add_index(self.frames.index(frame)) self.update_location_size() + self.wipe_blinds = get_blinds_rects(*self.location.size) + self.wipe_blinds.reverse() if frameset.length() > 1: self.play() @@ -205,14 +198,20 @@ class Sprite(Animation): def hide(self): for location in self.locations: location.hide() + for child in self.children.values(): + child.hide() def unhide(self): for location in self.locations: location.unhide() + for child in self.children.values(): + child.unhide() def toggle_hidden(self): for location in self.locations: location.toggle_hidden() + for child in self.children.values(): + child.toggle_hidden() def is_hidden(self): return all(location.is_hidden() for location in self.locations) @@ -248,19 +247,41 @@ class Sprite(Animation): def has_child(self, name): return name in self.children + def remove_child(self, name): + if self.has_child(name): + self.children.pop(name) + + def wipe_out(self): + for child in self.children.values(): + if not child.is_playing(child.wipe_out): + child.play(child.wipe_out) + self.current_wipe_index += 1 + if self.current_wipe_index == len(self.wipe_blinds): + self.current_wipe_index = 0 + self.halt(self.wipe_out) + self.hide() + + def get_current_blinds(self): + return self.wipe_blinds[self.current_wipe_index] + def update(self, areas=None, substitute=None, flags=0): Animation.update(self) if self.is_stepping(): self.move(self._step.dx, self._step.dy) - for neighbor in self.neighbors: - self.move(*get_step(self.location.center, neighbor.location.center, - neighbor.mass)) if self.get_current_frameset().length(): + if self.is_playing(self.wipe_out): + areas = self.get_current_blinds() self.draw(areas, substitute, flags) for child in self.children.values(): + if self.draw_children_on_frame: + child.display_surface = self.get_current_frame() + else: + child.display_surface = self.get_display_surface() + save_child_topleft = child.location.topleft + child.move(*self.location.topleft) child.update() - # for location in self.locations: - # location.update() + if not self.draw_children_on_frame: + child.location.topleft = save_child_topleft def draw(self, areas=None, substitute=None, flags=0): for location in self.locations: @@ -316,18 +337,6 @@ class Location(Rect): def is_hidden(self): return self.hidden - def update(self): - pass - # for neighbor in self.sprite.neighbors: - # if neighbor.mass: - # closest = neighbor.location - # for location in neighbor.locations[1:]: - # if get_distance(self.center, location.center) < \ - # get_distance(self.center, closest.center): - # closest = location - # self.move_ip(get_step(self.center, closest.center, - # neighbor.mass)) - class Fader(Surface): @@ -493,6 +502,7 @@ class BlinkingSprite(Sprite): def __init__(self, parent, blink_rate, framerate=None): Sprite.__init__(self, parent, framerate) self.register(self.blink, interval=blink_rate) + self.register(self.start_blinking, self.stop_blinking) self.play(self.blink) def reset(self): diff --git a/pgfw/extension.py b/pgfw/extension.py index bef954b..85dac9a 100644 --- a/pgfw/extension.py +++ b/pgfw/extension.py @@ -1,6 +1,7 @@ -import itertools, random +import itertools, random, os, glob from math import sin, cos, atan2, radians, sqrt, pi +import pygame from pygame import Surface, PixelArray, Color, Rect, draw, gfxdraw from pygame.mixer import get_num_channels, Channel from pygame.locals import * @@ -119,6 +120,16 @@ def place_in_rect(rect, incoming, contain=True, *args): if not collides: break +def constrain_dimensions_2d(vector, container): + dw = vector[0] - container[0] + dh = vector[1] - container[1] + if dw > 0 or dh > 0: + if dw > dh: + size = container[0], int(round(container[0] / vector[0] * vector[1])) + else: + size = int(round(container[1] / vector[1] * vector[0])), container[1] + return size + # from http://www.realtimerendering.com/resources/GraphicsGems/gemsii/xlines.c def get_intersection(p0, p1, p2, p3): x0, y0 = p0 @@ -215,16 +226,21 @@ def render_box(font=None, text=None, antialias=True, color=(0, 0, 0), background surface.fill(background) return get_boxed_surface(surface, background, border, border_width, padding) -def get_wrapped_text_surface(font, text, width, antialias, color, +def get_wrapped_text_surface(font, text, width, antialias=True, color=(0, 0, 0), background=None, border=None, border_width=1, - padding=0): - words = text.split() - if words: + padding=0, align="left"): + lines = [] + height = 0 + for chunk in text.split("\n"): line_text = "" - lines = [] - height = 0 ii = 0 finished = False + if chunk.startswith("\*") and chunk.endswith("\*"): + chunk = chunk.replace("\*", "*") + elif chunk.startswith("*") and chunk.endswith("*"): + chunk = chunk[1:-1] + font.set_italic(True) + words = chunk.split(" ") while not finished: line_width = font.size(line_text + " " + words[ii])[0] if line_width > width or ii == len(words) - 1: @@ -233,25 +249,26 @@ def get_wrapped_text_surface(font, text, width, antialias, color, line_text += " " line_text += words[ii] finished = True - line = font.render(line_text, antialias, color, background) + line = font.render(line_text, antialias, color) height += line.get_height() lines.append(line) line_text = "" else: line_text += " " + words[ii] ii += 1 - top = 0 - surface = Surface((width, height), SRCALPHA) - if background: - surface.fill(background) - rect = surface.get_rect() - for line in lines: - line_rect = line.get_rect() - line_rect.midtop = rect.centerx, top - surface.blit(line, line_rect) - top += line_rect.h - else: - surface = Surface((0, 0), SRCALPHA) + font.set_italic(False) + top = 0 + surface = Surface((width, height), pygame.SRCALPHA) + if background: + surface.fill(background) + rect = surface.get_rect() + for line in lines: + line_rect = line.get_rect() + line_rect.top = top + if align == "center": + line_rect.centerx = rect.centerx + surface.blit(line, line_rect) + top += line_rect.h return get_boxed_surface(surface, background, border, border_width, padding) def replace_color(surface, color, replacement): @@ -305,6 +322,37 @@ def fill_tile(surface, tile, rect=None): for y in range(0, surface.get_height(), h): surface.blit(tile, (x, y)) +def load_frames(path, transparency=False, ppa=True, key=None, query=None): + if os.path.isfile(path): + paths = [path] + else: + if query: + paths = sorted(glob.glob(os.path.join(path, query))) + else: + paths = [os.path.join(path, name) for name in sorted(os.listdir(path))] + frames = [] + for path in paths: + img = pygame.image.load(path) + if transparency: + if ppa: + frame = img.convert_alpha() + else: + frame = fill_colorkey(img, key) + else: + frame = img.convert() + frames.append(frame) + return frames + +def fill_colorkey(img, key=None): + if key is None: + key = 255, 0, 255 + img = img.convert_alpha() + frame = Surface(img.get_size()) + frame.fill(key) + frame.set_colorkey(key) + frame.blit(img, (0, 0)) + return frame + def get_shadowed_text(text, font, offset, color, antialias=True, shadow_color=(0, 0, 0), colorkey=(255, 0, 255)): foreground = font.render(text, antialias, color) @@ -321,6 +369,24 @@ def get_shadowed_text(text, font, offset, color, antialias=True, shadow_color=(0 (abs(offset[1]) - offset[1]) / 2)) return surface +def get_blinds_rects(w, h, step=.05, count=4): + blinds = [] + blind_h = int(round(h / float(count))) + for ii in range(1, count + 1): + blinds.append(Rect(0, blind_h * ii, w, 0)) + inflate_h = int(round(blind_h * step)) + if inflate_h < 1: + inflate_h = 1 + rects = [] + while blinds[0].h < blind_h: + rects.append([]) + for blind in blinds: + bottom = blind.bottom + blind.inflate_ip(0, inflate_h) + blind.bottom = bottom + rects[-1].append(blind.copy()) + return rects + def get_blinds_frames(surface, step=.05, count=4, fill=(0, 0, 0, 0)): frames = [] rects = []