Exemplo n.º 1
0
    def reset(self, bma, bpa_durations, pal_ani_durations, chunks_surfaces):
        self.reset_bma(bma)

        self.animation_context = AnimationContext(chunks_surfaces,
                                                  bpa_durations,
                                                  pal_ani_durations)
        self._tileset_drawer_overlay: Optional[MapTilesetOverlay] = None
Exemplo n.º 2
0
    def __init__(
        self,
        draw_area: Widget,
        tile_mappings: Union[None, List[TilemapEntry]],
        bpa_durations: int,
        pal_ani_durations: int,
        # Format: tile_surfaces[pal][tile_idx][pal_frame][frame]
        tile_surfaces: List[List[List[List[cairo.Surface]]]]):
        """

        :param draw_area: Widget to draw on
        :param tile_mappings: Either one tile or 3x3 tiles to draw. Can also be set later
        :param bpa_durations:
        :param tile_surfaces: List of all surfaces
        """
        self.draw_area = draw_area

        self.tile_mappings = tile_mappings

        self.tiling_width = 3
        self.tiling_height = 3

        if tile_mappings:
            self.set_tile_mappings(tile_mappings)

        self.animation_context = AnimationContext(tile_surfaces, bpa_durations,
                                                  pal_ani_durations)

        self.scale = 1

        self.drawing_is_active = False
Exemplo n.º 3
0
    def reset(self, bma, bpa_durations, pal_ani_durations, chunks_surfaces):
        if isinstance(bma, Bma):
            self.tiling_width = bma.tiling_width
            self.tiling_height = bma.tiling_height
            self.mappings = [bma.layer0, bma.layer1]
            self.width_in_chunks = bma.map_width_chunks
            self.height_in_chunks = bma.map_height_chunks
            self.width_in_tiles = bma.map_width_camera
            self.height_in_tiles = bma.map_height_camera
            self.collision1 = bma.collision
            self.collision2 = bma.collision2
            self.data_layer = bma.unknown_data_block
        else:
            self.tiling_width = 3
            self.tiling_height = 3
            self.mappings = [[], []]
            self.width_in_chunks = 1
            self.height_in_chunks = 1
            self.width_in_tiles = None
            self.height_in_tiles = None
            self.collision1 = None
            self.collision2 = None
            self.data_layer = None

        self.animation_context = AnimationContext(chunks_surfaces,
                                                  bpa_durations,
                                                  pal_ani_durations)
Exemplo n.º 4
0
    def reset(self, dbg, pal_ani_durations, chunks_surfaces):
        if isinstance(dbg, Dbg):
            self.mappings = dbg.mappings
        else:
            self.mappings = []

        self.animation_context = AnimationContext([chunks_surfaces], 0,
                                                  pal_ani_durations)
Exemplo n.º 5
0
 def reset_surfaces(self, chunks_surfaces):
     self.animation_context = AnimationContext([chunks_surfaces], 1, self.pal_ani_durations)
Exemplo n.º 6
0
class DungeonChunkDrawer:
    def __init__(
            self, draw_area: Widget, pal_ani_durations: int,
            # chunks_surfaces[chunk_idx][palette_animation_frame][frame]
            # Since we only have static tiles, frames should always only contain one element.
            chunks_surfaces: Iterable[Iterable[List[cairo.Surface]]]
    ):
        """
        Initialize a drawer...
        :param draw_area:  Widget to draw on.
        :param pal_ani_durations: How many frames to hold a palette animation.
        :param chunks_surfaces: Bg controller format chunk surfaces
        """
        self.draw_area = draw_area

        self.reset(pal_ani_durations, chunks_surfaces)

        self.scale = 2

        self.drawing_is_active = False

    # noinspection PyAttributeOutsideInit
    def reset(self, pal_ani_durations, chunks_surfaces):
        # Chunk to draw
        self.chunkidx = 0
        self.pal_ani_durations = pal_ani_durations
        self.reset_surfaces(chunks_surfaces)

    # noinspection PyAttributeOutsideInit
    def reset_surfaces(self, chunks_surfaces):
        self.animation_context = AnimationContext([chunks_surfaces], 1, self.pal_ani_durations)

    def start(self):
        """Start drawing on the DrawingArea"""
        self.drawing_is_active = True
        if isinstance(self.draw_area, Gtk.DrawingArea):
            self.draw_area.connect('draw', self.draw)
        self.draw_area.queue_draw()
        GLib.timeout_add(int(1000 / FPS), self._tick)

    def stop(self):
        self.drawing_is_active = False

    def _tick(self):
        if self.draw_area is None:
            return False
        if self.draw_area is not None and self.draw_area.get_parent() is None:
            # XXX: Gtk doesn't remove the widget on switch sometimes...
            self.draw_area.destroy()
            return False
        self.animation_context.advance()
        if EventManager.instance().get_if_main_window_has_fous():
            self.draw_area.queue_draw()
        return self.drawing_is_active

    def draw(self, wdg, ctx: cairo.Context):
        ctx.set_antialias(cairo.Antialias.NONE)
        ctx.scale(self.scale, self.scale)

        chunks_at_frame = self.animation_context.current()[0]
        if 0 <= self.chunkidx < len(chunks_at_frame):
            chunk = chunks_at_frame[self.chunkidx]
            ctx.set_source_surface(chunk, 0, 0)
            ctx.get_source().set_filter(cairo.Filter.NEAREST)
            ctx.paint()

    def set_scale(self, v):
        self.scale = v
