Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,11 @@ A number based on the current time is appended to the file name, e.g. `generatio
python src/main.py --ui
```

The UI draws a generated floor room by room. Every generated floor is kept,
so you can switch between them.
The UI draws a generated floor room by room, including the doors between the rooms
and an arrow from a teleport room to its target. Every generated floor is kept,
so you can switch between them. A legend next to the floor shows the room types
and how often they appear on the current floor.
All actions are also available in the File, View and Floor menus.

| Key | Action |
|---|---|
Expand All @@ -58,7 +61,7 @@ so you can switch between them.
| `Ctrl+S` | Save the current floor |
| `Ctrl+Shift+S` | Save the current floor as a new file |
| `Ctrl+T` | Switch between light and dark theme |
| `F1` | Show the legend of the room colors |
| `F1` | Show or hide the legend |
| `Ctrl+Q` | Quit |

## REST API
Expand Down
39 changes: 36 additions & 3 deletions src/generators/theme_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
Module to handle the theme of the application.
"""

from typing import Callable, Optional
from tkinter import Tk, Canvas, ttk
from typing import Callable, List, Optional
from tkinter import BooleanVar, Menu, Tk, Canvas, ttk

from rooms.tkinter import style

Expand All @@ -26,24 +26,57 @@ def __init__(
self._canvas = canvas
self._on_change = on_change
self._palette = style.LIGHT
self._menus: List[Menu] = []
# Shown as check mark in the view menu
self.dark = BooleanVar(tk, value=False)
self.apply_theme()

def switch_theme(self) -> None:
"""
Switches the theme of the application.
"""
self._palette = style.DARK if self._palette is style.LIGHT else style.LIGHT
self.set_dark(self._palette is style.LIGHT)

def set_dark(self, dark: bool) -> None:
"""
Switches to the dark or the light theme.
@param dark: True for the dark theme, False for the light theme
"""
self._palette = style.DARK if dark else style.LIGHT
self.dark.set(dark)
self.apply_theme()
if self._on_change is not None:
self._on_change()

def register_menu(self, menu: Menu) -> None:
"""
Colors the menu in the current theme and whenever the theme changes.
@param menu: menu to color
"""
self._menus.append(menu)
self._apply_to_menu(menu)

def _apply_to_menu(self, menu: Menu) -> None:
palette = self._palette
menu.configure(
background=palette.panel,
foreground=palette.text,
activebackground=palette.button_active,
activeforeground=palette.text,
selectcolor=palette.text,
borderwidth=0,
relief="flat",
)

def apply_theme(self) -> None:
"""
Applies the current theme to the application.
"""
palette = self._palette
style.set_palette(palette)
self._tk.configure(background=palette.window)
for menu in self._menus:
self._apply_to_menu(menu)
self._canvas.configure(
background=palette.canvas, highlightthickness=0, borderwidth=0
)
Expand Down
113 changes: 90 additions & 23 deletions src/generators/tkinter_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from generators.floor_manager import FloorManager
from generators.generator import Generator
from generators.theme_handler import ThemeHandler
from rooms.tkinter import legend_window
from rooms.tkinter.legend_panel import LegendPanel
from utils import util_functions, globals as my_globals


Expand All @@ -38,8 +38,9 @@ def __init__(
"""
super().__init__(seed, output_file_name, output_file_path, stage_id)
self._tk = tk.Tk()
self._content = ttk.Frame(self._tk)
self._canvas = tk.Canvas(
self._tk,
self._content,
height=my_globals.FLOOR_HEIGHT * my_globals.ROOM_HEIGHT,
width=my_globals.FLOOR_WIDTH * my_globals.ROOM_WIDTH,
)
Expand All @@ -49,7 +50,13 @@ def __init__(
self._theme_handler = ThemeHandler(
self._tk, self._canvas, on_change=self._redraw_floor
)
self._legend = LegendPanel(self._content)
self.show_legend = tk.BooleanVar(self._tk, value=True)
self._floor_manager = FloorManager(output_file_name)
# The position changes whenever another floor is shown
self._floor_manager.position.trace_add(
"write", lambda *args: self._update_legend()
)
self._tk.protocol("WM_DELETE_WINDOW", self.quit)

def _create_floor(self) -> None:
Expand Down Expand Up @@ -146,7 +153,9 @@ def run(self) -> None:
self._tk.resizable(False, False)
self.generate()
self._floor_manager.get_current_floor().draw()
self._canvas.pack(padx=16, pady=(16, 8))
self._content.pack(padx=16, pady=(16, 8))
self._canvas.pack(side=tk.LEFT)
self._apply_legend_visibility()
self.add_information_frame()
self.add_buttons()
self.create_menu_bar()
Expand All @@ -161,9 +170,40 @@ def quit(self) -> None:

def _redraw_floor(self) -> None:
"""
Draws the current floor again, e.g. after the theme has changed.
Draws the current floor and the legend again, e.g. after the theme has changed.
"""
self._floor_manager.get_current_floor().draw()
self._legend.redraw()
if self._floor_manager.current_floor_index >= 0:
self._floor_manager.get_current_floor().draw()

def generate(self, new_seed=None) -> None:
"""
Generates a new floor and updates the legend.
"""
super().generate(new_seed)
self._update_legend()

def _update_legend(self) -> None:
"""
Shows the room counts of the current floor in the legend.
"""
self._legend.update_counts(self._floor_manager.get_current_floor())

def toggle_legend(self) -> None:
"""
Shows or hides the legend next to the floor.
"""
self.show_legend.set(not self.show_legend.get())
self._apply_legend_visibility()

def _apply_legend_visibility(self) -> None:
"""
Shows or hides the legend depending on show_legend.
"""
if self.show_legend.get():
self._legend.pack(side=tk.LEFT, anchor=tk.N, padx=(20, 0))
else:
self._legend.pack_forget()

def _get_bottom_bar(self) -> ttk.Frame:
"""
Expand Down Expand Up @@ -252,34 +292,61 @@ def add_buttons(self) -> None:

def create_menu_bar(self) -> None:
"""
Creates a menu bar for the application.
Creates the menu bar with the file, view and floor menus.
"""
# Create a menu bar
menu_bar = tk.Menu(self._tk)
menu_bar = tk.Menu(self._tk, tearoff=False)
self._tk.config(menu=menu_bar)
menu_bar.add_command(label="Open", command=self.open, accelerator="Ctrl+o")
menu_bar.add_command(
label="Save",
command=self._save_current_floor,
accelerator="Ctrl+s",
self._theme_handler.register_menu(menu_bar)

file_menu = tk.Menu(menu_bar, tearoff=False)
file_menu.add_command(label="Open…", command=self.open, accelerator="Ctrl+O")
file_menu.add_command(
label="Save", command=self._save_current_floor, accelerator="Ctrl+S"
)
menu_bar.add_command(
label="Save As", command=self.save, accelerator="Ctrl+Shift+s"
file_menu.add_command(
label="Save As…", command=self.save, accelerator="Ctrl+Shift+S"
)
menu_bar.add_command(
label="Switch Theme",
command=self._theme_handler.switch_theme,
accelerator="Ctrl+t",
file_menu.add_separator()
file_menu.add_command(label="Exit", command=self.quit, accelerator="Ctrl+Q")
menu_bar.add_cascade(label="File", menu=file_menu)

view_menu = tk.Menu(menu_bar, tearoff=False)
view_menu.add_checkbutton(
label="Dark Theme",
variable=self._theme_handler.dark,
command=lambda: self._theme_handler.set_dark(
self._theme_handler.dark.get()
),
accelerator="Ctrl+T",
)
menu_bar.add_command(
label="Legend", command=legend_window.display_legend, accelerator="F1"
view_menu.add_checkbutton(
label="Legend",
variable=self.show_legend,
command=self._apply_legend_visibility,
accelerator="F1",
)
menu_bar.add_command(label="Exit", command=self.quit, accelerator="Ctrl+q")
menu_bar.add_cascade(label="View", menu=view_menu)

floor_menu = tk.Menu(menu_bar, tearoff=False)
floor_menu.add_command(
label="Generate", command=self._generate_and_draw_floor, accelerator="Space"
)
floor_menu.add_command(
label="Previous Floor", command=self._decrease_floor, accelerator="←"
)
floor_menu.add_command(
label="Next Floor", command=self._increase_floor, accelerator="→"
)
menu_bar.add_cascade(label="Floor", menu=floor_menu)

for menu in (file_menu, view_menu, floor_menu):
self._theme_handler.register_menu(menu)

self._tk.bind("<Control-o>", lambda event: self.open())
self._tk.bind("<Control-s>", lambda event: self._save_current_floor())
self._tk.bind("<Control-S>", lambda event: self.save())
self._tk.bind("<Control-t>", lambda event: self._theme_handler.switch_theme())
self._tk.bind("<F1>", lambda event: legend_window.display_legend())
self._tk.bind("<F1>", lambda event: self.toggle_legend())
self._tk.bind("<Control-q>", lambda event: self.quit())

def _save_current_floor(self) -> None:
Expand Down
98 changes: 98 additions & 0 deletions src/rooms/tkinter/legend_panel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
"""
This module contains the legend that explains the room types.
"""

import tkinter as tk
from collections import Counter
from tkinter import ttk
from typing import List

from floors.floor import Floor
from rooms.tkinter import style
from rooms.tkinter.tkinter_room import TkinterRoom
from utils.room_type import RoomType, room_names

_ROW_HEIGHT = 44
_WIDTH = 230


class LegendPanel(ttk.Frame):
"""
Panel next to the floor that shows the room types
and how often they appear on the current floor.
"""

def __init__(self, parent: tk.Misc) -> None:
super().__init__(parent)
ttk.Label(self, text="Legend", font=("DejaVu Sans", 11, "bold")).pack(
anchor=tk.W, pady=(0, 4)
)
self._canvas = tk.Canvas(
self,
width=_WIDTH,
height=len(RoomType.get_all()) * _ROW_HEIGHT,
highlightthickness=0,
)
self._canvas.pack()
# Keep references to the drawn rooms, otherwise tkinter discards their icons
self._rooms: List[TkinterRoom] = []
self._counts: Counter = Counter()
self.redraw()

def update_counts(self, floor: Floor) -> None:
"""
Counts the room types of the given floor and shows them in the legend.
@param floor: floor to count the rooms of
"""
self._counts = Counter(room.get_type() for room in floor.get_rooms())
self.redraw()

def get_count(self, room_type: RoomType) -> int:
"""
Returns how often the room type appears on the current floor.
"""
return self._counts[room_type]

def redraw(self) -> None:
"""
Draws the legend in the colors of the current theme.
"""
palette = style.get_palette()
canvas = self._canvas
canvas.delete("all")
canvas.configure(background=palette.panel)
self._rooms = []
for index, room_type in enumerate(RoomType.get_all()):
top = index * _ROW_HEIGHT
room = TkinterRoom(0, 0, index, room_type, width=72, height=_ROW_HEIGHT)
self._rooms.append(room)
_draw_at(canvas, room, 0, top)
canvas.create_text(
84,
top + _ROW_HEIGHT / 2,
anchor=tk.W,
text=room_names[room_type],
fill=palette.text,
font=("DejaVu Sans", 10),
)
canvas.create_text(
_WIDTH - 4,
top + _ROW_HEIGHT / 2,
anchor=tk.E,
text=str(self._counts[room_type]),
fill=palette.muted_text,
font=("DejaVu Sans", 10),
tags=("count",),
)


def _draw_at(canvas: tk.Canvas, room: TkinterRoom, x: int, y: int) -> None:
"""
Draws the room on the canvas and moves it to the given position.
"""
tag = f"legend-{room.get_id()}"
before = set(canvas.find_all())
room.draw(canvas)
for item in set(canvas.find_all()) - before:
canvas.addtag_withtag(tag, item)
canvas.move(tag, x, y)
Loading
Loading