def _create_image(self): grp = Group() for word in self.words: if len(grp.sprites()) == 0: word.scale(self.ratio+0.2) pm_w = word.rect.width pm_h = word.rect.height pm_x, pm_y = place_primary(pm_w, pm_h) word.rect.x, word.rect.y = pm_x, pm_y arch_x = pm_x + pm_w/2 arch_y = pm_y + pm_h/2 else: word.scale(self.ratio) for x, y in archimedean_spiral(False): if self.debug: rs = Surface((5, 5)) rs.fill((0, 0, 255)) rs.set_alpha(100) self.sf.blit(rs, (x + arch_x, y + arch_y)) word.set_position(x + arch_x, y + arch_y) x_out = CANVAS_WIDTH - CANVAS_PADDING < word.rect.x + word.rect.width or word.rect.x < CANVAS_PADDING y_out = CANVAS_HEIGHT - CANVAS_PADDING < word.rect.y + word.rect.height or word.rect.y < CANVAS_PADDING out_of_bound = x_out or y_out if spritecollideany(word, grp, collide_mask) is None and not out_of_bound: if self.debug: rs = Surface((5, 5)) rs.fill((0, 255, 0)) rs.set_alpha(100) self.sf.blit(rs, (x + arch_x, y + arch_y)) break self.sf.blit(word.image, word.rect) grp.add(word)
class Status_bar: def __init__(self, x, y): self.x = x self.y = y self.surface = Surface((360, 60)) self.surface.fill((65, 56, 231)) self.surface.set_colorkey((65, 56, 231)) self.surface.set_alpha(200) self.image = load("images/status_bar/status_bar.png") self.hp_image = load('images/status_bar/hp.png') self.friend_image = load('images/tanks/player_up_1.png') self.friend_image.set_colorkey((255, 255, 255)) self.enemy_image = load('images/tanks/enemy_up_1.png') self.enemy_image.set_colorkey((255, 255, 255)) self.font = font.Font('fonts/ComicTalecopy.ttf', 32) def show(self, hp, friends, enemies, lvl, scr, scr_w): self.surface.blit(self.image, (0, 0)) self.surface.blit(self.hp_image, (10, 10)) self.surface.blit(self.font.render('X' + str(hp), 1, (255, 255, 255)), (55, 15)) self.surface.blit(self.friend_image, (110, 10)) self.surface.blit( self.font.render('X' + str(friends - 1), 1, (255, 255, 255)), (155, 15)) self.surface.blit(self.enemy_image, (210, 10)) self.surface.blit( self.font.render('X' + str(enemies), 1, (255, 255, 255)), (255, 15)) scr.blit(self.surface, (0, 0)) scr.blit(self.font.render('Stage ' + str(lvl), 1, (255, 255, 255)), (1366 - self.font.size('stage' + str(lvl))[0] - 20, 10))
def draw(self): """ Method called each frame to (re)draw the objects and UI :return: """ if not self.overlay_drawn: overlay = Surface((self.scaled_width, LEVEL_HEIGHT)) overlay.set_alpha(128) overlay.fill((0, 0, 0)) self.overlay_drawn = True self.screen.blit(overlay, (0, 0)) o_max_width = max([option.get_rect().width for option in self.menu_options]) width = o_max_width + MENU_PADDING[0] * 2 height = self.menu_options[0].get_rect().height + MENU_PADDING[1] * 2 x = self.scaled_width / 2 - width / 2 y = LEVEL_HEIGHT / 2 - (height * len(self.menu_options)) / 2 counter = 0 for option in self.menu_options: if counter == self.menu_option: used_color = MENU_COLORS[1] else: used_color = MENU_COLORS[0] draw.rect(self.screen, used_color, (x, y + counter * height, width, height), 0) option_x = x + MENU_PADDING[0] + (o_max_width - option.get_rect().width) / 2 self.screen.blit(option, (option_x, y + height * counter + MENU_PADDING[1])) counter += 1
def load_transparent_image(self): image = Surface((32, 32)) image.fill(DEFAULT_COLORKEY) image.set_colorkey(DEFAULT_COLORKEY) image.blit(self.image, (0, 0)) image.set_alpha(120) self.transparent_image = image
class Cell(): def __init__(self, x, y, width, height, block_image): self.x = x self.y = y self.activated = False self.small_block_image = scale_image(block_image, (20, 20)) self.image = Surface((width, height)) self.image.fill((139,69,19)) self.image.set_alpha(60) def update(self, event, cells): if event.type == MOUSEBUTTONDOWN: if event.button == 1: if event.pos[0] in range(self.x, self.x + self.image.get_width()) and event.pos[1] in range(self.y, self.y + self.image.get_height()): button_press_sound.play() self.activated = True for cell in cells: if cell != self: cell.activated = False def draw(self, window): window.blit(self.image, (self.x, self.y)) if self.activated: draw_rect(window, (0, 0, 0), (self.x, self.y, self.image.get_width(), self.image.get_height()), 2) else: draw_rect(window, (150, 150, 150), (self.x, self.y, self.image.get_width(), self.image.get_height()), 2) window.blit(self.small_block_image, ((self.x + self.image.get_width() // 2) - self.small_block_image.get_width() // 2, (self.y + self.image.get_height() // 2) - self.small_block_image.get_height() // 2))
def finish_game(self): """ Function triggered on the game end, passed to the Level :return: """ self.game_finished = True self.end_labels = [] player1score = self.levels[0].player.score player2score = self.levels[1].player.score if player1score > player2score: self.end_labels.append(Assets.menu_font.render("Player 1 wins!", 1, (255, 255, 255))) elif player1score < player2score: self.end_labels.append(Assets.menu_font.render("Player 2 wins!", 1, (255, 255, 255))) else: self.end_labels.append(Assets.menu_font.render("Draw!", 1, (255, 255, 255))) self.end_labels.append(Assets.font.render("Player 1 score: " + str(player1score), 1, (255, 255, 255))) self.end_labels.append(Assets.font.render("Player 2 score: " + str(player2score), 1, (255, 255, 255))) self.end_labels.append(Assets.font.render("Press enter to return to menu", 1, (255, 255, 255))) overlay = Surface((LEVEL_WIDTH * 2, LEVEL_HEIGHT)) overlay.set_alpha(128) overlay.fill((0, 0, 0)) self.screen.blit(overlay, (0, 0)) y = LEVEL_HEIGHT / 2 - sum([label.get_rect().height / 2 for label in self.end_labels]) offset = 0 for label in self.end_labels: self.screen.blit(label, (LEVEL_WIDTH - label.get_rect().width / 2, y + offset)) offset = offset + label.get_rect().height
def mouse_over_effect(self, stage_node_x, stage_node_y): mouse_over = Surface((30, 30), SRCALPHA, 32) mouse_over.set_alpha(100) mouse_over.fill(Color('Green')) self.surface.blit(mouse_over, (stage_node_x, stage_node_y)) rect_hover = Rect(stage_node_x, stage_node_y, 30, 30) draw.rect(self.surface, Color('Tomato'), rect_hover, 2)
def blit_alpha(target, source, location, opacity): x,y = location temp = Surface((source.get_width(), source.get_height())).convert() temp.blit(target, (-x, -y)) temp.blit(source, (0, 0)) temp.set_alpha(opacity) target.blit(temp, location)
def __init__(self, x, y): sprite.Sprite.__init__(self) self.xvel = 0 self.yvel = 0 self.startX = x self.startY = y self.x = self.startX self.y = self.startY img = image.load(resources.MAPFILE) size = img.get_size() new_size = ( int(size[0] * resources.MAPFILE_SCALE), int(size[1] * resources.MAPFILE_SCALE), ) self.image = transform.scale(img, new_size) self.image.set_alpha(128) self.rect = self.image.get_rect() self.moveTo(self.x, self.y) fg = Surface((self.rect.width, self.rect.height)) fg.set_alpha(128) # fg.fill(Color(0, 255, 0)) points = [] seapoints = [] for sea in SEAS: seapoints += BgMap.by_points(sea) for land in LANDS: points += BgMap.by_points(land, seapoints) for p in points: rect = Rect(p[0] * MOVE_SPEED, p[1] * MOVE_SPEED, 32, 32) draw.rect(fg, Color(255, 255, 0), rect) self.image.blit(fg, (0, 0))
class Nest(Entity): """ Encapsulates a colony of ants """ def __init__(self, world, id, size, location, ant_count): self.id = id self.size = size self.world = world self.ant_count = ant_count super(Nest, self).__init__(world, location, size, None) self.spawn_ants() self.mark_home() self.set_image() def set_image(self): w, h = self.size w *= self.world.cell_size h *= self.world.cell_size self.image = Surface((w,h)) self.image.fill(YELLOW) self.image.set_alpha(196) def mark_home(self): """ Converts the cell at its location to its nest """ width, height = self.size # width /= self.world.settings["cell_size"] # height /= self.world.settings["cell_size"] x, y = self.location for i in range(width): for j in range(height): self.world[(x+i,y+j)].make_home(self.id) def spawn_ants(self): """ Creates instances of ants and adds them into the world """ for ant in self.ant_count: for i in range(self.ant_count[ant]): self.add_new_ant(ant) def add_new_ant(self, ant_type): x, y = self.location width, height = self.size direction = randint(1,8) location = x+randint(0,width/self.world.settings["cell_size"]), y+randint(0,height/self.world.settings["cell_size"]) new_ant = ant_type(self.world, self.world.images["ant"], direction, location, self) self.world.add_ant(new_ant) def add_new_ant_randomly(self): num = randint(1,100) if num<90: ant = WorkerAnt elif num<98: ant = SoldierAnt else: ant = QueenAnt self.add_new_ant(ant)
class Controls: def __init__(self, scr_w, scr_h, list): self.surface = Surface((scr_w, scr_h)) self.font = font.Font(None, 32) self.list = list self.surface.set_alpha(150) def show(self): for x in range(0, len(self.list)): self.surface.blit( self.font.render(self.list[x], 1, (255, 255, 255)), (100, x * 30 + 20))
def set_alpha(self): if self.alpha != 255: self.alpha += 10 if self.alpha != 255: surf = Surface(self.rect.size) surf.fill(self.anticolor) surf.set_colorkey(self.anticolor) surf.blit(self.text, (0, 0)) surf.set_alpha(self.alpha) return surf else: return self.text
def get_surface(self): surface = Surface(self.size) if self.colorkey: surface.fill(self.colorkey) if 0 < self.alpha < 255: surface.set_alpha(self.alpha, RLEACCEL) self.blit_templates(surface) ## self.blit_corners(surface) ## self.blit_sides(surface) surface.set_colorkey(self.colorkey, RLEACCEL) surface.set_clip(self.clip) return surface.convert()
class Pointer: def __init__(self): self.pointer = Surface((16, 8)) self.pointer.fill(palete['claro']) self.alpha = 255 self.alpha_vel = -10 def run(self): self.pointer.set_alpha(self.alpha) self.alpha += self.alpha_vel if self.alpha <= 0 or self.alpha >= 255: self.alpha_vel *= -1
def set_alpha(self): if self.alpha != 255: self.alpha += 10 if self.alpha != 255: surf = Surface(self.rect.size) surf.fill(self.anticolor) surf.set_colorkey(self.anticolor) surf.blit(self.text, (0, 0)) surf.set_alpha(self.alpha) return surf else: return self.text
class Platform(sprite.Sprite): def __init__(self, x, y, filename): super().__init__() self.dmg = False if filename is not None: if filename not in tiles_textures: tiles_textures[filename] = \ pygame.transform.scale(image.load(filename).convert_alpha(), (64, 64)) self.image = tiles_textures[filename] else: self.image = Surface((PLAT_W, PLAT_H)) self.image.set_alpha(0) self.rect = Rect(x, y, PLAT_W * 2, PLAT_H * 2) self.hitbox = pygame.Rect(x + PLAT_W // 2, y + PLAT_H // 2, PLAT_W, PLAT_H)
class button(): def __init__(self, x, y, width, length, text, color, pic=None): self.img = Surface((width, length)) self.cover = Surface((width, length)) self.cover.fill((0, 0, 0)) self.color = color self.img.fill((color)) self.x, self.y = x, y font1 = font.Font(font.get_default_font(), int(width * 0.09)) self.txt = font1.render(str(text), True, (255, 255, 255)) if pic: pic = transform.scale( pic, (width, length), ) self.icon = pic def draw_button(self, screen, posx, posy): self.ishover(posx, posy) screen.blit(self.img, (self.x, self.y)) screen.blit(self.cover, (self.x, self.y)) screen.blit( self.txt, (self.x + (self.img.get_size()[0] / 2 - self.txt.get_width() / 2), self.y + (self.img.get_size()[1] / 2 - self.txt.get_height() / 2))) if (self.icon): screen.blit( self.icon, (self.x + (self.img.get_size()[0] / 2 - self.icon.get_width() / 2), self.y + (self.img.get_size()[1] / 2 - self.icon.get_height() / 2))) def ishover(self, posx, posy): if (posx >= self.x and posx <= self.x + self.img.get_size()[0] and posy >= self.y and posy <= self.y + self.img.get_size()[1]): self.cover.set_alpha(100) else: self.cover.set_alpha(0) def isclicked(self, posx, posy): if (posx >= self.x and posx <= self.x + self.img.get_size()[0] and posy >= self.y and posy <= self.y + self.img.get_size()[1]): return True return False
class StatusBar: def __init__(self): self.x = 10 self.y = W_HEIGHT - 40 self.w = W_WIDTH - 20 self.h = 30 self.color = GRAY self.s = Surface((self.w, self.h)) def draw(self, screen, health): # draw.rect(screen, self.color, (self.x, self.y,self.w, self.h)) if health <= 0: self.color = RED self.s.set_alpha(100) self.s.fill(self.color) screen.blit(self.s, (10, W_HEIGHT - 40))
def generate_fire_sprite(self): if self.upgrade_levels[0] >= 3: self.set_fire_ring(None) return fire_width = self.fire_width fire_effect_image = Surface((92, 92)) fire_effect_image.fill(DEFAULT_COLORKEY) fire_effect_image.set_colorkey(DEFAULT_COLORKEY) pygame.draw.line(fire_effect_image, self.fire_color, (46, 30), (46, 0), fire_width) pygame.draw.line(fire_effect_image, self.fire_color, (30, 46), (0, 46), fire_width) pygame.draw.line(fire_effect_image, self.fire_color, (62, 46), (92, 46), fire_width) pygame.draw.line(fire_effect_image, self.fire_color, (46, 62), (46, 92), fire_width) fire_effect_image.set_alpha(225) self.fire_sprite = pygame.sprite.Sprite() self.fire_sprite.image = fire_effect_image self.fire_sprite.rect = Rect(self.rect.left - 30, self.rect.top - 30, 92, 92) self.fire_sprite.mask = pygame.mask.from_surface(self.fire_sprite.image)
def go(self, display): super().go(display) shade = Surface( (self._w, self._h * self._cooldown_left // self._cooldown_timer)) shade.set_alpha(63) shade.fill(constants.BLACK) y_pos = self._y + (self._h * (self._cooldown_timer - self._cooldown_left) // self._cooldown_timer) display.blit(shade, (self._x, y_pos)) pygame.draw.rect(display, constants.BLACK, Rect(self._x, self._y, self._w, self._h), 2) self._cooldown_left = max(0, self._cooldown_left - 1)
class LightColumn(GameObject): ''' This class exists to let the player know where exactly he's aiming. ''' SIZE = Rect(0, 0, 32, config.SCREEN_HEIGHT - 32 * 3) def __init__(self): super().__init__() self.image = Surface(self.__class__.SIZE.size, config.BLIT_FLAGS) self.position = [-300.0, -300.0] self.rect = Rect(self.position, self.__class__.SIZE.size) self.state = 1 self.image.fill(color.WHITE) self.image.set_alpha(128) del self.acceleration, self.velocity actions = {1: None}
class LightColumn(GameObject): ''' This class exists to let the player know where exactly he's aiming. ''' SIZE = Rect(0, 0, 32, config.SCREEN_HEIGHT - 32 * 3) def __init__(self): super().__init__() self.image = Surface(self.__class__.SIZE.size, config.BLIT_FLAGS) self.position = [-300.0, -300.0] self.rect = Rect(self.position, self.__class__.SIZE.size) self.state = 1 self.image.fill(color.WHITE) self.image.set_alpha(128) del self.acceleration, self.velocity actions = {1 : None}
def get_image(self, x: int, y: int, width: int, height: int, alpha: bool = False) -> Surface: """ Extracts sprite of given point (x, y) (left, top) and width and height. Alpha boolean keyword argument for converting the sprite in alpha or non-alpha. """ image = Surface((width, height)) image.blit(self.spritesheet, (0, 0), (x, y, width, height)) image.set_colorkey((0, 0, 0)) image.set_alpha(255) if alpha: return image.convert_alpha() return image.convert()
class VisionSurface: def __init__(self, tile_size, color, coords, surface): surface_size = ((Settings.VISION_DISTANCE * 2 + 1) * tile_size[0], (Settings.VISION_DISTANCE * 2 + 1) * tile_size[1]) self.parent_surface = surface self.surface = Surface(surface_size) self.surface.set_alpha(20) self.x, self.y = coords self.tile_size = tile_size self.color = color def draw(self): self.surface.fill(self.color) self.parent_surface.blit(self.surface, (self.x * self.tile_size[0], self.y * self.tile_size[1])) def move(self, dx=0, dy=0): self.x += dx self.y += dy
def render(self, surf: Surface): player_pos = LEVEL.get(self.player) enemy_pos = LEVEL.get(self.enemy) if global_vars.intro_part > 1: psurf = Surface(surf.get_size()).convert_alpha() psurf.fill((0, 0, 0, 0)) if self.show_enemy and self.enemy == self.player: pygame.draw.circle( psurf, PLAYER_COLOR if global_vars.blink_on else ENEMY_COLOR, enemy_pos.render, 10) else: if self.show_enemy and enemy_pos is not None: pygame.draw.circle(psurf, ENEMY_COLOR, enemy_pos.render, 10) if player_pos is not None: pygame.draw.circle(psurf, PLAYER_COLOR, player_pos.render, 10) psurf.set_alpha(CHARACTER_OPACITY) surf.blit(psurf, (0, 0))
def __init__(self, config, *args, **kwargs): super(Game, self).__init__(config, *args, **kwargs) self.background = Surface(config.RESOLUTION) self.rendering = sprite.LayeredDirty() self.player = sprite.GroupSingle() self.creeps = sprite.Group() image = Surface((20, 20)).convert(self.display) image.fill((255, 255, 255)) mob.Player(image, 0, config, self.rendering, self.player, self.rendering) self.rendering.change_layer(self.player.sprite, 1) self.camera = FollowCam(Vector(0, 0), self.player.sprite, config, max_dist=100, max_speed=(60)) offset = self.camera.get_offset() image = Surface((20, 20)).convert(self.display) image.fill((0, 128, 0)) m_image = Surface((100, 100)).convert(self.display) m_image.set_alpha(64) m_image.fill((128, 64, 192)) for x in xrange(config.INITIAL_SPAWN): mob.Creep(image, m_image, self.player.sprite, offset, config, self.rendering, self.creeps, self.rendering)
def contruct_menu_background(self, size): changes = 5 bg = Surface(size) # Surface with horizontal lines bg2 = Surface(size, pg.SRCALPHA, 32) # Surfave with vertical lines red, green, blue = (100, 100, 100) for y in range(HEIGHT): red, green, blue = transform_color((red, green, blue), changes, max_=200, min_=30) pg.draw.line(bg, (red, green, blue), (0, y), (WIDTH-1, y)) red, green, blue = (10, 10, 10) for x in range(0, WIDTH): red, green, blue = transform_color((red, green, blue), changes, max_=55) pg.draw.line(bg2, pg.Color(red, green, blue, 100), (x, 0), (x, HEIGHT)) bg2.set_alpha(255/2) # 50% vertical lines, 50% horizontal lines bg.blit(bg2, (0,0)) return bg
def finish_game(self): """ Function triggered on the game end, passed to the Level :return: """ self.game_finished = True self.end_labels = ( Assets.menu_font.render("Congratulations!", 1, (255, 255, 255)), Assets.font.render("Your final score: " + str(self.level.player.score), 1, (255, 255, 255)), Assets.font.render("Press enter to return to menu", 1, (255, 255, 255)) ) overlay = Surface((LEVEL_WIDTH, LEVEL_HEIGHT)) overlay.set_alpha(128) overlay.fill((0, 0, 0)) self.screen.blit(overlay, (0, 0)) y = LEVEL_HEIGHT / 2 - sum([label.get_rect().height / 2 for label in self.end_labels]) offset = 0 for label in self.end_labels: self.screen.blit(label, (LEVEL_WIDTH / 2 - label.get_rect().width / 2, y + offset)) offset = offset + label.get_rect().height
class World: def __init__(self): self.screen = None self.player = None self.camera = Camera() self.initialize_tiles() self.flash_counter = [0, 0] self.flash_image = Surface((WIN_WIDTH, WIN_HEIGHT)) def initialize_screen(self, screen_manager, game_screen): """ l.initialize_screen( ScreenManager, GameScreen ) -> None Associate this level with the given screen_manager and game_screen. """ self.screen_manager = screen_manager self.screen = game_screen.screen_image def initialize_tiles(self): #TEMP self.tiles = [] for y in xrange(WORLD_HEIGHT): self.tiles.append([]) for x in xrange(WORLD_WIDTH): t = Tile(x, y, MEADOW_GRASS) self.tiles[y].append(t) for y in range(WORLD_HEIGHT*3/8, WORLD_HEIGHT*3/8 + WORLD_HEIGHT/4): for x in range(WORLD_WIDTH*3/8, WORLD_WIDTH*3/8 + WORLD_WIDTH/4): self.tiles[y][x] = Tile(x, y, FOREST_GRASS) for y in range(WORLD_HEIGHT*5/8, WORLD_HEIGHT*6/8): for x in range(WORLD_WIDTH*3/8, WORLD_WIDTH*3/8 + WORLD_WIDTH/4): self.tiles[y][x] = Tile(x, y, BUG_GRASS) for y in range(0, WORLD_HEIGHT): for x in range(WORLD_WIDTH*7/8, WORLD_WIDTH): self.tiles[y][x] = Tile(x, y, CHARRED_GRASS) for y in range(19, 21): for x in range(38, 40): self.tiles[y][x] = Tile(x, y, DARK_GRASS) self.tiles[WORLD_HEIGHT/2][WORLD_WIDTH/2].set_entity(TileEntity(HEALING_TOTEM)) self.tiles[20][WORLD_WIDTH*15/16].set_entity(TileEntity(SVON)) def update(self, up, down, left, right): player = self.player self.camera.update(player) player.update() self.draw_tiles() self.screen.blit(player.world_image, self.camera.apply(player)) self.flash_update() def begin_flash(self, color, duration): self.flash_counter = [duration, duration] self.flash_image.fill(color) def draw_tiles(self): for row in self.tiles: for t in row: if t: self.screen.blit(t.image, self.camera.apply(t)) def flash_update(self): if self.flash_counter[0] <= 0: return half = self.flash_counter[1]/2 if half <= 0: return current = self.flash_counter[0] if current > half: alpha = int(255.0 - ((current - half)/float(half))*255.0) else: alpha = int(((current)/float(half))*255.0) self.flash_image.set_alpha(alpha) self.screen.blit(self.flash_image, (0, 0)) self.flash_counter[0] -= 1 def add_player(self, player, x, y): self.player = player player.rect.left, player.rect.top = x*TILE_SIZE, y*TILE_SIZE def add_tile(self, tile, x, y): self.tiles[y][x] = tile def tile_at(self, x, y): width, height = len(self.tiles[0]), len(self.tiles) if x < 0 or x > width: return None if y < 0 or y > height: return None return self.tiles[y][x]
def Game_loop(): size = DISPLAY_WIDTH / 16.0 posx = (DISPLAY_WIDTH - (size // 2) * 17) * 27 // 100 posy = 0 court = generate_court(size=size, start_posx=posx, start_posy=posy) snake = [(8, 18, 9), (8, 16, 8), (8, 14, 7)] prey = 0 button_group = Group() left_button = Text(text=u'<', x=5, y=50, size=22, font_file='a_Albionic.ttf', color=(250, 250, 250), surface=screen) right_button = Text(text=u'>', x=85, y=50, size=22, font_file='a_Albionic.ttf', color=(250, 250, 250), surface=screen) button_group.add(left_button, right_button) menu_button = Hexagon_Button(lable=u'меню', posx=87, posy=2, font_size=3, font_file='a_Albionic.ttf', color=(35, 125, 30), text_color=(210, 205, 10), border_color=(210, 205, 10)) wasted = Text(text=u'Потрачено!', x=6, y=35, size=7, font_file='a_Albionic.ttf', color=(250, 150, 120), surface=screen) win = Text(text=u'Победа!', x=20, y=35, size=14, font_file='a_Albionic.ttf', color=(250, 150, 120), surface=screen) points_label = Text(text=u'Очки: 0', x=2, y=2, size=3, font_file='a_Albionic.ttf', color=(85, 170, 10), surface=screen) fps = Text(text=u'', x=5, y=2, size=2, font_file='a_Albionic.ttf', color=(85, 170, 10), surface=screen) apple_eat_sound = mixer.Sound('sounds/Apple_eat.ogg') apple_eat_sound.set_volume(1.0) finally_background = Surface((DISPLAY_WIDTH, DISPLAY_HEIGHT)) vector = 1 alpha = 0 id = 8 x = 14 y = 7 dt = 0 clock = Clock() done = False while not done: mp = mouse.get_pos() for event in get(): if event.type == QUIT: done = True continue if event.type == KEYDOWN: if vector > 0: if event.key == K_LEFT: vector -= 1 if event.key == K_RIGHT: vector += 1 if event.type == MOUSEBUTTONDOWN: if vector > 0: if left_button.rect.collidepoint(mp): vector -= 1 elif right_button.rect.collidepoint(mp): vector += 1 if menu_button.rect.collidepoint(mp): done = True continue if vector < 1 and vector > -1: vector = 6 elif vector > 6: vector = 1 if not prey: prey = choice(tuple(court)) while prey.id_and_pos in snake: prey = choice(tuple(court)) if dt > 400: dt = 0 if vector == 1: x -= 2 y -= 1 elif vector == 2: x -= 1 if x % 2 != 0: y -= 1 id += 1 elif vector == 3: x += 1 if x % 2 == 0: y += 1 id += 1 elif vector == 4: x += 2 y += 1 elif vector == 5: x += 1 if x % 2 == 0: y += 1 id -= 1 elif vector == 6: x -= 1 if x % 2 != 0: y -= 1 id -= 1 next_step = (id, x, y) if next_step not in snake: if prey.id_and_pos != next_step: snake.append(next_step) snake.pop(0) else: snake.append(next_step) apple_eat_sound.play(0) points_label.set_text(text=u'Очки: %s' % str(len(snake) - 3)) prey = 0 #if len(snake) > 13: # vector = -1 #delay(10) else: vector = -1 if id < 0 or id > 16 or y < 0 or y > 9: vector = -1 screen.fill((20, 20, 40)) court.update(screen, snake, prey) if vector == -1: if alpha < 200: alpha += 3 finally_background.set_alpha(alpha) screen.blit(finally_background, (0, 0)) #if len(snake) < 12: # wasted.draw() #else: # win.draw() wasted.set_text(text=u'Уничтожено %s жертв!' % str(len(snake) - 3)) wasted.draw() fps.set_text(u'FPS: %s' % clock.get_fps()) fps.draw() button_group.draw(screen) menu_button.draw(screen, mp) points_label.draw() window.blit(screen, (0, 0)) flip() clock.tick(40) dt += clock.get_tick()
class Pheromone(AlphaGradient): """Pheromone control Ant movement. Pheromone are placed by Ants on cells during their movement through the environment. Pheromone are sigmoid functions whom intensity is between [0,1]. Pheromone provide an interface for decreasing and increasing their intensity. Args: rect (:obj:`Rect`): The position of the Pheromone on the display. surface (:obj:`Surface`): The surface to draw the Pheromone on. steepness (:obj:`float`, optional): The steepness of the sigmoid function. rel_tol (:obj:`float`, optional): Relative tolerance to 1. Attributes: intensity (:obj:`float`): Current intensity of the Pheromone. rect (:obj:`Rect`): The position of the Pheromone on the display. surface (:obj:`Surface`): The surface to draw the Pheromone on. """ MIN_INTENSITY = GAMMA MAX_INTENSITY = GAMMA def __init__(self, background, width, height, gamma=GAMMA, q=Q, rho=RHO): self.intensity = GAMMA self.background = background dwidth, dheight = 0.8 * width, 0.8 * height self.surface = Surface((dwidth, dheight)) self.rect = self.surface.get_rect( width=dwidth, height=dheight, centerx=width / 2, centery=height / 2, ) self._q = q self._gamma = gamma self._rho = rho self._xmin = __class__.MIN_INTENSITY self._xmax = __class__.MAX_INTENSITY self._ymin = 0 self._ymax = 255 def draw(self): """Draw Pheromone on surface.""" self.surface.fill(PHEROMONE_COLOR) self._xmin = __class__.MIN_INTENSITY self._xmax = __class__.MAX_INTENSITY self.surface.set_alpha(int(self.get_alpha(self.intensity))) self.background.blit(self.surface, self.rect) def update(self, length): """Increases the Pheromone intensity by a certain amount. Args: amount (:obj:`float`): Increase Pheromone intensity by this amount """ self.intensity = (1 - RHO) * self.intensity + 1 / length if self.intensity > __class__.MAX_INTENSITY: __class__.MAX_INTENSITY = self.intensity if self.intensity < __class__.MIN_INTENSITY: __class__.MIN_INTENSITY = self.intensity def decoy(self): self.intensity = (1 - RHO) * self.intensity def __str__(self): return '{} {}i'.format(__class__.__name__, self.intensity) def __repr__(self): return '<{}(gamma={}, q={}, rho={}) at {}>'.format( __class__.__name__, self._gamma, self._q, self._rho, hex(id(self)))
class Glyph(object): """ Main glyph class image-- the image that text is set to rect-- rect for blitting image to viewing surface spacing-- line spacing links-- dict of (link, [rects]) pairs """ ################################################################## # class methods def __init__(self, rect, bkg=BLACK, color=WHITE, font=FONT, spacing=0, ncols=1, col_space=20): # FUTURE COLS ADD """ Initialize a glyph object rect-- rect object for positioning glyph image on viewing surface **kwargs-- dictionary of (env_id, value) pairs required kwargs are: bkg-- background color color-- font color font-- font """ # initialize self.image = Surface(rect.size) self.image.fill(bkg) self.image.set_alpha(255) self._bkg = bkg self.rect = rect self.spacing = spacing self.links = defaultdict(list) # FUTURE ### self.editors = {} ############ # FUTURE COLS ### self.col_w = ((rect.w + col_space) / ncols) - col_space self.col_space = col_space self.col_n = 1 self.ncols = ncols ################# self._dest = Rect(0, 0, 0, 0) # rect to blit a txt line to image surface # list of (env_id, value) pairs for environments; # _envs is used as a stack data structure self._envs = [('bkg', bkg), ('color', color), ('font', font), ('link', None)] # link id string self.buff = deque() # rendered text buffer ################################################################## # helper methods def __read_env(self, _txt_): # interprets and returns an environment. environments set text # characteristics such as color or links. # accepts the _txt_ iterable at an environment starting point # return (environment type, environment) tuple (e.g (font, Font object)) # re to get env arguments (arguments may be paths and contain \ or /) r = re.compile('(\w+)(\s+((\"|\').*?(\"|\')|.*?))?;') charbuffer = '' # _txt_ is a generator, so iterating consumes the contents for the # references to _txt_ in the _interpret function for i, char in _txt_: charbuffer += char s = r.search(charbuffer) # search for environment name arguments if s: # if search successful groups = s.groups() # get environment env, args = groups[0], groups[2] if env in Macros: return Macros[env] # new environment types must be added here elif env == 'bkg': # return new backgroun color return ('bkg', tuple([int(e) for e in args.split(',')])) elif env == 'color': # return new font color return ('color', tuple([int(e) for e in args.split(',')])) elif env == 'font': # return new font path, size = args.split(',') # the font location and size return ('font', Font(os.path.realpath(path), int(size))) elif env == 'link': # return new link return ('link', args.strip()) # FUTURE ### elif env == 'editor': #editor is considered an environment because it must be #linked. any text in an editor environment is input to #that editor, and any nested environments are ignored. name, w = args.split(',') #extract editor kw args kw = dict(self._envs) del kw['link'] kw['spacing'] = self.spacing h = kw['font'].get_linesize() editor = Editor(Rect(0, 0, int(w), h), **kw) self.editors[name] = editor # treat as link env, get_collision will sort return ('link', name) ############ else: raise ValueError(env + ' is an unrecognized environment') # the space func could take a size and return a surface or rect... # really only need to shift tokens. surfs do that without requiring tokens # to have rects def __read_func(self, _txt_): # interprets and returns a function. functions are special surfaces or # objects. # accepts the _txt_ iterable at function starting point # returns (function type, function results) tuple # (eg. (space, Surface obejct)) r = re.compile('(\w+){(.*?)}') i, char = _txt_.next() if char in SPECIALS: return 'special', char if char in WHITESPACE: return 'whitespace', WHITESPACE[char] charbuff = char for i, char in _txt_: charbuff += char s = r.search(charbuff) if s: func, args = s.groups() if func in Macros: return func, Macros[func] if func == 'space': return func, Surface((int(args), 1)) #"if charbuff = 'img'"? if func == 'img': return func, pygame.image.load(args).convert() ################################################################## # private methods def _interpret(self, txt): # iterprets glyph markup language # accepts string literal # returns a list of (env, charbuff) pairs, # where env is a dictionary of environment types keyed to values and # charbuff a list of text strings and the surfaces created from # functions editors, envs = self.editors, self._envs read_env, read_func = self.__read_env, self.__read_func iswhitespace = _iswhitespace txt = txt.strip() # FUTURE ### # preamble, txt = read_preamble(txt) # if preamble: envs = preamble # ########## # initialize charbuff, renderbuff, interpreted text, and previous char charbuff, interpreted_txt, prevchar = [], [], '' _txt_ = enumerate(txt) for i, char in _txt_: if iswhitespace(char) and iswhitespace(prevchar): if char == '\n': charbuff[-1] = char continue if char == '/': # a function: func, char = read_func(_txt_) charbuff.append(char) prevchar = char elif char == '{': # a new environment has started # using dict(envs) allows new environments to overwrite default # environments, which are in the beginning of the list interpreted_txt.append((dict(envs), charbuff)) charbuff = [] envs.append(read_env(_txt_)) elif char == '}': # an environment has ended # FUTURE ### link = dict(envs)['link'] if link in editors: editor = editors[link] # this is a hack to feed char to editor using input method for char in charbuff: mod = 0 if char.isupper(): mod = 3 event = Event(KEYDOWN, key=None, mod=mod, unicode=char.encode('utf8')) editor.input(event) interpreted_txt.append((dict(envs), [editor.image])) else: interpreted_txt.append((dict(envs), charbuff)) #interpreted_txt.append((dict(envs), charbuff)) # FUTURE DEL ############ charbuff = [] envs.pop() else: # a normal, string, character charbuff.append(char) prevchar = char if charbuff: interpreted_txt.append((dict(envs), charbuff)) return interpreted_txt def _tokenize(self, interpreted_txt): # tokenizes text # accepts (envs, charbuff) # returns a list of tokens iswhitespace, token_builder = _iswhitespace, _token_builder charbuff, _interpreted_txt, tokenized_txt = [], [], [] for (envs, chars) in interpreted_txt: for char in chars: if iswhitespace(char): if charbuff: _interpreted_txt.append((envs, charbuff)) charbuff = [] if _interpreted_txt: yield token_builder(_interpreted_txt) _interpreted_txt = [] yield token_builder([(envs, [char])]) else: charbuff.append(char) if charbuff: _interpreted_txt.append((envs, charbuff)) charbuff = [] if _interpreted_txt: yield token_builder(_interpreted_txt) def _wrap(self, tokenized_txt, justify): # wrap interpreted text to page width # accepts a list of Token objects tuples # returns a list of Line objects, each wrapped to self.rect Line = _Line # FUTURE COLS ### rect_w = self.col_w #rect_w = self.rect.w # FUTURE COLS DEL ################# # linesize tracks the current size of the rendered line because moving # between environments will mean that there will be multiple surfaces # that need to be glued together line = [] # initialize line, and linesize for token in tokenized_txt: token_w = token.get_width() if token_w > rect_w: raise ValueError("The token '"+token.str+"' is " +str((token_w - rect_w)) +" pixels too wide to fit in the rect passed.") # the token fits, process it and check if the line still fits inside # rect area, if not, append line without token, reinitialize line # with token line.append(token) if unicode(token) == '\n': # don't justify a line that would not wrap if justify == 'justified': _justify = 'left' else: _justify = justify yield Line(line, rect_w, _justify) line = [] # reset line elif sum(token.get_width() for token in line) > rect_w: token = line.pop() # remove single trailing whitespace if line[-1].iswhitespace: line = line[:-1] yield Line(line, rect_w, justify) line = [] # reinitialize _line, line, linesize, and num objects # do not append whitespace as the first token of the new line if not token.iswhitespace: line.append(token) if line: # don't justify a line that would not wrap if justify == 'justified': _justify = 'left' else: _justify = justify yield Line(line, rect_w, _justify) ################################################################## # public methods def input(self, txt, justify=None, update=True): """ interprets, renders, wraps, and justifies input text txt -- raw text written with glyph markup justify -- a justify command; default is left justified left: left justified right: right justified justified: justified (both sides even) center: center justified returns nothing """ buff, interpret, tokenize, wrap = (self.buff, self._interpret, self._tokenize, self._wrap) interpreted_txt = interpret(txt) tokens = tokenize(interpreted_txt) lines = wrap(tokens, justify) buff.extend(lines) if update: self.update() def overwrite(self, txt, **kw): self.clear() self.input(txt, **kw) def update(self): """ updates the surface with the text input to the buffer by the input method, then deletes buffer accepts nothing returns nothing """ buff, dest = self.buff, self._dest spacing = self.spacing image, rect = self.image, self.rect editors, links = self.editors, self.links # FUTURE COLS ### ncols, col_n = self.ncols, self.col_n col_w, col_space = self.col_w, self.col_space ################# while buff: line = buff.popleft() line_h = line.get_height() if dest.y + line_h > rect.h: buff.appendleft(line) # FUTURE COLS ### if col_n < ncols: dest.move_ip(col_w+col_space, -dest.y) col_n += 1 else: stderr.write(WARN_BUFF) return 0 # break # FUTURE COLS DEL ################# else: image.blit(line, dest) for link in line.links: for _rect in line.links[link]: # move rect to token's pos on image and append to links _rect.move_ip(dest.topleft) # FUTURE ADD # FUTURE DEL #links[link].append(_rect.move(dest.topleft)) links[link].append(_rect) # FUTURE ### if link in editors: editors[link].rect = _rect ############ dest.y += line_h + spacing # FUTURE ### for editor in editors.values(): image.blit(editor.image, editor.rect) ############ # FUTURE COLS ### self.col_n = col_n ################# return 1 def clear(self, *a): """ draws background over surface_dest using self.rect and resets self._dest surface_dest-- the destination surface that self.image has been drawn to background-- the background to draw over the areas that self.image was drawn to on surface_dest returns nothing """ # reset glyph self._dest = Rect(0, 0, 0, 0) self.links = defaultdict(list) self.col_n = 1 self.buff = deque() rect = self.rect self.image = Surface(rect.size) self.image.fill(self._bkg) # if provided, clear a surface at glyph rect if a: surface_dest, background = a surface_dest.blit(background, rect, rect) def get_collisions(self, mpos): """ get collisions between a point and the linked text on the glyph surface mpos-- the point to check against the linked text if a link collides with mpos returns the link collinding with mpos if no link collides with mpos returns None """ editors, links, rect = self.editors, self.links, self.rect for link in links: for _rect in links[link]: if _rect.move(rect.topleft).collidepoint(mpos): return link
dir_path = os.path.dirname(os.path.realpath(__file__)) pygame.init() screen = pygame.display.set_mode((0, 0), pygame.FULLSCREEN) bg = pygame.image.load(dir_path + '/windowtests/textured_background.jpg').convert() pygame.display.set_caption('Magic Mirror') clock_fps = pygame.time.Clock() while True: for event in pygame.event.get(): if event.type == pygame.KEYDOWN: if event.key == ord("q"): pygame.quit() screen.blit(bg, (0, 0)) clock_surface = Surface((300, 300)) clock_surface.set_alpha(100) clock_surface.blit(sine_wave_clock.updateAndRender(), (0, 0)) screen.blit(clock_surface, (20, 50)) pygame.display.update() clock_fps.tick(30)
class MiniMap(object): def __init__(self, mapInfo): fp = open(mapInfo) jData = json.load(fp) fp.close() self.drawSize = 3 self.downScale = 2 self.__blinkCount = 0 self.__showPlayer = True self.__blinkSpeed = 0.003 self.__alpha = 200 self.__colors = { "poi" : (255, 255, 255), "grassland" : (38,200,80), "forest" : (0,60,5), "plains" : (150,150,90), "mountain" : (117,26,12), "water" : (0,30,100), "desert" : (255,255,102), "snow" : (190,190,255) } self.poiLocs = [] self.size = [x / self.downScale for x in jData["size"]] self.__map = Surface([x * self.drawSize for x in self.size]) self.canvas = Surface([x * self.drawSize for x in self.size]) self.canvas.set_alpha(self.__alpha) for x in range(self.size[0]): jRows = [] for i in range(self.downScale): jRows.append(jData["tiles"][x * self.downScale + i]) for y in range(self.size[1]): # draw each pixel tTypes = [] for jr in jRows: for j in range(self.downScale): tTypes.append(jr[y * self.downScale + j]) color = [0,0,0] cCount = 0 poiFound = False for t in tTypes: if t not in locations.keys(): cCount += 1 for rgb in range(3): color[rgb] += self.__colors[t][rgb] elif not poiFound: if t != "MasterSword": self.poiLocs.append((x,y)) poiFound = True for rgb in range(3): color[rgb] /= cCount area = Rect((x * self.drawSize, y * self.drawSize), (self.drawSize, self.drawSize)) draw.rect(self.__map, color, area) def draw(self, canvas, playerLocation, windowSize=(640,480)): self.canvas.blit(self.__map, (0,0)) if self.__showPlayer: for poi in self.poiLocs: area = Rect((poi[0] * self.drawSize, poi[1] * self.drawSize), (self.drawSize, self.drawSize)) draw.rect(self.canvas, (0,0,0), area) area = Rect((int(playerLocation[0] / self.downScale) * self.drawSize, int(playerLocation[1] / self.downScale) * self.drawSize), (self.drawSize, self.drawSize)) draw.rect(self.canvas, (255,0,0), area) canvas.blit(self.canvas, (windowSize[0] - (self.size[0] * self.drawSize), windowSize[1] - (self.size[1] * self.drawSize))) def update(self, ticks): self.__blinkCount += self.__blinkSpeed * ticks if self.__blinkCount > 1: self.__blinkCount -= 1 self.__showPlayer = not self.__showPlayer
def gasgraph_loop(limit_mass): done = False data = {} text_mass = 'Mass: N/A' text_radius = 'Radius: N/A' text_density = 'Density: N/A' invalid = True fondo = display.set_mode((ANCHO, ALTO), SCALED) fondo.fill(COLOR_BOX) numbers = WidgetGroup() for i in [i for i in range(len(radius_keys[:4]))]: n = Number(radius_imgs[i], x=i * 28 + 30, y=3) numbers.add(n) exes.append(n.rect.centerx) for i in [i + 4 for i in range(len(radius_keys[4:14]))]: n = Number(radius_imgs[i], x=i * 27 + 26, y=3) numbers.add(n) exes.append(n.rect.centerx) for i in [i + 14 for i in range(len(radius_keys[14:]))]: n = Number(radius_imgs[i], x=i * 22 + 90, y=3) numbers.add(n) exes.append(n.rect.centerx) for i in [i for i in range(len(mass_keys))]: n = Number(mass_imgs[i], right=30, centery=i * 20 + 21) numbers.add(n) yes.append(n.rect.centery) x = exes[radius_keys.index(1.02)] y = yes[mass_keys.index(2)] rect_super = Rect(31, y, x - 3, (img_rect.h / 2) - 60) rect_puffy = Rect(x + 28, 16, (img_rect.w / 2) + 100, y - 16) rect_giant = Rect(31, 16, x - 3, y - 16) lim_y = find_and_interpolate(limit_mass, mass_keys, yes) lim_rect = Rect(31, lim_y, img_rect.w, img_rect.h - lim_y + img_rect.y) lim_img = Surface(lim_rect.size) lim_img.set_alpha(150) lineas = WidgetGroup() linea_h = Linea(img_rect, img_rect.x, img_rect.centery, img_rect.w, 1, lineas) linea_v = Linea(img_rect, img_rect.centerx, img_rect.y, 1, img_rect.h, lineas) punto = Punto(img_rect, img_rect.centerx, img_rect.centery, lineas) move_x, move_y = True, True while not done: for e in event.get( [KEYDOWN, KEYUP, QUIT, MOUSEBUTTONDOWN, MOUSEMOTION]): if (e.type == KEYDOWN and e.key == K_ESCAPE) or e.type == QUIT: quit() exit() elif e.type == MOUSEMOTION: px, py = e.pos if move_y: linea_h.move_y(py) punto.move_y(py) if move_x: linea_v.move_x(px) punto.move_x(px) dx, dy = punto.rect.center valid = [ rect_puffy.collidepoint(dx, dy), rect_giant.collidepoint(dx, dy), rect_super.collidepoint(dx, dy) ] off_limit = lim_rect.collidepoint(dx, dy) if img_rect.collidepoint(px, py) and any(valid) and not off_limit: invalid = False mass = round( find_and_interpolate(linea_h.rect.y + 1, yes, mass_keys), 5) radius = round( find_and_interpolate(linea_v.rect.x, exes, radius_keys), 3) clase = 'Puffy Giant' if valid[0] else '' clase = 'Gas Giant' if valid[1] else clase clase = 'Super Jupiter' if valid[2] else clase data.update({ 'mass': mass, 'radius': radius, 'clase': clase, 'albedo': 42.25 }) d = round(density(mass, radius), 5) text_mass = 'Mass: {}'.format(mass) text_radius = 'Radius: {}'.format(radius) text_density = 'Density: {}'.format(d) else: invalid = True text_mass = 'Mass: N/A' text_radius = 'Radius: N/A' text_density = 'Density: N/A' elif e.type == MOUSEBUTTONDOWN: if e.button == 1: done = True elif e.type == KEYDOWN and not invalid: if e.key == K_SPACE: done = True elif e.key == K_LSHIFT: move_x = False elif e.key == K_LCTRL: move_y = False elif e.type == KEYUP: if e.key == K_LSHIFT: move_x = True elif e.key == K_LCTRL: move_y = True render_mass = fuente2.render(text_mass, True, COLOR_TEXTO, COLOR_BOX) render_radius = fuente2.render(text_radius, True, COLOR_TEXTO, COLOR_BOX) render_density = fuente.render(text_density, True, COLOR_TEXTO, COLOR_BOX) fondo.fill(COLOR_BOX) fondo.blit(render_mass, (3, ALTO - 20)) fondo.blit(render_radius, (150, ALTO - 20)) fondo.blit(render_density, (300, ALTO - 20)) fondo.blit(img, img_rect) fondo.blit(lim_img, lim_rect) numbers.draw(fondo) lineas.update() lineas.draw(fondo) display.update() display.quit() return data
class Nest(Entity): """ Encapsulates a colony of ants """ def __init__(self, world, id, size, location, ant_count): self.id = id self.size = size self.world = world self.ant_count = ant_count super(Nest, self).__init__(world, location, size, None) self.spawn_ants() self.mark_home() self.set_image() def set_image(self): w, h = self.size w *= self.world.cell_size h *= self.world.cell_size self.image = Surface((w, h)) self.image.fill(YELLOW) self.image.set_alpha(196) def mark_home(self): """ Converts the cell at its location to its nest """ width, height = self.size # width /= self.world.settings["cell_size"] # height /= self.world.settings["cell_size"] x, y = self.location for i in range(width): for j in range(height): self.world[(x + i, y + j)].make_home(self.id) def spawn_ants(self): """ Creates instances of ants and adds them into the world """ for ant in self.ant_count: for i in range(self.ant_count[ant]): self.add_new_ant(ant) def add_new_ant(self, ant_type): x, y = self.location width, height = self.size direction = randint(1, 8) location = x + randint( 0, width / self.world.settings["cell_size"]), y + randint( 0, height / self.world.settings["cell_size"]) new_ant = ant_type(self.world, self.world.images["ant"], direction, location, self) self.world.add_ant(new_ant) def add_new_ant_randomly(self): num = randint(1, 100) if num < 90: ant = WorkerAnt elif num < 98: ant = SoldierAnt else: ant = QueenAnt self.add_new_ant(ant)
class GameMenu: # Menu item details, perhaps consider putting this in a configuration # file in future menu_items = [ MenuItem('Start game', 'start', 'command'), MenuItem('Instructions', 'instructions', 'textpage'), MenuItem('Customize character', 'character', 'subcommand'), MenuItem('Game controls', 'controls', 'subcommand'), MenuItem('View high scores', 'highscore', 'subcommand'), MenuItem('Credits', 'credits', 'textpage'), MenuItem('Quit', 'quit', 'command') ] # Dictionary of text pages for menu entries # Note currently no word wrapping - needs \n to be added in advance menu_pages = { 'instructions':"INSTRUCTIONS\n\nFollow the direction at the top centre\nof the screen.\n\nMove the character using a joystick (Picade)\n or cursor keys (keyboard).\nPress top button or SPACE to duck\nPress RIGHT SHIFT to view the map\n\nAvoid the obstacles that appear on later levels\n", 'credits':"CREDITS\n\nCreate by Stewart Watkiss\nMade available under GPL v3 License\nSee: www.penguintutor.com/compassgame" } menu_spacing = 50 # distance between menu items top_spacing = 20 # distance between top of menu and start of menu left_spacing = 20 # distance between left and text for page text / command text menu_font_size = 45 # size of font for menu items menu_font_page = 32 # size of font for text page display status = STATUS_MENU # Track whether to display menu or in menu etc. # Requires width and height - these can be the same as the screen or smaller if need to constrain menu # Offset and border determine distance from origin of screen and any fixed area to avoid respectively def __init__(self, game_controls, width, height, offset=(0,0), border=100): self.game_controls = game_controls self.width = width # width of screen self.height = height # height of screen self.offset = offset # tuple x,y for offset from start of screen self.border = border # single value same for x and y # Start position of the menu area and size self.start_pos = (self.offset[0]+self.border, self.offset[1]+self.border) self.size = (self.width-2*self.start_pos[0], self.height-2*self.start_pos[1]) # Create a menu surface - this involves using pygame surface feature (rather than through pygame zero) # Allows for more advanced features such as alpha adjustment (partial transparency) self.menu_surface = Surface(self.size) # 75% opacity self.menu_surface.set_alpha(192) # Position of rect is 0,0 relative to the surface, not the screen self.menu_box = Rect((0,0),self.size) # Uses pygame rect so we can add it to own surface self.menu_rect = pygame.draw.rect(self.menu_surface , (200,200,200), self.menu_box) self.menu_pos = 0 # Tracks which menu item is selected # Timer restrict keyboard movements to prevent multiple presses self.menu_timer = Timer(0.12) # Finish setting up MenuItems # At the moment this doesn't provide much extra functionality, but by # placing it into the MenuItem object then makes it easier if we load # MenuItems from a configuration file in future for i in range (0,len(self.menu_items)): if self.menu_items[i].getCommand() in self.menu_pages: self.menu_items[i].setPage(self.menu_pages[self.menu_items[i].getCommand()]) # Update menu based on keyboard direction # If return is 'menu' then still in menu, so don't update anything else # If return is 'quit' then quit the application # Any other return is next instruction def update(self, keyboard): # set status_selected if menu status changed (through mouse click or press) selected_command_type = "" selected_command = "" # check if status is clicked - which means mouse was pressed on a valid entry if (self.status == STATUS_CLICKED): selected_command_type = self.menu_items[self.menu_pos].getMenuType() selected_command = self.menu_items[self.menu_pos].getCommand() self.status = STATUS_MENU # check if we are in menu timer in which case return until expired elif (self.menu_timer.getTimeRemaining() > 0): return 'menu' elif (self.game_controls.isPressed(keyboard,'up') and self.menu_pos>0): if (self.status == STATUS_MENU): self.menu_pos -= 1 self.menu_timer.startCountDown() elif (self.game_controls.isPressed(keyboard,'down') and self.menu_pos<len(self.menu_items)-1): if (self.status == STATUS_MENU): self.menu_pos += 1 self.menu_timer.startCountDown() elif (self.game_controls.isOrPressed(keyboard,['jump','duck'])): if (self.status == STATUS_MENU): selected_command_type = self.menu_items[self.menu_pos].getMenuType() selected_command = self.menu_items[self.menu_pos].getCommand() # If click was on text page then return to main menu elif (self.status == STATUS_PAGE): selected_command_type = 'menu' self.status = STATUS_MENU self.menu_timer.startCountDown() elif (self.game_controls.isPressed(keyboard,'escape')): selected_command_type = 'command' selected_command = 'quit' # If a menu object was clicked / chosen then handle if (selected_command_type == 'command'): # Reset menu to start position self.reset() return selected_command elif (selected_command_type == 'textpage'): self.status = STATUS_PAGE self.menu_timer.startCountDown() return 'menu' elif (selected_command_type == 'subcommand'): return selected_command else: return 'menu' def show(self, screen): # Create a rectangle across the area - provides transparancy screen.blit(self.menu_surface,self.start_pos) # draw directly onto the screen draw surface (transparency doesn't apply) if (self.status == STATUS_MENU): self.showMenu(screen) elif (self.status == STATUS_PAGE): self.showPage(screen) def showMenu(self, screen): for menu_num in range (0,len(self.menu_items)): if (menu_num == self.menu_pos): background_color = (255,255,255) else: background_color = None screen.draw.text(self.menu_items[menu_num].getText(), fontsize=self.menu_font_size, midtop=(self.width/2,self.offset[1]+self.border+(self.menu_spacing*menu_num)+self.top_spacing), color=(0,0,0), background=background_color) # Shows a page of text def showPage(self, screen): page_text = self.menu_items[self.menu_pos].getPage() screen.draw.text(page_text, fontsize=self.menu_font_page, topleft=(self.offset[0]+self.border+self.left_spacing,self.offset[1]+self.border+self.top_spacing), color=(0,0,0)) def mouse_move(self, pos): if (self.status == STATUS_MENU): return_val = self.get_mouse_menu_pos(pos) if return_val != -1: self.menu_pos = return_val def mouse_click(self, pos): if (self.status == STATUS_MENU): return_val = self.get_mouse_menu_pos(pos) if return_val != -1: self.menu_pos = return_val self.status = STATUS_CLICKED # If click from text page then return to menu elif (self.status == STATUS_PAGE): self.status = STATUS_MENU def reset(self): self.menu_pos = 0 self.status = STATUS_MENU # Checks if mouse is over menu and if so returns menu position # Otherwise returns -1 def get_mouse_menu_pos (self, pos): if (pos[0] > self.start_pos[0] and pos[1] > self.start_pos[1] + self.top_spacing and pos[0] < self.start_pos[0] + self.size[0] and pos[1] < self.start_pos[1] + self.size[1]): start_y = self.start_pos[1] + self.top_spacing for this_menu_pos in range(0,len(self.menu_items)): if (pos[1] - start_y >= this_menu_pos * self.menu_spacing and pos[1] - start_y <= (this_menu_pos * self.menu_spacing)+self.menu_spacing): return this_menu_pos # If not returned then not over menu return -1
class CustomControls: menu_spacing = 35 # distance between menu items top_spacing = 20 # distance between top of menu and start of menu left_spacing = 20 # distance between left and text for page text / command text menu_font_size = 32 # size of font for menu items status = STATUS_MENU # Track whether to display menu or in menu etc. # Requires width and height - these can be the same as the screen or smaller if need to constrain menu # Offset and border determine distance from origin of screen and any fixed area to avoid respectively def __init__(self, game_controls, width=800, height=600, offset=(0,0), border=100): self.game_controls = game_controls self.width = width # width of screen self.height = height # height of screen self.offset = offset # tuple x,y for offset from start of screen self.border = border # single value same for x and y # Start position of the menu area and size self.start_pos = (self.offset[0]+self.border, self.offset[1]+self.border) self.size = (self.width-2*self.start_pos[0], self.height-2*self.start_pos[1]) # Create a menu surface - this involves using pygame surface feature (rather than through pygame zero) # Allows for more advanced features such as alpha adjustment (partial transparency) self.menu_surface = Surface(self.size) # 75% opacity self.menu_surface.set_alpha(192) # Position of rect is 0,0 relative to the surface, not the screen self.menu_box = Rect((0,0),self.size) # Uses pygame rect so we can add it to own surface self.menu_rect = pygame.draw.rect(self.menu_surface , (200,200,200), self.menu_box) self.menu_pos = 0 # Tracks which menu item is selected # Timer restrict keyboard movements to prevent multiple presses self.menu_timer = Timer(0.12) self.updateMenuItems() # Updates the menu items - run this whenever the menu changes def updateMenuItems(self): self.menu_items = [] # Store keys in an array to fix order and make easier to identify selected key for this_key in self.game_controls.getKeys(): self.menu_items.append(MenuItem(this_key+" ("+str(self.game_controls.getKeyString(this_key))+")", this_key, 'control')) # Dummy entry - blank line self.menu_items.append(MenuItem("","","controls")) # Last Menu item is to save and return self.menu_items.append(MenuItem("Save settings", 'save', 'menu')) # Update menu based on keyboard direction # If return is 'controls' then still in custon controls, so don't update anything else # If return is 'menu' then return to main game menu def update(self, keyboard): # Handle erquest for new key if (self.status == STATUS_CUSTOM_KEY and self.menu_timer.getTimeRemaining() <= 0): keycode = self.checkKey(keyboard) if (keycode != None): self.game_controls.setKey(self.selected_key, keycode) self.menu_timer.startCountDown() self.status = STATUS_MENU self.updateMenuItems() return 'controls' # check if status is clicked - which means mouse was pressed on a valid entry if (self.status == STATUS_CLICKED): self.selected_key = self.menu_items[self.menu_pos].getCommand() self.reset() self.status = STATUS_CUSTOM_KEY # check if we are in menu timer in which case return until expired elif (self.menu_timer.getTimeRemaining() > 0): return 'controls' elif (self.game_controls.isPressed(keyboard,'up') and self.menu_pos>0): if (self.status == STATUS_MENU): self.menu_pos -= 1 self.menu_timer.startCountDown() elif (self.game_controls.isPressed(keyboard,'down') and self.menu_pos<len(self.menu_items)-1): if (self.status == STATUS_MENU): self.menu_pos += 1 self.menu_timer.startCountDown() elif (self.game_controls.isOrPressed(keyboard,['jump','duck'])): if (self.status == STATUS_MENU): self.selected_key = self.menu_items[self.menu_pos].getCommand() self.reset() # special case where selected_key is the save option if (self.selected_key == 'save'): # Save the controls self.game_controls.saveControls() return 'menu' # Another special case - blank entry used as a spacer # Ignore and continue with custom controls menu elif (self.selected_key == ''): self.menu_timer.startCountDown() return 'controls' self.status = STATUS_CUSTOM_KEY elif (self.game_controls.isPressed(keyboard,'escape')): return 'menu' return 'controls' # Checks pygame event queue for last key pressed def checkKey(self, keyboard): # Check all keycodes to see if any are high for this_code in keycodes: if (keyboard[this_code]): return this_code return None def draw(self, screen): # Create a rectangle across the area - provides transparancy screen.blit(self.menu_surface,self.start_pos) # draw directly onto the screen draw surface (transparency doesn't apply) if (self.status == STATUS_MENU): self.drawMenu(screen) elif (self.status == STATUS_CUSTOM_KEY): self.drawCustom(screen) def drawCustom(self, screen): screen.draw.text("Press custom key for "+self.selected_key, fontsize=self.menu_font_size, midtop=(self.width/2,self.offset[1]+self.border+(self.menu_spacing)+self.top_spacing), color=(0,0,0)) def drawMenu(self, screen): for menu_num in range (0,len(self.menu_items)): if (menu_num == self.menu_pos): background_color = (255,255,255) else: background_color = None screen.draw.text(self.menu_items[menu_num].getText(), fontsize=self.menu_font_size, midtop=(self.width/2,self.offset[1]+self.border+(self.menu_spacing*menu_num)+self.top_spacing), color=(0,0,0), background=background_color) def mouse_move(self, pos): if (self.status == STATUS_MENU): return_val = self.get_mouse_menu_pos(pos) if return_val != -1: self.menu_pos = return_val def mouse_click(self, pos): if (self.status == STATUS_MENU): return_val = self.get_mouse_menu_pos(pos) if return_val != -1: self.menu_pos = return_val self.status = STATUS_CLICKED # If click from text page then return to menu elif (self.status == STATUS_PAGE): self.status = STATUS_MENU def select(self): self.menu_timer.startCountDown() def reset(self): self.menu_timer.startCountDown() self.menu_pos = 0 self.status = STATUS_MENU # Checks if mouse is over menu and if so returns menu position # Otherwise returns -1 def get_mouse_menu_pos (self, pos): if (pos[0] > self.start_pos[0] and pos[1] > self.start_pos[1] + self.top_spacing and pos[0] < self.start_pos[0] + self.size[0] and pos[1] < self.start_pos[1] + self.size[1]): start_y = self.start_pos[1] + self.top_spacing for this_menu_pos in range(0,len(self.menu_items)): if (pos[1] - start_y >= this_menu_pos * self.menu_spacing and pos[1] - start_y <= (this_menu_pos * self.menu_spacing)+self.menu_spacing): return this_menu_pos # If not returned then not over menu return -1
class BaseWidget(BaseObject, sprite.Sprite): """BaseWidget () -> BaseWidget A basic widget class for user interface elements. The BaseWidget is the most basic widget class, from which any other widget class should be inherited. It provides the most basic attributes and methods, every widget needs. The widget is a visible (or non-vissible) element on the display, which allows the user to interact with it (active or passive) in a specific way. It has several methods and attributes to allow developers to control this interaction and supports accessibility through the ocempgui.access module. The widget can be placed on the display by accessing the various attributes of its 'rect' attribute directly. It exposes the following pygame.Rect attributes: top, left, bottom, right, topleft, bottomleft, topright, bottomright, midtop, midleft, midbottom, midright, center, centerx, centery, size, width, height Except the last three ones, 'size', 'width' and 'height' any of those can be assigned similarily to the pygame.Rect: widget.top = 10 widget.center = (10, 10) ... Note: This will only work for toplevel widgets as widgets are placed relative to their parent. Thus the 'top' attribute value of a widget, which is packed into another one, refers to its parents coordinates. So if it is placed four pixels to the left on its parent, its 'top' value will be 4, while the parent might be placed at e.g. 100, 100. You can get the absolute coordinates, the widget is placed on the display, by using the rect_to_client() method. To get the actual dimensions of the widget, it provides the read-only 'width', 'height' and 'size' attributes. if (widget.width > 50) or (widget.height > 50): ... if widget.size == (50, 50): ... To force a specific minimum size to occupy by the widget, the 'minsize' attribute or the respective set_minimum_size() method can be used. The occupied area of the widget will not be smaller than the size, but can grow bigger. widget.minsize = 100, 50 widget.set_minimum_size (10, 33) The counterpart of 'minsize' is the 'maxsize' attribute, which defines the maximum size, the widget can grow to. It will never exceed that size. widget.maxsize = 200, 200 wdget.set_maximum_size (100, 22) The 'image' and 'rect' attributes are used and needed by the pygame.sprite system. 'image' refers to the visible surface of the widget, which will be blitted on the display. 'rect' is a copy of the pygame.Rect object indicating the occupied area of the widget. The rect denotes the relative position of the widget on its parent (as explained above). The 'index' attribute and set_index() method set the navigation index position for the widget. It is highly recommended to set this value in order to provide a better accessibility (e.g. for keyboard navigation). The attribute can be used in ocempgui.access.IIndexable implementations for example. widget.index = 3 widget.set_index (0) Widgets support a 'style' attribute and create_style() method, which enable them to use different look than default one without the need to override their draw() method. The 'style' attribute of a widget usually defaults to a None value and can be set using the create_style() method. This causes the widget internals to setup the specific style for the widget and can be accessed through the 'style' attribute later on. A detailled documentation of the style can be found in the Style class. if not widget.style: widget.create_style () # Setup the style internals first. widget.style['font']['size'] = 18 widget.create_style ()['font']['name'] = Arial Widgets can be in different states, which cause the widgets to have a certain behaviour and/or look. Dependant on the widget, the actions it supports and actions, which have taken place, the state of the widget can change. The actual state of the widget can be looked up via the 'state' attribute and is one of the STATE_TYPES constants. if widget.state == STATE_INSENSITIVE: print 'The widget is currently insensitive and does not react.' Any widget supports layered drawing through the 'depth' attribute. The higher the depth is, the higher the layer on the z-axis will be, on which the widget will be drawn. Widgets might use the flag to set themselves on top or bottom of the display. # The widget will be placed upon all widgets with a depth lower than 4. widget.depth = 4 widget.set_depth (4) Widgets should set the 'dirty' attribute to True, whenever an update of the widget surface is necessary, which includes redrawing the widget (resp. calling draw_bg() and draw()). In user code, 'dirty' usually does not need to be modified manually, but for own widget implementations it is necessary (e.g. if a Label text changed). If the 'parent' attribute of the widget is set, the parent will be notified automatically, that it has to update portions of its surface. # Force redrawing the widget on the next update cycle of the render # group. widget.dirty = True Widgets support a focus mode, which denotes that the widget has the current input and action focus. Setting the focus can be done via the 'focus' attribute or the set_focus() method. widget.focus = True widget.set_focus (True) 'sensitive' is an attribute, which can block the widget's reaction upon events temporarily. It also influences the look of the widget by using other style values (see STATE_INSENSITIVE in the Style class). widget.sensitive = False widget.set_sensitive (False) Each widget supports transparency, which also includes all children which are drawn on it. By setting the 'opacity' attribute you can adjust the degree of transparency of the widget. The allowed values are ranged between 0 for fully transparent and 255 for fully opaque. widget.opacity = 100 widget.set_opacity (25) Widgets allow parent-child relationships via the 'parent' attribute. Parental relationships are useful for container classes, which can contain widgets and need to be informed, when the widget is destroyed, for example. Take a look the Bin and Container classes for details about possible implementations. Do NOT modify the 'parent' attribute value, if you do not know, what might happen. Widgets support locking themselves self temporarily using the lock() method. This is extremely useful to avoid multiple update/draw calls, when certain operations take place on it. To unlock the widget, the unlock() method should be called, which causes it to update itself instantly. widget.lock () # Acquire lock. widget.focus = False # Normally update() would be called here. widget.sensitive = False # Normally update() would be called here. widget.unlock () # Release lock and call update(). When using the lock() method in your own code, you have to ensure, that you unlock() the widget as soon as you do not need the lock anymore. The state of the lock on a widget can be queried using the 'locked' attribute: if widget.locked: print 'The widget is currently locked' Widgets can consist of other widgets. To guarantee that all of them will be added to the same event management system, set the same state, etc., the 'controls' attribute exists. It is a collection to and from which widgets can be attached or detached. Several methods make use of this attribute by iterating over the attached widgets and invoking their methods to put them into the same state, etc. as the main widget. widget.controls.append (sub_widget) for sub in widget.controls: ... Default action (invoked by activate()): None, will raise an NotImplementedError Mnemonic action (invoked by activate_mnemonic()): None Signals: SIG_FOCUSED - Invoked, when the widget received the focus (widget.focus=True). SIG_ENTER - Invoked, when the input device enters the widget. SIG_LEAVE - Invoked, when the input device leaves the wigdet. SIG_DESTROYED - Invoked, when the widget is destroyed. Attributes: minsize - Guaranteed size of the widget. maxsize - Counterpart to size and denotes the maximum size the widget. is allowed to occupy. Defaults to None usually. image - The visible surface of the widget. index - Navigation index of the widget. style - The style to use for drawing the widget. state - The current state of the widget. depth - The z-axis layer depth of the widget. dirty - Indicates, that the widget needs to be updated. focus - Indicates, that the widget has the current input focus. sensitive - Indicates, if the user can interact with the widget. parent - Slot for the creation of parent-child relationships. controls - Collection of attached controls for complex widgets. tooltip - The tool tip text to display for the widget. opacity - The degree of transparency to apply (0-255, 0 for fully transparent, 255 for fully opaque). indexable - The ocempgui.access.IIndexable implementation to use for the 'index' attribute support. entered - Indicates, that an input device is currently over the widget (e.g. the mouse cursor). locked - Indicates, whether the widget is locked. rect - The area occupied by the widget. x, y, ... - The widget allows to reposition itself through the various width, ... attributes offered by its rect attribute. size """ def __init__(self): BaseObject.__init__(self) sprite.Sprite.__init__(self) # Guaranteed sizes for the widget, see also the minsize/maxsize # attributes and set_*_size () methods. self._minwidth = 0 self._minheight = 0 self._maxwidth = 0 self._maxheight = 0 self._indexable = None self._image = None self._rect = Rect(0, 0, 0, 0) self._oldrect = Rect(0, 0, 0, 0) self._opacity = 255 self._style = None self._index = 0 self._state = STATE_NORMAL self._focus = False self._entered = False self._sensitive = True self._controls = [] self._depth = 0 self._dirty = True self._lock = 0 self._bg = None self.parent = None # Accessibility. self._tooltip = None # Signals, the widget listens to by default self._signals[SIG_FOCUSED] = [] self._signals[SIG_ENTER] = [] self._signals[SIG_LEAVE] = [] self._signals[SIG_DESTROYED] = [] def _get_rect_attr(self, attr): """W._get_rect_attr (...) -> var Gets the wanted attribute value from the underlying rect. """ return getattr(self._rect, attr) def _set_rect_attr(self, attr, value): """W._set_rect_attr (...) -> None Sets a specific attribute value on the underlying rect. Raises an AttributeError if the attr argument is the width, height or size. """ if attr in ("width", "height", "size"): # The width and height are protected! raise AttributeError("%s attribute is read-only" % attr) # TODO: This is just a hack around wrong positioning in # containers. self._oldrect = self.rect setattr(self._rect, attr, value) if (self.parent != None): if not isinstance(self.parent, BaseWidget): self.update() else: self._oldrect = self.rect def initclass(cls): """B.initclass () -> None Class method to expose the attributes of the own self.rect attribute. The method usually is called in the __init__.py script of the module. """ attributes = dir(Rect) for attr in attributes: if not attr.startswith ("__") and \ not callable (getattr (Rect, attr)): def get_attr(self, attr=attr): return cls._get_rect_attr(self, attr) def set_attr(self, value, attr=attr): return cls._set_rect_attr(self, attr, value) prop = property(get_attr, set_attr) setattr(cls, attr, prop) initclass = classmethod(initclass) def _get_rect(self): """W._get_rect () -> pygame.Rect Gets a copy of the widget's rect. """ return Rect(self._rect) # DEPRECATED def set_position(self, x, y): """W.set_position (...) -> None DEPRECATED - use the 'topleft' attribute instead """ print "*** Warning: set_position() is deprecated, use the topleft" print " attribute instead." self._set_rect_attr("topleft", (x, y)) def rect_to_client(self, rect=None): """W.rect_to_client (...) -> pygame.Rect Returns the absolute coordinates a rect is located at. In contrast to the widget.rect attribute, which denotes the relative position and size of the widget on its parent, this method returns the absolute position and occupied size on the screen for a passed rect. Usually this method will be called by children of the callee and the callee itself to detrmine their absolute positions on the screen. """ if self.parent and isinstance(self.parent, BaseWidget): re = self.rect if rect != None: re.x += rect.x re.y += rect.y re.width = rect.width re.height = rect.height return self.parent.rect_to_client(re) elif rect != None: rect.x = self.x + rect.x rect.y = self.y + rect.y return rect return self.rect def set_minimum_size(self, width, height): """W.set_minimum_size (...) -> None Sets the minimum size to occupy for the widget. Minimum size means that the widget can exceed the size by any time, but its width and height will never be smaller than these values. Raises a TypeError, if the passed arguments are not integers. Raises a ValueError, if the passed arguments are not positive. """ if (type(width) != int) or (type(height) != int): raise TypeError("width and height must be positive integers") if (width < 0) or (height < 0): raise ValueError("width and height must be positive integers") self._minwidth = width self._minheight = height self.dirty = True # DEPRECATED def set_size(self, width, height): """W.set_size (...) -> None DEPREACATED - use set_minimum_size () instead. """ print "*** Warning: set_size() is deprecated, use set_minimum_size()." self.set_minimum_size(width, height) def set_maximum_size(self, width, height): """W.set_maximum_size (...) -> None Sets the maximum size the widget is allowed to occupy. This is the counterpart to the set_minimum_size() method. """ if (type(width) != int) or (type(height) != int): raise TypeError("width and height must be positive integers") if (width < 0) or (height < 0): raise ValueError("width and height must be positive integers") self._maxwidth = width self._maxheight = height self.dirty = True def check_sizes(self, width, height): """W.check_sizes (...) -> int, int Checks the passed width and height for allowed values. Checks, whether the passed width an height match the upper and lower size ranges of the widget and returns corrected values, if they exceed those. Else the same values are returned. """ minwidth, minheight = self.minsize maxwidth, maxheight = self.maxsize if (minwidth != 0) and (width < minwidth): width = minwidth elif (maxwidth != 0) and (width > maxwidth): width = maxwidth if (minheight != 0) and (height < minheight): height = minheight elif (maxheight != 0) and (height > maxheight): height = maxheight return width, height def set_index(self, index): """W.set_index (...) -> None Sets the tab index of the widget. Sets the index position of the widget to the given value. It can be used by ocempgui.access.IIndexable implementations to allow easy navigation access and activation for the widgets. Raises a TypeError, if the passed argument is not a positive integer. """ if (type(index) != int) or (index < 0): raise TypeError("index must be a positive integer") self._index = index def set_depth(self, depth): """W.set_depth (...) -> None Sets the z-axis layer depth for the widget. Sets the z-axis layer depth for the widget. This will need a renderer, which makes use of layers such as the Renderer class. By default, the higher the depth value, the higher the drawing layer of the widget is. That means, that a widget with a depth of 1 is placed upon widgets with a depth of 0. Raises a TypeError, if the passed argument is not an integer. """ if type(depth) != int: raise TypeError("depth must be an integer") self.lock() old = self._depth self._depth = depth if isinstance(self.parent, BaseWidget): try: self.parent.update_layer(old, self) except: pass for c in self._controls: c.set_depth(depth) self.unlock() def set_dirty(self, dirty, update=True): """W.set_dirty (...) -> None Marks the widget as dirty. Marks the widget as dirty, so that it will be updated and redrawn. """ self._dirty = dirty if dirty and update: self.update() def set_event_manager(self, manager): """W.set_event_manager (...) -> None Sets the event manager of the widget and its controls. Adds the widget to an event manager and causes its controls to be added to the same, too. """ BaseObject.set_event_manager(self, manager) for control in self.controls: control.set_event_manager(manager) def set_indexable(self, indexable): """W.set_indexable (...) -> None Sets the IIndexable for the widget. The widget will invoke the add_index() method for itself on the IIndexable. """ if indexable and not isinstance(indexable, IIndexable): raise TypeError("indexable must inherit from IIndexable") if self._indexable == indexable: return if self._indexable != None: self._indexable.remove_index(self) self._indexable = indexable if indexable != None: indexable.add_index(self) for ctrl in self.controls: ctrl.set_indexable(indexable) # DEPRECATED def get_style(self): """W.get_style () -> WidgetStyle DEPRECATED - use the create_style() method instead """ print "*** Warning: get_style() is deprecated, use the create_style()" print " method instead." return self.create_style() def create_style(self): """W.create_style () -> WidgetStyle Creates the instance-specific style for the widget. Gets the style associated with the widget. If the widget had no style before, a new one will be created for it, based on the class name of the widget. The style will be copied internally and associated with the widget, so that modifications on it will be instance specific. More information about how a style looks like and how to modify them can be found in the Style class documentation. """ if not self._style: # Create a new style from the base style class. self._style = base.GlobalStyle.copy_style(self.__class__) self._style.set_value_changed(lambda: self.set_dirty(True)) return self._style def set_style(self, style): """W.set_style (...) -> None Sets the style of the widget. Sets the style of the widget to the passed style dictionary. This method currently does not perform any checks, whether the passed dictionary matches the criteria of the Style class. Raises a TypeError, if the passed argument is not a WidgetStyle object. """ if not isinstance(style, WidgetStyle): raise TypeError("style must be a WidgetStyle") self._style = style if not self._style.get_value_changed(): self._style.set_value_changed(lambda: self.set_dirty(True)) self.dirty = True def set_focus(self, focus=True): """W.set_focus (...) -> bool Sets the input and action focus of the widget. Sets the input and action focus of the widget and returns True upon success or False, if the focus could not be set. """ if not self.sensitive: return False if focus: if not self._focus: self._focus = True self.emit(SIG_FOCUSED, self) self.dirty = True self.run_signal_handlers(SIG_FOCUSED) else: if self._focus: self._focus = False self.dirty = True return True def set_entered(self, entered): """W.set_entered (...) -> None Sets the widget into an entered mode. """ if entered: if not self._entered: self._entered = True self.state = STATE_ENTERED self.emit(SIG_ENTER, self) self.run_signal_handlers(SIG_ENTER) elif self._entered: self._entered = False self.state = STATE_NORMAL self.run_signal_handlers(SIG_LEAVE) def set_sensitive(self, sensitive=True): """W.set_sensitive (...) -> None Sets the sensitivity of the widget. In a sensitive state (the default), widgets can react upon user interaction while they will not do so in an insensitive state. To support the visibility of this, the widget style should support the STATE_INSENSITIVE flag, while inheriting widgets should check for the sensitivity to enable or disable the event mechanisms. """ if sensitive != self._sensitive: if sensitive: self._sensitive = True self.state = STATE_NORMAL else: self._sensitive = False self.state = STATE_INSENSITIVE for control in self.controls: control.set_sensitive(sensitive) def set_state(self, state): """W.set_state (...) -> None Sets the state of the widget. Sets the state of the widget. The state of the widget is mainly used for the visible or non-visible appearance of the widget, so that the user can determine the state of the widget easier. Usually this method should not be invoked by user code. Raises a ValueError, if the passed argument is not a value of the STATE_TYPES tuple. """ if state not in STATE_TYPES: raise ValueError("state must be a value from STATE_TYPES") if self._state != state: self._state = state self.dirty = True def set_opacity(self, opacity): """W.set_opacity (...) -> None Sets the opacity of the widget. """ if type(opacity) != int: raise TypeError("opacity must be an integer") dirty = self._opacity != opacity self._opacity = opacity self.update() # DEPRECATED def set_event_area(self, area): """W.set_event_area (...) -> None DEPRECATED - this is no longer used. """ print "*** Warning: set_event_area() is no longer used!" def lock(self): """W.lock () -> None Acquires a lock on the Widget to suspend its updating methods. """ self._lock += 1 def unlock(self): """W.unlock () -> None Releases a previously set lock on the Widget and updates it instantly. """ if self._lock > 0: self._lock -= 1 if self._lock == 0: self.update() def set_tooltip(self, tooltip): """W.set_tooltip (...) -> None Sets the tooltip information for the widget. Raises a TypeError, if the passed argument is not a string or unicode. """ if type(tooltip) not in (str, unicode): raise TypeError("text must be a string or unicode") self._tooltip = tooltip def activate(self): """W.activate () -> None Activates the widget. Activates the widget, which means, that the default action of the widget will be invoked. This method should be implemented by inherited widgets. """ raise NotImplementedError def activate_mnemonic(self, mnemonic): """W.activate_mnemonic (...) -> bool Activates the widget through the set mnemonic. Activates the widget through the set mnemonic for it and returns True upon successful activation or False, if the widget was not activated. The BaseWidget.activate_mnemonic () method always returns False by default, so that this method should be implemented by inherited widgets, if they need explicit mnemonic support. """ return False def draw_bg(self): """W.draw_bg () -> Surface Draws the widget background surface and returns it. Creates the visible background surface of the widget and returns it to the caller. This method has to be implemented by inherited widgets. """ raise NotImplementedError def draw(self): """W.draw () -> None Draws the widget surface. Creates the visible surface of the widget and updates its internals. """ # Original surface. self._bg = self.draw_bg() try: self._bg = self._bg.convert() except PygameError: pass rect = self._bg.get_rect() # Current surface for blits. self._image = Surface((rect.width, rect.height)) self._image.blit(self._bg, (0, 0)) topleft = self._rect.topleft self._rect = rect self._rect.topleft = topleft self._oldrect = self.rect def notify(self, event): """W.notify (...) -> None Notifies the widget about an event. Note: Widgets, which are not visible (not shown) or are in a specific state (e.g. STATE_INSENSITIVE), usually do not receive any events. But dependant on the widget, this behaviour can be different, thus checking the visibility depends on the widget and implementation. """ if not self.sensitive: return if (event.signal == SIG_FOCUSED) and (event.data != self): self.focus = False elif (event.signal == SIG_ENTER) and (event.data != self): self.entered = False def update(self, **kwargs): """W.update (...) -> None Updates the widget. Updates the widget and causes its parent to update itself on demand. """ if self.locked: return oldrect = Rect(self._oldrect) resize = kwargs.get("resize", False) if not self.dirty: children = kwargs.get("children", {}) blit = self.image.blit items = children.items() # Clean up the dirty areas on the widget. for child, rect in items: blit(self._bg, rect, rect) # Blit the changes. for child, rect in items: blit(child.image, child.rect) self._image.set_alpha(self.opacity) # If a parent's available, reassign the child rects, so that # they point to the absolute position on the widget and build # one matching them all for an update. if self.parent: vals = children.values() rect = oldrect if len(vals) != 0: rect = vals[0] x = self.x y = self.y for r in vals: r.x += x r.y += y rect.unionall(vals[1:]) self.parent.update(children={self: rect}, resize=resize) self._lock = max(self._lock - 1, 0) return # Acquire lock to prevent recursion on drawing. self._lock += 1 # Draw the widget. self.draw() self._image.set_alpha(self.opacity) if self.parent != None: resize = oldrect != self._rect self.parent.update(children={self: oldrect}, resize=resize) # Release previously set lock. self._lock = max(self._lock - 1, 0) self.dirty = False def destroy(self): """W.destroy () -> None Destroys the widget and removes it from its event system. Causes the widget to destroy itself as well as its controls and removes all from the connected event manager and sprite groups using the sprite.kill() method. """ if isinstance(self.parent, BaseWidget): raise AttributeError("widget still has a parent relationship") self.run_signal_handlers(SIG_DESTROYED) self.emit(SIG_DESTROYED, self) # Clear the associated controls. _pop = self._controls.pop while len(self._controls) > 0: control = _pop() control.parent = None control.destroy() del control del self._controls if self._indexable != None: index = self._indexable self._indexable = None index.remove_index(self) if self._manager != None: self._manager.remove_object(self) BaseObject.destroy(self) # Clear BaseObject internals. self.kill() # Clear Sprite #del self.parent del self._indexable del self._bg del self._style del self._image del self._rect del self._oldrect del self # DEPRECATED position = property(lambda self: self.topleft, lambda self, (x, y): self.set_position(x, y), doc="The position of the topleft corner.") eventarea = property(lambda self: self.rect_to_client(), lambda self, var: self.set_event_area(var), doc="The area, which gets the events.") minsize = property(lambda self: (self._minwidth, self._minheight), lambda self, (w, h): self.set_minimum_size(w, h), doc="The guaranteed size of the widget.") maxsize = property(lambda self: (self._maxwidth, self._maxheight), lambda self, (w, h): self.set_maximum_size(w, h), doc="The maximum size to occupy by the widget.") image = property(lambda self: self._image, doc="The visible surface of the widget.") rect = property(lambda self: self._get_rect(), doc="The area occupied by the widget.") index = property(lambda self: self._index, lambda self, var: self.set_index(var), doc="The tab index position of the widget.") style = property(lambda self: self._style, lambda self, var: self.set_style(var), doc="The style of the widget.") state = property(lambda self: self._state, lambda self, var: self.set_state(var), doc="The current state of the widget.") focus = property(lambda self: self._focus, lambda self, var: self.set_focus(var), doc="The focus of the widget.") sensitive = property(lambda self: self._sensitive, lambda self, var: self.set_sensitive(var), doc="The sensitivity of the widget.") dirty = property(lambda self: self._dirty, lambda self, var: self.set_dirty(var), doc="""Indicates, whether the widget need to be redrawn.""") controls = property(lambda self: self._controls, doc="Widgets associated with the widget.") depth = property(lambda self: self._depth, lambda self, var: self.set_depth(var), doc="The z-axis layer depth of the widget.") tooltip = property(lambda self: self._tooltip, lambda self, var: self.set_tooltip(var), doc="The tool tip text to display for the widget.") locked = property(lambda self: self._lock > 0, doc="Indicates, whether the widget is locked.") indexable = property(lambda self: self._indexable, lambda self, var: self.set_indexable(var), doc="The IIndexable, the widget is attached to.") entered = property(lambda self: self._entered, lambda self, var: self.set_entered(var), doc="Indicates, whether the widget is entered.") opacity = property(lambda self: self._opacity, lambda self, var: self.set_opacity(var), doc="The opacity of the widget.")
def generateDefaultImage(size): i = Surface(size) i.fill((0, 128, 128)) i.set_alpha(128) return i
class BaseWidget (BaseObject, sprite.Sprite): """BaseWidget () -> BaseWidget A basic widget class for user interface elements. The BaseWidget is the most basic widget class, from which any other widget class should be inherited. It provides the most basic attributes and methods, every widget needs. The widget is a visible (or non-vissible) element on the display, which allows the user to interact with it (active or passive) in a specific way. It has several methods and attributes to allow developers to control this interaction and supports accessibility through the ocempgui.access module. The widget can be placed on the display by accessing the various attributes of its 'rect' attribute directly. It exposes the following pygame.Rect attributes: top, left, bottom, right, topleft, bottomleft, topright, bottomright, midtop, midleft, midbottom, midright, center, centerx, centery, size, width, height Except the last three ones, 'size', 'width' and 'height' any of those can be assigned similarily to the pygame.Rect: widget.top = 10 widget.center = (10, 10) ... Note: This will only work for toplevel widgets as widgets are placed relative to their parent. Thus the 'top' attribute value of a widget, which is packed into another one, refers to its parents coordinates. So if it is placed four pixels to the left on its parent, its 'top' value will be 4, while the parent might be placed at e.g. 100, 100. You can get the absolute coordinates, the widget is placed on the display, by using the rect_to_client() method. To get the actual dimensions of the widget, it provides the read-only 'width', 'height' and 'size' attributes. if (widget.width > 50) or (widget.height > 50): ... if widget.size == (50, 50): ... To force a specific minimum size to occupy by the widget, the 'minsize' attribute or the respective set_minimum_size() method can be used. The occupied area of the widget will not be smaller than the size, but can grow bigger. widget.minsize = 100, 50 widget.set_minimum_size (10, 33) The counterpart of 'minsize' is the 'maxsize' attribute, which defines the maximum size, the widget can grow to. It will never exceed that size. widget.maxsize = 200, 200 wdget.set_maximum_size (100, 22) The 'image' and 'rect' attributes are used and needed by the pygame.sprite system. 'image' refers to the visible surface of the widget, which will be blitted on the display. 'rect' is a copy of the pygame.Rect object indicating the occupied area of the widget. The rect denotes the relative position of the widget on its parent (as explained above). The 'index' attribute and set_index() method set the navigation index position for the widget. It is highly recommended to set this value in order to provide a better accessibility (e.g. for keyboard navigation). The attribute can be used in ocempgui.access.IIndexable implementations for example. widget.index = 3 widget.set_index (0) Widgets support a 'style' attribute and create_style() method, which enable them to use different look than default one without the need to override their draw() method. The 'style' attribute of a widget usually defaults to a None value and can be set using the create_style() method. This causes the widget internals to setup the specific style for the widget and can be accessed through the 'style' attribute later on. A detailled documentation of the style can be found in the Style class. if not widget.style: widget.create_style () # Setup the style internals first. widget.style['font']['size'] = 18 widget.create_style ()['font']['name'] = Arial Widgets can be in different states, which cause the widgets to have a certain behaviour and/or look. Dependant on the widget, the actions it supports and actions, which have taken place, the state of the widget can change. The actual state of the widget can be looked up via the 'state' attribute and is one of the STATE_TYPES constants. if widget.state == STATE_INSENSITIVE: print 'The widget is currently insensitive and does not react.' Any widget supports layered drawing through the 'depth' attribute. The higher the depth is, the higher the layer on the z-axis will be, on which the widget will be drawn. Widgets might use the flag to set themselves on top or bottom of the display. # The widget will be placed upon all widgets with a depth lower than 4. widget.depth = 4 widget.set_depth (4) Widgets should set the 'dirty' attribute to True, whenever an update of the widget surface is necessary, which includes redrawing the widget (resp. calling draw_bg() and draw()). In user code, 'dirty' usually does not need to be modified manually, but for own widget implementations it is necessary (e.g. if a Label text changed). If the 'parent' attribute of the widget is set, the parent will be notified automatically, that it has to update portions of its surface. # Force redrawing the widget on the next update cycle of the render # group. widget.dirty = True Widgets support a focus mode, which denotes that the widget has the current input and action focus. Setting the focus can be done via the 'focus' attribute or the set_focus() method. widget.focus = True widget.set_focus (True) 'sensitive' is an attribute, which can block the widget's reaction upon events temporarily. It also influences the look of the widget by using other style values (see STATE_INSENSITIVE in the Style class). widget.sensitive = False widget.set_sensitive (False) Each widget supports transparency, which also includes all children which are drawn on it. By setting the 'opacity' attribute you can adjust the degree of transparency of the widget. The allowed values are ranged between 0 for fully transparent and 255 for fully opaque. widget.opacity = 100 widget.set_opacity (25) Widgets allow parent-child relationships via the 'parent' attribute. Parental relationships are useful for container classes, which can contain widgets and need to be informed, when the widget is destroyed, for example. Take a look the Bin and Container classes for details about possible implementations. Do NOT modify the 'parent' attribute value, if you do not know, what might happen. Widgets support locking themselves self temporarily using the lock() method. This is extremely useful to avoid multiple update/draw calls, when certain operations take place on it. To unlock the widget, the unlock() method should be called, which causes it to update itself instantly. widget.lock () # Acquire lock. widget.focus = False # Normally update() would be called here. widget.sensitive = False # Normally update() would be called here. widget.unlock () # Release lock and call update(). When using the lock() method in your own code, you have to ensure, that you unlock() the widget as soon as you do not need the lock anymore. The state of the lock on a widget can be queried using the 'locked' attribute: if widget.locked: print 'The widget is currently locked' Widgets can consist of other widgets. To guarantee that all of them will be added to the same event management system, set the same state, etc., the 'controls' attribute exists. It is a collection to and from which widgets can be attached or detached. Several methods make use of this attribute by iterating over the attached widgets and invoking their methods to put them into the same state, etc. as the main widget. widget.controls.append (sub_widget) for sub in widget.controls: ... Default action (invoked by activate()): None, will raise an NotImplementedError Mnemonic action (invoked by activate_mnemonic()): None Signals: SIG_FOCUSED - Invoked, when the widget received the focus (widget.focus=True). SIG_ENTER - Invoked, when the input device enters the widget. SIG_LEAVE - Invoked, when the input device leaves the wigdet. SIG_DESTROYED - Invoked, when the widget is destroyed. Attributes: minsize - Guaranteed size of the widget. maxsize - Counterpart to size and denotes the maximum size the widget. is allowed to occupy. Defaults to None usually. image - The visible surface of the widget. index - Navigation index of the widget. style - The style to use for drawing the widget. state - The current state of the widget. depth - The z-axis layer depth of the widget. dirty - Indicates, that the widget needs to be updated. focus - Indicates, that the widget has the current input focus. sensitive - Indicates, if the user can interact with the widget. parent - Slot for the creation of parent-child relationships. controls - Collection of attached controls for complex widgets. tooltip - The tool tip text to display for the widget. opacity - The degree of transparency to apply (0-255, 0 for fully transparent, 255 for fully opaque). indexable - The ocempgui.access.IIndexable implementation to use for the 'index' attribute support. entered - Indicates, that an input device is currently over the widget (e.g. the mouse cursor). locked - Indicates, whether the widget is locked. rect - The area occupied by the widget. x, y, ... - The widget allows to reposition itself through the various width, ... attributes offered by its rect attribute. size """ def __init__ (self): BaseObject.__init__ (self) sprite.Sprite.__init__ (self) # Guaranteed sizes for the widget, see also the minsize/maxsize # attributes and set_*_size () methods. self._minwidth = 0 self._minheight = 0 self._maxwidth = 0 self._maxheight = 0 self._indexable = None self._image = None self._rect = Rect (0, 0, 0, 0) self._oldrect = Rect (0, 0, 0, 0) self._opacity = 255 self._style = None self._index = 0 self._state = STATE_NORMAL self._focus = False self._entered = False self._sensitive = True self._controls = [] self._depth = 0 self._dirty = True self._lock = 0 self._bg = None self.parent = None # Accessibility. self._tooltip = None # Signals, the widget listens to by default self._signals[SIG_FOCUSED] = [] self._signals[SIG_ENTER] = [] self._signals[SIG_LEAVE] = [] self._signals[SIG_DESTROYED] = [] def _get_rect_attr (self, attr): """W._get_rect_attr (...) -> var Gets the wanted attribute value from the underlying rect. """ return getattr (self._rect, attr) def _set_rect_attr (self, attr, value): """W._set_rect_attr (...) -> None Sets a specific attribute value on the underlying rect. Raises an AttributeError if the attr argument is the width, height or size. """ if attr in ("width", "height", "size"): # The width and height are protected! raise AttributeError ("%s attribute is read-only" % attr) # TODO: This is just a hack around wrong positioning in # containers. self._oldrect = self.rect setattr (self._rect, attr, value) if (self.parent != None): if not isinstance (self.parent, BaseWidget): self.update () else: self._oldrect = self.rect def initclass (cls): """B.initclass () -> None Class method to expose the attributes of the own self.rect attribute. The method usually is called in the __init__.py script of the module. """ attributes = dir (Rect) for attr in attributes: if not attr.startswith ("__") and \ not callable (getattr (Rect, attr)): def get_attr (self, attr=attr): return cls._get_rect_attr (self, attr) def set_attr (self, value, attr=attr): return cls._set_rect_attr (self, attr, value) prop = property (get_attr, set_attr) setattr (cls, attr, prop) initclass = classmethod (initclass) def _get_rect (self): """W._get_rect () -> pygame.Rect Gets a copy of the widget's rect. """ return Rect (self._rect) # DEPRECATED def set_position (self, x, y): """W.set_position (...) -> None DEPRECATED - use the 'topleft' attribute instead """ print "*** Warning: set_position() is deprecated, use the topleft" print " attribute instead." self._set_rect_attr ("topleft", (x, y)) def rect_to_client (self, rect=None): """W.rect_to_client (...) -> pygame.Rect Returns the absolute coordinates a rect is located at. In contrast to the widget.rect attribute, which denotes the relative position and size of the widget on its parent, this method returns the absolute position and occupied size on the screen for a passed rect. Usually this method will be called by children of the callee and the callee itself to detrmine their absolute positions on the screen. """ if self.parent and isinstance (self.parent, BaseWidget): re = self.rect if rect != None: re.x += rect.x re.y += rect.y re.width = rect.width re.height = rect.height return self.parent.rect_to_client (re) elif rect != None: rect.x = self.x + rect.x rect.y = self.y + rect.y return rect return self.rect def set_minimum_size (self, width, height): """W.set_minimum_size (...) -> None Sets the minimum size to occupy for the widget. Minimum size means that the widget can exceed the size by any time, but its width and height will never be smaller than these values. Raises a TypeError, if the passed arguments are not integers. Raises a ValueError, if the passed arguments are not positive. """ if (type (width) != int) or (type (height) != int): raise TypeError ("width and height must be positive integers") if (width < 0) or (height < 0): raise ValueError ("width and height must be positive integers") self._minwidth = width self._minheight = height self.dirty = True # DEPRECATED def set_size (self, width, height): """W.set_size (...) -> None DEPREACATED - use set_minimum_size () instead. """ print "*** Warning: set_size() is deprecated, use set_minimum_size()." self.set_minimum_size (width, height) def set_maximum_size (self, width, height): """W.set_maximum_size (...) -> None Sets the maximum size the widget is allowed to occupy. This is the counterpart to the set_minimum_size() method. """ if (type (width) != int) or (type (height) != int): raise TypeError ("width and height must be positive integers") if (width < 0) or (height < 0): raise ValueError ("width and height must be positive integers") self._maxwidth = width self._maxheight = height self.dirty = True def check_sizes (self, width, height): """W.check_sizes (...) -> int, int Checks the passed width and height for allowed values. Checks, whether the passed width an height match the upper and lower size ranges of the widget and returns corrected values, if they exceed those. Else the same values are returned. """ minwidth, minheight = self.minsize maxwidth, maxheight = self.maxsize if (minwidth != 0) and (width < minwidth): width = minwidth elif (maxwidth != 0) and (width > maxwidth): width = maxwidth if (minheight != 0) and (height < minheight): height = minheight elif (maxheight != 0) and (height > maxheight): height = maxheight return width, height def set_index (self, index): """W.set_index (...) -> None Sets the tab index of the widget. Sets the index position of the widget to the given value. It can be used by ocempgui.access.IIndexable implementations to allow easy navigation access and activation for the widgets. Raises a TypeError, if the passed argument is not a positive integer. """ if (type (index) != int) or (index < 0): raise TypeError ("index must be a positive integer") self._index = index def set_depth (self, depth): """W.set_depth (...) -> None Sets the z-axis layer depth for the widget. Sets the z-axis layer depth for the widget. This will need a renderer, which makes use of layers such as the Renderer class. By default, the higher the depth value, the higher the drawing layer of the widget is. That means, that a widget with a depth of 1 is placed upon widgets with a depth of 0. Raises a TypeError, if the passed argument is not an integer. """ if type (depth) != int: raise TypeError ("depth must be an integer") self.lock () old = self._depth self._depth = depth if isinstance (self.parent, BaseWidget): try: self.parent.update_layer (old, self) except: pass for c in self._controls: c.set_depth (depth) self.unlock () def set_dirty (self, dirty, update=True): """W.set_dirty (...) -> None Marks the widget as dirty. Marks the widget as dirty, so that it will be updated and redrawn. """ self._dirty = dirty if dirty and update: self.update () def set_event_manager (self, manager): """W.set_event_manager (...) -> None Sets the event manager of the widget and its controls. Adds the widget to an event manager and causes its controls to be added to the same, too. """ BaseObject.set_event_manager (self, manager) for control in self.controls: control.set_event_manager (manager) def set_indexable (self, indexable): """W.set_indexable (...) -> None Sets the IIndexable for the widget. The widget will invoke the add_index() method for itself on the IIndexable. """ if indexable and not isinstance (indexable, IIndexable): raise TypeError ("indexable must inherit from IIndexable") if self._indexable == indexable: return if self._indexable != None: self._indexable.remove_index (self) self._indexable = indexable if indexable != None: indexable.add_index (self) for ctrl in self.controls: ctrl.set_indexable (indexable) # DEPRECATED def get_style (self): """W.get_style () -> WidgetStyle DEPRECATED - use the create_style() method instead """ print "*** Warning: get_style() is deprecated, use the create_style()" print " method instead." return self.create_style () def create_style (self): """W.create_style () -> WidgetStyle Creates the instance-specific style for the widget. Gets the style associated with the widget. If the widget had no style before, a new one will be created for it, based on the class name of the widget. The style will be copied internally and associated with the widget, so that modifications on it will be instance specific. More information about how a style looks like and how to modify them can be found in the Style class documentation. """ if not self._style: # Create a new style from the base style class. self._style = base.GlobalStyle.copy_style (self.__class__) self._style.set_value_changed (lambda: self.set_dirty (True)) return self._style def set_style (self, style): """W.set_style (...) -> None Sets the style of the widget. Sets the style of the widget to the passed style dictionary. This method currently does not perform any checks, whether the passed dictionary matches the criteria of the Style class. Raises a TypeError, if the passed argument is not a WidgetStyle object. """ if not isinstance (style, WidgetStyle): raise TypeError ("style must be a WidgetStyle") self._style = style if not self._style.get_value_changed (): self._style.set_value_changed (lambda: self.set_dirty (True)) self.dirty = True def set_focus (self, focus=True): """W.set_focus (...) -> bool Sets the input and action focus of the widget. Sets the input and action focus of the widget and returns True upon success or False, if the focus could not be set. """ if not self.sensitive: return False if focus: if not self._focus: self._focus = True self.emit (SIG_FOCUSED, self) self.dirty = True self.run_signal_handlers (SIG_FOCUSED) else: if self._focus: self._focus = False self.dirty = True return True def set_entered (self, entered): """W.set_entered (...) -> None Sets the widget into an entered mode. """ if entered: if not self._entered: self._entered = True self.state = STATE_ENTERED self.emit (SIG_ENTER, self) self.run_signal_handlers (SIG_ENTER) elif self._entered: self._entered = False self.state = STATE_NORMAL self.run_signal_handlers (SIG_LEAVE) def set_sensitive (self, sensitive=True): """W.set_sensitive (...) -> None Sets the sensitivity of the widget. In a sensitive state (the default), widgets can react upon user interaction while they will not do so in an insensitive state. To support the visibility of this, the widget style should support the STATE_INSENSITIVE flag, while inheriting widgets should check for the sensitivity to enable or disable the event mechanisms. """ if sensitive != self._sensitive: if sensitive: self._sensitive = True self.state = STATE_NORMAL else: self._sensitive = False self.state = STATE_INSENSITIVE for control in self.controls: control.set_sensitive (sensitive) def set_state (self, state): """W.set_state (...) -> None Sets the state of the widget. Sets the state of the widget. The state of the widget is mainly used for the visible or non-visible appearance of the widget, so that the user can determine the state of the widget easier. Usually this method should not be invoked by user code. Raises a ValueError, if the passed argument is not a value of the STATE_TYPES tuple. """ if state not in STATE_TYPES: raise ValueError ("state must be a value from STATE_TYPES") if self._state != state: self._state = state self.dirty = True def set_opacity (self, opacity): """W.set_opacity (...) -> None Sets the opacity of the widget. """ if type (opacity) != int: raise TypeError ("opacity must be an integer") dirty = self._opacity != opacity self._opacity = opacity self.update () # DEPRECATED def set_event_area (self, area): """W.set_event_area (...) -> None DEPRECATED - this is no longer used. """ print "*** Warning: set_event_area() is no longer used!" def lock (self): """W.lock () -> None Acquires a lock on the Widget to suspend its updating methods. """ self._lock += 1 def unlock (self): """W.unlock () -> None Releases a previously set lock on the Widget and updates it instantly. """ if self._lock > 0: self._lock -= 1 if self._lock == 0: self.update () def set_tooltip (self, tooltip): """W.set_tooltip (...) -> None Sets the tooltip information for the widget. Raises a TypeError, if the passed argument is not a string or unicode. """ if type (tooltip) not in (str, unicode): raise TypeError ("text must be a string or unicode") self._tooltip = tooltip def activate (self): """W.activate () -> None Activates the widget. Activates the widget, which means, that the default action of the widget will be invoked. This method should be implemented by inherited widgets. """ raise NotImplementedError def activate_mnemonic (self, mnemonic): """W.activate_mnemonic (...) -> bool Activates the widget through the set mnemonic. Activates the widget through the set mnemonic for it and returns True upon successful activation or False, if the widget was not activated. The BaseWidget.activate_mnemonic () method always returns False by default, so that this method should be implemented by inherited widgets, if they need explicit mnemonic support. """ return False def draw_bg (self): """W.draw_bg () -> Surface Draws the widget background surface and returns it. Creates the visible background surface of the widget and returns it to the caller. This method has to be implemented by inherited widgets. """ raise NotImplementedError def draw (self): """W.draw () -> None Draws the widget surface. Creates the visible surface of the widget and updates its internals. """ # Original surface. self._bg = self.draw_bg () try: self._bg = self._bg.convert () except PygameError: pass rect = self._bg.get_rect () # Current surface for blits. self._image = Surface ((rect.width, rect.height)) self._image.blit (self._bg, (0, 0)) topleft = self._rect.topleft self._rect = rect self._rect.topleft = topleft self._oldrect = self.rect def notify (self, event): """W.notify (...) -> None Notifies the widget about an event. Note: Widgets, which are not visible (not shown) or are in a specific state (e.g. STATE_INSENSITIVE), usually do not receive any events. But dependant on the widget, this behaviour can be different, thus checking the visibility depends on the widget and implementation. """ if not self.sensitive: return if (event.signal == SIG_FOCUSED) and (event.data != self): self.focus = False elif (event.signal == SIG_ENTER) and (event.data != self): self.entered = False def update (self, **kwargs): """W.update (...) -> None Updates the widget. Updates the widget and causes its parent to update itself on demand. """ if self.locked: return oldrect = Rect (self._oldrect) resize = kwargs.get ("resize", False) if not self.dirty: children = kwargs.get ("children", {}) blit = self.image.blit items = children.items () # Clean up the dirty areas on the widget. for child, rect in items: blit (self._bg, rect, rect) # Blit the changes. for child, rect in items: blit (child.image, child.rect) self._image.set_alpha (self.opacity) # If a parent's available, reassign the child rects, so that # they point to the absolute position on the widget and build # one matching them all for an update. if self.parent: vals = children.values () rect = oldrect if len (vals) != 0: rect = vals[0] x = self.x y = self.y for r in vals: r.x += x r.y += y rect.unionall (vals[1:]) self.parent.update (children={ self : rect }, resize=resize) self._lock = max (self._lock - 1, 0) return # Acquire lock to prevent recursion on drawing. self._lock += 1 # Draw the widget. self.draw () self._image.set_alpha (self.opacity) if self.parent != None: resize = oldrect != self._rect self.parent.update (children={ self : oldrect }, resize=resize) # Release previously set lock. self._lock = max (self._lock - 1, 0) self.dirty = False def destroy (self): """W.destroy () -> None Destroys the widget and removes it from its event system. Causes the widget to destroy itself as well as its controls and removes all from the connected event manager and sprite groups using the sprite.kill() method. """ if isinstance (self.parent, BaseWidget): raise AttributeError ("widget still has a parent relationship") self.run_signal_handlers (SIG_DESTROYED) self.emit (SIG_DESTROYED, self) # Clear the associated controls. _pop = self._controls.pop while len (self._controls) > 0: control = _pop () control.parent = None control.destroy () del control del self._controls if self._indexable != None: index = self._indexable self._indexable = None index.remove_index (self) if self._manager != None: self._manager.remove_object (self) BaseObject.destroy (self) # Clear BaseObject internals. self.kill () # Clear Sprite #del self.parent del self._indexable del self._bg del self._style del self._image del self._rect del self._oldrect del self # DEPRECATED position = property (lambda self: self.topleft, lambda self, (x, y): self.set_position (x, y), doc = "The position of the topleft corner.") eventarea = property (lambda self: self.rect_to_client (), lambda self, var: self.set_event_area (var), doc = "The area, which gets the events.") minsize = property (lambda self: (self._minwidth, self._minheight), lambda self, (w, h): self.set_minimum_size (w, h), doc = "The guaranteed size of the widget.") maxsize = property (lambda self: (self._maxwidth, self._maxheight), lambda self, (w, h): self.set_maximum_size (w, h), doc = "The maximum size to occupy by the widget.") image = property (lambda self: self._image, doc = "The visible surface of the widget.") rect = property (lambda self: self._get_rect (), doc = "The area occupied by the widget.") index = property (lambda self: self._index, lambda self, var: self.set_index (var), doc = "The tab index position of the widget.") style = property (lambda self: self._style, lambda self, var: self.set_style (var), doc = "The style of the widget.") state = property (lambda self: self._state, lambda self, var: self.set_state (var), doc = "The current state of the widget.") focus = property (lambda self: self._focus, lambda self, var: self.set_focus (var), doc = "The focus of the widget.") sensitive = property (lambda self: self._sensitive, lambda self, var: self.set_sensitive (var), doc = "The sensitivity of the widget.") dirty = property (lambda self: self._dirty, lambda self, var: self.set_dirty (var), doc = """Indicates, whether the widget need to be redrawn.""") controls = property (lambda self: self._controls, doc = "Widgets associated with the widget.") depth = property (lambda self: self._depth, lambda self, var: self.set_depth (var), doc = "The z-axis layer depth of the widget.") tooltip = property (lambda self: self._tooltip, lambda self, var: self.set_tooltip (var), doc = "The tool tip text to display for the widget.") locked = property (lambda self: self._lock > 0, doc = "Indicates, whether the widget is locked.") indexable = property (lambda self: self._indexable, lambda self, var: self.set_indexable (var), doc = "The IIndexable, the widget is attached to.") entered = property (lambda self: self._entered, lambda self, var: self.set_entered (var), doc = "Indicates, whether the widget is entered.") opacity = property (lambda self: self._opacity, lambda self, var: self.set_opacity (var), doc = "The opacity of the widget.")
class Nutrient(AlphaGradient): """Nutrient is collected by Ants. Ants search for Nutrients. Each Ant can collect one Nutrient unit. Nutrient provide an interface for collection Nutrient units. Args: amount (:obj:`int`): The amount of nutrient units. rect (:obj:`Rect`): The position of the Nutrient on the display. surface (:obj:`Surface`): The surface to draw the Nutrient on. Attributes: rect (:obj:`Rect`): The position of the Nutrient on the display. surface (:obj:`Surface`): The surface to draw the Nutrient on. """ def __init__(self, amount, background, width, height): self.background = background width, height = width/2, height/2 self.surface = Surface((width, height)) self.rect = self.surface.get_rect( width=width, height=height, centerx=width, centery=height ) self._amount = int(abs(amount)) # Mixin attributes self._xmin = 0 self._xmax = amount self._ymin = 0 self._ymax = 255 @property def nutrient_unit(self): """:obj:`int`: Get a Nutrient unit.""" if not self.empty(): self._amount -= 1 return 1 raise NutrientEmptyError() def draw(self): """Draw Nutrient on surface.""" self.surface.fill(NUTRIENT_COLOR) self.surface.set_alpha(int(self.get_alpha(self._amount))) self.background.blit(self.surface, self.rect) def empty(self): """Is Nutrient empty? Returns: :obj:`bool`: Is Nutirent amount zero. """ return self._amount == 0 def __str__(self): return '{} {}u'.format(__class__.__name__, self._amount) def __repr__(self): return '<{}(amount={}) at {}>'.format(__class__.__name__, self._amount, hex(id(self)))
class Renderer(object): """ General object for a renderer. Store and manage cameras and has a method to render rects. """ def __init__(self, level, screen_size): self.tiledmap = level.tiledmap self.screen_size = screen_size self.tw = self.tiledmap.tilewidth self.th = self.tiledmap.tileheight self.map_size = level.map_size self.level = level # list of cameras self.current_camera = None self.current_camera_index = 0 self.camera_list = [] # fade in out things self.fade_opacity = 0 self.fading = 0 self.fade_start_time = None self.fade_color = BLACK self.generate_fade_image(self.display_size) self.fading_speed = 0.003 self.FADE_IN = 1 self.FADE_OUT = -1 def add_camera(self, camera): """ Adds a camera to this renderer. You can change the used camera while in game using next_camera(). """ if isinstance(camera, Camera): # the size of the screen diplayed to the player in tiles camera.screen_size_in_tiles = self.screen_size_in_tiles # the tile size in pixels tw = self.level.tiledmap.tilewidth th = self.level.tiledmap.tileheight mw = self.level.tiledmap.width mh = self.level.tiledmap.height camera.tile_size = (tw, th) # size of the whole map in tiles camera.map_size_in_tiles = (mw, mh) # size of the map in 'screens' mt = camera.map_size_in_tiles st = self.screen_size_in_tiles camera.map_size_in_screens = (mt[0] / st[0], mt[1] / st[1]) self.camera_list.append(camera) if not self.current_camera: self.current_camera = camera self.current_camera_index = 0 x = camera.sprite.rect.x y = camera.sprite.rect.y ts = (tw, th) # TODO move all this inside each camera with _after_init or # any similar name if isinstance(camera, ScrollCamera): st = self.screen_size_in_tiles # The player should be in a rect that is considered the center # of the camera x = camera.sprite.rect.x y = camera.sprite.rect.y camera.camera_rect = Rect(x, y, st[0] * ts[0], st[1] * ts[1]) camera.outview_rect = camera.camera_rect.inflate(camera.margins[0] * -1, camera.margins[1] * -1) # used when recentering the player in the camera camera.inview_rect = camera.camera_rect.inflate( camera.s_rect_size[0] - st[0] * ts[0], camera.s_rect_size[1] - st[1] * ts[1]) if isinstance(camera, FreeCamera): camera.camera_rect = Rect(x, y, st[0] * ts[0], st[1] * ts[1]) else: raise RendererError("Error! \'{0}\' is not a camera object!".format(camera)) def next_camera(self): """ Switch to the next camera in the list. """ try: self.current_camera = self.camera_list[self.current_camera_index + 1] self.current_camera_index += 1 except LookupError: self.current_camera = self.camera_list[0] self.current_camera_index = 0 def draw_rects(self, surface, rects, offset, size=1, color=WHITE): """ Draw all the pygame rects in a iterable. Used to draw the collision rects for debugging. """ for rect in rects: pygame.draw.rect(surface, color, rect.move(offset[0] * -1 , offset[1] * -1), size) def generate_fade_image(self, display_size): """ Generate the image used for fade in/out. """ self.display_size = display_size self.fade_image = Surface(self.display_size) self.fade_image.fill(self.fade_color) self.fade_image.set_alpha(0) def update_fading(self): """ Does the math to update the fade in/out alpha value. """ # functions to calculate the opacity of the fade image def fun1(time): time = time / 1000. return 1 - exp(time*( - self.fading_speed)) def fun2(time): time = time / 1000. return exp(time*(- self.fading_speed)) # in the first update there is no fade_start_time if not self.fade_start_time: self.fade_start_time = time.get_ticks() t = time.get_ticks() - self.fade_start_time if self.fading == self.FADE_IN: # make it zero when it's almost zero if self.fade_opacity > 0.003: self.fade_opacity = fun2(t) else: self.fading = 0 self.fade_opacity = 0 elif self.fading == self.FADE_OUT: # make it one when it's almost one if self.fade_opacity < 0.997: self.fade_opacity = fun1(t) else: self.fading = 0 self.fade_opacity = 1 # calculate the alpha value and set it in the fade_image a = ceil(255 * self.fade_opacity) if a > 255: a = 255 self.fade_image.set_alpha(a) def fade(self, value, fade_speed=3): """ Execute fade in/out. Use the constants FADE_IN FADE_OUT to specificate the type of fade, the speed with fade_speed (a positive non-zero float value). """ self.fading_speed = fade_speed self.fading = value self.fade_start_time = time.get_ticks() def toggle_fade(self): if self.fade_opacity == 1: self.fade(self.FADE_IN, 4) elif self.fade_opacity == 0: self.fade(self.FADE_OUT, 4) def dprint(self,text): """ If self.debugging is True it will print all the debugging lines. """ if self.debugging: print text
class Main(Sprite): """ Main glyph class image-- the image that text is set to rect-- rect for blitting image to viewing surface spacing-- line spacing links-- dict of (link, [rects]) pairs """ ################################################################## # class methods def __init__(self, rect, **kwargs): """ Initialize a glyph object rect-- rect object for positioning glyph image on viewing surface **kwargs-- dictionary of (env_id, value) pairs required kwargs are: bkg-- background color color-- font color font-- font """ self.image = Surface(rect.size)#dodal ALPHA self.image.fill(kwargs['bkg']) self.image.set_alpha(255) self._bkg = kwargs['bkg'] self.rect = rect self.spacing = kwargs['spacing'] self.links = defaultdict(list) self._dest = Rect(0, 0, 0, 0) # rect for blitting a text line to image surface # list of (env_id, value) pairs for environments; # _envs is used as a stack data structure self._envs = [('bkg', kwargs['bkg']), ('color', kwargs['color']), ('font', kwargs['font']), ('link', None)] # link id string #print self._envs self._buff = deque() # rendered text buffer ################################################################## # helper methods def __read_env(self, _txt_): # helper function to interpret # accepts the _txt_ iterable at an environment starting point # return (environment type, environment) tuple (e.g (font, Font object)) # re to get env arguments (arguments may be paths and contain \ or /) r = re.compile('(\w+)(?:\s+([\w\s\.,=\\\\/]+))?\s*;') charbuffer = '' # _txt_ is a generator, so iterating consumes the contents for the # references to _txt_ in the _interpret function for i, char in _txt_: charbuffer += char s = r.search(charbuffer) # search for environment name arguments if s: # if search successful env = s.groups() # get environment if env[0] in Macros: return Macros[env[0]] # new environment types must be added here elif env[0] == 'bkg': # return new backgroun color return ('bkg', tuple([int(e) for e in env[1].split(',')])) elif env[0] == 'color': # return new font color return ('color', tuple([int(e) for e in env[1].split(',')])) elif env[0] == 'font': # return new font path, size = env[1].split(',') # the font location and size return ('font', Font(os.path.realpath(path), int(size))) elif env[0] == 'link': # return new link return ('link', env[1].strip()) elif env[0] == 'id': #moje return('id', env[1].strip()) else: raise ValueError(env[0] + ' is an unrecognized environment') # the space func could take a size and return a surface or rect... # really only need to shift tokens. surfs do that without requiring tokens # to have rects def __read_func(self, _txt_): # helper function to interpret # accepts the _txt_ iterable at function starting point # returns (function type, function results) tuple # (eg. (space, Surface obejct)) r = re.compile('(\w+){(.*?)}') i, char = _txt_.next() if char in SPECIALS: return char if char in WHITESPACE: return WHITESPACE[char] charbuff = char for i, char in _txt_: charbuff += char s = r.search(charbuff) if s: func, args = s.groups() if func in Macros: return Macros[func] if func == 'space': return Surface((int(args), 1)) if charbuff == 'img': return image.load(args).convert() ################################################################## # private methods def _interpret(self, txt): # iterprets glyph markup language # accepts string literal # returns a list of (env, charbuff) pairs, # where env is a dictionary of environment types keyed to values and # charbuff a list of text strings and the surfaces created from functions envs = self._envs read_env, read_func = self.__read_env, self.__read_func iswhitespace = _iswhitespace # initialize charbuff, renderbuff, interpreted text, and previous character charbuff, interpreted_txt, prevchar = [], [], '' txt = txt.strip() _txt_ = enumerate(txt) for i, char in _txt_: if iswhitespace(char) and iswhitespace(prevchar): if char == '\n': charbuff[-1] = char continue if char == '/': # a function: char = read_func(_txt_) charbuff.append(char) prevchar = char elif char == '{': # a new environment has started # using dict(envs) allows new environments to overwrite default # environments, which are in the beginning of the list interpreted_txt.append((dict(envs), charbuff)) charbuff = [] envs.append(read_env(_txt_)) elif char == '}': # an environment has ended interpreted_txt.append((dict(envs), charbuff)) charbuff = [] envs.pop() else: # a normal, string, character charbuff.append(char) prevchar = char if charbuff: interpreted_txt.append((dict(envs), charbuff)) return interpreted_txt def _tokenize(self, interpreted_txt): # tokenizes text # accepts (envs, charbuff) # returns a list of tokens iswhitespace, token_builder = _iswhitespace, _token_builder charbuff, _interpreted_txt, tokenized_txt = [], [], [] for (envs, chars) in interpreted_txt: for char in chars: if iswhitespace(char): if charbuff: _interpreted_txt.append((envs, charbuff)) charbuff = [] if _interpreted_txt: yield token_builder(_interpreted_txt) _interpreted_txt = [] yield token_builder([(envs, [char])]) else: charbuff.append(char) if charbuff: _interpreted_txt.append((envs, charbuff)) charbuff = [] if _interpreted_txt: yield token_builder(_interpreted_txt) def _wrap(self, tokenized_txt, justify): # wrap interpreted text to page width # accepts a list of Token objects tuples # returns a list of Line objects, each wrapped to self.rect Line = _Line rect_w = self.rect.w # linesize tracks the current size of the rendered line because moving # between environments will mean that there will be multiple surfaces # that need to be glued together line = [] # initialize line, and linesize tokens = [] for token in tokenized_txt: token_w = token.get_width() if token_w > rect_w: raise ValueError("The token '"+token.str+"' is " +str((token_w - rect_w)) +" pixels too wide to fit in the rect passed.") # the token fits, process it and check if the line still fits inside # rect area, if not, append line without token, reinitialize line # with token txt = str(token) # print txt line.append(token) tokens.append(txt) if unicode(token) == '\n': # don't justify a line that would not wrap if justify == 'justified': _justify = 'left' else: _justify = justify yield Line(line, rect_w, _justify) line = [] # reset line elif sum(token.get_width() for token in line) > rect_w: token = line.pop() # remove single trailing whitespace if line[-1].iswhitespace: line = line[:-1] yield Line(line, rect_w, justify) line = [] # reinitialize _line, line, linesize, and number of objects # do not append whitespace as the first token of the new line if not token.iswhitespace: line.append(token) self.tokens = tokens if line: # don't justify a line that would not wrap if justify == 'justified': _justify = 'left' else: _justify = justify yield Line(line, rect_w, _justify) ################################################################## # public methods def input(self, txt, justify = None): """ interprets, renders, wraps, and justifies input text txt -- raw text written with glyph markup justify -- a justify command; default is left justified left: left justified right: right justified justified: justified (both sides even) center: center justified returns nothing """ buff, interpret, tokenize, wrap = self._buff, self._interpret, self._tokenize, self._wrap interpreted_txt = interpret(txt) tokens = tokenize(interpreted_txt) lines = wrap(tokens, justify) buff.extend(lines) def update(self): """ updates the surface with the text input to the buffer by the input method, then deletes buffer accepts nothing returns nothing """ buff, dest, image, links, rect, spacing = self._buff, self._dest, self.image, self.links, self.rect, self.spacing while buff: line = buff.popleft() line_h = line.get_height() if dest.y + line_h > rect.h: buff.append(line) break else: image.blit(line, dest) for link in line.links: for _rect in line.links[link]: # move rect to token's position in line and append to links links[link].append(_rect.move(dest.topleft)) dest.y += line_h + spacing def clear(self, surface_dest, background): """ draws background over surface_dest using self.rect and resets self._dest surface_dest-- the destination surface that self.image has been drawn to background-- the background to draw over the areas that self.image was drawn to on surface_dest returns nothing """ rect = self.rect self.image = Surface(rect.size) self.image.fill(self._bkg) self._dest = Rect(0, 0, 0, 0) self.links = defaultdict(list) surface_dest.blit(background, rect, rect) def get_collisions(self, mpos): """ get collisions between a point and the linked text on the glyph surface mpos-- the point to check against the linked text if a link collides with mpos returns the link collinding with mpos if no link collides with mpos returns None """ links, rect = self.links, self.rect for link in links: #print link for _rect in links[link]: if _rect.move(rect.topleft).collidepoint(mpos): return link
def render_tooltips(): """Render the tooltip for the object under the mouse.""" obj = object_under_mouse() if obj: if obj.__class__.__name__ == "ClientMonster": hp_bar = True else: hp_bar = False bar_len = 100 if CS.u.fov_map.in_fov(obj.x, obj.y): text = "You see: " + obj.name else: text = "You remember seeing: " + obj.name text_img = CS.font.render(text, True, CLR["white"]) text_rect = text_img.get_rect() if hp_bar and text_rect.w < bar_len: surf_w = bar_len else: surf_w = text_rect.w surf_w += cfg.PADDING * 2 + cfg.BORDER_W * 2 surf_h = text_rect.h + cfg.PADDING * 2 + cfg.BORDER_W * 2 if hp_bar: surf_h += CS.font_h # pylint: disable=E1121 tooltip_surf = Surface((surf_w, surf_h)).convert() # pylint: enable=E1121 tooltip_surf.fill(CLR["black"]) text_rect.centerx = surf_w / 2 text_rect.top = cfg.BORDER_W + cfg.PADDING tooltip_surf.blit(text_img, text_rect) if hp_bar: render_bar( tooltip_surf, cfg.BORDER_W + cfg.PADDING, cfg.BORDER_W + cfg.PADDING + text_rect.h, bar_len, obj.hp, obj.max_hp, CS.hp_bar_color, CS.hp_bar_bg_color, ) hp_img = CS.font.render(str(obj.hp) + " / " + str(obj.max_hp), True, CLR["white"]) hp_rect = hp_img.get_rect() hp_rect.centerx = cfg.BORDER_W + cfg.PADDING + bar_len / 2 hp_rect.top = cfg.BORDER_W + cfg.PADDING + text_rect.h tooltip_surf.blit(hp_img, hp_rect) rect = tooltip_surf.get_rect() x, rect.bottom = pygame.mouse.get_pos() # The lower left corner of the tooltip is next to the mouse, unless # doing so would print the tooltip off the right side of the screen. if x + rect.w > CS.screen_rect.w: rect.right = x else: rect.left = x add_surface_border(tooltip_surf) tooltip_surf.set_colorkey(CLR["floor_blue"]) tooltip_surf.set_alpha(cfg.TOOLTIP_ALPHA) CS.screen.blit(tooltip_surf, rect)
class Glyph(object): """ Main glyph class image-- the image that text is set to rect-- rect for blitting image to viewing surface spacing-- line spacing links-- dict of (link, [rects]) pairs """ ################################################################## # class methods def __init__(self, rect, bkg=BLACK, color=WHITE, font=FONT, spacing=0, ncols=1, col_space=20): # FUTURE COLS ADD """ Initialize a glyph object rect-- rect object for positioning glyph image on viewing surface **kwargs-- dictionary of (env_id, value) pairs required kwargs are: bkg-- background color color-- font color font-- font """ # initialize self.image = Surface(rect.size) self.image.fill(bkg) self.image.set_alpha(255) self._bkg = bkg self.rect = rect self.spacing = spacing self.links = defaultdict(list) # FUTURE ### self.editors = {} ############ # FUTURE COLS ### self.col_w = ((rect.w + col_space) / ncols) - col_space self.col_space = col_space self.col_n = 1 self.ncols = ncols ################# self._dest = Rect(0, 0, 0, 0) # rect to blit a txt line to image surface # list of (env_id, value) pairs for environments; # _envs is used as a stack data structure self._envs = [("bkg", bkg), ("color", color), ("font", font), ("link", None)] # link id string self.buff = deque() # rendered text buffer ################################################################## # helper methods def __read_env(self, _txt_): # interprets and returns an environment. environments set text # characteristics such as color or links. # accepts the _txt_ iterable at an environment starting point # return (environment type, environment) tuple (e.g (font, Font object)) # re to get env arguments (arguments may be paths and contain \ or /) r = re.compile("(\w+)(\s+((\"|').*?(\"|')|.*?))?;") charbuffer = "" # _txt_ is a generator, so iterating consumes the contents for the # references to _txt_ in the _interpret function for i, char in _txt_: charbuffer += char s = r.search(charbuffer) # search for environment name arguments if s: # if search successful groups = s.groups() # get environment env, args = groups[0], groups[2] if env in Macros: return Macros[env] # new environment types must be added here elif env == "bkg": # return new backgroun color return ("bkg", tuple([int(e) for e in args.split(",")])) elif env == "color": # return new font color return ("color", tuple([int(e) for e in args.split(",")])) elif env == "font": # return new font path, size = args.split(",") # the font location and size return ("font", Font(os.path.realpath(path), int(size))) elif env == "link": # return new link return ("link", args.strip()) # FUTURE ### elif env == "editor": # editor is considered an environment because it must be # linked. any text in an editor environment is input to # that editor, and any nested environments are ignored. name, w = args.split(",") # extract editor kw args kw = dict(self._envs) del kw["link"] kw["spacing"] = self.spacing h = kw["font"].get_linesize() editor = Editor(Rect(0, 0, int(w), h), **kw) self.editors[name] = editor # treat as link env, get_collision will sort return ("link", name) ############ else: raise ValueError(env + " is an unrecognized environment") # the space func could take a size and return a surface or rect... # really only need to shift tokens. surfs do that without requiring tokens # to have rects def __read_func(self, _txt_): # interprets and returns a function. functions are special surfaces or # objects. # accepts the _txt_ iterable at function starting point # returns (function type, function results) tuple # (eg. (space, Surface obejct)) r = re.compile("(\w+){(.*?)}") i, char = _txt_.next() if char in SPECIALS: return "special", char if char in WHITESPACE: return "whitespace", WHITESPACE[char] charbuff = char for i, char in _txt_: charbuff += char s = r.search(charbuff) if s: func, args = s.groups() if func in Macros: return func, Macros[func] if func == "space": return func, Surface((int(args), 1)) # "if charbuff = 'img'"? if func == "img": return func, pygame.image.load(args).convert() ################################################################## # private methods def _interpret(self, txt): # iterprets glyph markup language # accepts string literal # returns a list of (env, charbuff) pairs, # where env is a dictionary of environment types keyed to values and # charbuff a list of text strings and the surfaces created from # functions editors, envs = self.editors, self._envs read_env, read_func = self.__read_env, self.__read_func iswhitespace = _iswhitespace txt = txt.strip() # FUTURE ### # preamble, txt = read_preamble(txt) # if preamble: envs = preamble # ########## # initialize charbuff, renderbuff, interpreted text, and previous char charbuff, interpreted_txt, prevchar = [], [], "" _txt_ = enumerate(txt) for i, char in _txt_: if iswhitespace(char) and iswhitespace(prevchar): if char == "\n": charbuff[-1] = char continue if char == "/": # a function: func, char = read_func(_txt_) charbuff.append(char) prevchar = char elif char == "{": # a new environment has started # using dict(envs) allows new environments to overwrite default # environments, which are in the beginning of the list interpreted_txt.append((dict(envs), charbuff)) charbuff = [] envs.append(read_env(_txt_)) elif char == "}": # an environment has ended # FUTURE ### link = dict(envs)["link"] if link in editors: editor = editors[link] # this is a hack to feed char to editor using input method for char in charbuff: mod = 0 if char.isupper(): mod = 3 event = Event(KEYDOWN, key=None, mod=mod, unicode=char.encode("utf8")) editor.input(event) interpreted_txt.append((dict(envs), [editor.image])) else: interpreted_txt.append((dict(envs), charbuff)) # interpreted_txt.append((dict(envs), charbuff)) # FUTURE DEL ############ charbuff = [] envs.pop() else: # a normal, string, character charbuff.append(char) prevchar = char if charbuff: interpreted_txt.append((dict(envs), charbuff)) return interpreted_txt def _tokenize(self, interpreted_txt): # tokenizes text # accepts (envs, charbuff) # returns a list of tokens iswhitespace, token_builder = _iswhitespace, _token_builder charbuff, _interpreted_txt, tokenized_txt = [], [], [] for (envs, chars) in interpreted_txt: for char in chars: if iswhitespace(char): if charbuff: _interpreted_txt.append((envs, charbuff)) charbuff = [] if _interpreted_txt: yield token_builder(_interpreted_txt) _interpreted_txt = [] yield token_builder([(envs, [char])]) else: charbuff.append(char) if charbuff: _interpreted_txt.append((envs, charbuff)) charbuff = [] if _interpreted_txt: yield token_builder(_interpreted_txt) def _wrap(self, tokenized_txt, justify): # wrap interpreted text to page width # accepts a list of Token objects tuples # returns a list of Line objects, each wrapped to self.rect Line = _Line # FUTURE COLS ### rect_w = self.col_w # rect_w = self.rect.w # FUTURE COLS DEL ################# # linesize tracks the current size of the rendered line because moving # between environments will mean that there will be multiple surfaces # that need to be glued together line = [] # initialize line, and linesize for token in tokenized_txt: token_w = token.get_width() if token_w > rect_w: raise ValueError( "The token '" + token.str + "' is " + str((token_w - rect_w)) + " pixels too wide to fit in the rect passed." ) # the token fits, process it and check if the line still fits inside # rect area, if not, append line without token, reinitialize line # with token line.append(token) if unicode(token) == "\n": # don't justify a line that would not wrap if justify == "justified": _justify = "left" else: _justify = justify yield Line(line, rect_w, _justify) line = [] # reset line elif sum(token.get_width() for token in line) > rect_w: token = line.pop() # remove single trailing whitespace if line[-1].iswhitespace: line = line[:-1] yield Line(line, rect_w, justify) line = [] # reinitialize _line, line, linesize, and num objects # do not append whitespace as the first token of the new line if not token.iswhitespace: line.append(token) if line: # don't justify a line that would not wrap if justify == "justified": _justify = "left" else: _justify = justify yield Line(line, rect_w, _justify) ################################################################## # public methods def input(self, txt, justify=None, update=True): """ interprets, renders, wraps, and justifies input text txt -- raw text written with glyph markup justify -- a justify command; default is left justified left: left justified right: right justified justified: justified (both sides even) center: center justified returns nothing """ buff, interpret, tokenize, wrap = (self.buff, self._interpret, self._tokenize, self._wrap) interpreted_txt = interpret(txt) tokens = tokenize(interpreted_txt) lines = wrap(tokens, justify) buff.extend(lines) if update: self.update() def overwrite(self, txt, **kw): self.clear() self.input(txt, **kw) def update(self): """ updates the surface with the text input to the buffer by the input method, then deletes buffer accepts nothing returns nothing """ buff, dest = self.buff, self._dest spacing = self.spacing image, rect = self.image, self.rect editors, links = self.editors, self.links # FUTURE COLS ### ncols, col_n = self.ncols, self.col_n col_w, col_space = self.col_w, self.col_space ################# while buff: line = buff.popleft() line_h = line.get_height() if dest.y + line_h > rect.h: buff.appendleft(line) # FUTURE COLS ### if col_n < ncols: dest.move_ip(col_w + col_space, -dest.y) col_n += 1 else: stderr.write(WARN_BUFF) return 0 # break # FUTURE COLS DEL ################# else: image.blit(line, dest) for link in line.links: for _rect in line.links[link]: # move rect to token's pos on image and append to links _rect.move_ip(dest.topleft) # FUTURE ADD # FUTURE DEL # links[link].append(_rect.move(dest.topleft)) links[link].append(_rect) # FUTURE ### if link in editors: editors[link].rect = _rect ############ dest.y += line_h + spacing # FUTURE ### for editor in editors.values(): image.blit(editor.image, editor.rect) ############ # FUTURE COLS ### self.col_n = col_n ################# return 1 def clear(self, *a): """ draws background over surface_dest using self.rect and resets self._dest surface_dest-- the destination surface that self.image has been drawn to background-- the background to draw over the areas that self.image was drawn to on surface_dest returns nothing """ # reset glyph self._dest = Rect(0, 0, 0, 0) self.links = defaultdict(list) self.col_n = 1 self.buff = deque() rect = self.rect self.image = Surface(rect.size) self.image.fill(self._bkg) # if provided, clear a surface at glyph rect if a: surface_dest, background = a surface_dest.blit(background, rect, rect) def get_collisions(self, mpos): """ get collisions between a point and the linked text on the glyph surface mpos-- the point to check against the linked text if a link collides with mpos returns the link collinding with mpos if no link collides with mpos returns None """ editors, links, rect = self.editors, self.links, self.rect for link in links: for _rect in links[link]: if _rect.move(rect.topleft).collidepoint(mpos): return link
def dwarfgraph_loop(limit_mass=None): fondo = display.set_mode((ANCHO, ALTO), SCALED) fondo.fill(COLOR_BOX) fuente2 = font.SysFont('Verdana', 13, bold=True) render = fuente2.render('Mass', True, COLOR_TEXTO, COLOR_BOX) render = transform.rotate(render, -90) render_rect = render.get_rect(right=ANCHO - 53, centery=ALTO / 2) fondo.blit(render, render_rect) render = fuente2.render('Radius', True, COLOR_TEXTO, COLOR_BOX) render_rect = render.get_rect(x=3, y=3) fondo.blit(render, render_rect) text_mass = 'Mass: N/A' text_radius = 'Radius: N/A' text_density = 'Density: N/A' done = False data = {} lineas = WidgetGroup() linea_h = Linea(bg_rect, bg_rect.x, bg_rect.centery, bg_rect.w, 1, lineas) linea_v = Linea(bg_rect, bg_rect.centerx, bg_rect.y, 1, bg_rect.h, lineas) punto = Punto(bg_rect, bg_rect.centerx, bg_rect.centery, lineas) lim_img, lim_rect = None, None if limit_mass is not None: lim_y = find_and_interpolate(limit_mass, mass_keys, yes) lim_rect = Rect(54, lim_y + 26, bg_rect.w, bg_rect.h - lim_y - 26 + bg_rect.y) lim_img = Surface(lim_rect.size) lim_img.set_alpha(150) move_x, move_y = True, True while not done: for e in event.get( [KEYDOWN, QUIT, MOUSEMOTION, MOUSEBUTTONDOWN, KEYUP]): if (e.type == KEYDOWN and e.key == K_ESCAPE) or e.type == QUIT: quit() exit() elif e.type == MOUSEMOTION: x, y = e.pos if move_y: linea_h.move_y(y) punto.move_y(y) if move_x: linea_v.move_x(x) punto.move_x(x) dx, dy = punto.rect.center if bg_rect.collidepoint(dx, dy): mass = round( find_and_interpolate(linea_h.rect.y - 26, yes, mass_keys), 5) radius = round( find_and_interpolate(linea_v.rect.x, exes, radius_keys), 3) data.update({ 'mass': mass, 'radius': radius, 'clase': 'Dwarf Planet', 'albedo': 40 }) d = density(mass, radius) text_mass = 'Mass: {}'.format(mass) text_radius = 'Radius: {}'.format(radius) text_density = 'Density: {}'.format(d) color = bg.get_at((dx - 54, dy - 24)) composition = {} s, i, r = 0, 0, 0 if tuple(color) == (63, 223, 0, 255): i = 100 elif tuple(color) == (31, 255, 0, 255): # 10% silicates, 90% water ice i = round(roll(60, 90), 2) s = round(100 - i, 2) elif tuple(color) == (95, 191, 0, 255): # 60% silicates, 40% water ice s = round(roll(50, 60), 2) i = round(100 - s, 2) elif tuple(color) == (127, 159, 0, 255): # 90% silicates, 10% water ice s = round(roll(60, 90), 2) i = round(100 - s, 2) elif tuple(color) == (159, 127, 0, 255): # 90% silicates, 10% iron s = round(roll(60, 90), 2) r = round(100 - s, 2) elif tuple(color) == (191, 95, 0, 255): # 60% silicates, 40% iron s = round(roll(50, 60), 2) r = round(100 - s, 2) elif tuple(color) == (223, 63, 0, 255): # 10% silicates, 90% iron r = round(roll(60, 90), 2) s = round(100 - r, 2) elif tuple(color) == (255, 31, 0, 255): r = 100 if s: composition['silicates'] = s if i: composition['water ice'] = i if r: composition['iron'] = r data['composition'] = composition else: text_mass = 'Mass: N/A' text_radius = 'Radius: N/A' text_density = 'Density: N/A' elif e.type == MOUSEBUTTONDOWN: if e.button == 1: done = True elif e.type == KEYDOWN: if e.key == K_SPACE: done = True elif e.key == K_LSHIFT: move_x = False elif e.key == K_LCTRL: move_y = False elif e.type == KEYUP: if e.key == K_LSHIFT: move_x = True elif e.key == K_LCTRL: move_y = True render_mass = fuente.render(text_mass, True, COLOR_TEXTO, COLOR_BOX) render_radius = fuente.render(text_radius, True, COLOR_TEXTO, COLOR_BOX) render_density = fuente.render(text_density, True, COLOR_TEXTO, COLOR_BOX) fondo.fill(COLOR_BOX) fondo.blit(render_mass, (3, ALTO - 20)) fondo.blit(render_radius, (150, ALTO - 20)) fondo.blit(render_density, (300, ALTO - 20)) fondo.blit(bg, bg_rect) if limit_mass is not None: fondo.blit(lim_img, lim_rect) numbers.draw(fondo) lineas.update() lineas.draw(fondo) display.update() display.quit() return data