def _clamp_camera(self):
        left = self.ws_crop_w // 2
        right = self.world_w - int(math.ceil(self.ws_crop_w / 2.0))
        self.ws_camera_x = Utils.clamp(self.ws_camera_x, left, right)

        top = self.ws_crop_h // 2
        bottom = self.world_h - int(math.ceil(self.ws_crop_h / 2.0))
        self.ws_camera_y = Utils.clamp(self.ws_camera_y, top, bottom)
    def set_zoom(self, zoom):
        old_zoom = self.zoom
        self.zoom = Utils.clamp(zoom, self.min_zoom, self.max_zoom)

        # manually derive world and camera, since old camera position should only be scaled during zoom
        delta_zoom = 1.0 * self.zoom / old_zoom
        self.world_w = self.mask_w * self.zoom
        self.world_h = self.mask_h * self.zoom
        self.ws_camera_x = int(math.floor(self.ws_camera_x * delta_zoom))
        self.ws_camera_y = int(math.floor(self.ws_camera_y * delta_zoom))

        self._clamp_camera()
        self._derive_space_vars()
    def set_mouse_zoom(self, zoom, e):
        x, y = self.mouse_canvas_to_world(e)

        # move camera position with no clamping and no deriving space vars
        dx = x - self.ws_camera_x
        dy = y - self.ws_camera_y
        self.ws_camera_x += dx
        self.ws_camera_y += dy

        # zoom with no clamping and no deriving space vars
        old_zoom = self.zoom
        self.zoom = Utils.clamp(zoom, self.min_zoom, self.max_zoom)

        # manually derive world and camera, since old camera position should only be scaled during zoom
        delta_zoom = 1.0 * self.zoom / old_zoom
        self.world_w = self.mask_w * self.zoom
        self.world_h = self.mask_h * self.zoom
        self.ws_camera_x = int(math.floor(self.ws_camera_x * delta_zoom))
        self.ws_camera_y = int(math.floor(self.ws_camera_y * delta_zoom))

        # move the world so the new camera moves to the mouse
        # clamp and derive space vars
        self.move_camera_position(-dx, -dy)