예제 #1
0
    def draw_held_entity(self):
        if self.held_entity is None: return
        s = self.camera.get_cell_size_px()
        rect = pg.Rect(*self.mouse_pos, s + 1, s + 1)
        rect.move_ip(*(-self.hold_point * s))
        self.held_entity.draw_onto(self.screen, rect, self.edit_mode)   # pass in True here to show selection highlight

        # TEMPORARY: copied from `render_board`
        s = self.camera.get_cell_size_px()
        def grid_to_px(pos: V2) -> V2:
            return (V2(*self.viewport_surf.get_rect().center) + (pos - self.camera.center) * s).floor()

        # draw wiring while moving entity
        if self.held_entity.has_ports:
            e = self.held_entity
            wire_width = self.camera.get_wire_width()
            for index, (is_input, f, f_index) in enumerate(e.wirings):
                if f is None: continue
                f_pos = self.level.board.find(f)
                if f_pos is None:
                    continue
                    # raise RuntimeError("unable to find desired entity while drawing wiring")
                
                start_offset = e.get_port_offset(is_input, index)
                end_offset = f.get_port_offset(not is_input, f_index)
                start = V2(*rect.topleft) + V2(rect.width * start_offset[0], rect.height * start_offset[1]) 
                end = grid_to_px(f_pos + end_offset)
                color = WIRE_COLOR_ON if e.port_states[index] else WIRE_COLOR_OFF
                pg.draw.line(self.screen, color, tuple(start), tuple(end), wire_width)
예제 #2
0
 def get_port_offset(self, is_input, index):
     if is_input:
         gate_h = 1.0 - 2 * self.top_bottom_padding
         return V2(
             self.left_right_padding, self.top_bottom_padding + gate_h *
             (index + 1) / (self.num_inputs + 1))
     else:
         return V2(1.0 - self.left_right_padding, 0.5)
예제 #3
0
 def get_all(self, filter_type: Type[Entity] = None):
     """returns a generator containing all present entities (with positions)"""
     if filter_type is None:
         for pos, cell in self.cells.items():
             for e in cell:
                 yield V2(*pos), e
     else:
         for e_type in self.type_locs:
             if issubclass(e_type, filter_type):
                 for pos, e in self.type_locs[e_type]:
                     yield V2(*pos), e
예제 #4
0
 def draw_onto(self, surf: pg.Surface, rect: pg.Rect, **kwargs):
     super().draw_onto(surf, rect)
     s = rect.width
     compass_center = round(V2(rect.centerx, rect.centery - s * 0.1))
     # pg.draw.circle(surf, (0, 0, 0), tuple(compass_center), round(s * 0.1), width=round(s * 0.05))
     # draw_aacircle(surf, *compass_center, round(s * 0.05), (0, 0, 0))
     self.hitboxes = [(d,
                       draw_chevron(surf, compass_center + d * s * 0.25, d,
                                    (255, 255,
                                     255) if self.get_value() is d else
                                    (0, 0, 0), s * 0.12, s * 0.04))
                      for d in Direction if d is not Direction.NONE]
     render_text_centered_xy(self.label, (0, 0, 0), surf,
                             V2(rect.centerx, rect.bottom - s * 0.16),
                             FONT_SIZE)
예제 #5
0
    def handle_events(self, events):
        for event in events:
            if event.type == pg.QUIT:
                self.running = False
            elif event.type == pg.VIDEORESIZE:
                self.handle_window_resize(event.w, event.h)
            elif event.type == pg.KEYDOWN:
                # --------- TEMPORARY -------#
                if event.key in (pg.K_RETURN, pg.K_KP_ENTER):
                    self.level.won = True
                    print("FORCING LEVEL WIN")
                # ---------------------------#
                self.keys_pressed.add(event.key)
                self.handle_keydown(event.key)
            elif event.type == pg.KEYUP:
                self.keys_pressed.discard(event.key)
                self.handle_keyup(event.key)
            elif event.type == pg.MOUSEBUTTONDOWN:
                self.mouse_buttons_pressed.add(event.button)
                self.handle_mousebuttondown(event.button)
            elif event.type == pg.MOUSEBUTTONUP:
                self.mouse_buttons_pressed.discard(event.button)
                self.handle_mousebuttonup(event.button)
            elif event.type == pg.MOUSEMOTION:
                self.mouse_pos = V2(*event.pos)
                self.handle_mousemotion(event.rel)
        
        self.handle_keys_pressed()

        return set()    # we consume all events
