Example #1
0
    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
Example #2
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
Example #5
0
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))
Example #6
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)
Example #7
0
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()
Example #8
0
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
Example #10
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)
Example #11
0
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()
Example #12
0
File: gui.py Project: tnason/NetSeq
    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()
Example #13
0
    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)