added zoom levels, reload, home, cache TTL
This commit is contained in:
parent
a4249899e9
commit
9a3bbc95d9
@ -5,6 +5,7 @@ import os.path
|
|||||||
class Config:
|
class Config:
|
||||||
LAST_OPENED_FILE = ""
|
LAST_OPENED_FILE = ""
|
||||||
AUTOSAVE_INTERVAL = 5 * 60 * 1000
|
AUTOSAVE_INTERVAL = 5 * 60 * 1000
|
||||||
|
CACHE_TTL = 10
|
||||||
|
|
||||||
def __init__(self, path: str):
|
def __init__(self, path: str):
|
||||||
self._path: str = path
|
self._path: str = path
|
||||||
|
@ -3,12 +3,12 @@ from enum import Enum, auto
|
|||||||
from math import floor
|
from math import floor
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
import platformdirs
|
|
||||||
import pygame
|
import pygame
|
||||||
|
|
||||||
from src.config import Config
|
from src.config import Config
|
||||||
from src.image_handler import ImageHandler
|
|
||||||
from src.graph.graph import Graph
|
from src.graph.graph import Graph
|
||||||
|
from src.image_handler import ImageHandler
|
||||||
|
from src.utils.paths import CONFIG_DIR, CACHE_DIR
|
||||||
|
|
||||||
|
|
||||||
class Editor:
|
class Editor:
|
||||||
@ -17,13 +17,11 @@ class Editor:
|
|||||||
WIDTH: int = 800
|
WIDTH: int = 800
|
||||||
HEIGHT: int = 600
|
HEIGHT: int = 600
|
||||||
MAP_SIZE: int = 1024
|
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")
|
CONFIG_PATH: str = os.path.join(CONFIG_DIR, "config.json")
|
||||||
MAPS_DIR: str = os.path.join(CACHE_DIR, "maps")
|
MAPS_DIR: str = os.path.join(CACHE_DIR, "maps")
|
||||||
AUTOSAVE_PATH: str = os.path.join(CACHE_DIR, "AUTOSAVE.txt")
|
AUTOSAVE_PATH: str = os.path.join(CACHE_DIR, "AUTOSAVE.txt")
|
||||||
AUTOSAVE_EVENT: int = pygame.event.custom_type()
|
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
|
CROSSHAIR_SIZE: int = 10
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@ -34,19 +32,21 @@ class Editor:
|
|||||||
self.win: pygame.Surface = pygame.display.set_mode([self.width, self.height], pygame.RESIZABLE)
|
self.win: pygame.Surface = pygame.display.set_mode([self.width, self.height], pygame.RESIZABLE)
|
||||||
pygame.display.set_caption("Lycacraft Map Editor")
|
pygame.display.set_caption("Lycacraft Map Editor")
|
||||||
self.center: list[int] = [0, 0]
|
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.zoom: float = self.ZOOMS[self.zoom_i]
|
||||||
self.running: bool = False
|
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.clock: pygame.time.Clock = pygame.time.Clock()
|
||||||
self.left_drag_pos: Optional[tuple[int, int]] = None
|
self.left_drag_pos: Optional[tuple[int, int]] = None
|
||||||
self.mid_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.font: pygame.font.Font = pygame.font.SysFont("Ubuntu", 20)
|
||||||
self.loading_font: pygame.font.Font = pygame.font.SysFont("Ubuntu", 30)
|
self.loading_font: pygame.font.Font = pygame.font.SysFont("Ubuntu", 30)
|
||||||
self.zooms_texts: list[pygame.Surface] = list(map(
|
self.zooms_texts: list[pygame.Surface] = []
|
||||||
lambda z: self.font.render(str(z), True, (255, 255, 255)),
|
for zoom in self.ZOOMS:
|
||||||
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.is_renaming_node: bool = False
|
||||||
self.state: State = State.STOPPING
|
self.state: State = State.STOPPING
|
||||||
self.graph = Graph()
|
self.graph = Graph()
|
||||||
@ -61,6 +61,7 @@ class Editor:
|
|||||||
self.original_move_pos: Optional[tuple[int, int]] = None
|
self.original_move_pos: Optional[tuple[int, int]] = None
|
||||||
self.move_old_poses: Optional[dict[int, tuple[int, int]]] = None
|
self.move_old_poses: Optional[dict[int, tuple[int, int]]] = None
|
||||||
self.dirty: bool = False
|
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)
|
pygame.time.set_timer(self.AUTOSAVE_EVENT, self.config.AUTOSAVE_INTERVAL)
|
||||||
|
|
||||||
if os.path.exists(self.AUTOSAVE_PATH):
|
if os.path.exists(self.AUTOSAVE_PATH):
|
||||||
@ -72,7 +73,7 @@ class Editor:
|
|||||||
def mainloop(self) -> None:
|
def mainloop(self) -> None:
|
||||||
self.state = State.LOADING
|
self.state = State.LOADING
|
||||||
while self.state != State.STOPPING:
|
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:
|
if self.dirty:
|
||||||
caption += " (unsaved)"
|
caption += " (unsaved)"
|
||||||
pygame.display.set_caption(caption)
|
pygame.display.set_caption(caption)
|
||||||
@ -87,6 +88,7 @@ class Editor:
|
|||||||
if self.original_move_pos is not None:
|
if self.original_move_pos is not None:
|
||||||
self.move_poses()
|
self.move_poses()
|
||||||
self.render()
|
self.render()
|
||||||
|
self.image_handler.clean()
|
||||||
self.clock.tick(30)
|
self.clock.tick(30)
|
||||||
|
|
||||||
def quit(self) -> None:
|
def quit(self) -> None:
|
||||||
@ -138,6 +140,10 @@ class Editor:
|
|||||||
if len(self.selected_nodes) == 1:
|
if len(self.selected_nodes) == 1:
|
||||||
self.typing_text = self.graph.nodes[self.selected_nodes[0]].name
|
self.typing_text = self.graph.nodes[self.selected_nodes[0]].name
|
||||||
self.is_renaming_node = True
|
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:
|
elif event.type == pygame.KEYUP:
|
||||||
if event.key == pygame.K_m:
|
if event.key == pygame.K_m:
|
||||||
if self.original_move_pos is not None:
|
if self.original_move_pos is not None:
|
||||||
@ -198,7 +204,7 @@ class Editor:
|
|||||||
self.mid_drag_pos = mpos
|
self.mid_drag_pos = mpos
|
||||||
|
|
||||||
def render(self) -> None:
|
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_x = (self.center[0] * self.zoom) % self.MAP_SIZE
|
||||||
off_y = (self.center[1] * self.zoom) % self.MAP_SIZE
|
off_y = (self.center[1] * self.zoom) % self.MAP_SIZE
|
||||||
|
|
||||||
@ -254,13 +260,13 @@ class Editor:
|
|||||||
pygame.display.flip()
|
pygame.display.flip()
|
||||||
|
|
||||||
def render_zoom_slider(self) -> None:
|
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_h_margin = self.width * 0.02
|
||||||
zoom_v_margin = self.height * 0.05
|
zoom_v_margin = self.height * 0.05
|
||||||
zoom_x = self.width - zoom_h_margin
|
zoom_x = self.width - zoom_h_margin
|
||||||
zoom_y = self.height - zoom_v_margin - zoom_height
|
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
|
zoom_width = max(s.get_width() for s in self.zooms_texts) + 2 * zoom_r + 5
|
||||||
pygame.draw.rect(self.win, (80, 80, 80), [
|
pygame.draw.rect(self.win, (80, 80, 80), [
|
||||||
zoom_x + zoom_r - zoom_width - 5,
|
zoom_x + zoom_r - zoom_width - 5,
|
||||||
@ -277,6 +283,7 @@ class Editor:
|
|||||||
|
|
||||||
def render_loading(self) -> None:
|
def render_loading(self) -> None:
|
||||||
self.win.fill((0, 0, 0))
|
self.win.fill((0, 0, 0))
|
||||||
|
self.win.blit(self.loading_bg, [0, 0])
|
||||||
count = self.image_handler.count
|
count = self.image_handler.count
|
||||||
total = self.image_handler.total
|
total = self.image_handler.total
|
||||||
txt = self.loading_font.render(f"Loading maps - {count}/{total}", True, (255, 255, 255))
|
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:
|
if len(path.strip()) == 0:
|
||||||
path = last_path
|
path = last_path
|
||||||
|
|
||||||
self.graph = Graph.load(path)
|
if os.path.exists(path):
|
||||||
if save_config:
|
self.graph = Graph.load(path)
|
||||||
self.config.LAST_OPENED_FILE = path
|
if save_config:
|
||||||
self.config.save()
|
self.config.LAST_OPENED_FILE = path
|
||||||
self.dirty = False
|
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):
|
class State(Enum):
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import os
|
import os
|
||||||
import threading
|
import threading
|
||||||
|
import time
|
||||||
from math import floor
|
from math import floor
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
@ -7,10 +8,13 @@ import pygame
|
|||||||
|
|
||||||
|
|
||||||
class ImageHandler:
|
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.maps_dir: str = maps_dir
|
||||||
self.base_size: int = base_size
|
self.base_size: int = base_size
|
||||||
|
self.ttl: int = ttl
|
||||||
self.cache: dict = {}
|
self.cache: dict = {}
|
||||||
|
self.history: dict[tuple[float, tuple[int, int]], float] = {}
|
||||||
|
self.size: int = 0
|
||||||
self.count: int = 0
|
self.count: int = 0
|
||||||
self.total: int = 0
|
self.total: int = 0
|
||||||
self.loading: bool = False
|
self.loading: bool = False
|
||||||
@ -29,6 +33,7 @@ class ImageHandler:
|
|||||||
name, x, y = path.split(".")[0].split("_")
|
name, x, y = path.split(".")[0].split("_")
|
||||||
cache[(int(x), int(y))] = pygame.image.load(fullpath).convert_alpha()
|
cache[(int(x), int(y))] = pygame.image.load(fullpath).convert_alpha()
|
||||||
self.count += 1
|
self.count += 1
|
||||||
|
self.size += 1
|
||||||
|
|
||||||
self.cache = {
|
self.cache = {
|
||||||
1: cache
|
1: cache
|
||||||
@ -65,6 +70,19 @@ class ImageHandler:
|
|||||||
img = pygame.transform.scale_by(img, zoom)
|
img = pygame.transform.scale_by(img, zoom)
|
||||||
|
|
||||||
cache[pos] = img
|
cache[pos] = img
|
||||||
|
self.size += 1
|
||||||
|
|
||||||
self.cache[zoom] = cache
|
self.cache[zoom] = cache
|
||||||
|
self.history[(zoom, pos)] = time.time()
|
||||||
return cache[pos]
|
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
|
||||||
|
Loading…
Reference in New Issue
Block a user