예제 #6
0
 def take_snapshot(self, entity, dims) -> pg.Surface:
     surf = pg.Surface(dims)
     if entity is self.level_runner.held_entity:
         return self.take_snapshot_at_mouse(dims)
         # # TODO: maybe draw a hand icon here showing that the entity is being held (?)
         # # for now, draw a fake empty board
         # cam = Camera(V2(0.5, 0.5), self.zoom_level)
         # render_board(
         #     Board(), surf, cam,
         #     selected_entity=self.level_runner.selected_entity,
         #     substep_progress=self.level_runner.substep_progress
         # )
     else:
         pos = self.level_runner.level.board.find(entity)
         if pos is None:
             raise ValueError(
                 f"desired entity not found while taking snapshot ({entity})"
             )
         cam = Camera(pos + V2(0.5, 0.5), self.zoom_level)
         render_board(self.level_runner.level.board,
                      surf,
                      cam,
                      selected_entity=self.level_runner.selected_entity,
                      substep_progress=self.level_runner.substep_progress)
     return surf
예제 #7
0
    def reset_level(self):
        self.shelf_height_onscreen = SHELF_HEIGHT

        self.edit_mode = True
        self.paused = False
        self.fast_forward = False

        self.slow_motion = False

        self.wiring_widget: Optional[WireEditor] = None     # if not None, the WireEditor that is currently being used

        self.shelf_state = "open"           # "open", "closed", "opening", or "closing"
        self.editor_state = "closed"        # "open", "closed", "opening", or "closing"
        self.editor_state_queue = []
        self.substep_progress = 0.0         # float in [0, 1) denoting fraction of current step completed (for animation)

        self.editor_width_onscreen = 0
        self.editor_content_height = None

        self.editor_scroll_amt = 0
        self.read_only_indicator_blink_frames = 0

        # initialize camera to contain `level.board` (with some margin)
        rect = self.level.board.get_bounding_rect(margin=3)   # arbitrary value
        zoom_level = min(self.screen_width / rect.width, self.screen_height / rect.height) / DEFAULT_CELL_SIZE
        self.camera = Camera(center=V2(*rect.center), zoom_level=zoom_level)

        # initialize refresh sentinels
        self.window_size_changed    = True
        self.viewport_changed       = True
        self.shelf_changed          = True
        self.editor_changed         = True
        self.reblit_needed          = True

        self.held_entity: Union[Entity, None] = None
        self.hold_point: V2 = V2(0, 0)  # in [0, 1]^2

        self.selected_entity: Union[Entity, None] = None
        self.editing_entity: Union[Entity, None] = None
        # self.selected_entity_queue = []

        self.pressed_icon: Optional[str] = None

        self.current_modal: Union[Modal, None] = None
예제 #8
0
 def __init__(self,
              color: Color,
              velocity: Direction = Direction.NONE,
              locked: bool = False,
              **kwargs):
     super().__init__(locked, **kwargs)
     self.color = color
     self.velocity = velocity
     self.leaky = False
     self.draw_center = V2(0, 0)