Exemplo n.º 7
0
class DrawerTiled:
    def __init__(
        self,
        draw_area: Widget,
        tile_mappings: Union[None, List[TilemapEntry]],
        bpa_durations: int,
        pal_ani_durations: int,
        # Format: tile_surfaces[pal][tile_idx][pal_frame][frame]
        tile_surfaces: List[List[List[List[cairo.Surface]]]]):
        """

        :param draw_area: Widget to draw on
        :param tile_mappings: Either one tile or 3x3 tiles to draw. Can also be set later
        :param bpa_durations:
        :param tile_surfaces: List of all surfaces
        """
        self.draw_area = draw_area

        self.tile_mappings = tile_mappings

        self.tiling_width = 3
        self.tiling_height = 3

        if tile_mappings:
            self.set_tile_mappings(tile_mappings)

        self.animation_context = AnimationContext(tile_surfaces, bpa_durations,
                                                  pal_ani_durations)

        self.scale = 1

        self.drawing_is_active = False

    def set_tile_mappings(self, tile_mappings):
        self.tile_mappings = tile_mappings
        if len(self.tile_mappings) == 1:
            self.width = BPC_TILE_DIM
            self.height = BPC_TILE_DIM
        elif len(self.tile_mappings) == self.tiling_width * self.tiling_height:
            self.width = BPC_TILE_DIM * self.tiling_width
            self.height = BPC_TILE_DIM * self.tiling_height
        else:
            raise ValueError("Only 1x1 or 3x3 supported.")

    def start(self):
        """Start drawing on the DrawingArea"""
        self.drawing_is_active = True
        if isinstance(self.draw_area, Gtk.DrawingArea):
            self.draw_area.connect('draw', self.draw)
        self.draw_area.queue_draw()
        GLib.timeout_add(int(1000 / FPS), self._tick)

    def stop(self):
        self.drawing_is_active = False

    def _tick(self):
        if self.draw_area is None:
            return False
        if self.draw_area is not None and self.draw_area.get_parent() is None:
            # XXX: Gtk doesn't remove the widget on switch sometimes...
            self.draw_area.destroy()
            return False
        self.animation_context.advance()
        if EventManager.instance().get_if_main_window_has_fous():
            self.draw_area.queue_draw()
        return self.drawing_is_active

    def draw(self, wdg, ctx: cairo.Context):
        ctx.set_antialias(cairo.Antialias.NONE)
        ctx.scale(self.scale, self.scale)

        # Background
        ctx.set_source_rgb(0, 0, 0)
        ctx.rectangle(0, 0, self.width, self.height)
        ctx.fill()

        matrix_x_flip = cairo.Matrix(-1, 0, 0, 1, BPC_TILE_DIM, 0)
        matrix_y_flip = cairo.Matrix(1, 0, 0, -1, 0, BPC_TILE_DIM)
        tiles_for_pals = self.animation_context.current()
        for i, mapping in enumerate(self.tile_mappings):
            tiles_for_frame = tiles_for_pals[mapping.pal_idx]
            tile_at_pos = mapping.idx
            if 0 < tile_at_pos < len(tiles_for_frame):
                tile = tiles_for_frame[tile_at_pos]
                if mapping.flip_x:
                    ctx.transform(matrix_x_flip)
                if mapping.flip_y:
                    ctx.transform(matrix_y_flip)
                ctx.set_source_surface(tile, 0, 0)
                ctx.get_source().set_filter(cairo.Filter.NEAREST)
                ctx.paint()
                if mapping.flip_x:
                    ctx.transform(matrix_x_flip)
                if mapping.flip_y:
                    ctx.transform(matrix_y_flip)
            if (i + 1) % self.tiling_width == 0:
                # Move to beginning of next line
                ctx.translate(-BPC_TILE_DIM * (self.tiling_width - 1),
                              BPC_TILE_DIM)
            else:
                # Move to next tile in line
                ctx.translate(BPC_TILE_DIM, 0)
