diff --git a/resources/graphics/track1.jpg b/resources/graphics/track1.jpg new file mode 100644 index 0000000..fc8ab6c Binary files /dev/null and b/resources/graphics/track1.jpg differ diff --git a/resources/graphics/track2.jpg b/resources/graphics/track2.jpg new file mode 100644 index 0000000..a488b71 Binary files /dev/null and b/resources/graphics/track2.jpg differ diff --git a/resources/graphics/track3.jpg b/resources/graphics/track3.jpg new file mode 100644 index 0000000..f10a806 Binary files /dev/null and b/resources/graphics/track3.jpg differ diff --git a/resources/graphics/track4.jpg b/resources/graphics/track4.jpg new file mode 100644 index 0000000..a488b71 Binary files /dev/null and b/resources/graphics/track4.jpg differ diff --git a/src/main.py b/src/main.py index e8fc8a9..af0f0ea 100644 --- a/src/main.py +++ b/src/main.py @@ -13,6 +13,7 @@ from view.menu import Menu from view.track_view import TrackView from view.window import Window +from view.options_menu import OptionsMenu WINDOW_NAME = "CarsML" WINDOW_SIZE = (1280, 800) @@ -68,8 +69,7 @@ def main() -> None: window = Window(WINDOW_NAME, WINDOW_SIZE, resizable=True, min_size=WINDOW_MIN_SIZE) menu_options = { - "Track": Action(ActionType.CHANGE_VIEW, 1), - "Testing segment": Action(ActionType.CHANGE_VIEW, 2), + "Train": Action(ActionType.CHANGE_VIEW, 1), "Exit": Action(ActionType.SYS_EXIT), } menu = Menu(menu_options) @@ -78,10 +78,28 @@ def main() -> None: tv1 = TrackView(Track.from_points(tracks[1]["points"])) tv2 = TrackView(Track.from_points(tracks[3]["points"])) + tv3 = TrackView(Track.from_points(tracks[4]["points"])) + + track_options = { + "Track1": Action(ActionType.CHANGE_VIEW, 2), + "Track2": Action(ActionType.CHANGE_VIEW, 3), + "Track3": Action(ActionType.CHANGE_VIEW, 4), + } + track_images = [ + pygame.image.load("resources/graphics/track1.jpg"), + pygame.image.load("resources/graphics/track2.jpg"), + pygame.image.load("resources/graphics/track3.jpg"), + ] + options_menu = OptionsMenu(tracks=track_options, tracks_thumbnails=track_images) + options_menu.background_image = pygame.image.load( + "resources/graphics/menu-background.png" + ) window.add_view(menu, 0, True) - window.add_view(tv1, 1) - window.add_view(tv2, 2) + window.add_view(tv1, 2) + window.add_view(tv2, 3) + window.add_view(tv3, 4) + window.add_view(options_menu, 1) window.run() diff --git a/src/view/__init__.py b/src/view/__init__.py index e69de29..d6f43fd 100644 --- a/src/view/__init__.py +++ b/src/view/__init__.py @@ -0,0 +1,3 @@ +import pygame + +pygame.init() diff --git a/src/view/colors.py b/src/view/colors.py index 31d04b2..eb53608 100644 --- a/src/view/colors.py +++ b/src/view/colors.py @@ -4,5 +4,7 @@ LIGHTBLUE = (120, 140, 170) WHITE = (255, 255, 255) RED = (255, 0, 0) +GREEN = (0, 255, 0) +ORANGE = (255, 176, 21) BIZARRE_MASKING_PURPLE = (240, 0, 240) LIME = (50, 205, 50) diff --git a/src/view/menu.py b/src/view/menu.py index 4a89f11..15ba7cd 100644 --- a/src/view/menu.py +++ b/src/view/menu.py @@ -12,15 +12,16 @@ class Menu(View): + FONT_COLOR = colors.ORANGE + SELECTED_FONT_COLOR = colors.WHITE + BUTTON_COLOR = colors.WHITE + SELECTED_BUTTON_COLOR = colors.ORANGE + FONT = pygame.font.SysFont("Verdana", 24) + def __init__(self, menu_options: Dict[str, Action]): super().__init__() - pygame.font.init() - self.font = pygame.font.SysFont("Verdana", 24) self.selected_item = 0 self._options = OrderedDict(menu_options) - self.font_color = colors.WHITE - self.button_highlight = colors.GRAY - self.button_color = colors.LIGHTGRAY self._background: Optional[Tuple[Surface, Tuple[int, int]]] = None self._logo: Optional[Tuple[Surface, Tuple[int, int]]] = None self.background_image: Surface = Surface((1, 1)) @@ -31,7 +32,6 @@ def __init__(self, menu_options: Dict[str, Action]): def draw( self, destination: Surface, events: List[EventType], delta_time: float ) -> Optional[Action]: - # TODO add activation check for consistency size = destination.get_size() self._update_geometry(size) @@ -44,15 +44,21 @@ def draw( destination.blit(*self._logo) for pos, button in enumerate(self._options): - ofset_y = int(pos * 1.5 * self.button_dims[1]) - shifted_button_rect = self._button_rect.move(0, ofset_y) - color = ( - self.button_highlight - if pos == self.selected_item - else self.button_color - ) + offset_y = int(pos * 1.5 * self.button_dims[1]) + shifted_button_rect = self._button_rect.move(0, offset_y) + + if pos == self.selected_item: + shifted_button_rect = shifted_button_rect.inflate(20, 20) + color = Menu.SELECTED_BUTTON_COLOR + font_color = Menu.SELECTED_FONT_COLOR + else: + pygame.draw.rect(destination, colors.ORANGE, shifted_button_rect, 4) + color = Menu.BUTTON_COLOR + font_color = Menu.FONT_COLOR + pygame.draw.rect(destination, color, shifted_button_rect) - label = self.font.render(button, True, self.font_color) + + label = Menu.FONT.render(button, True, font_color) destination.blit( label, ( @@ -63,20 +69,20 @@ def draw( return None def _update_geometry(self, size: Tuple[int, int]) -> None: - ofset_y = int(self.divider * size[1]) - ofset_x = (size[0] - self.button_dims[0]) // 2 + offset_y = int(self.divider * size[1]) + offset_x = (size[0] - self.button_dims[0]) // 2 background_shape = Rect((0, 0), size) background_image = pygame.transform.scale(self.background_image, size) self._background = (background_image, background_shape) - self._button_rect = Rect((ofset_x, ofset_y), self.button_dims) + self._button_rect = Rect((offset_x, offset_y), self.button_dims) if not self.logo_image: return logo_dims = self.logo_image.get_size() logo_shape = Rect( - ((size[0] - logo_dims[0]) // 2, (ofset_y - logo_dims[1]) // 2), logo_dims + ((size[0] - logo_dims[0]) // 2, (offset_y - logo_dims[1]) // 2), logo_dims ) self._logo = (self.logo_image, logo_shape) diff --git a/src/view/options_menu.py b/src/view/options_menu.py index 54d98bf..eb98f3f 100644 --- a/src/view/options_menu.py +++ b/src/view/options_menu.py @@ -1,19 +1,131 @@ -from typing import List, Optional, Any, Dict +from typing import List, Optional, Dict, Tuple, Any +from collections import OrderedDict + +import pygame from pygame.event import EventType from pygame.surface import Surface +from pygame.rect import Rect -from view.action import Action +from view.action import Action, ActionType from view.view import View +from view import colors class OptionsMenu(View): - def __init__( - self, available: Any, output_dataset: Dict[str, Any], next: Optional[int] = None - ) -> None: + ARROW_COLOR = colors.ORANGE + FONT_COLOR = colors.WHITE + LOGO_IMAGE = pygame.image.load("resources/graphics/logo.png") + DIVIDER = 0.4 + FONT = pygame.font.SysFont("Verdana", 30) + MAIN_FONT = pygame.font.SysFont("comicsansms", 60) + + def __init__(self, tracks: Dict[str, Action], tracks_thumbnails: List[Any]) -> None: super().__init__() + self.selected_item = 0 + self._options = OrderedDict(tracks) + self._background: Optional[Tuple[Surface, Tuple[int, int]]] = None + self.background_image: Optional[Surface] = None + self.selected_action: Optional[Action] = None + self.thumbnails = tracks_thumbnails def draw( self, destination: Surface, events: List[EventType], delta_time: float ) -> Optional[Action]: - pass + size = destination.get_size() + self._update_geometry(size) + + if self._process_events(events): + return self.selected_action + + if self._background: + destination.blit(*self._background) + if self._logo: + destination.blit(*self._logo) + + mini_track = self._button_rect + thumbnail_image = pygame.transform.scale( + self.thumbnails[self.selected_item], (mini_track.w, mini_track.h) + ) + thumbnail = (thumbnail_image, mini_track) + destination.blit(*thumbnail) + pygame.draw.rect(destination, colors.WHITE, mini_track, 4) + + main_label = OptionsMenu.MAIN_FONT.render( + "Select track", True, OptionsMenu.FONT_COLOR + ) + destination.blit(main_label, (size[0] // 2 - 185, size[1] // 10)) + + track_label = OptionsMenu.FONT.render( + list(self._options.keys())[self.selected_item], True, OptionsMenu.FONT_COLOR + ) + destination.blit( + track_label, + ( + mini_track.centerx - (track_label.get_height() // 2) - 28, + mini_track.centery - (mini_track.size[1] // 2) - 68, + ), + ) + + left_arrow_points = ( + (self.offset_x - size[0] // 20, self.offset_y + mini_track.h // 2), + (self.offset_x - 10, self.offset_y + mini_track.h // 2 - size[0] // 40), + (self.offset_x - 10, self.offset_y + mini_track.h // 2 + size[0] // 40), + ) + right_arrow_points = ( + (3 * self.offset_x + size[0] // 20, self.offset_y + mini_track.h // 2), + (3 * self.offset_x + 10, self.offset_y + mini_track.h // 2 - size[0] // 40), + (3 * self.offset_x + 10, self.offset_y + mini_track.h // 2 + size[0] // 40), + ) + + if self.selected_item != 0: + pygame.draw.polygon(destination, OptionsMenu.ARROW_COLOR, left_arrow_points) + if self.selected_item != len(self._options) - 1: + pygame.draw.polygon( + destination, OptionsMenu.ARROW_COLOR, right_arrow_points + ) + return None + + def _update_geometry(self, size: Tuple[int, int]) -> None: + self.offset_y = int(OptionsMenu.DIVIDER * size[1]) + self.offset_x = size[0] // 4 + + self.button_dims = size[0] // 2, size[1] // 2 + + background_shape = Rect((0, 0), size) + background_image = pygame.transform.scale(self.background_image, size) + self._background = (background_image, background_shape) + self._button_rect = Rect((self.offset_x, self.offset_y), self.button_dims) + + logo_image = pygame.transform.scale( + OptionsMenu.LOGO_IMAGE, (size[1] // 3, size[1] // 12) + ) + logo_shape = Rect((10, 10), (size[0] // 8, size[1] // 8)) + self._logo = (logo_image, logo_shape) + + def _process_events(self, events: List[EventType]) -> bool: + for event in events: + if event.type == pygame.KEYDOWN: + if event.key == pygame.K_LEFT and self.selected_item != 0: + self.selected_item = self.selected_item - 1 + elif ( + event.key == pygame.K_RIGHT + and self.selected_item != len(self._options) - 1 + ): + self.selected_item = self.selected_item + 1 + elif event.key == pygame.K_a and self.selected_item != 0: + self.selected_item = self.selected_item - 1 + elif ( + event.key == pygame.K_d + and self.selected_item != len(self._options) - 1 + ): + self.selected_item = self.selected_item + 1 + elif event.key == pygame.K_RETURN: + self.selected_action = list(self._options.values())[ + self.selected_item + ] + return True + elif event.key == pygame.K_ESCAPE: + self.selected_action = Action(ActionType.CHANGE_VIEW, 0) + return True + return False diff --git a/src/view/track_view.py b/src/view/track_view.py index 490d97b..6468dae 100644 --- a/src/view/track_view.py +++ b/src/view/track_view.py @@ -144,7 +144,7 @@ def _process_events(self, events: List[EventType]) -> Optional[Action]: elif event.key == pygame.K_KP_MINUS: self.scale *= 0.625 self._prepare_board() - elif event.key == pygame.K_RETURN or event.key == pygame.K_KP_ENTER: + elif event.key == pygame.K_SPACE: self._paused = not self._paused # TODO change to previous view return None diff --git a/src/view/window.py b/src/view/window.py index cd702b1..7627025 100644 --- a/src/view/window.py +++ b/src/view/window.py @@ -18,7 +18,6 @@ def __init__( resizable: bool = False, min_size: Optional[Tuple[int, int]] = None, ): - pygame.init() pygame.display.set_caption(name) self._mode = pygame.HWSURFACE | pygame.DOUBLEBUF if fullscreen: