From a8bbc47668ef5517e3ed0e54a93a68fdc7f6b4fb Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Tue, 7 Mar 2023 21:44:01 -0800 Subject: [PATCH] Move all interface Windows to their own modules in interface.window --- erynrl/interface/__init__.py | 56 +----------- erynrl/interface/window/__init__.py | 83 +++++++++++++++++ erynrl/interface/window/info.py | 45 ++++++++++ erynrl/interface/{window.py => window/map.py} | 88 ++----------------- erynrl/interface/window/message_log.py | 21 +++++ 5 files changed, 160 insertions(+), 133 deletions(-) create mode 100644 erynrl/interface/window/__init__.py create mode 100644 erynrl/interface/window/info.py rename erynrl/interface/{window.py => window/map.py} (67%) create mode 100644 erynrl/interface/window/message_log.py diff --git a/erynrl/interface/__init__.py b/erynrl/interface/__init__.py index 26b151e..d843d44 100644 --- a/erynrl/interface/__init__.py +++ b/erynrl/interface/__init__.py @@ -10,14 +10,12 @@ from tcod import event as tev from tcod.console import Console from tcod.context import Context -from .color import HealthBar from .events import InterfaceEventHandler -from .percentage_bar import PercentageBar -from .window import Window, MapWindow +from .window.info import InfoWindow +from .window.map import MapWindow +from .window.message_log import MessageLogWindow from ..engine import Engine -from ..geometry import Point, Rect, Size -from ..messages import MessageLog -from ..object import Entity, Hero +from ..geometry import Rect, Size class Interface: @@ -76,49 +74,3 @@ class Interface: continue self.engine.process_input_action(action) - - -class InfoWindow(Window): - '''A window that displays information about the player''' - - def __init__(self, bounds: Rect): - super().__init__(bounds, framed=True) - - self.turn_count: int = 0 - - drawable_area = self.drawable_bounds - self.hit_points_bar = PercentageBar( - position=Point(drawable_area.min_x + 6, drawable_area.min_y), - width=20, - colors=list(HealthBar.bar_colors())) - - def update_hero(self, hero: Hero): - '''Update internal state for the hero''' - assert hero.fighter - - fighter = hero.fighter - hp, max_hp = fighter.hit_points, fighter.maximum_hit_points - - self.hit_points_bar.percent_filled = hp / max_hp - - def draw(self, console): - super().draw(console) - - drawable_bounds = self.drawable_bounds - console.print(x=drawable_bounds.min_x + 2, y=drawable_bounds.min_y, string='HP:') - self.hit_points_bar.render_to_console(console) - - if self.turn_count: - console.print(x=drawable_bounds.min_x, y=drawable_bounds.min_y + 1, string=f'Turn: {self.turn_count}') - - -class MessageLogWindow(Window): - '''A window that displays a list of messages''' - - def __init__(self, bounds: Rect, message_log: MessageLog): - super().__init__(bounds, framed=True) - self.message_log = message_log - - def draw(self, console): - super().draw(console) - self.message_log.render_to_console(console, self.drawable_bounds) diff --git a/erynrl/interface/window/__init__.py b/erynrl/interface/window/__init__.py new file mode 100644 index 0000000..bde8a08 --- /dev/null +++ b/erynrl/interface/window/__init__.py @@ -0,0 +1,83 @@ +# Eryn Wells + +from typing import Optional + +from tcod import event as tev +from tcod.console import Console + +from ...geometry import Point, Rect, Vector + + +class Window: + '''A user interface window. It can be framed and it can handle events.''' + + class EventHandler(tev.EventDispatch[bool]): + ''' + Handles events for a Window. Event dispatch methods return True if the event + was handled and no further action is needed. + ''' + + def __init__(self, window: 'Window'): + super().__init__() + self.window = window + + def mouse_point_for_event(self, event: tev.MouseState) -> Point: + ''' + Return the mouse point in tiles for a window event. Raises a ValueError + if the event is not a mouse event. + ''' + if not isinstance(event, tev.MouseState): + raise ValueError("Can't get mouse point for non-mouse event") + + return Point(event.tile.x, event.tile.y) + + def ev_keydown(self, event: tev.KeyDown) -> bool: + return False + + def ev_keyup(self, event: tev.KeyUp) -> bool: + return False + + def ev_mousemotion(self, event: tev.MouseMotion) -> bool: + mouse_point = self.mouse_point_for_event(event) + + if mouse_point not in self.window.bounds: + return False + + return False + + def __init__(self, bounds: Rect, *, framed: bool = True, event_handler: Optional['EventHandler'] = None): + self.bounds = bounds + self.is_framed = framed + self.event_handler = event_handler or self.__class__.EventHandler(self) + + @property + def drawable_bounds(self) -> Rect: + ''' + The bounds of the window that is drawable, inset by its frame if + `is_framed` is `True`. + ''' + if self.is_framed: + return self.bounds.inset_rect(1, 1, 1, 1) + return self.bounds + + def convert_console_point(self, point: Point) -> Optional[Point]: + ''' + Converts a point in console coordinates to window-relative coordinates. + If the point is out of bounds of the window, return None. + ''' + converted_point = point - Vector.from_point(self.bounds.origin) + return converted_point if converted_point in self.bounds else None + + def draw(self, console: Console): + '''Draw the window to the conole''' + if self.is_framed: + console.draw_frame( + self.bounds.origin.x, + self.bounds.origin.y, + self.bounds.size.width, + self.bounds.size.height) + + drawable_bounds = self.drawable_bounds + console.draw_rect(drawable_bounds.min_x, drawable_bounds.min_y, + drawable_bounds.width, drawable_bounds.height, + ord(' '), (255, 255, 255), (0, 0, 0)) diff --git a/erynrl/interface/window/info.py b/erynrl/interface/window/info.py new file mode 100644 index 0000000..6252fdc --- /dev/null +++ b/erynrl/interface/window/info.py @@ -0,0 +1,45 @@ +# Eryn Wells + +''' +Declares the InfoWindow. +''' + +from . import Window +from ..color import HealthBar +from ..percentage_bar import PercentageBar +from ...geometry import Point, Rect +from ...object import Hero + + +class InfoWindow(Window): + '''A window that displays information about the player''' + + def __init__(self, bounds: Rect): + super().__init__(bounds, framed=True) + + self.turn_count: int = 0 + + drawable_area = self.drawable_bounds + self.hit_points_bar = PercentageBar( + position=Point(drawable_area.min_x + 6, drawable_area.min_y), + width=20, + colors=list(HealthBar.bar_colors())) + + def update_hero(self, hero: Hero): + '''Update internal state for the hero''' + assert hero.fighter + + fighter = hero.fighter + hp, max_hp = fighter.hit_points, fighter.maximum_hit_points + + self.hit_points_bar.percent_filled = hp / max_hp + + def draw(self, console): + super().draw(console) + + drawable_bounds = self.drawable_bounds + console.print(x=drawable_bounds.min_x + 2, y=drawable_bounds.min_y, string='HP:') + self.hit_points_bar.render_to_console(console) + + if self.turn_count: + console.print(x=drawable_bounds.min_x, y=drawable_bounds.min_y + 1, string=f'Turn: {self.turn_count}') diff --git a/erynrl/interface/window.py b/erynrl/interface/window/map.py similarity index 67% rename from erynrl/interface/window.py rename to erynrl/interface/window/map.py index 61d0e5e..118b0f6 100644 --- a/erynrl/interface/window.py +++ b/erynrl/interface/window/map.py @@ -1,90 +1,16 @@ # Eryn Wells -from typing import List, Optional +from typing import List import numpy as np -from tcod import event as tev +import tcod.event as tev from tcod.console import Console -from .. import log -from ..geometry import Point, Rect, Size, Vector -from ..map import Map -from ..object import Entity, Hero - - -class Window: - '''A user interface window. It can be framed and it can handle events.''' - - class EventHandler(tev.EventDispatch[bool]): - ''' - Handles events for a Window. Event dispatch methods return True if the event - was handled and no further action is needed. - ''' - - def __init__(self, window: 'Window'): - super().__init__() - self.window = window - - def mouse_point_for_event(self, event: tev.MouseState) -> Point: - ''' - Return the mouse point in tiles for a window event. Raises a ValueError - if the event is not a mouse event. - ''' - if not isinstance(event, tev.MouseState): - raise ValueError("Can't get mouse point for non-mouse event") - - return Point(event.tile.x, event.tile.y) - - def ev_keydown(self, event: tev.KeyDown) -> bool: - return False - - def ev_keyup(self, event: tev.KeyUp) -> bool: - return False - - def ev_mousemotion(self, event: tev.MouseMotion) -> bool: - mouse_point = self.mouse_point_for_event(event) - - if mouse_point not in self.window.bounds: - return False - - return False - - def __init__(self, bounds: Rect, *, framed: bool = True, event_handler: Optional['EventHandler'] = None): - self.bounds = bounds - self.is_framed = framed - self.event_handler = event_handler or self.__class__.EventHandler(self) - - @property - def drawable_bounds(self) -> Rect: - ''' - The bounds of the window that is drawable, inset by its frame if - `is_framed` is `True`. - ''' - if self.is_framed: - return self.bounds.inset_rect(1, 1, 1, 1) - return self.bounds - - def convert_console_point(self, point: Point) -> Optional[Point]: - ''' - Converts a point in console coordinates to window-relative coordinates. - If the point is out of bounds of the window, return None. - ''' - converted_point = point - Vector.from_point(self.bounds.origin) - return converted_point if converted_point in self.bounds else None - - def draw(self, console: Console): - '''Draw the window to the conole''' - if self.is_framed: - console.draw_frame( - self.bounds.origin.x, - self.bounds.origin.y, - self.bounds.size.width, - self.bounds.size.height) - - drawable_bounds = self.drawable_bounds - console.draw_rect(drawable_bounds.min_x, drawable_bounds.min_y, - drawable_bounds.width, drawable_bounds.height, - ord(' '), (255, 255, 255), (0, 0, 0)) +from . import Window +from ... import log +from ...geometry import Point, Rect, Size, Vector +from ...map import Map +from ...object import Entity, Hero class MapWindow(Window): diff --git a/erynrl/interface/window/message_log.py b/erynrl/interface/window/message_log.py new file mode 100644 index 0000000..13d6091 --- /dev/null +++ b/erynrl/interface/window/message_log.py @@ -0,0 +1,21 @@ +# Eryn Wells + +''' +Declares the MessageLogWindow. +''' + +from . import Window +from ...geometry import Rect +from ...messages import MessageLog + + +class MessageLogWindow(Window): + '''A window that displays a list of messages''' + + def __init__(self, bounds: Rect, message_log: MessageLog): + super().__init__(bounds, framed=True) + self.message_log = message_log + + def draw(self, console): + super().draw(console) + self.message_log.render_to_console(console, self.drawable_bounds)