From 9a3bbc95d9d49b3513199bcbeb96cf058d5c59d1 Mon Sep 17 00:00:00 2001 From: LordBaryhobal Date: Fri, 5 Jul 2024 22:23:03 +0200 Subject: [PATCH] added zoom levels, reload, home, cache TTL --- src/config.py | 1 + src/editor.py | 56 +++++++++++++++++++++++++++----------------- src/image_handler.py | 20 +++++++++++++++- 3 files changed, 55 insertions(+), 22 deletions(-) diff --git a/src/config.py b/src/config.py index 31e3804..145f5b3 100644 --- a/src/config.py +++ b/src/config.py @@ -5,6 +5,7 @@ import os.path class Config: LAST_OPENED_FILE = "" AUTOSAVE_INTERVAL = 5 * 60 * 1000 + CACHE_TTL = 10 def __init__(self, path: str): self._path: str = path diff --git a/src/editor.py b/src/editor.py index 82222f7..adf8e56 100644 --- a/src/editor.py +++ b/src/editor.py @@ -3,12 +3,12 @@ from enum import Enum, auto from math import floor from typing import Optional -import platformdirs import pygame from src.config import Config -from src.image_handler import ImageHandler from src.graph.graph import Graph +from src.image_handler import ImageHandler +from src.utils.paths import CONFIG_DIR, CACHE_DIR class Editor: @@ -17,13 +17,11 @@ class Editor: WIDTH: int = 800 HEIGHT: int = 600 MAP_SIZE: int = 1024 - CACHE_DIR: str = platformdirs.user_cache_dir(appname=APP_NAME, appauthor=APP_AUTHOR, ensure_exists=True) - CONFIG_DIR: str = platformdirs.user_config_dir(appname=APP_NAME, appauthor=APP_AUTHOR, ensure_exists=True) CONFIG_PATH: str = os.path.join(CONFIG_DIR, "config.json") MAPS_DIR: str = os.path.join(CACHE_DIR, "maps") AUTOSAVE_PATH: str = os.path.join(CACHE_DIR, "AUTOSAVE.txt") AUTOSAVE_EVENT: int = pygame.event.custom_type() - ZOOMS: tuple[float] = (0.25, 0.5, 1, 2, 4) + ZOOMS: tuple[float] = tuple(2**p for p in range(-6, 7)) CROSSHAIR_SIZE: int = 10 def __init__(self): @@ -34,19 +32,21 @@ class Editor: self.win: pygame.Surface = pygame.display.set_mode([self.width, self.height], pygame.RESIZABLE) pygame.display.set_caption("Lycacraft Map Editor") self.center: list[int] = [0, 0] - self.zoom_i: int = 2 + self.zoom_i: int = self.ZOOMS.index(1) self.zoom: float = self.ZOOMS[self.zoom_i] self.running: bool = False - self.image_handler: ImageHandler = ImageHandler(self.MAPS_DIR, self.MAP_SIZE) + self.image_handler: ImageHandler = ImageHandler(self.MAPS_DIR, self.MAP_SIZE, self.config.CACHE_TTL) self.clock: pygame.time.Clock = pygame.time.Clock() self.left_drag_pos: Optional[tuple[int, int]] = None self.mid_drag_pos: Optional[tuple[int, int]] = None self.font: pygame.font.Font = pygame.font.SysFont("Ubuntu", 20) self.loading_font: pygame.font.Font = pygame.font.SysFont("Ubuntu", 30) - self.zooms_texts: list[pygame.Surface] = list(map( - lambda z: self.font.render(str(z), True, (255, 255, 255)), - self.ZOOMS - )) + self.zooms_texts: list[pygame.Surface] = [] + for zoom in self.ZOOMS: + txt = str(zoom) + if zoom < 1: + txt = f"1/{int(1/zoom):d}" + self.zooms_texts.append(self.font.render(txt, True, (255, 255, 255))) self.is_renaming_node: bool = False self.state: State = State.STOPPING self.graph = Graph() @@ -61,6 +61,7 @@ class Editor: self.original_move_pos: Optional[tuple[int, int]] = None self.move_old_poses: Optional[dict[int, tuple[int, int]]] = None self.dirty: bool = False + self.loading_bg: pygame.Surface = pygame.Surface([self.width, self.height]) pygame.time.set_timer(self.AUTOSAVE_EVENT, self.config.AUTOSAVE_INTERVAL) if os.path.exists(self.AUTOSAVE_PATH): @@ -72,7 +73,7 @@ class Editor: def mainloop(self) -> None: self.state = State.LOADING while self.state != State.STOPPING: - caption = f"Lycacraft Map Editor - {self.clock.get_fps():.2f}fps" + caption = f"Lycacraft Map Editor - {self.clock.get_fps():.2f}fps - {self.image_handler.size} images" if self.dirty: caption += " (unsaved)" pygame.display.set_caption(caption) @@ -87,6 +88,7 @@ class Editor: if self.original_move_pos is not None: self.move_poses() self.render() + self.image_handler.clean() self.clock.tick(30) def quit(self) -> None: @@ -138,6 +140,10 @@ class Editor: if len(self.selected_nodes) == 1: self.typing_text = self.graph.nodes[self.selected_nodes[0]].name self.is_renaming_node = True + elif event.key == pygame.K_HOME: + self.center = [0, 0] + elif event.key == pygame.K_F5: + self.reload() elif event.type == pygame.KEYUP: if event.key == pygame.K_m: if self.original_move_pos is not None: @@ -198,7 +204,7 @@ class Editor: self.mid_drag_pos = mpos def render(self) -> None: - self.win.fill((0, 0, 0)) + self.win.fill((50, 50, 50)) off_x = (self.center[0] * self.zoom) % self.MAP_SIZE off_y = (self.center[1] * self.zoom) % self.MAP_SIZE @@ -254,13 +260,13 @@ class Editor: pygame.display.flip() def render_zoom_slider(self) -> None: - zoom_height = self.height * 0.2 + zoom_r = self.height / 80 + zoom_space = zoom_r * 4 + zoom_height = zoom_space * (len(self.ZOOMS) - 1) zoom_h_margin = self.width * 0.02 zoom_v_margin = self.height * 0.05 zoom_x = self.width - zoom_h_margin zoom_y = self.height - zoom_v_margin - zoom_height - zoom_space = zoom_height / 4 - zoom_r = zoom_space / 4 zoom_width = max(s.get_width() for s in self.zooms_texts) + 2 * zoom_r + 5 pygame.draw.rect(self.win, (80, 80, 80), [ zoom_x + zoom_r - zoom_width - 5, @@ -277,6 +283,7 @@ class Editor: def render_loading(self) -> None: self.win.fill((0, 0, 0)) + self.win.blit(self.loading_bg, [0, 0]) count = self.image_handler.count total = self.image_handler.total txt = self.loading_font.render(f"Loading maps - {count}/{total}", True, (255, 255, 255)) @@ -642,11 +649,18 @@ class Editor: if len(path.strip()) == 0: path = last_path - self.graph = Graph.load(path) - if save_config: - self.config.LAST_OPENED_FILE = path - self.config.save() - self.dirty = False + if os.path.exists(path): + self.graph = Graph.load(path) + if save_config: + self.config.LAST_OPENED_FILE = path + self.config.save() + self.dirty = False + + def reload(self) -> None: + self.state = State.LOADING + self.loading_bg = self.win.copy() + del self.image_handler + self.image_handler = ImageHandler(self.MAPS_DIR, self.MAP_SIZE, self.config.CACHE_TTL) class State(Enum): diff --git a/src/image_handler.py b/src/image_handler.py index 85fa68b..5be3043 100644 --- a/src/image_handler.py +++ b/src/image_handler.py @@ -1,5 +1,6 @@ import os import threading +import time from math import floor from typing import Optional @@ -7,10 +8,13 @@ import pygame class ImageHandler: - def __init__(self, maps_dir: str, base_size: int): + def __init__(self, maps_dir: str, base_size: int, ttl: int): self.maps_dir: str = maps_dir self.base_size: int = base_size + self.ttl: int = ttl self.cache: dict = {} + self.history: dict[tuple[float, tuple[int, int]], float] = {} + self.size: int = 0 self.count: int = 0 self.total: int = 0 self.loading: bool = False @@ -29,6 +33,7 @@ class ImageHandler: name, x, y = path.split(".")[0].split("_") cache[(int(x), int(y))] = pygame.image.load(fullpath).convert_alpha() self.count += 1 + self.size += 1 self.cache = { 1: cache @@ -65,6 +70,19 @@ class ImageHandler: img = pygame.transform.scale_by(img, zoom) cache[pos] = img + self.size += 1 self.cache[zoom] = cache + self.history[(zoom, pos)] = time.time() return cache[pos] + + def clean(self) -> None: + t = time.time() + new_history = {} + for (zoom, pos), t0 in self.history.items(): + if zoom != 1 and t0 + self.ttl < t: + del self.cache[zoom][pos] + self.size -= 1 + else: + new_history[(zoom, pos)] = t0 + self.history = new_history