class SegmentTool(Tool): def setup(self): self.pos1 = None def mousedown(self, x, y): self.pos1 = Position(round(x), round(y)) def mousemove(self, x, y): if self.pos1: pos2 = self._closest_position(round(x), round(y)) self.canvas.redraw() if self.pos1.pos() != pos2.pos(): segment = self._make_segment(self.pos1, pos2) segment.draw(self.canvas) def mouseup(self, x, y): if self.pos1: pos2 = self._closest_position(round(x), round(y)) if self.pos1.pos() != pos2.pos(): segment = self._make_segment(self.pos1, pos2) self.level.walls.append(segment) self.canvas.redraw() self.pos1 = None def _closest_position(self, x, y): if abs(x - self.pos1.x) > abs(y - self.pos1.y): return Position(x, self.pos1.y) else: return Position(self.pos1.x, y) def _make_segment(self, pos1, pos2): raise NotImplementedError
class PortalTool(Tool): def setup(self): self.pos1 = None def mousedown(self, x, y): self.pos1 = Position(round(x * 2) * 0.5, round(y * 2) * 0.5) def mousemove(self, x, y): if self.pos1: pos2 = self._closest_position(x, y) self.canvas.redraw() if self.pos1.pos() != pos2.pos(): portal = self._make_portal(self.pos1, pos2) portal.draw(self.canvas) def mouseup(self, x, y): if self.pos1: pos2 = self._closest_position(x, y) if self.pos1.pos() != pos2.pos(): portal = self._make_portal(self.pos1, pos2) self.level.add_entity(portal) self.canvas.redraw() self.pos1 = None def _closest_position(self, x, y): x = round(x * 2) * 0.5 y = round(y * 2) * 0.5 if x == self.pos1.x and y == self.pos1.y: return Position(x, y) if abs(x - self.pos1.x) > abs(y - self.pos1.y): if x > self.pos1.x: return Position(self.pos1.x + 1, self.pos1.y) else: return Position(self.pos1.x - 1, self.pos1.y) else: if y > self.pos1.y: return Position(self.pos1.x, self.pos1.y + 1) else: return Position(self.pos1.x, self.pos1.y - 1) def _make_portal(self, pos1, pos2): raise NotImplementedError
class LevelCanvas(tk.Canvas): def __init__(self, app, level, width, height): self.level = level self.width = width self.height = height self.scale = None super().__init__(app, width=width, height=height) self.pan_from = None self.pan_orig = None self.pan_offset = Position(0, 0) self.calculate_transform() self.path_lines = [] self.bind('<Configure>', self._resize) self.bind('<Button-2>', self._pan_start) self.bind('<B2-Motion>', self._pan_move) self.bind('<ButtonRelease-2>', self._pan_end) def set_scale(self, scale): self.scale = float(scale) self.calculate_transform() self.redraw() def calculate_transform(self): (x_min, y_min, x_max, y_max) = self.level.bounds center_x = (x_min + x_max) * 0.5 center_y = (y_min + y_max) * 0.5 level_width = x_max - x_min or 20 level_height = y_max - y_min or 20 scale = self.scale or min((self.width - 50) / level_width, (self.height - 50) / level_height) # A stack of Transforms self.transform = [ Translate(-center_x, -center_y), Dilate(scale), Translate(self.width * 0.5, self.height * 0.5), Translate(self.pan_offset.x, self.pan_offset.y), ] self.inverse_transform = [t.inverse() for t in reversed(self.transform)] def transform_point(self, pos): return functools.reduce((lambda p, t: t.apply(p)), self.transform, pos) def preimage_point(self, pos): return functools.reduce((lambda p, t: t.apply(p)), self.inverse_transform, pos) def create_line(self, x1, y1, x2, y2, **options): x1_new, y1_new = self.transform_point((x1, y1)) x2_new, y2_new = self.transform_point((x2, y2)) return super().create_line(x1_new, y1_new, x2_new, y2_new, **options) def create_rectangle(self, x1, y1, x2, y2, **options): x1_new, y1_new = self.transform_point((x1, y1)) x2_new, y2_new = self.transform_point((x2, y2)) return super().create_rectangle(x1_new, y1_new, x2_new, y2_new, **options) def create_oval(self, x1, y1, x2, y2, **options): x1_new, y1_new = self.transform_point((x1, y1)) x2_new, y2_new = self.transform_point((x2, y2)) return super().create_oval(x1_new, y1_new, x2_new, y2_new, **options) def redraw(self): self.delete('all') self.level.draw(self) def _resize(self, event): self.width = event.width self.height = event.height self.calculate_transform() self.redraw() def _pan_start(self, event): self.pan_from = Position(event.x, event.y) self.pan_orig = Position(*self.pan_offset.pos()) def _pan_move(self, event): self.pan_offset.x = self.pan_orig.x + event.x - self.pan_from.x self.pan_offset.y = self.pan_orig.y + event.y - self.pan_from.y self.calculate_transform() self.redraw() def _pan_end(self, event): self._pan_move(event) self.pan_from = None self.pan_orig = None