예제 #9
0
    def draw_onto_base(self,
                       surf: pg.Surface,
                       rect: pg.Rect,
                       edit_mode: bool,
                       step_progress: float = 0.0,
                       neighborhood=(([], ) * 5, ) * 5):
        s = rect.width
        self.draw_center = V2(*rect.center)

        for anim in self.animations:
            if anim[0] == "translate":
                amt = self.travel_curve(step_progress)
                self.draw_center += anim[1] * (s - 1) * (amt - 1)
            elif anim[0] == "shift":
                a = 0.18
                if step_progress < a:
                    amt = 0
                elif step_progress < 0.5:
                    amt = (step_progress - a) * 2 * Piston.max_amt
                else:
                    amt = self.travel_curve(step_progress)
                self.draw_center += anim[1] * (s - 1) * (amt - 1)

        draw_radius = s * 0.3
        draw_color_rgb = self.color.rgb()

        # check for intersection with other barrel
        # if intersection found, take weighted average of colors
        n = len(neighborhood)
        for x in range(-n // 2, n // 2 + 1):
            for y in range(-n // 2, n // 2 + 1):
                for e in neighborhood[y + n // 2][x + n // 2]:
                    if e is self: continue  # skip self
                    if isinstance(e, Barrel):
                        dist = (e.draw_center - self.draw_center).length()
                        if dist < draw_radius * 2:
                            percentage = 1.0 - dist / (2 * draw_radius)
                            # print(f"intersecting by {dist} pixels ({percentage * 100:.0f}%)")
                            # smoothly transition towards merged color
                            draw_color_rgb = interpolate_colors(
                                self.color.rgb(), (self.color + e.color).rgb(),
                                percentage)

        # pg.draw.circle(surf, draw_color_rgb, tuple(self.draw_center), draw_radius)
        draw_aacircle(surf, *round(self.draw_center), round(draw_radius),
                      draw_color_rgb)

        if edit_mode:
            draw_chevron(surf,
                         self.draw_center + self.velocity * (s * 0.42),
                         self.velocity,
                         VELOCITY_CHEVRON_COLOR,
                         round(s * 0.25),
                         round(s**0.5 * 0.4),
                         angle=120)
예제 #10
0
 def draw_onto_base(self,
                    surf: pg.Surface,
                    rect: pg.Rect,
                    edit_mode: bool,
                    step_progress: float = 0.0,
                    neighborhood=(([], ) * 5, ) * 5):
     s = rect.width
     for i in range(3):
         draw_chevron(
             surf,
             V2(*rect.center) + self.orientation * (i - 0.4) * (s // 5),
             self.orientation, (0, 0, 0), s // 3, round(s * 0.05))
예제 #11
0
 def handle_mousemotion(self, rel):
     # pan camera if right click is held
     if 3 in self.mouse_buttons_pressed:
         disp = V2(*rel) / self.camera.get_cell_size_px()
         self.camera.pan_abs(-disp)
         self.viewport_changed = True
     
     if self.held_entity is not None:
         self.reblit_needed = True
         self.editor_changed = True
     
     if self.wiring_widget is not None:
         self.editor_changed = True
예제 #12
0
 def get_cells(self, window: pg.Rect = None):
     """returns a generator containing all non-empty cells (along with positions) contained within `window`"""
     if window is None:
         for pos, cell in self.cells.items():
             yield V2(*pos), cell
     else:
         # choose the most efficient iteration method
         # if this ever proves insufficient (doubtful), use a more efficient data structure (e.g. 2D range tree)
         if window.width * window.height < self.get_cell_count():
             for x in range(window.width):
                 for y in range(window.height):
                     pos = (window.left + x, window.top + y)
                     if pos in self.cells:
                         yield V2(*pos), self.cells[pos]
             return (
                 V2(*window.topleft) + V2(x, y)
                 for y in range(window.height)
                 for x in range(window.width)
             )
         else:
             # when window is large, this will almost always be preferred
             for pos, cell in self.cells.items():
                 if window.collidepoint(*pos):
                     yield V2(*pos), cell
예제 #13
0
 def draw_onto_base(self,
                    surf: pg.Surface,
                    rect: pg.Rect,
                    edit_mode: bool,
                    step_progress: float = 0.0,
                    neighborhood=(([], ) * 5, ) * 5):
     # TEMPORARY
     s = rect.width
     w = round(s * 0.1)
     draw_aacircle(surf, *rect.center, round(s * 0.35), (210, 210, 210))
     draw_chevron(surf,
                  V2(*rect.center) + self.orientation * (s * 0.432),
                  self.orientation, (210, 210, 210),
                  round(s * 0.28),
                  w,
                  angle=108)
예제 #14
0
    def __init__(self, level_queue: List[Level], postprocessing_effects: Sequence[PostprocessingEffect]=[]):
        self.level_queue = level_queue
        self.postprocessing_effects = postprocessing_effects

        self.screen_width = DEFAULT_SCREEN_WIDTH
        self.screen_height = DEFAULT_SCREEN_HEIGHT

        self.advance_level()

        self.keys_pressed = set()
        self.mouse_buttons_pressed = set()
        self.mouse_pos = V2(0, 0)

        self.palette_rects: Sequence[Tuple[pg.Rect, Type[Entity]]]  = []    # store palette item rects for easier collision
        self.widget_rects: Sequence[Tuple[pg.Rect, Widget]]         = []    # store widget rects for easier collision
        self.shelf_icon_rects: Sequence[Tuple[pg.Rect, str]]        = []    # store shelf icon rects for easier collision

        self.snapshot_provider = SnapshotProvider(self)
예제 #15
0
    def draw_onto_base(self,
                       surf: pg.Surface,
                       rect: pg.Rect,
                       edit_mode: bool,
                       step_progress: float = 0.0,
                       neighborhood=(([], ) * 5, ) * 5):
        eye_box_width = rect.width * 0.75
        pupil_radius = rect.width * 0.15
        angular_width = pi * 0.63
        draw_width = round(rect.width * 0.04)
        num_lines = 5
        line_length = rect.width * 0.15

        # draw edges of eye
        pg.draw.arc(surf, (0, 0, 0),
                    pg.Rect(rect.left + (rect.width - eye_box_width) / 2,
                            rect.centery - pupil_radius, eye_box_width,
                            rect.height / 2 + pupil_radius),
                    pi / 2 - angular_width / 2,
                    pi / 2 + angular_width / 2,
                    width=draw_width)

        pg.draw.arc(surf, (0, 0, 0),
                    pg.Rect(rect.left + (rect.width - eye_box_width) / 2,
                            rect.top, eye_box_width,
                            rect.height / 2 + pupil_radius),
                    -pi / 2 - angular_width / 2,
                    -pi / 2 + angular_width / 2,
                    width=draw_width)

        # draw pupil
        pg.draw.circle(surf, (0, 0, 0), rect.center, pupil_radius)

        for i in range(num_lines):
            theta = 90 / num_lines * (i - num_lines // 2)
            offset = self.orientation.rotate(round(theta))
            start = V2(*rect.center) + offset * pupil_radius * 1.4
            end = start + offset * line_length
            pg.draw.line(surf, (0, 0, 0),
                         tuple(start),
                         tuple(end),
                         width=round(draw_width / 2))
예제 #16
0
    def draw_onto(self,
                  surf: pg.Surface,
                  rect: pg.Rect,
                  snapshot_provider=None) -> None:
        super().draw_onto(surf, rect)
        render_text_left_justified(
            self.label, (0, 0, 0), surf,
            V2(rect.left + rect.width * 0.03, rect.centery), FONT_SIZE)

        w = rect.width * 0.45
        h = w  # rect.height * 0.9
        self.snapshot_rect = pg.Rect(rect.left + rect.width * 0.70 - w / 2,
                                     rect.top + (rect.height - h) / 2, w, h)

        # draw background
        pg.draw.rect(surf, (255, 255, 255), self.snapshot_rect)

        if self.get_value()[0] is None and not self.in_use:
            size = FONT_SIZE * 0.65
            render_text_centered_xy("not", (63, 63, 63), surf,
                                    (self.snapshot_rect.centerx,
                                     self.snapshot_rect.centery - size / 2),
                                    size)
            render_text_centered_xy("connected", (63, 63, 63), surf,
                                    (self.snapshot_rect.centerx,
                                     self.snapshot_rect.centery + size / 2),
                                    size)
        else:
            if self.in_use:
                snap = snapshot_provider.take_snapshot_at_mouse(
                    self.snapshot_rect.size)
            else:
                snap = snapshot_provider.take_snapshot(self.get_value()[0],
                                                       self.snapshot_rect.size)
            surf.blit(snap, self.snapshot_rect)

        # draw border
        color = HIGHLIGHT_COLOR if self.in_use else (0, 0, 0)
        pg.draw.rect(surf, color, self.snapshot_rect, 2)
예제 #17
0
    def draw_onto(self, surf: pg.Surface, rect: pg.Rect, **kwargs):
        super().draw_onto(surf, rect)

        self.set_value(clamp(self.get_value(), *self.get_limits()))

        render_text_left_justified(
            self.label, (0, 0, 0), surf,
            V2(rect.left + rect.width * 0.03, rect.centery), FONT_SIZE)
        # TODO: draw number selector boxes
        box_width = int(rect.height * 0.75)
        box_height = int(rect.height * 0.75)
        box_thickness = 2
        self.hitboxes.clear()
        low, high = self.get_limits()
        for i, n in enumerate(range(low, high + 1)):  # inclusive
            box = pg.Rect(
                rect.left + rect.width * 0.375 +
                (box_width - box_thickness // 2) * i,
                rect.centery - box_height / 2, box_width, box_height)
            color = (255, 255, 255) if n == self.get_value() else (0, 0, 0)
            draw_rectangle(surf, box, (0, 0, 0), thickness=box_thickness)
            render_text_centered_xy(str(n), color, surf, box.center, FONT_SIZE)
            self.hitboxes.append((n, box))
예제 #18
0
def render_board(board: Board,
                 surf: pg.Surface,
                 cam: Camera,
                 edit_mode: bool = False,
                 selected_entity: Entity = None,
                 substep_progress: float = 0.0,
                 wiring_visible: bool = True):
    """render `board` to `surf` with the given parameters"""
    # TODO: draw carpets, then grid, then blocks
    # z_pos:     < 0          = 0        > 0
    surf.fill(VIEWPORT_BG_COLOR)

    s = cam.get_cell_size_px()
    surf_center = V2(*surf.get_rect().center)
    surf_width, surf_height = surf.get_size()

    def grid_to_px(pos: V2) -> V2:
        return (surf_center + (pos - cam.center) * s).floor()

    w = surf_width / s + 2
    h = surf_height / s + 2
    grid_rect = pg.Rect(floor(cam.center.x - w / 2),
                        floor(cam.center.y - h / 2),
                        ceil(w) + 1,
                        ceil(h) + 1)

    grid_line_width = cam.get_grid_line_width()

    # draw board
    for grid_pos, cell in board.get_cells(grid_rect):
        if not cell: continue
        draw_pos = grid_to_px(grid_pos)
        neighborhood = [[
            board.get(*(grid_pos + V2(x_offset, y_offset)))
            for x_offset in range(-2, 3)
        ] for y_offset in range(-2, 3)]
        for e in sorted(cell, key=lambda e: e.draw_precedence):
            rect = pg.Rect(*draw_pos, s + 1, s + 1)
            e.draw_onto(surf, rect, edit_mode, selected_entity is e,
                        substep_progress, neighborhood)

    # draw grid with dynamic line width
    for x in range(grid_rect.width):
        x_grid = grid_rect.left + x
        x_px, _ = grid_to_px(V2(x_grid, 0))
        pg.draw.line(surf,
                     GRID_LINE_COLOR, (x_px, 0), (x_px, surf_height),
                     width=grid_line_width)
    for y in range(grid_rect.height):
        y_grid = grid_rect.top + y
        _, y_px = grid_to_px(V2(0, y_grid))
        pg.draw.line(surf,
                     GRID_LINE_COLOR, (0, y_px), (surf_width, y_px),
                     width=grid_line_width)

    # draw wires
    # TODO: make lines correct thickness when slanted
    # TODO: antialias lines
    if wiring_visible:
        wire_width = cam.get_wire_width()
        for pos, e in board.get_all(filter_type=Wirable):
            for index, (is_input, f, f_index) in enumerate(e.wirings):
                if f is None: continue
                f_pos = board.find(f)
                if f_pos is None:
                    continue
                    # raise RuntimeError("unable to find desired entity while drawing wiring")
                start = grid_to_px(pos + e.get_port_offset(is_input, index))
                end = grid_to_px(f_pos +
                                 f.get_port_offset(not is_input, f_index))
                color = WIRE_COLOR_ON if e.port_states[
                    index] else WIRE_COLOR_OFF
                pg.draw.line(surf, color, tuple(start), tuple(end), wire_width)
예제 #19
0
 def get_world_coords(self, pos: V2, screen_width, screen_height):
     """converts the given pixel `pos` to world coordinates"""
     screen_center = V2(screen_width / 2, screen_height / 2)
     diff = pos - screen_center
     return self.center + diff * (1 / self.get_cell_size_px())
예제 #20
0
 def grid_to_px(pos: V2) -> V2:
     return (V2(*self.viewport_surf.get_rect().center) + (pos - self.camera.center) * s).floor()
예제 #21
0
 def get_port_offset(self, is_input, index):
     return V2(0.5, 0.5)
예제 #22
0
    def handle_mousebuttondown(self, button):
        mouse_over_shelf = self.mouse_pos.y >= self.screen_height - self.shelf_height_onscreen
        mouse_over_editor = self.mouse_pos.x >= self.screen_width - self.editor_width_onscreen \
                            and self.mouse_pos.y < self.screen_height - SHELF_HEIGHT

        # scroll wheel
        if button in (4, 5):
            if button == 4:
                scroll_direction = 1
            elif button == 5:
                scroll_direction = -1
            
            # handle editor scrolling
            editor_scrolled = False
            if mouse_over_editor:
                old_scroll_amt = self.editor_scroll_amt
                max_scroll_amt = self.editor_content_height - self.editor_surf.get_height()
                if max_scroll_amt < 0: max_scroll_amt = 0
                self.editor_scroll_amt = clamp(
                    self.editor_scroll_amt + scroll_direction * EDITOR_SCROLL_SPEED, 
                    -max_scroll_amt, 0
                )
                editor_scrolled = old_scroll_amt != self.editor_scroll_amt
                if editor_scrolled:
                    self.editor_changed = True
            
            # # if unable to scroll editor, then zoom the camera
            # if not editor_scrolled:
            # if not over editor editor, then zoom the camera
            if not mouse_over_editor:
                zoom_direction = 0
                if button == 4:    # zoom in
                    zoom_direction = 1
                elif button == 5:   # zoom out
                    zoom_direction = -1
                
                if zoom_direction != 0:
                    pivot = self.camera.get_world_coords(self.mouse_pos, self.screen_width, self.screen_height)
                    self.camera.zoom(zoom_direction, pivot)
                    self.viewport_changed = True
        
        # left click
        if button == 1:
            # handle shelf icons
            for rect, icon in self.shelf_icon_rects:
                if rect.collidepoint(*self.mouse_pos):
                    self.pressed_icon = icon
                    self.reblit_needed = True
                    return
            
            # if not self.edit_mode:
            #     return

            entity_clicked = None
            clicked_wire_widget = False

            # handle entity holding (left click)
            if mouse_over_shelf:
                adjusted_pos = self.mouse_pos - V2(0, self.screen_height - self.shelf_height_onscreen)
                for rect, e_prototype in self.palette_rects:
                    if rect.collidepoint(*adjusted_pos):
                        # pick up entity (from palette)
                        new = e_prototype.get_instance()  # create new entity
                        self.held_entity = new
                        self.hold_point = V2(0.5, 0.5)
                        self.level.palette.remove(e_prototype)
                        self.deselect_entity()
                        self.select_entity(new)
                        self.shelf_changed = True
                        break
            elif mouse_over_editor:
                if self.edit_mode:      # cannot interact with editor if not in edit mode
                    adjusted_pos = self.mouse_pos - V2(self.screen_width - self.editor_width_onscreen, 0)
                    for hitbox, widget in self.widget_rects:
                        if hitbox.collidepoint(*adjusted_pos):
                            clicked_wire_widget = widget.handle_click(adjusted_pos)
                            if clicked_wire_widget:
                                self.finish_wiring(None)    # deselect previously selected wire widget
                                self.wiring_widget = clicked_wire_widget
                            self.editor_changed = True      # just redraw every time (easier)
                            self.viewport_changed = True    # ^^^
                            break
                else:   # if not in edit mode, trigger read-only indicator to blink
                    if self.read_only_indicator_blink_frames == 0:                      # if not currently blinking
                        self.read_only_indicator_blink_frames = 2 * BLINK_DURATION      # blink twice
            else:   # mouse is over board
                pos_float = self.camera.get_world_coords(self.mouse_pos, self.screen_width, self.screen_height)
                pos = pos_float.floor()
                cell = self.level.board.get(*pos)
                if cell:
                    entity_clicked = cell[-1]    # click last element; TODO: figure out if this is a problem lol
            
                if self.wiring_widget is None:  # don't change selection if in wiring mode
                    # deselect current selection if clicking on something else
                    if self.selected_entity is not entity_clicked:
                        self.deselect_entity()
                    if entity_clicked is not None and not entity_clicked.locked:
                        # pick up entity (from board)
                        if self.edit_mode:
                            self.held_entity = entity_clicked
                            self.hold_point = pos_float.fmod(1)
                            self.level.board.remove(*pos, self.held_entity)
                            self.viewport_changed = True
                        if entity_clicked.editable:
                            self.select_entity(entity_clicked)
                    else:
                        self.deselect_entity()

            if not clicked_wire_widget:
                self.finish_wiring(entity_clicked)