def __init__(self): self.width = 30 #int(settings.game_data['map_width']) self.height = 20 #int(settings.game_data['map_height']) self.seed = 42352336 self.water = 5 # int(settings.game_data['water_level']) self.max_diff = 1 self.canvas = Canvas() self.tile = generator.generate_map( width=self.width, height=self.height, seed=self.seed, water=self.water, max_diff=self.max_diff ) for x in range(self.width): for y in range(self.height): self.tile[x][y].map = self self.canvas.add(self.tile[x][y]) # real pixel dimensions self.real_x = 0 self.real_y = 0 self.real_w = 0 self.real_h = 0
def draw(self): app = App.get_running_app() if not self.canvas: self.canvas = Canvas() self.canvas.clear() with self.canvas: Color(rgb=app.COLOR_PALETTE[self.bg_color]) Rectangle(pos=self.pos, size=(self.width, self.height)) Color(rgb=app.COLOR_PALETTE[self.fg_color]) Rectangle(texture=self.texture, pos=self.pos, size=(self.width, self.height))
def __init__(self, **kwargs): super().__init__(**kwargs) if self.canvas is None: self.canvas = Canvas(opacity=self.opacity) self.add_background(self.user_pos, opacity=self.opacity, last_canvas=self.canvas)
def __init__(self, canvas=None): self.drawBones = True self.drawRegionAttachments = True self.drawBoundingBoxes = True self.drawMeshHull = True self.drawMeshTriangles = True self.bounds = SkeletonBounds() self.shapes = None self.scale = 1 self.boneWidth = 2 self.preMultipliedAlpha = False if canvas is None: self.shapes = Canvas() else: self.shapes = canvas
class CharDisplay(Widget): binary_char_data = ListProperty([]) fg_color = NumericProperty(0) bg_color = NumericProperty(1) def __init__(self, **kwargs): self.texture = Texture.create(size=(8, 8)) self.texture.min_filter = 'nearest' self.texture.mag_filter = 'nearest' super(CharDisplay, self).__init__(**kwargs) def on_binary_char_data(self, *args, **kwargs): buf = [] for b in numpy.unpackbits(self.binary_char_data): buf.append(255) buf.append(255) buf.append(255) buf.append(255 if b else 0) buf = bytes(buf) self.texture.blit_buffer(buf, colorfmt='rgba', bufferfmt='ubyte') self.draw() def draw(self): app = App.get_running_app() if not self.canvas: self.canvas = Canvas() self.canvas.clear() with self.canvas: Color(rgb=app.COLOR_PALETTE[self.bg_color]) Rectangle(pos=self.pos, size=(self.width, self.height)) Color(rgb=app.COLOR_PALETTE[self.fg_color]) Rectangle(texture=self.texture, pos=self.pos, size=(self.width, self.height))
def initialize(self): self.initialized = True try: self.window_width self.window_height except AttributeError: win = self.get_parent_window() # `window_width` & `window_height`: dimensions of the window, in pixels self.window_width = win.width self.window_height = win.height # `matrix`: the matrix holding the maze. Values in this matrix are: # - nonnegative numbers: corridors; the value represents the shortest # distance to the exit (these will be set using set_walls later) # -1: regular (breakable) wall # -2: perimeter (unbreakable) wall # -3: exit # -4: entrance self.matrix = -numpy.transpose( numpy.array(create_maze( self.window_width // MAZE_CELL_SIZE, self.window_height // MAZE_CELL_SIZE, ), dtype=numpy.int8) ) # `width`/`height`: size of the maze, in tiles self.width, self.height = self.matrix.shape # `cell_width`/`cell_height`: size of a tile, in pixels self.cell_width = self.window_width / self.width self.cell_height = self.window_height / self.height # `cell_size`: average size of a tile, as a scalar self.cell_size = (self.cell_width + self.cell_height) / 2 # Initialize perimeter walls and entrance/exit for x in range(self.width): self.matrix[x, 0] = self.matrix[x, self.height - 1] = -2 for y in range(self.height): self.matrix[0, y] = self.matrix[self.width - 1, y] = -2 self.matrix[0, 1] = -3 self.matrix[self.width - 1, self.height - 2] = -4 # `balls`: list of Ball widgets on this board self.balls = [] # `start_point`, `start_cranny`: Coordinates of the entrance and the # tile next to it. (The corresponding coordinates for the exit are # simply (0, 1) and (1, 1)) self.start_point = self.width - 2, self.height - 2 self.start_cranny = self.width - 1, self.height - 2 # `bdist`: same as `matrix` except positive numbers indicate shortest # distance to the entrance or nearest ball # `dist`: ditto with shortest distance to just the entrance self.bdist = self.dist = self.matrix self.set_walls(self.matrix) # Initialize the graphic representation self.background_canvas = Canvas() self.canvas.add(self.background_canvas) self.draw() # Add the ball source (zero-sized initially) self.ball_source = BallSource(size=(0, 0), pos=(self.window_width, self.window_height)) self.add_widget(self.ball_source) # Initialize the countdown for the solver Clock.schedule_once(lambda dt: self.countdown(COUNTDOWN_START), 1) # Redraw every 30th of a second Clock.schedule_interval(self.redraw, 0.03)
class MazeBoard(TickingWidget): """A maze for a single turn in the game MazeBoard contains the maze. Balls and builders are child widgets of a MazeBoard. """ darkWalls = True def __init__(self): self.initialized = False super(MazeBoard, self).__init__() Clock.schedule_once(lambda dt: self.initialize()) self.must_redraw = False self.time = 0 def initialize(self): self.initialized = True try: self.window_width self.window_height except AttributeError: win = self.get_parent_window() # `window_width` & `window_height`: dimensions of the window, in pixels self.window_width = win.width self.window_height = win.height # `matrix`: the matrix holding the maze. Values in this matrix are: # - nonnegative numbers: corridors; the value represents the shortest # distance to the exit (these will be set using set_walls later) # -1: regular (breakable) wall # -2: perimeter (unbreakable) wall # -3: exit # -4: entrance self.matrix = -numpy.transpose( numpy.array(create_maze( self.window_width // MAZE_CELL_SIZE, self.window_height // MAZE_CELL_SIZE, ), dtype=numpy.int8) ) # `width`/`height`: size of the maze, in tiles self.width, self.height = self.matrix.shape # `cell_width`/`cell_height`: size of a tile, in pixels self.cell_width = self.window_width / self.width self.cell_height = self.window_height / self.height # `cell_size`: average size of a tile, as a scalar self.cell_size = (self.cell_width + self.cell_height) / 2 # Initialize perimeter walls and entrance/exit for x in range(self.width): self.matrix[x, 0] = self.matrix[x, self.height - 1] = -2 for y in range(self.height): self.matrix[0, y] = self.matrix[self.width - 1, y] = -2 self.matrix[0, 1] = -3 self.matrix[self.width - 1, self.height - 2] = -4 # `balls`: list of Ball widgets on this board self.balls = [] # `start_point`, `start_cranny`: Coordinates of the entrance and the # tile next to it. (The corresponding coordinates for the exit are # simply (0, 1) and (1, 1)) self.start_point = self.width - 2, self.height - 2 self.start_cranny = self.width - 1, self.height - 2 # `bdist`: same as `matrix` except positive numbers indicate shortest # distance to the entrance or nearest ball # `dist`: ditto with shortest distance to just the entrance self.bdist = self.dist = self.matrix self.set_walls(self.matrix) # Initialize the graphic representation self.background_canvas = Canvas() self.canvas.add(self.background_canvas) self.draw() # Add the ball source (zero-sized initially) self.ball_source = BallSource(size=(0, 0), pos=(self.window_width, self.window_height)) self.add_widget(self.ball_source) # Initialize the countdown for the solver Clock.schedule_once(lambda dt: self.countdown(COUNTDOWN_START), 1) # Redraw every 30th of a second Clock.schedule_interval(self.redraw, 0.03) def countdown(self, num): """The countdown for the solving player For positive `num`, shows the number near the maze entrance, animates it, and schedules a call to countdown(num-1) for one second. For num == 0, display 'Go!', animate it, and grow the ball source. """ label = Label( text=str(num) if num else 'Go!', font_size=24, color=(0, 0, 1, 1), ) with label.canvas.before: PushMatrix() Translate( self.window_width - self.cell_size * 1, self.window_height - self.cell_size * 5, 0, ) Rotate(90, 0, 0, 1) with label.canvas.after: PopMatrix() animation = Animation(font_size=256, color=(0, 0, 1, 0), t='in_cubic', duration=1.5) animation.start(label) self.add_widget(label) if num > 0: Clock.schedule_once(lambda dt: self.countdown(num - 1), 1) else: self.ball_source.grow(self.cell_size * 3) if self.parent: self.parent.set_message(True, u'Take a ball from the blue corner.') def add_time(self, time): """Add time to the solving player's clock """ self.time += time if self.time > 15: # If the player is taking a long time, display a (hopefully # helpful) hint self.parent.set_message(True, u"Take another ball if you're really stuck.") self.parent.add_time(time) def draw(self): """Draw the board. The colors are filled in later, in tick() """ self.colors = {} self.background_canvas.clear() with self.background_canvas: for y in range(self.height): y_coord = y * self.cell_height for x in range(self.width): x_coord = x * self.cell_width self.colors[x, y] = Color(0, 0, 0) Rectangle(pos=(x_coord, y_coord), size=(self.cell_width, self.cell_height)) self.redraw() def redraw(self, dt=None): """Called any time the maze's colors change """ self.must_redraw = True def tick(self, dt=None): """Set all tile colors to the appropriate values """ if not self.must_redraw: return else: self.must_redraw = False sys.stdout.flush() self.set_walls(self.matrix) for y in range(self.height): y_coord = y * self.cell_height for x in range(self.width): self.colors[x, y].rgba = self.tile_color(x, y) self.background_canvas.ask_update() def tile_color(self, x, y): """Get the color for the tile at (x, y) """ if self.matrix[x, y] == -4: # Entrance; same color as neighboring tile return self.tile_color(x - 1, y) elif self.matrix[x, y] == -3: # Exit; same color as neighboring tile return self.tile_color(x + 1, y) elif self.matrix[x, y] < 0: # Wall, transparent black if self.darkWalls: return 0, 0, 0, 0 else: # (with light walls; white) return 1, 1, 1, 1 else: # Corridor; a light color based on distances to the entrance, exit # and nearest ball m = self.matrix[x, y] d = self.dist[x, y] b = self.bdist[x, y] mmax = self.matrix.max() or 1 dmax = self.dist.max() or 1 bmax = self.bdist.max() or 1 r = m / mmax g = 1 - d / dmax b = 1 - min(m, b) / min(mmax, bmax) if self.darkWalls: a = (numpy.array((max(g, b), max(r, b), max(r, g), 0)) ** 8) return 1 - a / 2 else: # (with light walls; a dark color) return numpy.array((r, g, b, 1)) ** 8 def set_walls(self, matrix): """Set walls and solve the maze. No-op if maze is unsolvable. `matrix`: A 2D matrix with negative values for walls """ mstart = numpy.zeros(matrix.shape + (3,), dtype=numpy.int32) mstart[self.start_point + (0,)] = mstart[(1, 1, 1)] = 1 mstart[self.start_cranny + (2,)] = 1 mstart[self.start_point + (2,)] = 1 for ball in self.balls: tile_pos = self.pixel_to_tile(ball.pos) mstart[int(tile_pos[0]), int(tile_pos[1]), 2] = 1 corridors = matrix >= 0 m = solvemaze(corridors, mstart) if m is None: return False m[self.start_point + (0,)] = m[(1, 1, 1)] = 1 self.bdist = m[:, :, 2] self.dist = m[:, :, 1] m = m[:, :, 0] self.matrix = numpy.select([matrix < 0, True], [matrix, m]) # Clear cache of unsuccessful building attempts self.build_tries = defaultdict(set) return True def set_wall(self, coord, create): """Set (create== True) or destroy (create == False) a wall at coord If the operation would create an unsolvable maze, nothing is done. These unsuccessful attempts are cached in `build_tries` until the maze is changed """ if coord in self.build_tries[create]: return False self.build_tries[create].add(coord) if coord == self.start_cranny or coord == (2, 1): return False try: current_state = self.matrix[coord] except IndexError: return False m = self.matrix.copy() if not create and current_state >= 0: # Build a wall m[coord] = -1 rv = self.set_walls(m) elif create and current_state == -1: # Build a corridor m[coord] = 0 rv = self.set_walls(m) else: return False if rv: self.redraw() return rv def pixel_to_tile(self, pos): """Convert pixel coordinates to tile coordinates Returns a pair of floats """ return ( pos[0] / self.cell_width, pos[1] / self.cell_height, ) def tile_to_pixel(self, tile_coord): """Convert tile coordinates to pixel coordinates Returns a pair of floats """ return ( float(tile_coord[0] * self.cell_width), float(tile_coord[1] * self.cell_height), ) def wall_at_pixel(self, pos): """True if there is a wall at the specified pixel coordinates """ return self.wall_at_tile(self.pixel_to_tile(pos)) def wall_at_tile(self, tile_coord): """True if there is a wall at the specified tile coordinates """ try: tile_value = self.matrix[int(tile_coord[0]), int(tile_coord[1])] except IndexError: # Outside of the maze – a wall return True else: return tile_value in (-1, -2) def on_touch_down(self, touch): if not self.initialized: return for child in self.children[:]: # Pass the event to children if child.dispatch('on_touch_down', touch): return True if self.ball_source.collide_point(touch.x, touch.y): # Create a new ball ball = Ball(self, pos=self.tile_to_pixel(( (self.start_cranny[0] + 0.5), (self.start_cranny[1] + 0.5), ))) self.add_widget(ball) self.balls.append(ball) ball.on_touch_down(touch, force=True) self.parent.set_message(True, "Move the ball to the opposite corner.") else: # Create a new builder tile_coord = self.pixel_to_tile(touch.pos) build_loop = BuildLoop( spin=-1 if self.wall_at_tile(tile_coord) else 1, pos=self.tile_to_pixel(( int(tile_coord[0]) + 0.5, int(tile_coord[1]) + 0.5, )) ) self.add_widget(build_loop) build_loop.on_touch_down(touch, force=True) self.parent.set_message(False, "Loop clockwise to build corridors, CCW to destroy them.") def win(self): """End the current round, destroy the widget. Called when a ball reaches the goal area. """ for child in list(self.children): self.remove_widget(child) self.balls = [] self.ball_source.size = 0, 0 self.parent.win()
class Map: def __init__(self): self.width = 30 #int(settings.game_data['map_width']) self.height = 20 #int(settings.game_data['map_height']) self.seed = 42352336 self.water = 5 # int(settings.game_data['water_level']) self.max_diff = 1 self.canvas = Canvas() self.tile = generator.generate_map( width=self.width, height=self.height, seed=self.seed, water=self.water, max_diff=self.max_diff ) for x in range(self.width): for y in range(self.height): self.tile[x][y].map = self self.canvas.add(self.tile[x][y]) # real pixel dimensions self.real_x = 0 self.real_y = 0 self.real_w = 0 self.real_h = 0 def update_canvas(self, x, y, w, h): # gets maximal rectangle dimensions in pixels to contain canvas help_points_size = (self.width * 3 + 1, self.height * 2 + 1) if w / self.width > h / (self.height * numpy.sqrt(3)): # more padding on left and right uy = h // help_points_size[1] ux = int(uy // numpy.sqrt(3)) else: # more padding on top and bottom ux = w // help_points_size[0] uy = int(ux * numpy.sqrt(3)) + 1 self.real_x = (w - help_points_size[0] * ux) / 2 self.real_y = (h - help_points_size[1] * uy) / 2 self.real_w = help_points_size[0] * ux self.real_h = help_points_size[1] * uy for column in range(self.width): x = 3 * ux * column + self.real_x for row in range(self.height): y = uy * 2 * row + uy * ((column + 1) % 2) + uy - ux * 2 + self.real_y self.tile[column][row].set_pos_and_size(pos=(x, y), size=(ux * 4 - 2, ux * 4 - 2)) def click(self, position): tiles = [] for x in range(self.width): for y in range(self.height): if self.tile[x][y].contains(position): tiles.append(self.tile[x][y]) tile = util_get_closest_tile(tiles, position) if tile is not None: side = tile.get_side(position) print(f"{tile.grid_pos} {side}") tile.activate_line(side) else: print("Poza") def random_free_tile(self): x = random.randrange(0, self.width) y = random.randrange(0, self.height) while self.tile[x][y].depth != 0: x = random.randrange(0, self.width) y = random.randrange(0, self.height) return self.tile[x][y]
class SkeletonRendererDebug: boneLineColor = Color(1, 0, 0, 1) boneOriginColor = Color(0, 1, 0, 1) attachmentLineColor = Color(0, 0, 1, 0.5) triangleLineColor = Color(1, 0.64, 0, 0.5) boundingBoxColor = Color(0, 1, 0, 0.8) aabbColor = Color(0, 1, 0, 0.5) def __init__(self, canvas=None): self.drawBones = True self.drawRegionAttachments = True self.drawBoundingBoxes = True self.drawMeshHull = True self.drawMeshTriangles = True self.bounds = SkeletonBounds() self.shapes = None self.scale = 1 self.boneWidth = 2 self.preMultipliedAlpha = False if canvas is None: self.shapes = Canvas() else: self.shapes = canvas def draw(self, skeleton): self.shapes.clear() skeletonX = skeleton.getX() skeletonY = skeleton.getY() glEnable(GL_BLEND) srcFunc = GL_ONE if self.preMultipliedAlpha else GL_SRC_ALPHA glBlendFunc(srcFunc, GL_ONE_MINUS_SRC_ALPHA) bones = skeleton.getBones() if self.drawBones: self.shapes.add(self.boneLineColor) for i in range(len(bones)): bone = bones[i] if bone.parent is None: continue x = skeletonX + bone.data.length * bone.m00 + bone.worldX y = skeletonY + bone.data.length * bone.m10 + bone.worldY self.shapes.add( Line(points=[ skeletonX + bone.worldX, skeletonY + bone.worldY, x, y ], width=self.boneWidth * self.scale)) self.shapes.add(Line(circle=(skeletonX, skeletonY, 4 * self.scale))) if self.drawRegionAttachments: self.shapes.add(self.attachmentLineColor) slots = skeleton.getSlots() for i in range(len(slots)): slot = slots[i] attachment = slot.attachment if isinstance(attachment, RegionAttachment): attachment.updateWorldVertices(slot, False) vertices = attachment.getWorldVertices() self.shapes.add( Line(points=([ vertices[X1], vertices[Y1], vertices[X2], vertices[Y2] ]))) self.shapes.add( Line(points=([ vertices[X2], vertices[Y2], vertices[X3], vertices[Y3] ]))) self.shapes.add( Line(points=([ vertices[X3], vertices[Y3], vertices[X4], vertices[Y4] ]))) self.shapes.add( Line(points=([ vertices[X4], vertices[Y4], vertices[X1], vertices[Y1] ]))) if self.drawMeshHull or self.drawMeshTriangles: slots = skeleton.getSlots() for i in range(len(slots)): slot = slots[i] attachment = slot.attachment vertices = None triangles = None hullLength = 0 if isinstance(attachment, MeshAttachment): attachment.updateWorldVertices(slot, False) vertices = attachment.getWorldVertices() triangles = attachment.getTriangles() hullLength = attachment.getHullLength() elif isinstance(attachment, SkinnedMeshAttachment): attachment.updateWorldVertices(slot, False) vertices = attachment.getWorldVertices() triangles = attachment.getTriangles() hullLength = attachment.getHullLength() if vertices is None or triangles is None: continue if self.drawMeshTriangles: self.shapes.add(self.triangleLineColor) for ii in range(0, len(triangles), 3): v1, v2, v3 = triangles[ii] * 5, triangles[ ii + 1] * 5, triangles[ii + 3] * 5 self.shapes.add( Line(points=[ vertices[v1], vertices[v1 + 1], vertices[v2], vertices[v2 + 1], vertices[v3], vertices[v3 + 1] ])) if self.drawMeshHull and hullLength > 0: self.shapes.add(self.attachmentLineColor) hullLength = hullLength / 2 * 5 lastX, lastY = vertices[hullLength - 5], vertices[hullLength - 4] for ii in range(0, hullLength, 5): x, y = vertices[ii], vertices[ii + 1] self.shapes.add(Line(points=[x, y, lastX, lastY])) lastX = x lastY = y if self.drawBoundingBoxes: bounds = self.bounds bounds.update(skeleton, True) self.shapes.add(self.aabbColor) self.shapes.add( Rectangle(x=bounds.getMinX(), y=bounds.getMinY(), width=bounds.getWidth(), height=bounds.getHeight())) self.shapes.add(self.boundingBoxColor) polygons = bounds.getPolygons() for polygon in polygons: self.shapes.add(Line(points=polygon)) if self.drawBones: self.shapes.add(self.boneOriginColor) for bone in bones: self.shapes.add(Color(0, 1, 0, 1)) self.shapes.add( Line(circle=(skeletonX + bone.worldX, skeletonY + bone.worldY, 3 * self.scale))) del bones def getShapeRenderer(self): return self.shapes def setBones(self, bones): self.drawBones = bones def setScale(self, scale): self.scale = scale def setRegionAttachments(self, regionAttachments): self.drawRegionAttachments = regionAttachments def setBoundingBoxes(self, boundingBoxes): self.drawBoundingBoxes = boundingBoxes def setMeshHull(self, meshHull): self.drawMeshHull = meshHull def setMeshTriangles(self, meshTriangles): self.drawMeshTriangles = meshTriangles def setPremultipliedAlpha(self, premultipliedAlpha): self.preMultipliedAlpha = premultipliedAlpha
def initialize(self): self.initialized = True try: self.window_width self.window_height except AttributeError: win = self.get_parent_window() # `window_width` & `window_height`: dimensions of the window, in pixels self.window_width = win.width self.window_height = win.height # `matrix`: the matrix holding the maze. Values in this matrix are: # - nonnegative numbers: corridors; the value represents the shortest # distance to the exit (these will be set using set_walls later) # -1: regular (breakable) wall # -2: perimeter (unbreakable) wall # -3: exit # -4: entrance self.matrix = -numpy.transpose( numpy.array(create_maze( self.window_width // MAZE_CELL_SIZE, self.window_height // MAZE_CELL_SIZE, ), dtype=numpy.int8)) # `width`/`height`: size of the maze, in tiles self.width, self.height = self.matrix.shape # `cell_width`/`cell_height`: size of a tile, in pixels self.cell_width = self.window_width / self.width self.cell_height = self.window_height / self.height # `cell_size`: average size of a tile, as a scalar self.cell_size = (self.cell_width + self.cell_height) / 2 # Initialize perimeter walls and entrance/exit for x in range(self.width): self.matrix[x, 0] = self.matrix[x, self.height - 1] = -2 for y in range(self.height): self.matrix[0, y] = self.matrix[self.width - 1, y] = -2 self.matrix[0, 1] = -3 self.matrix[self.width - 1, self.height - 2] = -4 # `balls`: list of Ball widgets on this board self.balls = [] # `start_point`, `start_cranny`: Coordinates of the entrance and the # tile next to it. (The corresponding coordinates for the exit are # simply (0, 1) and (1, 1)) self.start_point = self.width - 2, self.height - 2 self.start_cranny = self.width - 1, self.height - 2 # `bdist`: same as `matrix` except positive numbers indicate shortest # distance to the entrance or nearest ball # `dist`: ditto with shortest distance to just the entrance self.bdist = self.dist = self.matrix self.set_walls(self.matrix) # Initialize the graphic representation self.background_canvas = Canvas() self.canvas.add(self.background_canvas) self.draw() # Add the ball source (zero-sized initially) self.ball_source = BallSource(size=(0, 0), pos=(self.window_width, self.window_height)) self.add_widget(self.ball_source) # Initialize the countdown for the solver Clock.schedule_once(lambda dt: self.countdown(COUNTDOWN_START), 1) # Redraw every 30th of a second Clock.schedule_interval(self.redraw, 0.03)
class MazeBoard(TickingWidget): """A maze for a single turn in the game MazeBoard contains the maze. Balls and builders are child widgets of a MazeBoard. """ darkWalls = True def __init__(self): self.initialized = False super(MazeBoard, self).__init__() Clock.schedule_once(lambda dt: self.initialize()) self.must_redraw = False self.time = 0 def initialize(self): self.initialized = True try: self.window_width self.window_height except AttributeError: win = self.get_parent_window() # `window_width` & `window_height`: dimensions of the window, in pixels self.window_width = win.width self.window_height = win.height # `matrix`: the matrix holding the maze. Values in this matrix are: # - nonnegative numbers: corridors; the value represents the shortest # distance to the exit (these will be set using set_walls later) # -1: regular (breakable) wall # -2: perimeter (unbreakable) wall # -3: exit # -4: entrance self.matrix = -numpy.transpose( numpy.array(create_maze( self.window_width // MAZE_CELL_SIZE, self.window_height // MAZE_CELL_SIZE, ), dtype=numpy.int8)) # `width`/`height`: size of the maze, in tiles self.width, self.height = self.matrix.shape # `cell_width`/`cell_height`: size of a tile, in pixels self.cell_width = self.window_width / self.width self.cell_height = self.window_height / self.height # `cell_size`: average size of a tile, as a scalar self.cell_size = (self.cell_width + self.cell_height) / 2 # Initialize perimeter walls and entrance/exit for x in range(self.width): self.matrix[x, 0] = self.matrix[x, self.height - 1] = -2 for y in range(self.height): self.matrix[0, y] = self.matrix[self.width - 1, y] = -2 self.matrix[0, 1] = -3 self.matrix[self.width - 1, self.height - 2] = -4 # `balls`: list of Ball widgets on this board self.balls = [] # `start_point`, `start_cranny`: Coordinates of the entrance and the # tile next to it. (The corresponding coordinates for the exit are # simply (0, 1) and (1, 1)) self.start_point = self.width - 2, self.height - 2 self.start_cranny = self.width - 1, self.height - 2 # `bdist`: same as `matrix` except positive numbers indicate shortest # distance to the entrance or nearest ball # `dist`: ditto with shortest distance to just the entrance self.bdist = self.dist = self.matrix self.set_walls(self.matrix) # Initialize the graphic representation self.background_canvas = Canvas() self.canvas.add(self.background_canvas) self.draw() # Add the ball source (zero-sized initially) self.ball_source = BallSource(size=(0, 0), pos=(self.window_width, self.window_height)) self.add_widget(self.ball_source) # Initialize the countdown for the solver Clock.schedule_once(lambda dt: self.countdown(COUNTDOWN_START), 1) # Redraw every 30th of a second Clock.schedule_interval(self.redraw, 0.03) def countdown(self, num): """The countdown for the solving player For positive `num`, shows the number near the maze entrance, animates it, and schedules a call to countdown(num-1) for one second. For num == 0, display 'Go!', animate it, and grow the ball source. """ label = Label( text=str(num) if num else 'Go!', font_size=24, color=(0, 0, 1, 1), ) with label.canvas.before: PushMatrix() Translate( self.window_width - self.cell_size * 1, self.window_height - self.cell_size * 5, 0, ) Rotate(90, 0, 0, 1) with label.canvas.after: PopMatrix() animation = Animation(font_size=256, color=(0, 0, 1, 0), t='in_cubic', duration=1.5) animation.start(label) self.add_widget(label) if num > 0: Clock.schedule_once(lambda dt: self.countdown(num - 1), 1) else: self.ball_source.grow(self.cell_size * 3) if self.parent: self.parent.set_message(True, u'Take a ball from the blue corner.') def add_time(self, time): """Add time to the solving player's clock """ self.time += time if self.time > 15: # If the player is taking a long time, display a (hopefully # helpful) hint self.parent.set_message( True, u"Take another ball if you're really stuck.") self.parent.add_time(time) def draw(self): """Draw the board. The colors are filled in later, in tick() """ self.colors = {} self.background_canvas.clear() with self.background_canvas: for y in range(self.height): y_coord = y * self.cell_height for x in range(self.width): x_coord = x * self.cell_width self.colors[x, y] = Color(0, 0, 0) Rectangle(pos=(x_coord, y_coord), size=(self.cell_width, self.cell_height)) self.redraw() def redraw(self, dt=None): """Called any time the maze's colors change """ self.must_redraw = True def tick(self, dt=None): """Set all tile colors to the appropriate values """ if not self.must_redraw: return else: self.must_redraw = False sys.stdout.flush() self.set_walls(self.matrix) for y in range(self.height): y_coord = y * self.cell_height for x in range(self.width): self.colors[x, y].rgba = self.tile_color(x, y) self.background_canvas.ask_update() def tile_color(self, x, y): """Get the color for the tile at (x, y) """ if self.matrix[x, y] == -4: # Entrance; same color as neighboring tile return self.tile_color(x - 1, y) elif self.matrix[x, y] == -3: # Exit; same color as neighboring tile return self.tile_color(x + 1, y) elif self.matrix[x, y] < 0: # Wall, transparent black if self.darkWalls: return 0, 0, 0, 0 else: # (with light walls; white) return 1, 1, 1, 1 else: # Corridor; a light color based on distances to the entrance, exit # and nearest ball m = self.matrix[x, y] d = self.dist[x, y] b = self.bdist[x, y] mmax = self.matrix.max() or 1 dmax = self.dist.max() or 1 bmax = self.bdist.max() or 1 r = m / mmax g = 1 - d / dmax b = 1 - min(m, b) / min(mmax, bmax) if self.darkWalls: a = (numpy.array((max(g, b), max(r, b), max(r, g), 0))**8) return 1 - a / 2 else: # (with light walls; a dark color) return numpy.array((r, g, b, 1))**8 def set_walls(self, matrix): """Set walls and solve the maze. No-op if maze is unsolvable. `matrix`: A 2D matrix with negative values for walls """ mstart = numpy.zeros(matrix.shape + (3, ), dtype=numpy.int32) mstart[self.start_point + (0, )] = mstart[(1, 1, 1)] = 1 mstart[self.start_cranny + (2, )] = 1 mstart[self.start_point + (2, )] = 1 for ball in self.balls: tile_pos = self.pixel_to_tile(ball.pos) mstart[int(tile_pos[0]), int(tile_pos[1]), 2] = 1 corridors = matrix >= 0 m = solvemaze(corridors, mstart) if m is None: return False m[self.start_point + (0, )] = m[(1, 1, 1)] = 1 self.bdist = m[:, :, 2] self.dist = m[:, :, 1] m = m[:, :, 0] self.matrix = numpy.select([matrix < 0, True], [matrix, m]) # Clear cache of unsuccessful building attempts self.build_tries = defaultdict(set) return True def set_wall(self, coord, create): """Set (create== True) or destroy (create == False) a wall at coord If the operation would create an unsolvable maze, nothing is done. These unsuccessful attempts are cached in `build_tries` until the maze is changed """ if coord in self.build_tries[create]: return False self.build_tries[create].add(coord) if coord == self.start_cranny or coord == (2, 1): return False try: current_state = self.matrix[coord] except IndexError: return False m = self.matrix.copy() if not create and current_state >= 0: # Build a wall m[coord] = -1 rv = self.set_walls(m) elif create and current_state == -1: # Build a corridor m[coord] = 0 rv = self.set_walls(m) else: return False if rv: self.redraw() return rv def pixel_to_tile(self, pos): """Convert pixel coordinates to tile coordinates Returns a pair of floats """ return ( pos[0] / self.cell_width, pos[1] / self.cell_height, ) def tile_to_pixel(self, tile_coord): """Convert tile coordinates to pixel coordinates Returns a pair of floats """ return ( float(tile_coord[0] * self.cell_width), float(tile_coord[1] * self.cell_height), ) def wall_at_pixel(self, pos): """True if there is a wall at the specified pixel coordinates """ return self.wall_at_tile(self.pixel_to_tile(pos)) def wall_at_tile(self, tile_coord): """True if there is a wall at the specified tile coordinates """ try: tile_value = self.matrix[int(tile_coord[0]), int(tile_coord[1])] except IndexError: # Outside of the maze – a wall return True else: return tile_value in (-1, -2) def on_touch_down(self, touch): if not self.initialized: return for child in self.children[:]: # Pass the event to children if child.dispatch('on_touch_down', touch): return True if self.ball_source.collide_point(touch.x, touch.y): # Create a new ball ball = Ball(self, pos=self.tile_to_pixel(( (self.start_cranny[0] + 0.5), (self.start_cranny[1] + 0.5), ))) self.add_widget(ball) self.balls.append(ball) ball.on_touch_down(touch, force=True) self.parent.set_message(True, "Move the ball to the opposite corner.") else: # Create a new builder tile_coord = self.pixel_to_tile(touch.pos) build_loop = BuildLoop( spin=-1 if self.wall_at_tile(tile_coord) else 1, pos=self.tile_to_pixel(( int(tile_coord[0]) + 0.5, int(tile_coord[1]) + 0.5, ))) self.add_widget(build_loop) build_loop.on_touch_down(touch, force=True) self.parent.set_message( False, "Loop clockwise to build corridors, CCW to destroy them.") def win(self): """End the current round, destroy the widget. Called when a ball reaches the goal area. """ for child in list(self.children): self.remove_widget(child) self.balls = [] self.ball_source.size = 0, 0 self.parent.win()
def __init__(self, app, music_player): """Create main GUI Arguments: parent -- parent application to this widget music_player -- audio generator for full application network -- network client and server handler """ # Initialize Tkinter, and instruct it hide root window self.tk_root = Tkinter.Tk() self.tk_root.withdraw() # Perform widget initializations super(GUI, self).__init__() # OVERALL STRUCTURE WINDOW_WIDTH = 800 WINDOW_HEIGHT = 600 OUTER_PADDING = 20 # Set default parameters to be used in accurately loading an # initial state to the GUI self.app = app self.music_player = music_player self.track_id = MusicPlayer.WAVETABLE_A self.popup_count = 0 # Turn off multi-touch in this GUI Config.set('input', 'mouse', 'mouse,disable_multitouch') # Determine image directory IMAGE_DIR = system.get_images_dir() # For dynamic GUI coloring self.TRACK_COLORS = [[.7, .4, .9, 1.0], [.6, .9, .4, 1.0], [.8, .5, .3, 1.0]] self.colorables = [] # Create widget for the main layout. This will be added separately # from each of our popup windows self.main_layout = NSWidget() self.add_widget(self.main_layout) # BUTTON GRID NOTE_BUTTON_WIDTH = 48 NOTE_BUTTON_HEIGHT = 48 NOTE_BUTTON_PADDING = 7 ROW_LABEL_WIDTH = 54 ROW_LABEL_HEIGHT = NOTE_BUTTON_HEIGHT ROW_LABEL_FONT_SIZE = 10 NOTE_BUTTON_ROWS = MusicPlayer.NUM_ROWS NOTE_BUTTON_COLS = MusicPlayer.NUM_COLS GRID_WIDTH = NOTE_BUTTON_PADDING + ROW_LABEL_WIDTH + \ NOTE_BUTTON_PADDING + (NOTE_BUTTON_COLS * (NOTE_BUTTON_WIDTH + NOTE_BUTTON_PADDING)) GRID_HEIGHT = NOTE_BUTTON_PADDING + (NOTE_BUTTON_ROWS * (NOTE_BUTTON_HEIGHT + NOTE_BUTTON_PADDING)) GRID_X = OUTER_PADDING GRID_Y = WINDOW_HEIGHT - OUTER_PADDING - GRID_HEIGHT PLAYHEAD_WIDTH = NOTE_BUTTON_WIDTH + 4 PLAYHEAD_HEIGHT = GRID_HEIGHT PLAYHEAD_OPACITY = .5 PLAYHEAD_COLOR = Color(1.0, 1.0, 1.0) # Playhead playhead_widget = Widget() playhead_canvas = Canvas() playhead_canvas.add(PLAYHEAD_COLOR) playhead = Rectangle(size=[PLAYHEAD_WIDTH, PLAYHEAD_HEIGHT]) playhead_canvas.add(playhead) playhead_canvas.opacity = PLAYHEAD_OPACITY playhead_widget.canvas = playhead_canvas self.main_layout.add_widget(playhead_widget) self.playhead = playhead # For each row, create labels and notes self.row_labels = [] self.note_buttons = [] row_top = GRID_Y + GRID_HEIGHT - NOTE_BUTTON_PADDING for row in range(0, NOTE_BUTTON_ROWS): col_x = GRID_X + NOTE_BUTTON_PADDING # Make label for row row_label = Label(text=str(row), width=ROW_LABEL_WIDTH, height=ROW_LABEL_HEIGHT, text_size=[ROW_LABEL_WIDTH, ROW_LABEL_HEIGHT], font_size=ROW_LABEL_FONT_SIZE, halign='center', valign='middle') row_label.x = col_x row_label.top = row_top self.main_layout.add_widget(row_label) self.row_labels.append(row_label) col_x = col_x + ROW_LABEL_WIDTH + NOTE_BUTTON_PADDING # Create all buttons for row row_notes = [] for col in range(0, NOTE_BUTTON_COLS): col_button = NSToggleButton(width=NOTE_BUTTON_WIDTH, height=NOTE_BUTTON_HEIGHT) col_button.id = 'row' + str(row) + ',col' + str(col) col_button.x = col_x col_button.top = row_top col_button.bind(on_press=self.trigger_note) row_notes.append(col_button) self.main_layout.add_widget(col_button) self.colorables.append(col_button) col_x = col_x + NOTE_BUTTON_WIDTH + NOTE_BUTTON_PADDING self.note_buttons.append(row_notes) row_top = row_top - NOTE_BUTTON_PADDING - NOTE_BUTTON_HEIGHT # Set playhead start position leftmost_note = self.note_buttons[0][0] playhead_x = leftmost_note.center_x - (PLAYHEAD_WIDTH / 2) playhead_y = GRID_Y self.playhead.pos = [playhead_x, playhead_y] # PLAYBACK MENU PLAYBACK_X = OUTER_PADDING PLAYBACK_Y = OUTER_PADDING PLAYBACK_WIDTH = GRID_WIDTH PLAYBACK_HEIGHT = WINDOW_HEIGHT - OUTER_PADDING - GRID_HEIGHT - \ OUTER_PADDING - OUTER_PADDING PLAYBACK_CENTER_Y = PLAYBACK_Y + (PLAYBACK_HEIGHT / 2) PLAYBACK_TOP = PLAYBACK_Y + PLAYBACK_HEIGHT PLAY_BUTTON_WIDTH = 48 PLAY_BUTTON_HEIGHT = 48 PLAYALL_BUTTON_WIDTH = 60 PLAYALL_BUTTON_HEIGHT = PLAY_BUTTON_HEIGHT / 2 PLAYALL_BUTTON_FONT_SIZE = 8 PLAYALL_BUTTON_TEXT_SIZE = [PLAYALL_BUTTON_WIDTH, PLAYALL_BUTTON_HEIGHT] PAGE_BUTTON_WIDTH = 20 PAGE_BUTTON_HEIGHT = 30 NUM_PAGE_BUTTONS = MusicPlayer.NUM_PAGES PAGE_LABEL_WIDTH = (PAGE_BUTTON_WIDTH * NUM_PAGE_BUTTONS) PAGE_LABEL_HEIGHT = 20 PAGE_LABEL_FONT_SIZE = 10 PAGE_LABEL_OFFSET = 5 TRACK_BUTTON_WIDTH = 48 TRACK_BUTTON_HEIGHT = 48 NUM_TRACK_BUTTONS = MusicPlayer.NUM_TRACKS NUM_PLAYBACK_ELEMENTS = 4 TRACK_LABEL_WIDTH = TRACK_BUTTON_WIDTH * NUM_TRACK_BUTTONS TRACK_LABEL_HEIGHT = PAGE_LABEL_HEIGHT TRACK_LABEL_FONT_SIZE = PAGE_LABEL_FONT_SIZE TRACK_LABEL_TEXT_SIZE = [TRACK_LABEL_WIDTH, TRACK_LABEL_HEIGHT] TRACK_LABEL_OFFSET = PAGE_LABEL_OFFSET PLAYBACK_PADDING = (PLAYBACK_WIDTH - (PAGE_BUTTON_WIDTH * NUM_PAGE_BUTTONS) - (PLAY_BUTTON_WIDTH) - (TRACK_BUTTON_WIDTH * NUM_TRACK_BUTTONS) - PLAYALL_BUTTON_WIDTH) / (NUM_PLAYBACK_ELEMENTS + 1) # Play/pause button PLAY_BUTTON_X = PLAYBACK_X + PLAYBACK_PADDING # TODO: add a border for this button play_button = ToggleButton(width=PLAY_BUTTON_WIDTH, height=PLAY_BUTTON_HEIGHT) play_button.bind(on_press=self.play_pause) play_button.background_normal = \ os.path.join(IMAGE_DIR, "media-playback-start-4.png") play_button.background_down = \ os.path.join(IMAGE_DIR, "media-playback-pause-4.png") play_button.x = PLAY_BUTTON_X play_button.center_y = PLAYBACK_CENTER_Y self.play_button = play_button self.main_layout.add_widget(play_button) self.colorables.append(play_button) # Buttons to play one page or all one_page_button = NSToggleButton(width=PLAYALL_BUTTON_WIDTH, height=PLAYALL_BUTTON_HEIGHT, text='One page', text_size=PLAYALL_BUTTON_TEXT_SIZE, font_size=PLAYALL_BUTTON_FONT_SIZE, halign='center', valign='middle') one_page_button.bind(on_press=self.play_one_page) one_page_button.x = play_button.right + PLAYBACK_PADDING one_page_button.top = PLAYBACK_CENTER_Y + PLAYALL_BUTTON_HEIGHT self.one_page_button = one_page_button self.main_layout.add_widget(one_page_button) self.colorables.append(one_page_button) all_pages_button = NSToggleButton(width=PLAYALL_BUTTON_WIDTH, height=PLAYALL_BUTTON_HEIGHT, text='All pages', text_size=PLAYALL_BUTTON_TEXT_SIZE, font_size=PLAYALL_BUTTON_FONT_SIZE, halign='center', valign='middle') all_pages_button.bind(on_press=self.play_all_pages) all_pages_button.x = one_page_button.x all_pages_button.top = PLAYBACK_CENTER_Y self.all_pages_button = all_pages_button self.main_layout.add_widget(all_pages_button) self.colorables.append(all_pages_button) if music_player.play_all == False: one_page_button.state = 'down' all_pages_button.state = 'normal' elif music_player.play_all == True: one_page_button.state = 'normal' all_pages_button.state = 'down' # Page selection buttons self.page_buttons = [] page_buttons = [] page_label = Label(text='Page Select', text_size=[PAGE_LABEL_WIDTH, PAGE_LABEL_HEIGHT], font_size=PAGE_LABEL_FONT_SIZE, width=PAGE_LABEL_WIDTH, height=PAGE_LABEL_HEIGHT, halign='center', valign='middle') page_button_x = all_pages_button.right + PLAYBACK_PADDING page_label.x = page_button_x page_label.top = PLAYBACK_CENTER_Y - (PAGE_BUTTON_HEIGHT / 2) - \ PAGE_LABEL_OFFSET self.main_layout.add_widget(page_label) for page_index in range(0, NUM_PAGE_BUTTONS): page_id = 'page' + str(page_index) page_button = NSToggleButton(width=PAGE_BUTTON_WIDTH, height=PAGE_BUTTON_HEIGHT, id=page_id) page_button.bind(on_press=self.select_page) page_button.x = page_button_x page_button.center_y = PLAYBACK_CENTER_Y page_buttons.append(page_button) self.main_layout.add_widget(page_button) self.colorables.append(page_button) page_button_x += PAGE_BUTTON_WIDTH self.page_buttons = page_buttons # Select the current music player's page with the GUI page_buttons[music_player.page_index].state = 'down' # Track selection buttons TRACK_BUTTON_FONT_SIZE = 10 TRACK_BUTTON_TEXT_SIZE = [TRACK_BUTTON_WIDTH, TRACK_BUTTON_HEIGHT] track_text = ["Bass", "Lead", "Drum"] track_buttons = [] self.track_buttons = [] track_button_x = page_buttons[len(page_buttons) - 1].right + \ PLAYBACK_PADDING for track_index in range(0, NUM_TRACK_BUTTONS): track_id = 'track' + str(track_index) track_button = NSToggleButton(text=track_text[track_index], width=TRACK_BUTTON_WIDTH, height=TRACK_BUTTON_HEIGHT, id=track_id, text_size=TRACK_BUTTON_TEXT_SIZE, font_size=TRACK_BUTTON_FONT_SIZE, halign='center', valign='middle') track_button.bind(on_press=self.select_track) track_button.x = track_button_x track_button.center_y = PLAYBACK_CENTER_Y track_buttons.append(track_button) self.main_layout.add_widget(track_button) track_button_x += TRACK_BUTTON_WIDTH self.track_buttons = track_buttons # Select the current track in the GUI track_buttons[self.track_id].state = 'down' leftmost_track_button = self.track_buttons[0] track_label = Label(text='Instrument Select', text_size=TRACK_LABEL_TEXT_SIZE, font_size=TRACK_LABEL_FONT_SIZE, width=TRACK_LABEL_WIDTH, height=TRACK_LABEL_HEIGHT, halign='center', valign='middle') track_label.x = leftmost_track_button.x track_label.top = leftmost_track_button.y - TRACK_LABEL_OFFSET # self.main_layout.add_widget(track_label) # SETTINGS TABS TABS_X = OUTER_PADDING + GRID_WIDTH + OUTER_PADDING TABS_Y = GRID_Y TABS_WIDTH = WINDOW_WIDTH - OUTER_PADDING - GRID_WIDTH - \ OUTER_PADDING - OUTER_PADDING TABS_HEIGHT = GRID_HEIGHT # Element is button, label, etc. Section is vertical group of elements TAB_SECTION_PADDING = 20 TAB_ELEMENT_PADDING = 10 # Note: it's a good idea to make these tabs the size of our icons, # which is 48x48 TAB_HEADER_WIDTH = 48 TAB_HEADER_HEIGHT = TAB_HEADER_WIDTH TAB_HEADER_FONT_SIZE = 20 SECTION_LABEL_FONT_SIZE = 16 SECTION_LABEL_WIDTH = TABS_WIDTH - TAB_SECTION_PADDING * 2 SECTION_LABEL_HEIGHT = 30 SECTION_LABEL_TEXT_SIZE = [SECTION_LABEL_WIDTH, SECTION_LABEL_HEIGHT] ELEMENT_LABEL_FONT_SIZE = 10 ELEMENT_LABEL_WIDTH = TABS_WIDTH - TAB_ELEMENT_PADDING * 2 ELEMENT_LABEL_HEIGHT = 20 ELEMENT_LABEL_TEXT_SIZE = [ELEMENT_LABEL_WIDTH, ELEMENT_LABEL_HEIGHT] TAB_CONTENT_HEIGHT = TABS_HEIGHT - TAB_HEADER_HEIGHT TAB_CONTENT_TOP = TABS_Y + TAB_CONTENT_HEIGHT # Create main tabbed panel tabs = TabbedPanel(tab_width=TAB_HEADER_WIDTH, tab_height=TAB_HEADER_HEIGHT, width=TABS_WIDTH, height=TABS_HEIGHT) tabs.x = TABS_X tabs.y = TABS_Y self.main_layout.add_widget(tabs) self.tabs = tabs # Music tab (default) music_tab_content = Widget(width=TABS_WIDTH, height=TAB_CONTENT_HEIGHT) tabs.default_tab_content = music_tab_content tabs.default_tab.text = "" # TODO: make these paths absolute? tabs.default_tab.background_normal = \ os.path.join(IMAGE_DIR, "audio-keyboard.png") print "@@ default tab bg: ", tabs.default_tab.background_normal tabs.default_tab.background_down = \ os.path.join(IMAGE_DIR, "audio-keyboard-down.png") # Global music options global_music_label = Label(text='Global', font_size=SECTION_LABEL_FONT_SIZE, width=SECTION_LABEL_WIDTH, height=SECTION_LABEL_HEIGHT, text_size=SECTION_LABEL_TEXT_SIZE, halign='center', valign='middle') global_music_label.center_x = tabs.center_x global_music_label.top = TAB_CONTENT_TOP - TAB_SECTION_PADDING music_tab_content.add_widget(global_music_label) MUSIC_SLIDER_WIDTH = TABS_WIDTH - 40 MUSIC_SLIDER_HEIGHT = 20 # Note: these sliders buttons have a predefined height, so we are a # slave to that height for positioning the sliders global_volume_slider = NSSlider(min=MusicPlayer.MIN_VOLUME, max=MusicPlayer.MAX_VOLUME, value=music_player.global_volume, orientation='horizontal', height=MUSIC_SLIDER_HEIGHT, width=MUSIC_SLIDER_WIDTH) global_volume_slider.bind(on_touch_move=self.change_global_volume) global_volume_slider.center_x = tabs.center_x global_volume_slider.top = global_music_label.y - TAB_ELEMENT_PADDING music_tab_content.add_widget(global_volume_slider) self.global_volume_slider = global_volume_slider self.colorables.append(global_volume_slider) global_volume_label = Label(text='Volume', font_size=ELEMENT_LABEL_FONT_SIZE, width=ELEMENT_LABEL_WIDTH, height=ELEMENT_LABEL_HEIGHT, text_size=ELEMENT_LABEL_TEXT_SIZE, halign='center', valign='middle') global_volume_label.center_x = tabs.center_x global_volume_label.top = global_volume_slider.y - TAB_ELEMENT_PADDING music_tab_content.add_widget(global_volume_label) global_tempo_slider = NSSlider(min=MusicPlayer.MIN_TEMPO, max=MusicPlayer.MAX_TEMPO, value=music_player.tempo, orientation='horizontal', height=MUSIC_SLIDER_HEIGHT, width=MUSIC_SLIDER_WIDTH) global_tempo_slider.bind(on_touch_move=self.change_global_tempo) global_tempo_slider.center_x = tabs.center_x global_tempo_slider.top = global_volume_label.y - TAB_ELEMENT_PADDING music_tab_content.add_widget(global_tempo_slider) self.global_tempo_slider = global_tempo_slider self.colorables.append(global_tempo_slider) global_tempo_label = Label(text='Tempo', font_size=ELEMENT_LABEL_FONT_SIZE, width=ELEMENT_LABEL_WIDTH, height=ELEMENT_LABEL_HEIGHT, text_size=ELEMENT_LABEL_TEXT_SIZE, halign='center', valign='middle') global_tempo_label.center_x = tabs.center_x global_tempo_label.top = global_tempo_slider.y - TAB_ELEMENT_PADDING music_tab_content.add_widget(global_tempo_label) # Instrument settings track_music_label = Label(text='Instrument', font_size=SECTION_LABEL_FONT_SIZE, width=SECTION_LABEL_WIDTH, height=SECTION_LABEL_HEIGHT, text_size=SECTION_LABEL_TEXT_SIZE, halign='center', valign='middle') track_music_label.center_x = tabs.center_x track_music_label.top = global_tempo_label.y - TAB_SECTION_PADDING music_tab_content.add_widget(track_music_label) track_volume_initial = music_player.get_volume(self.track_id) track_volume_slider = NSSlider(min=MusicPlayer.MIN_VOLUME, max=MusicPlayer.MAX_VOLUME, value=track_volume_initial, orientation='horizontal', height=MUSIC_SLIDER_HEIGHT, width=MUSIC_SLIDER_WIDTH) track_volume_slider.bind(on_touch_move=self.change_track_volume) track_volume_slider.center_x = tabs.center_x track_volume_slider.top = track_music_label.y - TAB_ELEMENT_PADDING music_tab_content.add_widget(track_volume_slider) self.track_volume_slider = track_volume_slider self.colorables.append(track_volume_slider) track_volume_label = Label(text='Volume', font_size=ELEMENT_LABEL_FONT_SIZE, width=ELEMENT_LABEL_WIDTH, height=ELEMENT_LABEL_HEIGHT, text_size=ELEMENT_LABEL_TEXT_SIZE, halign='center', valign='middle') track_volume_label.center_x = tabs.center_x track_volume_label.top = track_volume_slider.y - TAB_ELEMENT_PADDING music_tab_content.add_widget(track_volume_label) track_reverb_initial = music_player.get_reverb(self.track_id) track_reverb_slider = NSSlider(min=MusicPlayer.MIN_REVERB, max=MusicPlayer.MAX_REVERB, value=track_reverb_initial, orientation='horizontal', height=MUSIC_SLIDER_HEIGHT, width=MUSIC_SLIDER_WIDTH) track_reverb_slider.bind(on_touch_move=self.change_track_reverb) track_reverb_slider.center_x = tabs.center_x track_reverb_slider.top = track_volume_label.y - TAB_ELEMENT_PADDING music_tab_content.add_widget(track_reverb_slider) self.track_reverb_slider = track_reverb_slider self.colorables.append(track_reverb_slider) track_reverb_label = Label(text='Reverb', font_size=ELEMENT_LABEL_FONT_SIZE, width=ELEMENT_LABEL_WIDTH, height=ELEMENT_LABEL_HEIGHT, text_size=ELEMENT_LABEL_TEXT_SIZE, halign='center', valign='middle') track_reverb_label.center_x = tabs.center_x track_reverb_label.top = track_reverb_slider.y - TAB_ELEMENT_PADDING music_tab_content.add_widget(track_reverb_label) # Network tab network_tab = TabbedPanelHeader() network_tab.text = "" network_tab.background_normal = \ os.path.join(IMAGE_DIR, "network-wired-2.png") network_tab.background_down = \ os.path.join(IMAGE_DIR, "network-wired-2-down.png") tabs.add_widget(network_tab) TEXT_INPUT_HEIGHT = 30 PORT_INPUT_WIDTH = 70 IP_INPUT_WIDTH = TABS_WIDTH - TAB_SECTION_PADDING - \ PORT_INPUT_WIDTH - TAB_ELEMENT_PADDING - \ TAB_SECTION_PADDING PORT_LABEL_TEXT_SIZE = [PORT_INPUT_WIDTH, ELEMENT_LABEL_HEIGHT] IP_LABEL_TEXT_SIZE = [IP_INPUT_WIDTH, ELEMENT_LABEL_HEIGHT] NETWORK_BUTTON_WIDTH = TABS_WIDTH - TAB_SECTION_PADDING * 2 NETWORK_BUTTON_HEIGHT = 80 NETWORK_BUTTON_FONT_SIZE = 16 NETWORK_BUTTON_TEXT_SIZE = [NETWORK_BUTTON_WIDTH, NETWORK_BUTTON_HEIGHT] SERVER_PORT_TEXT = 'Server Port' SERVER_IP_TEXT = 'Server IP Address' network_tab_content = Widget(width=TABS_WIDTH, height=TAB_CONTENT_HEIGHT) network_tab.content = network_tab_content # Server input labels server_port_label = Label(text=SERVER_PORT_TEXT, width=PORT_INPUT_WIDTH, height=ELEMENT_LABEL_HEIGHT, text_size=PORT_LABEL_TEXT_SIZE, font_size=ELEMENT_LABEL_FONT_SIZE) server_port_label.top = TAB_CONTENT_TOP - TAB_SECTION_PADDING server_port_label.x = TABS_X + TAB_SECTION_PADDING network_tab_content.add_widget(server_port_label) server_ip_label = Label(text=SERVER_IP_TEXT, width=IP_INPUT_WIDTH, height=ELEMENT_LABEL_HEIGHT, text_size=IP_LABEL_TEXT_SIZE, font_size=ELEMENT_LABEL_FONT_SIZE) server_ip_label.top = server_port_label.top server_ip_label.x = server_port_label.right + TAB_ELEMENT_PADDING network_tab_content.add_widget(server_ip_label) # Server startup input server_port_input = NSTextInput(text='', width=PORT_INPUT_WIDTH, height=TEXT_INPUT_HEIGHT, multiline=False) server_port_input.bind(focus=self.select_text_input) server_port_input.original_text = SERVER_PORT_TEXT server_port_input.x = server_port_label.x server_port_input.top = server_port_label.y - TAB_ELEMENT_PADDING network_tab_content.add_widget(server_port_input) self.server_port_input = server_port_input server_ip_input = NSTextInput(text='', width=IP_INPUT_WIDTH, height=TEXT_INPUT_HEIGHT, multiline=False) server_ip_input.bind(focus=self.select_text_input) server_ip_input.original_text=SERVER_IP_TEXT server_ip_input.x = server_ip_label.x server_ip_input.top = server_port_input.top network_tab_content.add_widget(server_ip_input) self.server_ip_input = server_ip_input server_start_button = NSDisableButton(text='Start server', width=NETWORK_BUTTON_WIDTH, height=NETWORK_BUTTON_HEIGHT, text_size=NETWORK_BUTTON_TEXT_SIZE, font_size=NETWORK_BUTTON_FONT_SIZE, halign='center', valign='middle') server_start_button.bind(on_press=self.start_server) server_start_button.center_x = tabs.center_x server_start_button.top = server_ip_input.y - TAB_ELEMENT_PADDING network_tab_content.add_widget(server_start_button) self.server_start_button = server_start_button join_server_button = NSDisableButton(text='Join server', width=NETWORK_BUTTON_WIDTH, height=NETWORK_BUTTON_HEIGHT, text_size=NETWORK_BUTTON_TEXT_SIZE, font_size=NETWORK_BUTTON_FONT_SIZE, halign='center', valign='middle') join_server_button.bind(on_press=self.ask_join_server) join_server_button.x = server_start_button.x join_server_button.top = server_start_button.y - TAB_ELEMENT_PADDING network_tab_content.add_widget(join_server_button) self.join_server_button = join_server_button end_connection_button = NSDisableButton(text='End connection', width=NETWORK_BUTTON_WIDTH, height=NETWORK_BUTTON_HEIGHT, text_size=NETWORK_BUTTON_TEXT_SIZE, font_size=NETWORK_BUTTON_FONT_SIZE, halign='center', valign='middle') end_connection_button.bind(on_press=self.ask_end_connection) end_connection_button.disable() end_connection_button.x = server_start_button.x end_connection_button.top = join_server_button.y - TAB_ELEMENT_PADDING network_tab_content.add_widget(end_connection_button) self.end_connection_button = end_connection_button # System options tab system_tab = TabbedPanelHeader() system_tab.background_normal = \ os.path.join(IMAGE_DIR, "media-floppy.png") system_tab.background_down = \ os.path.join(IMAGE_DIR, "media-floppy-down.png") tabs.add_widget(system_tab) system_tab_content = Widget(width=TABS_WIDTH, height=TAB_CONTENT_HEIGHT) system_tab.content = system_tab_content NUM_SYSTEM_BUTTONS = 3 SYSTEM_BUTTON_PADDING = 20 SYSTEM_BUTTON_FONT_SIZE = 24 SYSTEM_BUTTON_WIDTH = TABS_WIDTH - SYSTEM_BUTTON_PADDING * 2 SYSTEM_BUTTON_HEIGHT = (TAB_CONTENT_HEIGHT - SYSTEM_BUTTON_PADDING * (NUM_SYSTEM_BUTTONS + 1)) / NUM_SYSTEM_BUTTONS SYSTEM_BUTTON_TEXT_SIZE = [SYSTEM_BUTTON_WIDTH, SYSTEM_BUTTON_HEIGHT] # Load button load_button = NSDisableButton(text='Load', width=SYSTEM_BUTTON_WIDTH, height=SYSTEM_BUTTON_HEIGHT, text_size=SYSTEM_BUTTON_TEXT_SIZE, font_size=SYSTEM_BUTTON_FONT_SIZE, halign='center', valign='middle') load_button.bind(on_press=self.load_file) load_button.center_x = tabs.center_x load_button.top = TAB_CONTENT_TOP - SYSTEM_BUTTON_PADDING system_tab_content.add_widget(load_button) self.load_button = load_button # Save button save_button = NSDisableButton(text='Save', width=SYSTEM_BUTTON_WIDTH, height=SYSTEM_BUTTON_HEIGHT, text_size=SYSTEM_BUTTON_TEXT_SIZE, font_size=SYSTEM_BUTTON_FONT_SIZE, halign='center', valign='middle') save_button.bind(on_press=self.save_file) save_button.center_x = tabs.center_x save_button.top = load_button.y - SYSTEM_BUTTON_PADDING system_tab_content.add_widget(save_button) # Quit button quit_button = NSDisableButton(text='Quit', width=SYSTEM_BUTTON_WIDTH, height=SYSTEM_BUTTON_HEIGHT, text_size=SYSTEM_BUTTON_TEXT_SIZE, font_size=SYSTEM_BUTTON_FONT_SIZE, halign='center', valign='middle') quit_button.bind(on_press=self.request_exit) quit_button.center_x = tabs.center_x quit_button.top = save_button.y - SYSTEM_BUTTON_PADDING system_tab_content.add_widget(quit_button) # APPLICATION TITLE TITLE_WIDTH = TABS_WIDTH TITLE_HEIGHT = 50 TITLE_TEXT_SIZE = [TITLE_WIDTH, TITLE_HEIGHT] TITLE_FONT_SIZE = 30 SUBTITLE_WIDTH = TITLE_WIDTH SUBTITLE_HEIGHT = 30 SUBTITLE_TEXT_SIZE = [SUBTITLE_WIDTH, SUBTITLE_HEIGHT] SUBTITLE_FONT_SIZE = 15 TITLE_X = TABS_X title_label = Label(text='NetSeq', width=TITLE_WIDTH, height=TITLE_HEIGHT, halign='center', valign='middle', text_size=TITLE_TEXT_SIZE, font_size=TITLE_FONT_SIZE) title_label.top = PLAYBACK_TOP title_label.x = TITLE_X self.main_layout.add_widget(title_label) subtitle_label = Label(text='Music with Friends', width=SUBTITLE_WIDTH, height=SUBTITLE_HEIGHT, text_size=SUBTITLE_TEXT_SIZE, font_size=SUBTITLE_FONT_SIZE, halign='center', valign='middle') subtitle_label.top = title_label.y subtitle_label.x = TITLE_X self.main_layout.add_widget(subtitle_label) # Finishing steps self.set_color(self.track_id) self.reload_row_labels()
def __init__(self): super(MyWidget, self).__init__() self.cos_table = CosTable([(0,0), (100,1), (1000,.25), (8191,0)]) self.osc_out = Osc(table=self.cos_table, freq=220) # For each element of the GUI, make sure to # 1. Create a unique reference attached to this object for # future manipulation. e.g. self.main_box = main_box after you've # created a BoxLayout called main_box # Interesting things about Kivy UI programming: # 1. y starts counting from bottom left # 2. set size_hint to '[None, None]' for all new widgets if defining # the size and manually # MAIN LAYOUT main_layout = FloatLayout(size=[800,600], orientation='horizontal') self.main_layout = main_layout self.add_widget(main_layout) # TABS WITH GAME CONTROL # Tabbed panel for music, settings, network # Y-position of tabbed panel depends on tab height! tabs = TabbedPanel(tab_width=50, size=[160, 480], pos=[0, 120], size_hint=(None, None)) self.tabs = tabs main_layout.add_widget(tabs) # Music tab music_button = Button(text="Music things") tabs.default_tab_content = music_button tabs.default_tab.text = "Music" # Network tab network_tab = TabbedPanelHeader(text="Net") tabs.add_widget(network_tab) network_layout = BoxLayout(orientation="vertical", padding=10) server_button = Button(text="Start server") ip_label = Label(text="Your IP is\n123.234.456.789"); client_label = Label(text="Connect to server: "); server_ip_input = TextInput(text="Enter server IP") network_layout.add_widget(server_button) network_layout.add_widget(ip_label) network_layout.add_widget(client_label) network_layout.add_widget(server_ip_input) network_tab.content = network_layout # Global tab global_tab = TabbedPanelHeader(text="Global") tabs.add_widget(global_tab) global_button = Button(text="Global things") global_tab.content = global_button # END TABS # RIGHT-SIDE LAYOUT: NOTES GRID AND PLAYBACK UI music_layout = FloatLayout(size=[640, 600], pos=[161, 0], size_hint=[None, None]) self.music_layout = music_layout main_layout.add_widget(music_layout) # NOTES GRID # Right now Kivy isn't really paying attention to button size and # padding. Later on, we'll fix this with a FloatLayout note_rows = note_cols = 8 padding_between = 5 note_grid = GridLayout(size=[640, 480], pos=[161, 121], size_hint=[None, None], rows=note_rows, cols=note_cols, padding=padding_between) music_layout.add_widget(note_grid) edge_padding = 30 grid_start_x = note_grid.x + edge_padding grid_end_x = note_grid.right - edge_padding grid_start_y = note_grid.y + edge_padding grid_end_y = note_grid.top - edge_padding notes_matrix = [] note_width = grid_end_x - grid_start_x - padding_between * \ (note_rows - 1) note_height = grid_end_y - grid_start_y - padding_between * \ (note_rows - 1) # Adding a rectangle to test playback indicator self.playback_indicator = Rectangle(pos=[161,121], size=[75, 480]) self.playback_canvas = Canvas() playback_widget = Widget() self.playback_canvas.add(self.playback_indicator) self.playback_canvas.opacity = .5 playback_widget.canvas = self.playback_canvas music_layout.add_widget(playback_widget) for row in range(0, note_rows): for col in range(0, note_cols): new_id = str(row) + "," + str(col) new_button = ToggleButton(text=new_id, id=new_id, width=note_width, height=note_height) active_color = (1, 0, 0, 1) new_button.background_color = active_color new_button.bind(on_press=self.play_note) note_grid.add_widget(new_button) # PLAYBACK BUTTONS playback = Button(text="For playback", size=[640, 120], size_hint=[None, None], pos=[161, 0]) music_layout.add_widget(playback)