class TextEditor(gtk.TextView): def __init__(self, textbuffer = None, *args, **kwargs): if textbuffer is None: textbuffer = TextBuffer() gtk.TextView.__init__(self, textbuffer, *args, **kwargs) self.buffer = textbuffer self.anno_width = 200 self.anno_padding = 10 self.anno_layout = Layout(self) self.anno_views = {} self.show_annotations = True self.handle_links = True self.set_right_margin(50 + self.anno_padding) self.connect('map-event', self._on_map_event) self.connect('expose-event', self._on_expose_event) self.connect('motion-notify-event', self._on_motion_notify_event) self.connect('event-after', self._on_event_after) self.buffer.connect('mark-set', self._on_buffer_mark_set) self.buffer.connect('annotation-added', self._on_annotation_added) self.buffer.connect('annotation-removed', self._on_annotation_removed) self.set_wrap_mode(gtk.WRAP_WORD) def _on_motion_notify_event(self, editor, event): if not self.handle_links: return x, y = self.window_to_buffer_coords(gtk.TEXT_WINDOW_WIDGET, int(event.x), int(event.y)) tags = self.get_iter_at_location(x, y).get_tags() # Without this call, further motion notify events don't get # triggered. self.window.get_pointer() # If any of the tags are links, show a hand. cursor = gtk.gdk.Cursor(gtk.gdk.XTERM) for tag in tags: if tag.get_data('link'): cursor = gtk.gdk.Cursor(gtk.gdk.HAND2) break self.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(cursor) return False def _on_event_after(self, textview, event): # Handle links here. Only when a button was released. if event.type != gtk.gdk.BUTTON_RELEASE: return False if event.button != 1: return False if not self.handle_links: return # Don't follow a link if the user has selected something. bounds = self.buffer.get_selection_bounds() if bounds: start, end = bounds if start.get_offset() != end.get_offset(): return False # Check whether the cursor is pointing at a link. x, y = self.window_to_buffer_coords(gtk.TEXT_WINDOW_WIDGET, int(event.x), int(event.y)) iter = textview.get_iter_at_location(x, y) for tag in iter.get_tags(): link = tag.get_data('link') if not link: continue self.emit('link-clicked', link) break return False def _on_buffer_mark_set(self, buffer, iter, mark): self._update_annotations() def _on_map_event(self, widget, event): self._update_annotation_area() self._update_annotations() def _on_expose_event(self, widget, event): text_window = widget.get_window(gtk.TEXT_WINDOW_TEXT) if event.window != text_window: return # Create the cairo context. ctx = event.window.cairo_create() # Restrict Cairo to the exposed area; avoid extra work ctx.rectangle(event.area.x, event.area.y, event.area.width, event.area.height) ctx.clip() self.draw(ctx, *event.window.get_size()) def draw(self, ctx, w, h): if ctx is None: return if not self.show_annotations: return # Draw the dashes that connect annotations to their marker. ctx.set_line_width(1) ctx.set_dash((3, 2)) right_margin = self.get_right_margin() for annotation in self.anno_layout.get_children(): mark_x, mark_y = self._get_annotation_mark_position(annotation) anno_x, anno_y, anno_w, anno_h, d = annotation.window.get_geometry() path = [(mark_x, mark_y - 5), (mark_x, mark_y), (w - right_margin, mark_y), (w, anno_y + anno_h / 2)] stroke_color = annotation.get_border_color() ctx.set_source_rgba(*color.to_rgba(stroke_color)) ctx.move_to(*path[0]) for x, y in path[1:]: ctx.line_to(x, y) ctx.stroke() def _get_annotation_mark_offset(self, view1, view2): mark1 = view1.annotation.start_mark mark2 = view2.annotation.start_mark iter1 = self.get_buffer().get_iter_at_mark(mark1) iter2 = self.get_buffer().get_iter_at_mark(mark2) rect1 = self.get_iter_location(iter1) rect2 = self.get_iter_location(iter2) if rect1.y != rect2.y: return rect1.y - rect2.y return rect2.x - rect1.x def _get_annotation_mark_position(self, view): start_mark = view.annotation.start_mark iter = self.get_buffer().get_iter_at_mark(start_mark) rect = self.get_iter_location(iter) mark_x, mark_y = rect.x, rect.y + rect.height return self.buffer_to_window_coords(gtk.TEXT_WINDOW_TEXT, mark_x, mark_y) def _update_annotation(self, annotation): # Resize the annotation. item_width = self.anno_width - 2 * self.anno_padding annotation.set_size_request(item_width, -1) # Find the x, y of the annotation's mark. mark_x, mark_y = self._get_annotation_mark_position(annotation) self.anno_layout.pull(annotation, mark_y) def _update_annotation_area(self): # Update the width and color of the annotation area. self.set_border_window_size(gtk.TEXT_WINDOW_RIGHT, self.anno_width) bg_color = self.get_style().base[gtk.STATE_NORMAL] window = self.get_window(gtk.TEXT_WINDOW_RIGHT) if window: window.set_background(bg_color) def _update_annotations(self): if not self.show_annotations: return # Sort the annotations by line/char number and update them. iter = self.get_buffer().get_end_iter() rect = self.get_iter_location(iter) height = rect.y + rect.height self.anno_layout.sort(self._get_annotation_mark_offset) for annotation in self.anno_layout.get_children(): self._update_annotation(annotation) self.anno_layout.update(self.anno_width, height) # Update lines. self.queue_draw() def set_annotation_area_width(self, width, padding = 10): self.anno_width = width self.anno_padding = padding self._update_annotations() def _on_annotation_added(self, buffer, annotation): if self.show_annotations == False: return annotation.set_display_buffer(self.buffer) view = AnnotationView(annotation) self.anno_views[annotation] = view for event in ('focus-in-event', 'focus-out-event'): view.connect(event, self._on_annotation_event, annotation, event) view.show_all() self._update_annotation_area() self.anno_layout.add(view) self.add_child_in_window(view, gtk.TEXT_WINDOW_RIGHT, self.anno_padding, 0) self._update_annotations() def _on_annotation_removed(self, buffer, annotation): view = self.anno_views[annotation] self.anno_layout.remove(view) self.remove(view) def _on_annotation_event(self, buffer, *args): annotation = args[-2] event_name = args[-1] self.emit('annotation-' + event_name, annotation) def set_show_annotations(self, active = True): if self.show_annotations == active: return # Unfortunately gtk.TextView deletes all children from the # border window if its size is 0. So we must re-add them when the # window reappears. self.show_annotations = active if active: for annotation in self.buffer.get_annotations(): self._on_annotation_added(self.buffer, annotation) else: for annotation in self.buffer.get_annotations(): self._on_annotation_removed(self.buffer, annotation) self.set_border_window_size(gtk.TEXT_WINDOW_RIGHT, 0) def set_handle_links(self, handle): """ Defines whether the cursor is changed and whether clicks are accepted when hovering over text with tags that have data named "link" attached. """ self.handle_links = handle
class TextEditor(gtk.TextView): def __init__(self, textbuffer=None, *args, **kwargs): if textbuffer is None: textbuffer = TextBuffer() gtk.TextView.__init__(self, textbuffer, *args, **kwargs) self.buffer = textbuffer self.anno_width = 200 self.anno_padding = 10 self.anno_layout = Layout(self) self.anno_views = {} self.show_annotations = True self.handle_links = True self.set_right_margin(50 + self.anno_padding) self.connect('map-event', self._on_map_event) self.connect('expose-event', self._on_expose_event) self.connect('motion-notify-event', self._on_motion_notify_event) self.connect('event-after', self._on_event_after) self.buffer.connect('mark-set', self._on_buffer_mark_set) self.buffer.connect('annotation-added', self._on_annotation_added) self.buffer.connect('annotation-removed', self._on_annotation_removed) self.set_wrap_mode(gtk.WRAP_WORD) def _on_motion_notify_event(self, editor, event): if not self.handle_links: return x, y = self.window_to_buffer_coords(gtk.TEXT_WINDOW_WIDGET, int(event.x), int(event.y)) tags = self.get_iter_at_location(x, y).get_tags() # Without this call, further motion notify events don't get # triggered. self.window.get_pointer() # If any of the tags are links, show a hand. cursor = gtk.gdk.Cursor(gtk.gdk.XTERM) for tag in tags: if tag.get_data('link'): cursor = gtk.gdk.Cursor(gtk.gdk.HAND2) break self.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(cursor) return False def _on_event_after(self, textview, event): # Handle links here. Only when a button was released. if event.type != gtk.gdk.BUTTON_RELEASE: return False if event.button != 1: return False if not self.handle_links: return # Don't follow a link if the user has selected something. bounds = self.buffer.get_selection_bounds() if bounds: start, end = bounds if start.get_offset() != end.get_offset(): return False # Check whether the cursor is pointing at a link. x, y = self.window_to_buffer_coords(gtk.TEXT_WINDOW_WIDGET, int(event.x), int(event.y)) iter = textview.get_iter_at_location(x, y) for tag in iter.get_tags(): link = tag.get_data('link') if not link: continue self.emit('link-clicked', link) break return False def _on_buffer_mark_set(self, buffer, iter, mark): self._update_annotations() def _on_map_event(self, widget, event): self._update_annotation_area() self._update_annotations() def _on_expose_event(self, widget, event): text_window = widget.get_window(gtk.TEXT_WINDOW_TEXT) if event.window != text_window: return # Create the cairo context. ctx = event.window.cairo_create() # Restrict Cairo to the exposed area; avoid extra work ctx.rectangle(event.area.x, event.area.y, event.area.width, event.area.height) ctx.clip() self.draw(ctx, *event.window.get_size()) def draw(self, ctx, w, h): if ctx is None: return if not self.show_annotations: return # Draw the dashes that connect annotations to their marker. ctx.set_line_width(1) ctx.set_dash((3, 2)) right_margin = self.get_right_margin() for annotation in self.anno_layout.get_children(): mark_x, mark_y = self._get_annotation_mark_position(annotation) anno_x, anno_y, anno_w, anno_h, d = annotation.window.get_geometry( ) path = [(mark_x, mark_y - 5), (mark_x, mark_y), (w - right_margin, mark_y), (w, anno_y + anno_h / 2)] stroke_color = annotation.get_border_color() ctx.set_source_rgba(*color.to_rgba(stroke_color)) ctx.move_to(*path[0]) for x, y in path[1:]: ctx.line_to(x, y) ctx.stroke() def _get_annotation_mark_offset(self, view1, view2): mark1 = view1.annotation.start_mark mark2 = view2.annotation.start_mark iter1 = self.get_buffer().get_iter_at_mark(mark1) iter2 = self.get_buffer().get_iter_at_mark(mark2) rect1 = self.get_iter_location(iter1) rect2 = self.get_iter_location(iter2) if rect1.y != rect2.y: return rect1.y - rect2.y return rect2.x - rect1.x def _get_annotation_mark_position(self, view): start_mark = view.annotation.start_mark iter = self.get_buffer().get_iter_at_mark(start_mark) rect = self.get_iter_location(iter) mark_x, mark_y = rect.x, rect.y + rect.height return self.buffer_to_window_coords(gtk.TEXT_WINDOW_TEXT, mark_x, mark_y) def _update_annotation(self, annotation): # Resize the annotation. item_width = self.anno_width - 2 * self.anno_padding annotation.set_size_request(item_width, -1) # Find the x, y of the annotation's mark. mark_x, mark_y = self._get_annotation_mark_position(annotation) self.anno_layout.pull(annotation, mark_y) def _update_annotation_area(self): # Update the width and color of the annotation area. self.set_border_window_size(gtk.TEXT_WINDOW_RIGHT, self.anno_width) bg_color = self.get_style().base[gtk.STATE_NORMAL] window = self.get_window(gtk.TEXT_WINDOW_RIGHT) if window: window.set_background(bg_color) def _update_annotations(self): if not self.show_annotations: return # Sort the annotations by line/char number and update them. iter = self.get_buffer().get_end_iter() rect = self.get_iter_location(iter) height = rect.y + rect.height self.anno_layout.sort(self._get_annotation_mark_offset) for annotation in self.anno_layout.get_children(): self._update_annotation(annotation) self.anno_layout.update(self.anno_width, height) # Update lines. self.queue_draw() def set_annotation_area_width(self, width, padding=10): self.anno_width = width self.anno_padding = padding self._update_annotations() def _on_annotation_added(self, buffer, annotation): if self.show_annotations == False: return annotation.set_display_buffer(self.buffer) view = AnnotationView(annotation) self.anno_views[annotation] = view for event in ('focus-in-event', 'focus-out-event'): view.connect(event, self._on_annotation_event, annotation, event) view.show_all() self._update_annotation_area() self.anno_layout.add(view) self.add_child_in_window(view, gtk.TEXT_WINDOW_RIGHT, self.anno_padding, 0) self._update_annotations() def _on_annotation_removed(self, buffer, annotation): view = self.anno_views[annotation] self.anno_layout.remove(view) self.remove(view) def _on_annotation_event(self, buffer, *args): annotation = args[-2] event_name = args[-1] self.emit('annotation-' + event_name, annotation) def set_show_annotations(self, active=True): if self.show_annotations == active: return # Unfortunately gtk.TextView deletes all children from the # border window if its size is 0. So we must re-add them when the # window reappears. self.show_annotations = active if active: for annotation in self.buffer.get_annotations(): self._on_annotation_added(self.buffer, annotation) else: for annotation in self.buffer.get_annotations(): self._on_annotation_removed(self.buffer, annotation) self.set_border_window_size(gtk.TEXT_WINDOW_RIGHT, 0) def set_handle_links(self, handle): """ Defines whether the cursor is changed and whether clicks are accepted when hovering over text with tags that have data named "link" attached. """ self.handle_links = handle
class MainMenuState(GameState): @staticmethod def main_menu_button(text: str, on_click: Callable[[], None]): return Button( x=0, y=0, width=100, height=50, surface=filled_rect((200, 100), hex_to_rgb('B7CBFF')), surface_hover=filled_rect((200, 100), hex_to_rgb('85A7FF')), surface_click=filled_rect((200, 100), hex_to_rgb('6B95FF')), text=text, text_color=hex_to_rgb('FFFFFF'), font=FONT, on_click=on_click, ) def __init__(self): self.start_game_button = MainMenuState.main_menu_button( 'start game', lambda: pygame.event.post(make_event( GameEvent.CHANGE_STATE, state=LevelState() )) ) self.ai_button = MainMenuState.main_menu_button( 'ai', lambda: print('click ai'), ) self.quit_game_button = MainMenuState.main_menu_button( 'quit game', lambda: pygame.event.post(pygame.event.Event(QUIT)) ) self.button_sprites: Union[pygame.sprite.Group, Iterable[Button]] = pygame.sprite.Group() self.button_sprites.add(self.start_game_button) self.button_sprites.add(self.ai_button) self.button_sprites.add(self.quit_game_button) self.button_layout = Layout(vpadding_inner=20) self.button_layout.add_all(self.button_sprites) def handle_events(self, game: 'Game'): for event in pygame.event.get(): if event.type == QUIT: return game.quit() if event.type == DispatchableEvent.EVENT.value: if event.event == GameEvent.CHANGE_STATE: game.set_state(event.state) for button in self.button_sprites: button.handle_event(event) def update(self, game: 'Game'): self.button_layout.update( game.screen.get_rect(), (LayoutLocation.CENTER, LayoutLocation.CENTER) ) def draw(self, game: 'Game'): game.screen.fill((0, 0, 0)) game.grid.draw_background(game.screen) self.button_sprites.draw(game.screen)
def update(self, render=None): Active.update(self) currentHowActiveMouse = None if self.isActive and self.isSelect: done = False for widgetList in self._widget: for child in widgetList: if isinstance(child, Active) and child.isSelect and child is not self._currentSelect: self._currentSelect = child done = True break if done: break if not self._currentSelect: done = False for widgetList in self._widget: for child in widgetList: if isinstance(child, Active): self._currentSelect = child done = True break if done: break self._deselectOtherWidget() if self.event and self._currentSelect: currentHowActiveMouse = self._currentSelect.howActiveMouse if not self._currentSelect.howSelect(): self._currentSelect.howActiveMouse=[None] posCurrentSelect = self.getWidgetPosition(self._currentSelect) caseCurrentSelect = self.getWidgetCase(self._currentSelect) if not self.changeLeft in self._currentSelect.howActiveKeyboard and\ self.event.getOnePressedKeys(self.changeLeft): done = False for x in range(posCurrentSelect.x-1, -1, -1): for y in range(posCurrentSelect.y, len(self._widget[x])): child = self.__getitem__(sf.Vector2(x, y)) if isinstance(child, Active) and child is not self._currentSelect: done = True self._currentSelect = child break if done: break for y in range(0, posCurrentSelect.y): child = self.__getitem__(sf.Vector2(x, y)) if isinstance(child, Active) and child is not self._currentSelect: done = True self._currentSelect = child break if done: break if not done: for x in range(len(self._widget)-1, posCurrentSelect.x, -1): for y in range(posCurrentSelect.y, len(self._widget[x])): child = self.__getitem__(sf.Vector2(x, y)) if isinstance(child, Active) and child is not self._currentSelect: done = True self._currentSelect = child break if done: break for y in range(0, posCurrentSelect.y): child = self.__getitem__(sf.Vector2(x, y)) if isinstance(child, Active) and child is not self._currentSelect: done = True self._currentSelect = child break if done: break elif not self.changeRight in self._currentSelect.howActiveKeyboard and\ self.event.getOnePressedKeys(self.changeRight): done = False y = posCurrentSelect.y for x in range(posCurrentSelect.x + caseCurrentSelect.x, \ len(self._widget)): for y in range(posCurrentSelect.y, len(self._widget[x])): child = self.__getitem__(sf.Vector2(x, y)) if isinstance(child, Active) and child is not self._currentSelect: done = True self._currentSelect = child break if done: break for y in range(0, posCurrentSelect.y): child = self.__getitem__(sf.Vector2(x, y)) if isinstance(child, Active) and child is not self._currentSelect: done = True self._currentSelect = child break if done: break if not done: for x in range(0, posCurrentSelect.x): for y in range(posCurrentSelect.y, len(self._widget[x])): child = self.__getitem__(sf.Vector2(x, y)) if isinstance(child, Active) and child is not self._currentSelect: done = True self._currentSelect = child break if done: break for y in range(0, posCurrentSelect.y): child = self.__getitem__(sf.Vector2(x, y)) if isinstance(child, Active) and child is not self._currentSelect: done = True self._currentSelect = child break if done: break elif not self.changeTop in self._currentSelect.howActiveKeyboard and \ self.event.getOnePressedKeys(self.changeTop): done = False for y in range(posCurrentSelect.y-1, -1, -1): for x in range(posCurrentSelect.x, len(self._widget)): child = self.__getitem__(sf.Vector2(x, y)) if isinstance(child, Active) and child is not self._currentSelect: done = True self._currentSelect = child break if done: break for x in range(0, posCurrentSelect.x): child = self.__getitem__(sf.Vector2(x, y)) if isinstance(child, Active) and child is not self._currentSelect: done = True self._currentSelect = child break if done: break if not done: for y in range(len(self._widget[0])-1, posCurrentSelect.y, -1): for x in range(posCurrentSelect.x, len(self._widget)): child = self.__getitem__(sf.Vector2(x, y)) if isinstance(child, Active) and child is not self._currentSelect: done = True self._currentSelect = child break if done: break for x in range(0, posCurrentSelect.x): child = self.__getitem__(sf.Vector2(x, y)) if isinstance(child, Active) and child is not self._currentSelect: done = True self._currentSelect = child break if done: break elif not self.changeBottom in self._currentSelect.howActiveKeyboard and\ self.event.getOnePressedKeys(self.changeBottom): done = False x = posCurrentSelect.x for y in range(posCurrentSelect.y + caseCurrentSelect.y, \ len(self._widget[0])): for x in range(posCurrentSelect.x, len(self._widget)): child = self.__getitem__(sf.Vector2(x, y)) if isinstance(child, Active) and child is not self._currentSelect: done = True self._currentSelect = child break if done: break for x in range(0, posCurrentSelect.x): child = self.__getitem__(sf.Vector2(x, y)) if isinstance(child, Active) and child is not self._currentSelect: done = True self._currentSelect = child break if done: break if not done: for y in range(0, posCurrentSelect.y): for x in range(posCurrentSelect.x, len(self._widget)): child = self.__getitem__(sf.Vector2(x, y)) if isinstance(child, Active) and child is not self._currentSelect: done = True self._currentSelect = child break if done: break for x in range(0, posCurrentSelect.x): child = self.__getitem__(sf.Vector2(x, y)) if isinstance(child, Active) and child is not self._currentSelect: done = True self._currentSelect = child break if done: break self._deselectOtherWidget() else: if self._currentSelect: self._currentSelect.permanentSelection=False self._currentSelect.deselectIt() Layout.update(self, render) if self._currentSelect: self._currentSelect.howActiveMouse = currentHowActiveMouse