Exemplo n.º 8
0
class Drawer:
    def __init__(
        self,
        draw_area: Widget,
        dbg: Union[Dbg, None],
        pal_ani_durations: int,
        # chunks_surfaces[chunk_idx][palette_animation_frame][frame]
        chunks_surfaces: Iterable[Iterable[List[cairo.Surface]]]):
        """
        Initialize a drawer...
        :param draw_area:  Widget to draw on.
        :param dbg: Either a DBG with chunk indexes or None, has to be set manually then for drawing
        :param chunks_surfaces: Bg controller format chunk surfaces
        """
        self.draw_area = draw_area

        self.reset(dbg, pal_ani_durations, chunks_surfaces)

        self.draw_chunk_grid = True
        self.draw_tile_grid = True
        self.use_pink_bg = False

        # Interaction
        self.interaction_chunks_selected_id = 0

        self.mouse_x = 99999
        self.mouse_y = 99999

        self.tiling_width = DBG_TILING_DIM
        self.tiling_height = DBG_TILING_DIM
        self.width_in_chunks = DBG_WIDTH_AND_HEIGHT
        self.height_in_chunks = DBG_WIDTH_AND_HEIGHT
        self.width_in_tiles = DBG_WIDTH_AND_HEIGHT * 3
        self.height_in_tiles = DBG_WIDTH_AND_HEIGHT * 3

        self.selection_plugin = SelectionDrawerPlugin(
            DPCI_TILE_DIM, DPCI_TILE_DIM, self.selection_draw_callback)
        self.tile_grid_plugin = GridDrawerPlugin(DPCI_TILE_DIM, DPCI_TILE_DIM)
        self.chunk_grid_plugin = GridDrawerPlugin(
            DPCI_TILE_DIM * self.tiling_width,
            DPCI_TILE_DIM * self.tiling_height,
            color=(0.15, 0.15, 0.15, 0.25))

        self.scale = 1

        self.drawing_is_active = False

    # noinspection PyAttributeOutsideInit
    def reset(self, dbg, pal_ani_durations, chunks_surfaces):
        if isinstance(dbg, Dbg):
            self.mappings = dbg.mappings
        else:
            self.mappings = []

        self.animation_context = AnimationContext([chunks_surfaces], 0,
                                                  pal_ani_durations)

    def start(self):
        """Start drawing on the DrawingArea"""
        self.drawing_is_active = True
        if isinstance(self.draw_area, Gtk.DrawingArea):
            self.draw_area.connect('draw', self.draw)
        self.draw_area.queue_draw()
        GLib.timeout_add(int(1000 / FPS), self._tick)

    def stop(self):
        self.drawing_is_active = False

    def _tick(self):
        if self.draw_area is None:
            return False
        if self.draw_area is not None and self.draw_area.get_parent() is None:
            # XXX: Gtk doesn't remove the widget on switch sometimes...
            self.draw_area.destroy()
            return False
        self.animation_context.advance()
        if EventManager.instance().get_if_main_window_has_fous():
            self.draw_area.queue_draw()
        return self.drawing_is_active

    def draw(self, wdg, ctx: cairo.Context, do_translates=True):
        ctx.set_antialias(cairo.Antialias.NONE)
        ctx.scale(self.scale, self.scale)
        chunk_width = self.tiling_width * DPCI_TILE_DIM
        chunk_height = self.tiling_height * DPCI_TILE_DIM
        # Background
        if not self.use_pink_bg:
            ctx.set_source_rgb(0, 0, 0)
        else:
            ctx.set_source_rgb(1.0, 0, 1.0)
        ctx.rectangle(
            0, 0, self.width_in_chunks * self.tiling_width * DPCI_TILE_DIM,
            self.height_in_chunks * self.tiling_height * DPCI_TILE_DIM)
        ctx.fill()

        # Layers
        for chunks_at_frame in self.animation_context.current():
            for i, chunk_at_pos in enumerate(self.mappings):
                if 0 < chunk_at_pos < len(chunks_at_frame):
                    chunk = chunks_at_frame[chunk_at_pos]
                    ctx.set_source_surface(chunk, 0, 0)
                    ctx.get_source().set_filter(cairo.Filter.NEAREST)
                    ctx.paint()
                if (i + 1) % self.width_in_chunks == 0:
                    # Move to beginning of next line
                    if do_translates:
                        ctx.translate(
                            -chunk_width * (self.width_in_chunks - 1),
                            chunk_height)
                else:
                    # Move to next tile in line
                    if do_translates:
                        ctx.translate(chunk_width, 0)

            # Move back to beginning
            if do_translates:
                ctx.translate(0, -chunk_height * self.height_in_chunks)
            break

        size_w, size_h = self.draw_area.get_size_request()
        size_w /= self.scale
        size_h /= self.scale
        # Selection
        self.selection_plugin.set_size(self.tiling_width * DPCI_TILE_DIM,
                                       self.tiling_height * DPCI_TILE_DIM)
        self.selection_plugin.draw(ctx, size_w, size_h, self.mouse_x,
                                   self.mouse_y)

        # Tile Grid
        if self.draw_tile_grid:
            self.tile_grid_plugin.draw(ctx, size_w, size_h, self.mouse_x,
                                       self.mouse_y)

        # Chunk Grid
        if self.draw_chunk_grid:
            self.chunk_grid_plugin.draw(ctx, size_w, size_h, self.mouse_x,
                                        self.mouse_y)
        return True

    def selection_draw_callback(self, ctx: cairo.Context, x: int, y: int):
        # Draw a chunk
        chunks_at_frame = self.animation_context.current()[0]
        ctx.set_source_surface(
            chunks_at_frame[self.interaction_chunks_selected_id], x, y)
        ctx.get_source().set_filter(cairo.Filter.NEAREST)
        ctx.paint()

    def set_mouse_position(self, x, y):
        self.mouse_x = x
        self.mouse_y = y

    def set_selected_chunk(self, chunk_id):
        self.interaction_chunks_selected_id = chunk_id

    def get_selected_chunk_id(self):
        return self.interaction_chunks_selected_id

    def set_draw_chunk_grid(self, v):
        self.draw_chunk_grid = v

    def set_draw_tile_grid(self, v):
        self.draw_tile_grid = v

    def set_pink_bg(self, v):
        self.use_pink_bg = v

    def set_scale(self, v):
        self.scale = v
