def _highlight_border(self, context: cairo.Context, row: int, col: int): width = self._total_width height = self._total_height line_width = 3 row_rectangles = [ Rectangle(Point(1, row * self.cell_height - line_width / 2), width - 2, line_width), Rectangle(Point(1, (row + 1) * self.cell_height - line_width / 2), width - 2, line_width) ] col_rectangles = [ Rectangle(Point(col * self.cell_width - line_width / 2, 1), line_width, height - 2), Rectangle(Point((col + 1) * self.cell_width - line_width / 2, 1), line_width, height - 2) ] context.save() r, g, b = self.highlight_color context.set_source_rgba(r, g, b, .6) for row_rectangle in row_rectangles: context.rectangle(row_rectangle.start.x, row_rectangle.start.y, row_rectangle.width, row_rectangle.height) context.fill() for col_rectangle in col_rectangles: context.rectangle(col_rectangle.start.x, col_rectangle.start.y, col_rectangle.width, col_rectangle.height) context.fill() context.restore()
def is_point_in(self, p: Point, category=MouseEvent.UNKNOWN): v = self._vertexes start = Point(min(x.x for x in v), min(x.y for x in v)) width = max(x.x for x in v) - start.x height = max(x.y for x in v) - start.y rectangle = Rectangle(start, width, height) return rectangle.is_point_in(p)
def is_point_in(self,p: Point,category=MouseEvent.UNKNOWN): v = self._vertexes start = Point(min(x.x for x in v), min(x.y for x in v)) width = max(x.x for x in v) - start.x height = max(x.y for x in v) - start.y rectangle = Rectangle(start, width, height) return rectangle.is_point_in(p)
def __init__(self, side=40, protrusion=30): super().__init__() base_size = side height = protrusion colour = global_constants.start_selected arrow_up = Arrow(base_size, height, Arrow.UP, colour) arrow_down = Arrow(base_size, height, Arrow.DOWN, colour) arrow_left = Arrow(base_size, height, Arrow.LEFT, colour) arrow_right = Arrow(base_size, height, Arrow.RIGHT, colour) factor=6 center = height + base_size/2 + (base_size/factor) distance = (base_size + height)/2 + (base_size/factor) arrow_up.translate(center, center - distance) arrow_left.translate(center - distance, center) arrow_right.translate(center + distance, center) arrow_down.translate(center, center + distance) self.background_rectangle = Rectangle(Point(0,0), center * 2, center * 2) self.add(arrow_up) self.add(arrow_down) self.add(arrow_left) self.add(arrow_right) self._should_pass_move = False
def layout(self, context: cairo.Context): super().layout(context) context.set_font_size(self.font_size) xb, yb, w, h, xa, ya = context.text_extents(self.title) font_shape = Rectangle(Point(h / 2 + self.distance, self.distance), xa, h) self.__title_start_point = Point(font_shape.start.x, font_shape.start.y + h) outer_font_box = DrawableRectangle( Point(font_shape.start.x - self.distance, font_shape.start.y - self.distance), font_shape.width + 2 * self.distance, font_shape.height + 2 * self.distance) self.__outer_font_box = outer_font_box wrapper_shape = DrawableRectangle( Point(0, outer_font_box.start.y + outer_font_box.height / 2), outer_font_box.start.x + max(outer_font_box.width, self.widget.shape.width) + self.distance, outer_font_box.height / 2 + 2 * self.distance + self.widget.shape.height) self.widget.set_translate( outer_font_box.start.x, outer_font_box.start.y + outer_font_box.height + self.distance) self.__wrapper_shape = wrapper_shape self.shape = DrawableRectangle( Point(0, 0), wrapper_shape.width, wrapper_shape.start.y + wrapper_shape.height)
def __init__(self, width, height, background_color=None, *args, **kwargs): super().__init__(*args, **kwargs) self.width = width self.height = height self.font_size = 10 self.clip_rectangle = Rectangle(Point(0, 0), width, height) self._text = "" self._background_color = background_color
def get_cell(self, p: Point): delta = self.line_distance for index, element in enumerate(self._elements): if Rectangle( Point(*element.position) + Point(-delta, delta), element.width + 2 * delta, -element.height - 2 * delta).is_point_in(p): return index return None
def _update_selection_rectangle(self): if self._selection_start_point is not None and \ self._selection_end_point is not None: s = self._selection_start_point e = self._selection_end_point upper = Point(min(s.col, e.col), min(s.row, e.row)) lower = Point(max(s.col, e.col), max(s.row, e.row)) height = lower.row - upper.row width = lower.col - upper.col self._selection_rectangle = Rectangle(upper, width, height)
def layout(self, context: cairo.Context): super().layout(context) width, height = self.container_size self.__background_rectangle = DrawableRectangle(Point(0, 0), width, height) context.set_font_size(self.font_size) xb, yb, w, h, xa, ya = context.text_extents(self.label) padding = self.padding label_start = Point((width - xa)/2, (height - h)/2) self.__label_start = Point(label_start.x, label_start.y + h) label_shape = Rectangle(label_start, xa, h) back_shape = self.__back_button.shape ok_shape = self.__ok_button.shape button_y = label_start.y + label_shape.height + padding/2 total_height = label_shape.height +\ max(back_shape.height, ok_shape.height) +\ 1.5 * padding button_line_width = back_shape.width + ok_shape.width + 2*padding if button_line_width + 2*padding <= label_shape.width: self.__back_button.set_translate( label_start.x + padding, # (width - button_line_width)/2 + back_shape.width, button_y ) self.__ok_button.set_translate( label_start.x + label_shape.width - padding, # (width + button_line_width)/2 - ok_shape.width, button_y ) self.__overlay_shape = DrawableRectangle( Point(label_start.x - padding, label_start.y - padding/2), label_shape.width + 2 * padding, total_height ) else: self.__back_button.set_translate( (width - button_line_width)/2, button_y ) self.__ok_button.set_translate( (width + button_line_width)/2, button_y ) self.__overlay_shape = DrawableRectangle( Point((width - button_line_width)/2 - padding, label_start.y - padding/2), 2*padding + button_line_width, total_height )
def _get_text_max_size(self, context, lines): sizes = [] for l in lines: xb, yb, width, height, _, _ = context.text_extents(l) sizes.append(Rectangle(Point(xb, yb), width, height)) maxW, maxH = 0, 0 for s in sizes: maxW = max(maxW, s.width - s.start.x) maxH = max(maxH, s.height - s.start.y) return maxH, maxW
def _highlight_rectangles(self, context, row: int, col: int): self._highlight_border(context, row, col) width = self._total_width height = self._total_height row_rectangle = Rectangle(Point(col * self.cell_width, 0), self.cell_width, height) col_rectangle = Rectangle(Point(0, row * self.cell_height), width, self.cell_height) r, g, b = self.highlight_color context.save() context.set_source_rgba(r, g, b, .1) context.rectangle(row_rectangle.start.x, row_rectangle.start.y, row_rectangle.width, row_rectangle.height) context.fill() context.rectangle(col_rectangle.start.x, col_rectangle.start.y, col_rectangle.width, col_rectangle.height) context.fill() context.restore()
def _update_cell_list(self): """Should be called only after on_draw has been called at least once""" self._cell_list = [] for (line_index, element_list) in enumerate(self.elements): line = self._line_coordinates(line_index) if self.orientation == Guides.HORIZONTAL: next_x = line[0].x - self.font_size // 2 next_y = line[0].y - (2 * self.font_size) // 3 elif self.orientation == Guides.VERTICAL: next_x = line[0].x - self.font_size // 2 next_y = line[0].y - self.font_size // 2 total_length = 0 #self.line_length = max_numbers * (self._number_height + self.font_size // 2) + 5 for element in reversed(element_list): text = str(element.value) if self.orientation == Guides.HORIZONTAL: width = self._number_width * len(text) height = self._number_height rectangle = Rectangle(Point(next_x - width, next_y), width, -height) wide_rectangle = Rectangle(Point(line[0].x, next_y), -self.cell_size, -height) next_y = next_y - height - self.font_size // 2 total_length += height + self.font_size // 2 elif self.orientation == Guides.VERTICAL: width = self._number_width * (len(text) + 1) height = self._number_height rectangle = Rectangle(Point(next_x - width, next_y), width, -height) wide_rectangle = Rectangle( Point(next_x - width, line[0].y), width, -self.cell_size) next_x -= width + self.font_size // 2 total_length += width + self.font_size // 2 element.cell = rectangle element.wide_cell = wide_rectangle self.line_length = max(total_length + 5, self.line_length) self._update_clip()
def on_draw(self, widget, context): context.save() context.set_line_width(1) width = self._total_width height = self._total_height for row in range(self.number_of_rows): for col in range(self.number_of_cols): value = self.get_cell_value(row, col) rectangle = Rectangle( Point(col * self.cell_width, row * self.cell_height), self.cell_width + 2, self.cell_height + 2) self._draw_cell(context, value, rectangle) context.set_source_rgb(0, 0, 0) context.set_line_cap(cairo.LINE_CAP_SQUARE) context.set_line_width(2.5) DrawableRectangle(Point(0, 0), width, height).draw_on_context(context) context.stroke() if not self.victory_screen: for i, x in enumerate( range(0, width + self.cell_width, self.cell_width)): if i % 5 == 0: context.set_line_width(2.5) else: context.set_line_width(1) context.move_to(x, 0) context.line_to(x, height) context.stroke() for i, y in enumerate( range(0, height + self.cell_height, self.cell_height)): if i % 5 == 0: context.set_line_width(2.5) else: context.set_line_width(1) context.move_to(0, y) context.line_to(width, y) context.stroke() if self._highlight_col is not None and self._highlight_row is not None\ and self._should_highlight and not self.victory_screen: self._highlight_rectangles(context, self._highlight_row, self._highlight_col) context.restore()
def clip_rectangle(self): if super().is_clip_set(): return super().clip_rectangle else: vertexes = [] for child in self.list: if child.is_clip_set(): child_vertexes = [Point(child.fromWidgetCoords.transform_point(p.x,p.y))\ for p in child.clip_rectangle.get_vertexes()] vertexes.extend(child_vertexes) else: return super().clip_rectangle if not vertexes: return super().clip_rectangle else: return Rectangle.from_points(vertexes)
def set_shape_from_context(self, context: cairo.Context): widths_heights = [] done = False while not done: max_width = 0 max_height = 0 context.set_font_size(self.font_size) for e in reversed(self._elements): xb, yb, w, h, xa, ya = context.text_extents(e.label) widths_heights.append((xa, h)) e.width = xa e.height = h max_width = max(max_width, xa) max_height = max(max_height, h) # adjust font size in case it's too big if self.max_size is None: done = True else: if self.orientation == Orientation.HORIZONTAL: reference = max_height else: reference = max_width if reference + 2 * self.line_distance <= self.max_size: done = True else: self.font_size -= 1 positions = [] width = self.element_distance height = self.element_distance def get_padding(actual_size): if self.max_size is not None: return (self.max_size - actual_size) / 2 else: return self.line_distance if self.orientation == Orientation.HORIZONTAL: def handle_extents(e: _GuideElement): nonlocal width, height, positions, max_height width += e.width e.position = (-width, max_height + get_padding(max_height)) width += self.element_distance else: def handle_extents(e: _GuideElement): nonlocal width, height, positions, max_width e.position = (get_padding(e.width), -height) height += e.height + self.element_distance # for w, h in widths_heights: # handle_extents(w, h) for element in reversed(self._elements): handle_extents(element) if self.orientation == Orientation.HORIZONTAL: height = max_height + get_padding(max_height) * 2 width = width - self.element_distance + self.line_distance base_point = Point(-width, 0) else: width = max_width + get_padding(max_width) * 2 height = height - self.element_distance + self.line_distance base_point = Point(0, -height) self.shape = Rectangle(base_point, width, height)
def __init__(self, crucipixel: core.Crucipixel, cell_width: int = 20, cell_height: int = 20, **kwargs): super().__init__(**kwargs) self.crucipixel = crucipixel self.cell_width = cell_width self.cell_height = cell_height self.clip_rectangle = Rectangle(Point(-1, -1), self._total_width + 2, self._total_height + 2) self.highlight_color = global_constants.highlight self._cell_value_order = [ CrucipixelCellValue.SELECTED, CrucipixelCellValue.EMPTY, CrucipixelCellValue.DEFAULT ] self.crucipixel_cell_value_to_color = { CrucipixelCellValue.EMPTY: global_constants.start_empty, CrucipixelCellValue.DEFAULT: global_constants.start_default, CrucipixelCellValue.SELECTED: global_constants.start_selected } self._mouse_button_to_crucipixel_cell_value = { MouseButton.LEFT: CrucipixelCellValue.SELECTED, MouseButton.RIGHT: CrucipixelCellValue.EMPTY, MouseButton.MIDDLE: CrucipixelCellValue.DEFAULT } self._keyboard_value_to_crucipixel_cell_value = { 'space': CrucipixelCellValue.SELECTED, 'enter': CrucipixelCellValue.EMPTY, 'backspace': CrucipixelCellValue.DEFAULT, 'z': CrucipixelCellValue.SELECTED, 'x': CrucipixelCellValue.EMPTY, 'c': CrucipixelCellValue.DEFAULT, 'i': CrucipixelCellValue.SELECTED, 'o': CrucipixelCellValue.EMPTY, 'p': CrucipixelCellValue.DEFAULT, } self._keyboard_value_to_movement_directions = { 'w': (Direction.UP, ), 'a': (Direction.LEFT, ), 's': (Direction.DOWN, ), 'd': (Direction.RIGHT, ), 'k': (Direction.UP, ), 'h': (Direction.LEFT, ), 'j': (Direction.DOWN, ), 'l': (Direction.RIGHT, ), 'y': (Direction.UP, Direction.LEFT), 'u': (Direction.UP, Direction.RIGHT), 'b': (Direction.DOWN, Direction.LEFT), 'n': (Direction.DOWN, Direction.RIGHT) } self._key_pressed = set() self._should_highlight = False self._highlight_row = None self._highlight_col = None self.victory_screen = False self._selection_value = None self._selection_start_point = None self._selection_end_point = None self._selection_rectangle = None self.is_destroyed = False def default_guide_update(orientation: Orientation, line: int, status: GuideStatus): pass self.on_guide_update = default_guide_update def timeout_function() -> bool: self.invalidate() return not self.is_destroyed GLib.timeout_add(33, timeout_function)
def shape(self): w, h = self.container_size return Rectangle(Point(0, 0), w, h)
class _GuideLine(Widget): def __init__(self, elements: List[int], orientation: Orientation, line_thickness: int, font_size: int, max_size: int = None, element_distance: int = 10, line_distance: int = 1.8, **kwargs): super().__init__(**kwargs) if not elements: elements = [0] self._elements = [_GuideElement(str(e)) for e in elements] self.orientation = orientation self.line_thickness = line_thickness self.font_size = font_size self.shape = None self.max_size = max_size self.element_distance = element_distance self.line_distance = line_distance self.mouse_was_down = False self.line_extension = 0 self._selected_cell = None def set_done(self): for e in self._elements: e.is_done = True e.is_wrong = False def set_wrong(self): for e in self._elements: e.is_wrong = True e.is_done = False def set_default(self): for e in self._elements: e.is_wrong = False e.is_done = False def set_cancelled(self, is_it: bool, index: int): self._elements[index].is_cancelled = is_it def toggle_cancelled(self, index: int): e = self._elements[index] e.is_cancelled = not e.is_cancelled def set_shape_from_context(self, context: cairo.Context): widths_heights = [] done = False while not done: max_width = 0 max_height = 0 context.set_font_size(self.font_size) for e in reversed(self._elements): xb, yb, w, h, xa, ya = context.text_extents(e.label) widths_heights.append((xa, h)) e.width = xa e.height = h max_width = max(max_width, xa) max_height = max(max_height, h) # adjust font size in case it's too big if self.max_size is None: done = True else: if self.orientation == Orientation.HORIZONTAL: reference = max_height else: reference = max_width if reference + 2 * self.line_distance <= self.max_size: done = True else: self.font_size -= 1 positions = [] width = self.element_distance height = self.element_distance def get_padding(actual_size): if self.max_size is not None: return (self.max_size - actual_size) / 2 else: return self.line_distance if self.orientation == Orientation.HORIZONTAL: def handle_extents(e: _GuideElement): nonlocal width, height, positions, max_height width += e.width e.position = (-width, max_height + get_padding(max_height)) width += self.element_distance else: def handle_extents(e: _GuideElement): nonlocal width, height, positions, max_width e.position = (get_padding(e.width), -height) height += e.height + self.element_distance # for w, h in widths_heights: # handle_extents(w, h) for element in reversed(self._elements): handle_extents(element) if self.orientation == Orientation.HORIZONTAL: height = max_height + get_padding(max_height) * 2 width = width - self.element_distance + self.line_distance base_point = Point(-width, 0) else: width = max_width + get_padding(max_width) * 2 height = height - self.element_distance + self.line_distance base_point = Point(0, -height) self.shape = Rectangle(base_point, width, height) # for e, p in zip(self._elements, reversed(positions)): # e.position = p def on_draw(self, widget: Widget, context: cairo.Context): self.set_shape_from_context(context) shape = self.shape context.set_line_width(self.line_thickness) if self.orientation == Orientation.HORIZONTAL: context.move_to(shape.start.x - self.line_extension, shape.start.y) context.line_to(shape.start.x + shape.width, shape.start.y) else: context.move_to(shape.start.x, shape.start.y - self.line_extension) context.line_to(shape.start.x, shape.start.y + shape.height) context.stroke() for element in self._elements: context.move_to(*element.position) context.set_source_rgb(*element.color) context.show_text(element.label) def get_cell(self, p: Point): delta = self.line_distance for index, element in enumerate(self._elements): if Rectangle( Point(*element.position) + Point(-delta, delta), element.width + 2 * delta, -element.height - 2 * delta).is_point_in(p): return index return None def on_mouse_down(self, widget: Widget, event: MouseEvent): self._selected_cell = self.get_cell(event) def on_mouse_up(self, widget: Widget, event: MouseEvent): if self._selected_cell is not None and \ self._selected_cell == self.get_cell(event): self.toggle_cancelled(self._selected_cell) self.invalidate() self._selected_cell = None def is_point_in(self, p: Point, category=MouseEvent.UNKNOWN): if self.shape is None: return False elif (category == MouseEvent.MOUSE_UP and self.mouse_was_down) \ or self.shape.is_point_in(p): return True else: return False
def shape(self) -> Rectangle: return Rectangle(Point(0, 0), self._total_width, self._total_height)
class Navigator(UncheckedContainer): def __init__(self, side=40, protrusion=30): super().__init__() base_size = side height = protrusion colour = global_constants.start_selected arrow_up = Arrow(base_size, height, Arrow.UP, colour) arrow_down = Arrow(base_size, height, Arrow.DOWN, colour) arrow_left = Arrow(base_size, height, Arrow.LEFT, colour) arrow_right = Arrow(base_size, height, Arrow.RIGHT, colour) factor=6 center = height + base_size/2 + (base_size/factor) distance = (base_size + height)/2 + (base_size/factor) arrow_up.translate(center, center - distance) arrow_left.translate(center - distance, center) arrow_right.translate(center + distance, center) arrow_down.translate(center, center + distance) self.background_rectangle = Rectangle(Point(0,0), center * 2, center * 2) self.add(arrow_up) self.add(arrow_down) self.add(arrow_left) self.add(arrow_right) self._should_pass_move = False def on_mouse_up(self, widget, event): self._should_pass_move = False super().on_mouse_up(widget, event) return False def on_mouse_enter(self) -> bool: if self.mouse_is_down: self._should_pass_move = True return super().on_mouse_enter() def on_mouse_exit(self) -> bool: self._should_pass_move = False return super().on_mouse_exit() def on_mouse_move(self, widget, event): super().on_mouse_move(widget, event) return False def on_mouse_down(self, widget, event): super().on_mouse_down(widget, event) return True @property def fromWidgetCoords(self): width = self.background_rectangle.width height = self.background_rectangle.height total_width, total_height = self.container_size transl_width, transl_height = self._fromScale.transform_point(width, height) self.set_translate(total_width - transl_width, total_height - transl_height) return super().fromWidgetCoords @property def width(self): return self.background_rectangle.width def on_draw(self,widget,context): start = self.background_rectangle.start width = self.background_rectangle.width height = self.background_rectangle.height context.save() context.rectangle(start.x,start.y,width,height) context.set_source_rgb(0,0,0) context.stroke_preserve() context.set_source_rgb(*global_constants.background) context.fill() context.restore() super().on_draw(widget,context) def is_point_in(self, p:"Point", category=MouseEvent.UNKNOWN): return self.background_rectangle.is_point_in(p)
def clip_rectangle(self): if not self.is_clip_set(): raise AttributeError("Clip rectangle not set") return Rectangle(self._clip_start, self._clip_width, self._clip_height)
class _GuideLine(Widget): def __init__(self, elements: List[int], orientation: Orientation, line_thickness: int, font_size: int, max_size: int = None, element_distance: int=10, line_distance: int=1.8, **kwargs): super().__init__(**kwargs) if not elements: elements = [0] self._elements = [_GuideElement(str(e)) for e in elements] self.orientation = orientation self.line_thickness = line_thickness self.font_size = font_size self.shape = None self.max_size = max_size self.element_distance = element_distance self.line_distance = line_distance self.mouse_was_down = False self.line_extension = 0 self._selected_cell = None def set_done(self): for e in self._elements: e.is_done = True e.is_wrong = False def set_wrong(self): for e in self._elements: e.is_wrong = True e.is_done = False def set_default(self): for e in self._elements: e.is_wrong = False e.is_done = False def set_cancelled(self, is_it: bool, index: int): self._elements[index].is_cancelled = is_it def toggle_cancelled(self, index: int): e = self._elements[index] e.is_cancelled = not e.is_cancelled def set_shape_from_context(self, context: cairo.Context): widths_heights = [] done = False while not done: max_width = 0 max_height = 0 context.set_font_size(self.font_size) for e in reversed(self._elements): xb, yb, w, h, xa, ya = context.text_extents(e.label) widths_heights.append((xa, h)) e.width = xa e.height = h max_width = max(max_width, xa) max_height = max(max_height, h) # adjust font size in case it's too big if self.max_size is None: done = True else: if self.orientation == Orientation.HORIZONTAL: reference = max_height else: reference = max_width if reference + 2 * self.line_distance <= self.max_size: done = True else: self.font_size -= 1 positions = [] width = self.element_distance height = self.element_distance def get_padding(actual_size): if self.max_size is not None: return (self.max_size - actual_size) / 2 else: return self.line_distance if self.orientation == Orientation.HORIZONTAL: def handle_extents(e: _GuideElement): nonlocal width, height, positions, max_height width += e.width e.position = (-width, max_height + get_padding(max_height)) width += self.element_distance else: def handle_extents(e: _GuideElement): nonlocal width, height, positions, max_width e.position = (get_padding(e.width), -height) height += e.height + self.element_distance # for w, h in widths_heights: # handle_extents(w, h) for element in reversed(self._elements): handle_extents(element) if self.orientation == Orientation.HORIZONTAL: height = max_height + get_padding(max_height) * 2 width = width - self.element_distance + self.line_distance base_point = Point(-width, 0) else: width = max_width + get_padding(max_width) * 2 height = height - self.element_distance + self.line_distance base_point = Point(0, -height) self.shape = Rectangle(base_point, width, height) # for e, p in zip(self._elements, reversed(positions)): # e.position = p def on_draw(self, widget: Widget, context: cairo.Context): self.set_shape_from_context(context) shape = self.shape context.set_line_width(self.line_thickness) if self.orientation == Orientation.HORIZONTAL: context.move_to(shape.start.x - self.line_extension, shape.start.y) context.line_to(shape.start.x + shape.width, shape.start.y) else: context.move_to(shape.start.x, shape.start.y - self.line_extension) context.line_to(shape.start.x, shape.start.y + shape.height) context.stroke() for element in self._elements: context.move_to(*element.position) context.set_source_rgb(*element.color) context.show_text(element.label) def get_cell(self, p: Point): delta = self.line_distance for index, element in enumerate(self._elements): if Rectangle(Point(*element.position) + Point(-delta, delta), element.width + 2 * delta, -element.height - 2 * delta).is_point_in(p): return index return None def on_mouse_down(self, widget: Widget, event: MouseEvent): self._selected_cell = self.get_cell(event) def on_mouse_up(self, widget: Widget, event: MouseEvent): if self._selected_cell is not None and \ self._selected_cell == self.get_cell(event): self.toggle_cancelled(self._selected_cell) self.invalidate() self._selected_cell = None def is_point_in(self, p: Point, category=MouseEvent.UNKNOWN): if self.shape is None: return False elif (category == MouseEvent.MOUSE_UP and self.mouse_was_down) \ or self.shape.is_point_in(p): return True else: return False