diff --git a/main.py b/main.py index d3ce526..af8420b 100644 --- a/main.py +++ b/main.py @@ -1,4 +1,5 @@ # libraries +import pygame import time from pyglet.gl import * # for blocky textures import random @@ -6,18 +7,15 @@ import random import project_constants as const import minefield as mf import searching_algorithms.a_star as a_star + from display_assets import blit_graphics -from ui.input_box import * -from ui.button import * -from ui.text_box import * -import json_generator as jg +from project_constants import HIGHLIGHT, INPUT_ROW, INPUT_COLUMN, RANDOM_BUTTON, OK_BUTTON +from ui.ui_components_list import UiComponentsList def main(): pygame.init() pygame.display.set_caption(const.V_NAME_OF_WINDOW) - jes = jg.JsonGenerator() - print(jes.get_grid()) # for blocky textures glEnable(GL_TEXTURE_2D) @@ -33,84 +31,86 @@ def main(): running = True in_menu = True - # counting coordinates for ui components - ib_width, ib_height = 100, 65 - bt_width, bt_height = 210, 55 - ib_y = 200 - ib1_x = const.SCREEN.get_width() / 2 - ib_width - 5 - ib2_x = const.SCREEN.get_width() / 2 + 5 - bt_x = const.SCREEN.get_width() / 2 - bt_width / 2 - bt1_y = ib_y + ib_height + 10 - bt2_y = bt1_y + bt_height + 10 - - # creating ui components used in menu - input1 = InputBox( - position=(ib1_x, ib_y), - dimensions=(ib_width, ib_height), - text="row", - input_centered=True, - clear_input_on_click=True - ) - input2 = InputBox( - position=(ib2_x, ib_y), - dimensions=(ib_width, ib_height), - text="column", - input_centered=True, - clear_input_on_click=True - ) - random_button = Button(position=(bt_x, bt1_y), dimensions=(bt_width, bt_height), text="random") - ok_button = Button(position=(bt_x, bt2_y), dimensions=(bt_width, bt_height), text="ok") + ui_components_list = UiComponentsList([INPUT_ROW, INPUT_COLUMN, RANDOM_BUTTON, OK_BUTTON]) # initializing goal position - to_x, to_y = 0, 0 + row, column = 0, 0 + + # drawing map so black screen doesn't appear + blit_graphics(minefield) while running: while running and in_menu: - const.SCREEN.fill((255, 255, 255)) events = pygame.event.get() for event in events: if event.type == pygame.QUIT: running = False - input1.run(const.SCREEN, pygame.mouse.get_pos(), events) - input2.run(const.SCREEN, pygame.mouse.get_pos(), events) + # graphics (from display_assets) + blit_graphics(minefield) - ok_button.draw(const.SCREEN, pygame.mouse.get_pos()) - random_button.draw(const.SCREEN, pygame.mouse.get_pos()) + # drawing gui + INPUT_ROW.run(const.SCREEN, pygame.mouse.get_pos(), events) + INPUT_COLUMN.run(const.SCREEN, pygame.mouse.get_pos(), events) + OK_BUTTON.draw(const.SCREEN, pygame.mouse.get_pos()) + RANDOM_BUTTON.draw(const.SCREEN, pygame.mouse.get_pos()) - if ok_button.is_clicked(pygame.mouse.get_pos(), events): - in_menu = False - if random_button.is_clicked(pygame.mouse.get_pos(), events): - input1.set_texts(user_input=str(random.randint(0, 9))) - input2.set_texts(user_input=str(random.randint(0, 9))) + # highlighting chosen tile destination (if exists) + if not(INPUT_ROW.empty() or INPUT_COLUMN.empty()): + row = min(9, int(INPUT_ROW.get_input())) + column = min(9, int(INPUT_COLUMN.get_input())) + const.SCREEN.blit(HIGHLIGHT, const.get_tile_coordinates((row, column))) + + # updating graphics pygame.display.flip() + ui_components_list.switch_selected_objects_with_arrow_keys(events, pygame.mouse.get_pos()) + + OK_BUTTON.set_flags(is_active=(not(INPUT_ROW.empty() or INPUT_COLUMN.empty()))) + + if OK_BUTTON.is_clicked(pygame.mouse.get_pos(), events) or OK_BUTTON.enter_pressed(events): + in_menu = False + + if RANDOM_BUTTON.is_clicked(pygame.mouse.get_pos(), events) or RANDOM_BUTTON.enter_pressed(events): + INPUT_ROW.set_texts(user_input=str(random.randint(0, 9))) + INPUT_COLUMN.set_texts(user_input=str(random.randint(0, 9))) + clock.tick(const.V_FPS) if running: - to_x = int(input1.get_input()) - to_y = int(input2.get_input()) + for component in ui_components_list.selectable_ui_components: + component.set_flags(is_active=False) action_sequence = a_star.graphsearch( initial_state=a_star.State( row=minefield.agent.position[0], column=minefield.agent.position[1], direction=minefield.agent.direction), - minefield=minefield, tox=to_x, toy=to_y) + minefield=minefield, tox=row, toy=column) while running and not in_menu: + for event in pygame.event.get(): + if event.type == pygame.QUIT: + running = False + # FPS control clock.tick(const.V_FPS) # graphics (from display_assets) blit_graphics(minefield) + const.SCREEN.blit(HIGHLIGHT, const.get_tile_coordinates((row, column))) + + # drawing ui components so they don't "disappear" + ui_components_list.draw_all(const.SCREEN, pygame.mouse.get_pos()) + + # updating graphics + pygame.display.flip() # make the next move from sequence of actions if any(action_sequence): - action = action_sequence.pop(0) if action == const.Action.ROTATE_LEFT: @@ -124,10 +124,12 @@ def main(): else: in_menu = True + if not any([x.is_selected for x in ui_components_list.ui_components]): + INPUT_ROW.set_is_selected(True) time.sleep(const.ACTION_INTERVAL) - # 'for event in pygame.event.get(): - # if event.type == pygame.QUIT: - # running = False + + for component in ui_components_list.selectable_ui_components: + component.set_flags(is_active=True) if __name__ == "__main__": diff --git a/project_constants.py b/project_constants.py index 232efa8..f11c4a1 100644 --- a/project_constants.py +++ b/project_constants.py @@ -2,9 +2,14 @@ import pygame import os from enum import Enum +from ui.button import Button +from ui.input_box import InputBox + # VARIABLE STARTS WITH ... IF IT'S # V a value like a string or an int # STRUCT a list or other structure of values +# FUNC a function +# OBJ a classes instance # ASSET a png file (or other graphic format) # MAP a JSON map file @@ -27,9 +32,14 @@ V_NUMBER_PADDING = 50 V_TILE_AREA_HEIGHT = V_TILE_SIZE * V_GRID_VER_TILES V_TILE_AREA_WIDTH = V_TILE_SIZE * V_GRID_HOR_TILES +# side menu values +V_SIDE_MENU_WIDTH = 80 +V_INPUT_BOX_HEIGHT = 55 +V_BUTTON_HEIGHT = 40 + SCREEN = pygame.display.set_mode( ( - V_TILE_AREA_WIDTH + 2 * V_SCREEN_PADDING + V_NUMBER_PADDING, # screen width + V_TILE_AREA_WIDTH + 2 * V_SCREEN_PADDING + V_NUMBER_PADDING + V_SIDE_MENU_WIDTH + 10, # screen width V_TILE_AREA_HEIGHT + 2 * V_SCREEN_PADDING + V_NUMBER_PADDING # screen height ) ) @@ -119,6 +129,65 @@ STRUCT_MINE_ATTRIBUTE_TYPES = { "time": [int] } + +# ============== # +# ==== FUNC ==== # +# ============== # + +def get_tile_coordinates(position): + row, column = position + padding = V_SCREEN_PADDING + V_NUMBER_PADDING + + return tuple((padding + column * V_TILE_SIZE, padding + row * V_TILE_SIZE)) + + +# ============= # +# ==== OBJ ==== # +# ============= # + +# initializing pygame modules so all instances can be constructed properly +pygame.init() + +# creating ui components used in menu +HIGHLIGHT = pygame.transform.scale( + pygame.image.load(os.path.join(DIR_ASSETS, "old_tiles/tile_white.png")), + (V_TILE_SIZE, V_TILE_SIZE) +) +HIGHLIGHT.set_alpha(100) + +_gui_width = V_SIDE_MENU_WIDTH +_ib_height, _bt_height = V_INPUT_BOX_HEIGHT, V_BUTTON_HEIGHT +_gui_x = V_TILE_AREA_WIDTH + 2 * V_SCREEN_PADDING + V_NUMBER_PADDING +_gui_y = SCREEN.get_height() / 2 - (2 * _ib_height + 2 * _bt_height + 30) / 2 + +INPUT_ROW = InputBox( + position=(_gui_x, _gui_y), + dimensions=(_gui_width, _ib_height), + text="row", + valid_input_characters="123456890", + input_centered=True, + clear_input_on_click=True +) +INPUT_COLUMN = InputBox( + position=(_gui_x, _gui_y + _ib_height + 10), + dimensions=(_gui_width, _ib_height), + text="column", + valid_input_characters="1234567890", + input_centered=True, + clear_input_on_click=True +) +RANDOM_BUTTON = Button( + position=(_gui_x, _gui_y + 2 * _ib_height + 20), + dimensions=(_gui_width, _bt_height), + text="random" +) +OK_BUTTON = Button( + position=(_gui_x, _gui_y + 2 * _ib_height + _bt_height + 30), + dimensions=(_gui_width, _bt_height), + text="ok" +) + + # ============== # # ==== MAPS ==== # # ============== # diff --git a/ui/button.py b/ui/button.py index 054529d..37db605 100644 --- a/ui/button.py +++ b/ui/button.py @@ -9,6 +9,8 @@ from ui.text_box import TextBox class Button(TextBox): + was_enter_hit = False + # constructor that can be used to set parameters of Button instance def __init__( self, @@ -21,6 +23,7 @@ class Button(TextBox): font_size=None, is_visible=True, is_active=True, + is_selected=False, fit_text=True, outline=True, irregular_outline=False, @@ -30,10 +33,13 @@ class Button(TextBox): outline_color=(50, 50, 50), box_transform_hover=1.16, box_transform_inactive=0.9, + box_transform_selected=1.16, font_transform_hover=1.24, font_transform_inactive=0.85, + font_transform_selected=1.24, outline_transform_hover=1, - outline_transform_inactive=0.9 + outline_transform_inactive=0.9, + outline_transform_selected=1 ): # calling base class constructor super().__init__( @@ -56,20 +62,27 @@ class Button(TextBox): # setting attributes self.is_active = is_active + self.is_selected = is_selected self.box_transform_hover = box_transform_hover self.box_transform_inactive = box_transform_inactive + self.box_transform_selected = box_transform_selected self.font_transform_hover = font_transform_hover self.font_transform_inactive = font_transform_inactive + self.font_transform_selected = font_transform_selected self.outline_transform_hover = outline_transform_hover self.outline_transform_inactive = outline_transform_inactive + self.outline_transform_selected = outline_transform_selected - # counting colors on: hover, inactive + # counting colors on: hover, inactive and selected self.box_hover_color = get_fixed_rgb(_transform_rgb(self.box_color, self.box_transform_hover)) self.box_inactive_color = get_fixed_rgb(_transform_rgb(self.box_color, self.box_transform_inactive)) + self.box_selected_color = get_fixed_rgb(_transform_rgb(self.box_color, self.box_transform_selected)) self.font_hover_color = get_fixed_rgb(_transform_rgb(self.font_color, self.font_transform_hover)) self.font_inactive_color = get_fixed_rgb(_transform_rgb(self.font_color, self.font_transform_inactive)) + self.font_selected_color = get_fixed_rgb(_transform_rgb(self.font_color, self.font_transform_selected)) self.outline_hover_color = get_fixed_rgb(_transform_rgb(self.outline_color, self.outline_transform_hover)) self.outline_inactive_color = get_fixed_rgb(_transform_rgb(self.outline_color, self.outline_transform_inactive)) + self.outline_selected_color = get_fixed_rgb(_transform_rgb(self.outline_color, self.outline_transform_selected)) # draws the Button instance @_return_itself @@ -80,14 +93,20 @@ class Button(TextBox): outline_attribute_value = self.outline_color # temporarily changing box and font color if necessary + # is selected + if self.is_selected: + self.box_color = self.box_selected_color + self.font_color = self.font_selected_color + self.outline_color = self.outline_selected_color + # is active and mouse is over - if self.is_active and self.is_over(mouse_position): + elif self.is_active and self.is_over(mouse_position): self.box_color = self.box_hover_color self.font_color = self.font_hover_color self.outline_color = self.outline_hover_color # is not active - else: + elif not self.is_active: self.box_color = self.box_inactive_color self.font_color = self.font_inactive_color self.outline_color = self.outline_inactive_color @@ -112,6 +131,20 @@ class Button(TextBox): return False + # returns True if enter key is hit, or False otherwise + def enter_pressed(self, events): + + if self.is_active and self.is_selected: + for event in events: + if event.type == pygame.KEYDOWN and event.key == pygame.K_RETURN: + return True + + return self.was_enter_hit + + # sets is_selected attribute to given value + def set_is_selected(self, is_selected): + self.is_selected = is_selected + # sets chosen color attributes def set_colors( self, diff --git a/ui/input_box.py b/ui/input_box.py index fe5e1b0..8eb2dc7 100644 --- a/ui/input_box.py +++ b/ui/input_box.py @@ -25,6 +25,7 @@ class InputBox(Button): input_box_position=None, input_box_dimensions=None, user_input="", + valid_input_characters="abcdefghijklmnoprstuwxyz1234567890", box_color=(195, 195, 195), input_box_color=(225, 245, 245), writing_highlight_color=(150, 255, 255), @@ -35,6 +36,7 @@ class InputBox(Button): font_size=None, is_visible=True, is_active=True, + is_selected=False, input_centered=False, fit_text=True, clear_input_on_click=False, @@ -46,8 +48,10 @@ class InputBox(Button): outline_color=(50, 50, 50), box_transform_hover=1, box_transform_inactive=1, + box_transform_selected=1, font_transform_hover=1, font_transform_inactive=1, + font_transform_selected=1, outline_transform_hover=1, outline_transform_inactive=0.9, input_box_transform_hover=1.07, @@ -62,6 +66,7 @@ class InputBox(Button): font_color=font_color, is_visible=is_visible, is_active=is_active, + is_selected=is_selected, fit_text=fit_text, outline=outline, irregular_outline=irregular_outline, @@ -71,8 +76,10 @@ class InputBox(Button): outline_color=outline_color, box_transform_hover=box_transform_hover, box_transform_inactive=box_transform_inactive, + box_transform_selected=box_transform_selected, font_transform_hover=font_transform_hover, font_transform_inactive=font_transform_inactive, + font_transform_selected=font_transform_selected, outline_transform_hover=outline_transform_hover, outline_transform_inactive=outline_transform_inactive ) @@ -95,6 +102,7 @@ class InputBox(Button): self.input_box_position = _return_value_or_default(input_box_position, default_input_box_position) self.input_box_dimensions = _return_value_or_default(input_box_dimensions, default_input_box_dimensions) self.user_input = user_input + self.valid_input_characters = valid_input_characters self.ib_color = get_fixed_rgb(input_box_color) self.typing_highlight_color = get_fixed_rgb(writing_highlight_color) self.inner_box_color = get_fixed_rgb(inner_box_color) @@ -104,7 +112,6 @@ class InputBox(Button): self.font = pygame.font.SysFont(font, self.font_size) self.input_font_size = max(15, floor(self.input_box_dimensions[1] - 2)) self.input_font = pygame.font.SysFont(font, self.input_font_size) - self.is_selected = False self.is_active = is_active self.input_centered = input_centered self.ib_transform_hover = input_box_transform_hover @@ -224,8 +231,10 @@ class InputBox(Button): for event in events: if event.type == pygame.KEYDOWN: if event.key == pygame.K_BACKSPACE: + if not self.backspace_pressed_down: self.user_input = self.user_input[:-1] + self.backspace_pressed_down = True elif event.key == pygame.K_RETURN: @@ -234,7 +243,8 @@ class InputBox(Button): # if text isn't too long - adding a character elif rendered_input.get_width() + 10 < self.input_box_dimensions[0]: - self.user_input += event.unicode + if event.unicode in self.valid_input_characters: + self.user_input += event.unicode elif event.type == pygame.KEYUP: if event.key == pygame.K_BACKSPACE: @@ -260,23 +270,13 @@ class InputBox(Button): self.clear_input() # returns True if user input is empty, or False otherwise - def is_input_empty(self): + def empty(self): return not any(self.user_input) # clears user's input def clear_input(self): self.user_input = "" - # returns True if enter key is hit, or False otherwise - def is_enter_hit(self, events): - - if self.is_active and self.is_selected: - for event in events: - if event.type == pygame.KEYDOWN and event.key == pygame.K_RETURN: - return True - - return self.was_enter_hit - # returns InputBox'es user's input def get_input(self): return self.user_input @@ -402,10 +402,6 @@ class InputBox(Button): if fit_text is not None: self.fit_text = fit_text - # sets is_selected with a given value - def set_is_selected(self, is_selected): - self.is_selected = is_selected - def _delete_characters_if_backspace_pressed(self): if self.backspace_pressed_down and \ (self.backspace_tick + 1) % self.deleting_speeds[0] == 0 and \ diff --git a/ui/ui_components_list.py b/ui/ui_components_list.py new file mode 100644 index 0000000..433c821 --- /dev/null +++ b/ui/ui_components_list.py @@ -0,0 +1,46 @@ +import pygame +from ui.button import Button + + +class UiComponentsList: + selected = 0 + + def __init__(self, ui_components=None, select_first_item=True): + if ui_components is not None: + self.ui_components = ui_components + self.selectable_ui_components = list() + + for component in ui_components: + self.selectable_ui_components.append(component) if issubclass(component.__class__, Button) else 0 + + else: + self.ui_components = list() + + self.selectable_ui_components[0].set_is_selected(select_first_item) + + def draw_all(self, window, mouse_position): + for component in filter(lambda x: x not in self.selectable_ui_components, self.ui_components): + component.draw(window) + + for component in self.selectable_ui_components: + component.draw(window, mouse_position) + + def switch_selected_objects_with_arrow_keys(self, events, mouse_position): + for event in events: + if event.type == pygame.KEYDOWN: + if event.key == pygame.K_ESCAPE: + self.selectable_ui_components[self.selected].set_is_selected(False) + + if event.key == pygame.MOUSEBUTTONDOWN: + selected_component = self.selectable_ui_components[self.selected] + selected_component.set_is_selected(selected_component.is_over(mouse_position)) + + if event.key == pygame.K_UP or event.key == pygame.K_LEFT: + self.selectable_ui_components[self.selected].set_is_selected(False) + self.selected = self.selected - 1 if self.selected != 0 else len(self.selectable_ui_components) - 1 + self.selectable_ui_components[self.selected].set_is_selected(True) + + if event.key == pygame.K_DOWN or event.key == pygame.K_RIGHT: + self.selectable_ui_components[self.selected].set_is_selected(False) + self.selected = (self.selected + 1) % len(self.selectable_ui_components) + self.selectable_ui_components[self.selected].set_is_selected(True)