def __init__(self, file_name, conf): self.file_name = file_name self.map = self.load(file_name) self.config = conf # type: Config self.screen = self.get_screen() # type: pygame.SurfaceType self.clock = pygame.time.Clock() self.dirty_rects = [] self._offset = self.get_default_offset() self.start_drag_pos = None # type: Pos self.start_drag_offset = None # type: Pos self.left_bar_pos = 42.1 # placeholder self.cursor = Pos(0, 0) self.overtype = True self.exit = False repeat_every(10)(self.save) # Log if the FPS drops @repeat_every(1, start_offset=1) def get_fps(): fps = self.clock.get_fps() if fps < self.FPS / 2: logging.warning('Low fps: %s', fps) self.reset_screen()
def update_left_bar(self): """Check if the left bar has changed and if so, draw it agin on next render.""" pos = self.map_to_screen_pos(Pos(self.map.col_min, 0))[0] - 1 if self.left_bar_pos != pos: rect = self.get_left_bar_rect() self.dirty_rects.append(rect) rect = rect.copy() rect.x = pos self.dirty_rects.append(rect) self.left_bar_pos = rect.x
def render(self): # clear the dirt for rect in self.dirty_rects: self.screen.fill(COLORS.BACKGROUND, rect) # render what we need cursor_rendered = False # use a copy because the list can grow and we don't care about the new rects dirty_rects = self.dirty_rects[:] for pos, char in self.map: for dirt_rect in dirty_rects: pos = Pos(pos) rect = self.map_to_screen_rect(pos) # if we need to rerender this part of the screen if rect.colliderect(dirt_rect): bg = COLORS.BACKGROUND color = COLORS.TEXT if pos == self.cursor and self.overtype: bg, color = color, bg cursor_rendered = True surf = MAINFONT.render_char(char, color, bg) self.screen.blit(surf, rect) # try to minimize the overlappings would be nice if not dirt_rect.contains(rect): self.dirty_rects.append(rect) break cursor_rect = self.map_to_screen_rect(self.cursor) if not cursor_rendered and self.has_dirt(cursor_rect): if self.overtype: char = self.map[self.cursor.row, self.cursor.col] surf = MAINFONT.render_char(char, COLORS.BACKGROUND, COLORS.TEXT) self.screen.blit(surf, cursor_rect) else: rect = self.map_to_screen_rect(self.cursor) rect.width = 2 self.screen.fill(COLORS.TEXT, rect) self.dirty_rects.append(cursor_rect) left_bar_rect = self.get_left_bar_rect() if self.has_dirt(left_bar_rect): self.screen.fill(COLORS.TEXT, left_bar_rect) self.dirty_rects.append(left_bar_rect)
def set_size(self, new_size): """Chage the size of the font but keep it between 80 and 2.""" # clamp the size self.font_size = min(80, max(2, round(new_size))) # clear all caches self.render_char.cache_clear() self.render_text.cache_clear() for dep in self._dependant_caches: dep.cache_clear() font = pygame.font.Font(self.font_name, self.font_size) self.char_size = Pos(font.size(".")) self.font = font return font
def set_cursor(self, x, y): self.dirty_rects.append(self.map_to_screen_rect(self.cursor)) self.cursor = Pos(x, y) new_rect = pygame.Rect(self.map_to_screen_rect(self.cursor)) self.dirty_rects.append(new_rect) screen_rect = self.screen.get_rect() # type: pygame.rect.RectType if new_rect.x < 0: # the modulo part it to keep the grid aligned with what it was before, # just changing by a mutliple of charsize self.offset -= new_rect.x - new_rect.x % MAINFONT.char_size.x, 0 elif new_rect.right > screen_rect.right: # don't ask why it works self.offset += ( (screen_rect.right - new_rect.left) // MAINFONT.char_size.x - 1) * MAINFONT.char_size.x, 0 if new_rect.y < 0: self.offset -= 0, new_rect.y - new_rect.y % MAINFONT.char_size.y elif new_rect.bottom > screen_rect.bottom: self.offset += 0, ( (screen_rect.bottom - new_rect.top) // MAINFONT.char_size.y - 1) * MAINFONT.char_size.y
def get_mouse_pos(self): x, y = pygame.mouse.get_pos() if self.config.retina: x *= 2 y *= 2 return Pos(x, y)
def get_default_offset(self): return Pos(250, 15)
def map_cursor(self): """Get the position of the cursor in the map ((row, col) instead of (x, y)).""" return Pos(self.cursor.row, self.cursor.col)
def update(self): mouse = self.get_mouse_pos() for e in pygame.event.get(): if e.type == pygame.QUIT: return self.quit() elif e.type == pygame.KEYDOWN: if e.key == pygame.K_ESCAPE: return self.quit() elif e.key == pygame.K_RIGHT: self.move_cursor(1, 0) elif e.key == pygame.K_LEFT: self.move_cursor(-1, 0) elif e.key == pygame.K_UP: self.move_cursor(0, -1) elif e.key == pygame.K_DOWN: self.move_cursor(0, 1) elif e.key == pygame.K_RETURN: self.map.insert(self.map_cursor, '\n') self.set_cursor(self.map.col_min, self.cursor.row + 1) self.reset_screen() elif e.key == pygame.K_BACKSPACE: self.move_cursor(-1, 0) self.map.suppr((self.cursor.row, self.cursor.col)) self.reset_screen() elif e.key == pygame.K_DELETE: self.map.suppr((self.cursor.row, self.cursor.col)) self.reset_screen() elif e.key == pygame.K_INSERT: self.overtype = not self.overtype self.dirty_rects.append( self.map_to_screen_rect(self.cursor)) elif e.key == pygame.K_F5: self.launch_debugger() elif e.mod & pygame.KMOD_CTRL: if e.key == pygame.K_r: # reset position and size self.offset = self.get_default_offset() self.start_drag_pos = None self.start_drag_offset = None self.cursor = Pos(0, 0) self.set_font_size(DEFAULT_FONT_SIZE) elif e.key == pygame.K_EQUALS: # I would like the + but apparently it doesn't work self.change_font_size(1) elif e.key == pygame.K_MINUS: self.change_font_size(-1) elif e.key == pygame.K_s: self.save() else: s = e.unicode # type: str if s and s.isprintable(): if self.overtype: self.map[self.cursor.row, self.cursor.col] = s # no need to update more than where the cursor was and it's done by move.cursor else: self.map.insert(self.map_cursor, s) self.reset_screen() self.move_cursor(1, 0) self.update_left_bar() elif e.type == pygame.MOUSEBUTTONDOWN: if e.button == 1: self.set_cursor( *self.screen_to_map_pos(self.get_mouse_pos())) elif e.button == 3: self.start_drag_pos = mouse self.start_drag_offset = self.offset self.reset_screen() elif e.type == pygame.MOUSEBUTTONUP: if e.button == 3: self.start_drag_pos = None self.start_drag_offset = None self.reset_screen() # drag the code if needed if self.start_drag_pos is not None: actual_pos = mouse dx, dy = actual_pos - self.start_drag_pos if abs(dx) < 20: dx = 0 if abs(dy) < 20: dy = 0 self.offset = self.start_drag_offset + (dx, dy)