Exemplo n.º 9
0
class Drawer:
    def __init__(
        self,
        draw_area: Widget,
        bma: Union[BmaProtocol, None],
        bpa_durations: int,
        pal_ani_durations: int,
        # chunks_surfaces[layer_number][chunk_idx][palette_animation_frame][frame]
        chunks_surfaces: Iterable[Iterable[Iterable[Iterable[cairo.Surface]]]]
    ):
        """
        Initialize a drawer...
        :param draw_area:  Widget to draw on.
        :param bma: Either a BMA with tile indexes or None, has to be set manually then for drawing
        :param bpa_durations: How many frames to hold a BPA animation tile
        :param chunks_surfaces: Bg controller format chunk surfaces
        """
        self.draw_area = draw_area

        self.reset(bma, bpa_durations, pal_ani_durations, chunks_surfaces)

        self.draw_chunk_grid = False
        self.draw_tile_grid = False
        self.use_pink_bg = False

        # Interaction
        self.interaction_mode = DrawerInteraction.NONE
        self.interaction_chunks_selected_id = 0
        self.interaction_col_solid = False
        self.interaction_dat_value = 0

        self.mouse_x = 99999
        self.mouse_y = 99999
        self.edited_layer = -1
        self.edited_collision = -1
        self.show_only_edited_layer = False
        self.dim_layers = False
        self.draw_collision1 = False
        self.draw_collision2 = False
        self.draw_data_layer = False

        self.selection_plugin = SelectionDrawerPlugin(
            BPC_TILE_DIM, BPC_TILE_DIM, self.selection_draw_callback)
        self.tile_grid_plugin = GridDrawerPlugin(BPC_TILE_DIM, BPC_TILE_DIM)
        self.chunk_grid_plugin = GridDrawerPlugin(
            BPC_TILE_DIM * self.tiling_width,
            BPC_TILE_DIM * self.tiling_height,
            color=(0.15, 0.15, 0.15, 0.25))

        self.scale = 1

        self.drawing_is_active = False

    def reset_bma(self, bma):
        if isinstance(bma, BmaProtocol):
            self.tiling_width = bma.tiling_width
            self.tiling_height = bma.tiling_height
            self.mappings: List[Sequence[int]] = [bma.layer0,
                                                  bma.layer1]  # type: ignore
            self.width_in_chunks = bma.map_width_chunks
            self.height_in_chunks = bma.map_height_chunks
            self.width_in_tiles: Optional[u8] = bma.map_width_camera
            self.height_in_tiles: Optional[u8] = bma.map_height_camera
            self.collision1 = bma.collision
            self.collision2 = bma.collision2
            self.data_layer = bma.unknown_data_block
        else:
            self.tiling_width = u8(3)
            self.tiling_height = u8(3)
            self.mappings = [[], []]
            self.width_in_chunks = u8(1)
            self.height_in_chunks = u8(1)
            self.width_in_tiles = None
            self.height_in_tiles = None
            self.collision1 = None
            self.collision2 = None
            self.data_layer = None

    # noinspection PyAttributeOutsideInit
    def reset(self, bma, bpa_durations, pal_ani_durations, chunks_surfaces):
        self.reset_bma(bma)

        self.animation_context = AnimationContext(chunks_surfaces,
                                                  bpa_durations,
                                                  pal_ani_durations)
        self._tileset_drawer_overlay: Optional[MapTilesetOverlay] = None

    def start(self):
        """Start drawing on the DrawingArea"""
        self.drawing_is_active = True
        if isinstance(self.draw_area, Gtk.DrawingArea):
            self.draw_area.connect('draw', self.draw)
        self.draw_area.queue_draw()
        GLib.timeout_add(int(1000 / FPS), self._tick)

    def stop(self):
        self.drawing_is_active = False

    def _tick(self):
        if self.draw_area is None:
            return False
        if self.draw_area is not None and self.draw_area.get_parent() is None:
            # XXX: Gtk doesn't remove the widget on switch sometimes...
            self.draw_area.destroy()
            return False
        self.animation_context.advance()
        if EventManager.instance().get_if_main_window_has_fous():
            self.draw_area.queue_draw()
        return self.drawing_is_active

    def draw(self, wdg, ctx: cairo.Context, do_translates=True):
        ctx.set_antialias(cairo.Antialias.NONE)
        ctx.scale(self.scale, self.scale)
        chunk_width = self.tiling_width * BPC_TILE_DIM
        chunk_height = self.tiling_height * BPC_TILE_DIM
        # Background
        if not self.use_pink_bg:
            ctx.set_source_rgb(0, 0, 0)
        else:
            ctx.set_source_rgb(1.0, 0, 1.0)
        ctx.rectangle(
            0, 0, self.width_in_chunks * self.tiling_width * BPC_TILE_DIM,
            self.height_in_chunks * self.tiling_height * BPC_TILE_DIM)
        ctx.fill()

        if self._tileset_drawer_overlay is not None and self._tileset_drawer_overlay.enabled:
            self._tileset_drawer_overlay.draw_full(ctx, self.mappings[0],
                                                   self.width_in_chunks,
                                                   self.height_in_chunks)
        else:
            # Layers
            for layer_idx, chunks_at_frame in enumerate(
                    self.animation_context.current()):
                if self.show_only_edited_layer and layer_idx != self.edited_layer:
                    continue
                current_layer_mappings = self.mappings[layer_idx]
                for i, chunk_at_pos in enumerate(current_layer_mappings):
                    if 0 < chunk_at_pos < len(chunks_at_frame):
                        chunk = chunks_at_frame[chunk_at_pos]
                        ctx.set_source_surface(chunk, 0, 0)
                        ctx.get_source().set_filter(cairo.Filter.NEAREST)
                        if self.edited_layer != -1 and layer_idx > 0 and layer_idx != self.edited_layer:
                            # For Layer 1 if not the current edited: Set an alpha mask
                            ctx.paint_with_alpha(0.7)
                        else:
                            ctx.paint()
                    if (i + 1) % self.width_in_chunks == 0:
                        # Move to beginning of next line
                        if do_translates:
                            ctx.translate(
                                -chunk_width * (self.width_in_chunks - 1),
                                chunk_height)
                    else:
                        # Move to next tile in line
                        if do_translates:
                            ctx.translate(chunk_width, 0)

                # Move back to beginning
                if do_translates:
                    ctx.translate(0, -chunk_height * self.height_in_chunks)

                if (self.edited_layer != -1 and layer_idx < 1 and layer_idx != self.edited_layer) \
                    or (layer_idx == 1 and self.dim_layers) \
                    or (layer_idx == 0 and self.animation_context.num_layers < 2 and self.dim_layers):
                    # For Layer 0 if not the current edited: Draw dark rectangle
                    # or for layer 1 if dim layers
                    # ...or for layer 0 if dim layers and no second layer
                    ctx.set_source_rgba(0, 0, 0, 0.5)
                    ctx.rectangle(
                        0, 0, self.width_in_chunks * self.tiling_width *
                        BPC_TILE_DIM, self.height_in_chunks *
                        self.tiling_height * BPC_TILE_DIM)
                    ctx.fill()

        # Col 1 and 2
        for col_index, should_draw in enumerate(
            [self.draw_collision1, self.draw_collision2]):
            if should_draw:
                if col_index == 0:
                    ctx.set_source_rgba(1, 0, 0, 0.4)
                    col: Sequence[bool] = self.collision1  # type: ignore
                else:
                    ctx.set_source_rgba(0, 1, 0, 0.4)
                    col = self.collision2  # type: ignore

                for i, c in enumerate(col):
                    if c:
                        ctx.rectangle(0, 0, BPC_TILE_DIM, BPC_TILE_DIM)
                        ctx.fill()
                    if (i + 1) % self.width_in_tiles == 0:  # type: ignore
                        # Move to beginning of next line
                        if do_translates:
                            ctx.translate(-BPC_TILE_DIM *
                                          (self.width_in_tiles - 1),
                                          BPC_TILE_DIM)  # type: ignore
                    else:
                        # Move to next tile in line
                        if do_translates:
                            ctx.translate(BPC_TILE_DIM, 0)
                # Move back to beginning
                if do_translates:
                    ctx.translate(0, -BPC_TILE_DIM *
                                  self.height_in_tiles)  # type: ignore

        # Data
        if self.draw_data_layer:
            ctx.select_font_face("monospace", cairo.FONT_SLANT_NORMAL,
                                 cairo.FONT_WEIGHT_NORMAL)
            ctx.set_font_size(6)
            ctx.set_source_rgb(0, 0, 1)
            assert self.data_layer is not None
            for i, dat in enumerate(self.data_layer):
                if dat > 0:
                    ctx.move_to(0, BPC_TILE_DIM - 2)
                    ctx.show_text(f"{dat:02x}")
                if (i + 1) % self.width_in_tiles == 0:  # type: ignore
                    # Move to beginning of next line
                    if do_translates:
                        ctx.translate(-BPC_TILE_DIM *
                                      (self.width_in_tiles - 1),
                                      BPC_TILE_DIM)  # type: ignore
                else:
                    # Move to next tile in line
                    if do_translates:
                        ctx.translate(BPC_TILE_DIM, 0)
            # Move back to beginning
            if do_translates:
                ctx.translate(0, -BPC_TILE_DIM *
                              self.height_in_tiles)  # type: ignore

        size_w, size_h = self.draw_area.get_size_request()
        size_w /= self.scale
        size_h /= self.scale
        # Selection
        if self.interaction_mode == DrawerInteraction.CHUNKS:
            self.selection_plugin.set_size(self.tiling_width * BPC_TILE_DIM,
                                           self.tiling_height * BPC_TILE_DIM)
        else:
            self.selection_plugin.set_size(BPC_TILE_DIM, BPC_TILE_DIM)
        self.selection_plugin.draw(ctx, size_w, size_h, self.mouse_x,
                                   self.mouse_y)

        # Tile Grid
        if self.draw_tile_grid:
            self.tile_grid_plugin.draw(ctx, size_w, size_h, self.mouse_x,
                                       self.mouse_y)

        # Chunk Grid
        if self.draw_chunk_grid:
            self.chunk_grid_plugin.draw(ctx, size_w, size_h, self.mouse_x,
                                        self.mouse_y)
        return True

    def selection_draw_callback(self, ctx: cairo.Context, x: int, y: int):
        if self.interaction_mode == DrawerInteraction.CHUNKS:
            # Draw a chunk
            chunks_at_frame = self.animation_context.current()[
                self.edited_layer]
            ctx.set_source_surface(
                chunks_at_frame[self.interaction_chunks_selected_id], x, y)
            ctx.get_source().set_filter(cairo.Filter.NEAREST)
            ctx.paint()
        elif self.interaction_mode == DrawerInteraction.COL:
            # Draw collision
            if self.interaction_col_solid:
                ctx.set_source_rgba(1, 0, 0, 1)
                ctx.rectangle(x, y, BPC_TILE_DIM, BPC_TILE_DIM)
                ctx.fill()
        elif self.interaction_mode == DrawerInteraction.DAT:
            # Draw data
            if self.interaction_dat_value > 0:
                ctx.select_font_face("monospace", cairo.FONT_SLANT_NORMAL,
                                     cairo.FONT_WEIGHT_NORMAL)
                ctx.set_font_size(6)
                ctx.set_source_rgb(1, 1, 1)
                ctx.move_to(x, y + BPC_TILE_DIM - 2)
                ctx.show_text(f"{self.interaction_dat_value:02x}")

    def set_mouse_position(self, x, y):
        self.mouse_x = x
        self.mouse_y = y

    def set_selected_chunk(self, chunk_id):
        self.interaction_chunks_selected_id = chunk_id

    def get_selected_chunk_id(self):
        return self.interaction_chunks_selected_id

    def set_interaction_col_solid(self, v):
        self.interaction_col_solid = v

    def get_interaction_col_solid(self):
        return self.interaction_col_solid

    def set_interaction_dat_value(self, v):
        self.interaction_dat_value = v

    def get_interaction_dat_value(self):
        return self.interaction_dat_value

    def set_edited_layer(self, layer_id):
        # The layer that is not edited will be drawn with a bit of transparency or darker
        # Default is -1, which shows all at full opacity
        self.dim_layers = False
        self.edited_layer = layer_id
        self.draw_collision1 = False
        self.draw_collision2 = False
        self.draw_data_layer = False
        self.edited_collision = -1
        self.interaction_mode = DrawerInteraction.CHUNKS

    def set_show_only_edited_layer(self, v):
        self.show_only_edited_layer = v

    def set_edited_collision(self, collision_id):
        self.dim_layers = True
        self.edited_layer = -1
        self.draw_collision1 = False
        self.draw_collision2 = False
        self.draw_data_layer = False
        if collision_id == 0:
            self.draw_collision1 = True
        elif collision_id == 1:
            self.draw_collision2 = True
        self.edited_collision = collision_id
        self.interaction_mode = DrawerInteraction.COL

    def get_edited_collision(self):
        return self.edited_collision

    def set_edit_data_layer(self):
        self.dim_layers = True
        self.edited_layer = -1
        self.edited_collision = -1
        self.draw_collision1 = False
        self.draw_collision2 = False
        self.draw_data_layer = True
        self.interaction_mode = DrawerInteraction.DAT

    def get_interaction_mode(self):
        return self.interaction_mode

    def set_draw_chunk_grid(self, v):
        self.draw_chunk_grid = v

    def set_draw_tile_grid(self, v):
        self.draw_tile_grid = v

    def set_pink_bg(self, v):
        self.use_pink_bg = v

    def set_scale(self, v):
        self.scale = v

    def add_overlay(self, tileset_drawer_overlay):
        self._tileset_drawer_overlay = tileset_drawer_overlay

    @typing.no_type_check
    def unload(self):
        self.draw_area = None
        self.reset(None, None, None, None)
        self.draw_chunk_grid = False
        self.draw_tile_grid = False
        self.use_pink_bg = False
        self.interaction_mode = DrawerInteraction.NONE
        self.interaction_chunks_selected_id = 0
        self.interaction_col_solid = False
        self.interaction_dat_value = 0
        self.mouse_x = 99999
        self.mouse_y = 99999
        self.edited_layer = -1
        self.edited_collision = -1
        self.show_only_edited_layer = False
        self.dim_layers = False
        self.draw_collision1 = False
        self.draw_collision2 = False
        self.draw_data_layer = False
        self.selection_plugin = None
        self.tile_grid_plugin = None
        self.chunk_grid_plugin = None
        self.scale = 1
        self.drawing_is_active = False