def test_enable(self, _init_pygame: None, default_ui_manager: UIManager, _display_surface_return_none: None): container = UIScrollingContainer(pygame.Rect(100, 100, 200, 200), manager=default_ui_manager) button_1 = UIButton(relative_rect=pygame.Rect(10, 10, 150, 30), text="Test Button", tool_tip_text="This is a test of the button's tool tip functionality.", manager=default_ui_manager, container=container) button_2 = UIButton(relative_rect=pygame.Rect(10, 50, 150, 30), text="Test Button 2", manager=default_ui_manager, container=container) container.disable() container.enable() assert container.is_enabled is True assert button_1.is_enabled is True assert button_2.is_enabled is True # process a mouse button down event button_1.process_event( pygame.event.Event(pygame.MOUSEBUTTONDOWN, {'button': 1, 'pos': button_1.rect.center})) # process a mouse button up event button_1.process_event( pygame.event.Event(pygame.MOUSEBUTTONUP, {'button': 1, 'pos': button_1.rect.center})) button_1.update(0.01) assert button_1.check_pressed() is True
def test_check_pressed(self, _init_pygame: None, default_ui_manager: UIManager, _display_surface_return_none: None): button = UIButton(relative_rect=pygame.Rect(10, 10, 150, 30), text="Test Button", tool_tip_text="This is a test of the button's tool tip functionality.", manager=default_ui_manager) # process a mouse button down event button.process_event(pygame.event.Event(pygame.MOUSEBUTTONDOWN, {'button': 1, 'pos': (50, 25)})) # process a mouse button up event button.process_event(pygame.event.Event(pygame.MOUSEBUTTONUP, {'button': 1, 'pos': (50, 25)})) button.update(0.01) assert button.check_pressed() is True
def test_disable(self, _init_pygame: None, default_ui_manager: UIManager, _display_surface_return_none: None): panel = UIPanel(relative_rect=pygame.Rect(0, 0, 150, 400), starting_layer_height=5, manager=default_ui_manager) button_1 = UIButton( relative_rect=pygame.Rect(10, 10, 150, 30), text="Test Button", tool_tip_text= "This is a test of the button's tool tip functionality.", manager=default_ui_manager, container=panel) button_2 = UIButton(relative_rect=pygame.Rect(10, 50, 150, 30), text="Test Button 2", manager=default_ui_manager, container=panel) panel.disable() assert panel.is_enabled is False assert button_1.is_enabled is False assert button_2.is_enabled is False # process a mouse button down event button_1.process_event( pygame.event.Event(pygame.MOUSEBUTTONDOWN, { 'button': 1, 'pos': button_1.rect.center })) # process a mouse button up event button_1.process_event( pygame.event.Event(pygame.MOUSEBUTTONUP, { 'button': 1, 'pos': button_1.rect.center })) button_1.update(0.01) assert button_1.check_pressed() is False
class UIClosedDropDownState: """ The closed state of the drop down just displays the currently chosen option and a button that will switch the menu to the expanded state. """ def __init__(self, drop_down_menu_ui, selected_option, base_position_rect, open_button_width, expand_direction, manager, container, element_ids, object_ids): self.drop_down_menu_ui = drop_down_menu_ui self.selected_option_button = None self.open_button = None self.selected_option = selected_option self.base_position_rect = base_position_rect self.expand_direction = expand_direction self.ui_manager = manager self.ui_container = container self.element_ids = element_ids self.object_ids = object_ids self.shape_type = None self.drawable_shape = None self.open_button_width = open_button_width self.should_transition = False self.target_state = 'expanded' def rebuild(self): theming_parameters = {'normal_bg': self.drop_down_menu_ui.background_colour, 'normal_border': self.drop_down_menu_ui.border_colour, 'border_width': self.drop_down_menu_ui.border_width, 'shadow_width': self.drop_down_menu_ui.shadow_width, 'shape_corner_radius': self.drop_down_menu_ui.shape_corner_radius} if self.drop_down_menu_ui.shape_type == 'rectangle': self.drawable_shape = RectDrawableShape(self.drop_down_menu_ui.rect, theming_parameters, ['normal'], self.ui_manager) elif self.drop_down_menu_ui.shape_type == 'rounded_rectangle': self.drawable_shape = RoundedRectangleShape(self.drop_down_menu_ui.rect, theming_parameters, ['normal'], self.ui_manager) self.drop_down_menu_ui.image = self.drawable_shape.get_surface('normal') # extra if self.open_button is not None: expand_button_symbol = '▼' if self.expand_direction is not None: if self.expand_direction == 'up': expand_button_symbol = '▲' elif self.expand_direction == 'down': expand_button_symbol = '▼' self.open_button.set_text(expand_button_symbol) def start(self): """ Called each time we enter the closed state. It creates the necessary elements, the selected option and the open button. """ self.rebuild() self.should_transition = False self.selected_option_button = UIButton(pygame.Rect((self.base_position_rect.x, self.base_position_rect.y), (self.base_position_rect.width - self.open_button_width, self.base_position_rect.height)), self.selected_option, self.ui_manager, self.ui_container, starting_height=2, parent_element=self.drop_down_menu_ui, object_id='#selected_option') open_button_x = self.base_position_rect.x + self.base_position_rect.width - self.open_button_width expand_button_symbol = '▼' if self.expand_direction is not None: if self.expand_direction == 'up': expand_button_symbol = '▲' elif self.expand_direction == 'down': expand_button_symbol = '▼' self.open_button = UIButton(pygame.Rect((open_button_x, self.base_position_rect.y), (self.open_button_width, self.base_position_rect.height)), expand_button_symbol, self.ui_manager, self.ui_container, starting_height=2, parent_element=self.drop_down_menu_ui, object_id='#expand_button') def finish(self): """ Called when we leave the closed state. Kills the open button and the selected option button. """ self.selected_option_button.kill() self.open_button.kill() def update(self): if self.open_button.check_pressed(): self.should_transition = True
class UIExpandedDropDownState: """ The expanded state of the drop down displays the currently chosen option, all the available options and a button to close the menu and return to the closed state. Picking an option will also close the menu. """ def __init__(self, drop_down_menu_ui, options_list, selected_option, base_position_rect, close_button_width, expand_direction, manager, container, element_ids, object_ids): self.drop_down_menu_ui = drop_down_menu_ui self.should_transition = False self.options_list = options_list self.selected_option = selected_option self.base_position_rect = base_position_rect self.selected_option_rect = None self.expand_direction = expand_direction self.ui_manager = manager self.ui_container = container self.element_ids = element_ids self.object_ids = object_ids self.rect_height_offset = 0 self.close_button_width = close_button_width self.selected_option_button = None self.close_button = None self.drawable_shape = None self.menu_buttons = [] self.should_transition = False self.target_state = 'closed' def rebuild(self): # shape for expanded drop down is a little trick because it is two rectangles, one on top of the other # forming an 'L' shape (or an inverted L if dropping down) if self.expand_direction == 'down': overall_background_rect = pygame.Rect(self.drop_down_menu_ui.rect.topleft, (self.drop_down_menu_ui.rect.width + 50, self.base_position_rect.height * (1 + len(self.options_list)) + 2 * self.drop_down_menu_ui.shadow_width + 2 * self.drop_down_menu_ui.border_width)) options_background_rect = pygame.Rect(self.drop_down_menu_ui.rect.topleft, (self.base_position_rect.width - self.close_button_width + 2 * self.drop_down_menu_ui.shadow_width + 2 * self.drop_down_menu_ui.border_width, self.base_position_rect.height * (1 + len(self.options_list)) + 2 * self.drop_down_menu_ui.shadow_width + 2 * self.drop_down_menu_ui.border_width)) self.rect_height_offset = 0 self.selected_option_rect = pygame.Rect((0, 0), self.drop_down_menu_ui.rect.size) else: # need to adjust the position of the rect so it appears in the right position self.rect_height_offset = self.base_position_rect.height * len(self.options_list) self.drop_down_menu_ui.rect.y = self.drop_down_menu_ui.rect.y - self.rect_height_offset self.drop_down_menu_ui.relative_rect.y = self.drop_down_menu_ui.relative_rect.y - self.rect_height_offset self.selected_option_rect = pygame.Rect((0, self.rect_height_offset), self.drop_down_menu_ui.rect.size) overall_background_rect = pygame.Rect(self.drop_down_menu_ui.rect.topleft, (self.drop_down_menu_ui.rect.width + 50, self.base_position_rect.height * (1 + len(self.options_list)) + 2 * self.drop_down_menu_ui.shadow_width + 2 * self.drop_down_menu_ui.border_width)) options_background_rect = pygame.Rect(self.drop_down_menu_ui.rect.topleft, (self.base_position_rect.width - self.close_button_width + 2 * self.drop_down_menu_ui.shadow_width + 2 * self.drop_down_menu_ui.border_width, self.base_position_rect.height * (1 + len(self.options_list)) + 2 * self.drop_down_menu_ui.shadow_width + 2 * self.drop_down_menu_ui.border_width)) self.drop_down_menu_ui.image = pygame.Surface(overall_background_rect.size, flags=pygame.SRCALPHA) self.drop_down_menu_ui.image.fill(pygame.Color('#00000000')) theming_parameters = {'normal_bg': self.drop_down_menu_ui.background_colour, 'normal_border': self.drop_down_menu_ui.border_colour, 'border_width': self.drop_down_menu_ui.border_width, 'shadow_width': self.drop_down_menu_ui.shadow_width, 'shape_corner_radius': self.drop_down_menu_ui.shape_corner_radius} if self.drop_down_menu_ui.shape_type == 'rectangle': drawable_shape = RectDrawableShape(self.selected_option_rect, theming_parameters, ['normal'], self.ui_manager) self.drop_down_menu_ui.image.blit(drawable_shape.get_surface('normal'), self.selected_option_rect.topleft) self.drop_down_menu_ui.image.fill(pygame.Color('#00000000'), pygame.Rect((0, 0), (options_background_rect.width - self.drop_down_menu_ui.shadow_width - self.drop_down_menu_ui.border_width, options_background_rect.height))) options_drawable_shape = RectDrawableShape(options_background_rect, theming_parameters, ['normal'], self.ui_manager) self.drop_down_menu_ui.image.blit(options_drawable_shape.get_surface('normal'), (0, 0)) elif self.drop_down_menu_ui.shape_type == 'rounded_rectangle': drawable_shape = RoundedRectangleShape(self.selected_option_rect, theming_parameters, ['normal'], self.ui_manager) self.drop_down_menu_ui.image.blit(drawable_shape.get_surface('normal'), self.selected_option_rect.topleft) self.drop_down_menu_ui.image.fill(pygame.Color('#00000000'), pygame.Rect((0, 0), (options_background_rect.width - self.drop_down_menu_ui.shadow_width - self.drop_down_menu_ui.border_width, options_background_rect.height))) options_drawable_shape = RoundedRectangleShape(options_background_rect, theming_parameters, ['normal'], self.ui_manager) self.drop_down_menu_ui.image.blit(options_drawable_shape.get_surface('normal'), (0, 0)) # extra if self.close_button is not None: expand_button_symbol = '▼' if self.expand_direction is not None: if self.expand_direction == 'up': expand_button_symbol = '▲' elif self.expand_direction == 'down': expand_button_symbol = '▼' self.close_button.set_text(expand_button_symbol) def start(self): """ Called each time we enter the expanded state. It creates the necessary elements, the selected option, all the other available options and the close button. """ self.should_transition = False option_y_pos = self.base_position_rect.y self.selected_option_button = UIButton(pygame.Rect(self.base_position_rect.topleft, (self.base_position_rect.width - self.close_button_width, self.base_position_rect.height)), self.selected_option, self.ui_manager, self.ui_container, starting_height=2, parent_element=self.drop_down_menu_ui, object_id='#selected_option') expand_button_symbol = '▼' select_button_dist_to_move = self.selected_option_button.rect.height option_button_dist_to_move = self.base_position_rect.height if self.expand_direction is not None: if self.expand_direction == 'up': expand_button_symbol = '▲' select_button_dist_to_move = -self.selected_option_button.rect.height option_button_dist_to_move = -self.base_position_rect.height elif self.expand_direction == 'down': expand_button_symbol = '▼' select_button_dist_to_move = self.selected_option_button.rect.height option_button_dist_to_move = self.base_position_rect.height close_button_x = self.base_position_rect.x + self.base_position_rect.width - self.close_button_width self.close_button = UIButton(pygame.Rect((close_button_x, self.base_position_rect.y), (self.close_button_width, self.base_position_rect.height)), expand_button_symbol, self.ui_manager, self.ui_container, starting_height=2, parent_element=self.drop_down_menu_ui, object_id='#expand_button') option_y_pos += select_button_dist_to_move for option in self.options_list: new_button = UIButton(pygame.Rect((self.base_position_rect.x, option_y_pos), (self.base_position_rect.width - self.close_button_width, self.base_position_rect.height)), option, self.ui_manager, self.ui_container, starting_height=3, # height allows options to overlap other UI elements parent_element=self.drop_down_menu_ui, object_id='#option') option_y_pos += option_button_dist_to_move self.menu_buttons.append(new_button) self.rebuild() def finish(self): """ cleans everything up upon exiting the expanded menu state. """ for button in self.menu_buttons: button.kill() self.menu_buttons.clear() self.selected_option_button.kill() self.close_button.kill() self.drop_down_menu_ui.rect.y += self.rect_height_offset self.drop_down_menu_ui.relative_rect.y += self.rect_height_offset def update(self): if self.close_button is not None and self.close_button.check_pressed(): self.should_transition = True for button in self.menu_buttons: if button.check_pressed(): self.drop_down_menu_ui.selected_option = button.text self.should_transition = True drop_down_changed_event = pygame.event.Event(pygame.USEREVENT, {'user_type': 'ui_drop_down_menu_changed', 'text': button.text, 'ui_element': self.drop_down_menu_ui, 'ui_object_id': self.object_ids[-1]}) pygame.event.post(drop_down_changed_event)
class EverythingWindow(UIWindow): def __init__(self, rect, ui_manager): element_ids = ['everything_window'] super().__init__(rect, ui_manager, element_ids=element_ids) # create shadow shadow_padding = (2, 2) self.image = self.ui_manager.get_shadow(self.rect.size) self.image.fill(self.ui_manager.get_theme().get_colour(self.object_ids, self.element_ids, 'dark_bg'), pygame.Rect(shadow_padding, (self.rect.width - shadow_padding[0] * 2, self.rect.height - shadow_padding[1] * 2) )) self.get_container().relative_rect.width = self.rect.width - shadow_padding[0] * 2 self.get_container().relative_rect.height = self.rect.height - shadow_padding[1] * 2 self.get_container().relative_rect.x = self.get_container().relative_rect.x + shadow_padding[0] self.get_container().relative_rect.y = self.get_container().relative_rect.y + shadow_padding[1] self.get_container().update_containing_rect_position() self.close_window_button = UIButton(relative_rect=pygame.Rect((self.get_container().rect.width-20, 0), (20, 20)), text='╳', manager=ui_manager, container=self.get_container(), parent_element=self ) self.menu_bar = UIButton(relative_rect=pygame.Rect((0, 0), (self.get_container().rect.width-20, 20)), text='Everything Container', manager=ui_manager, container=self.get_container(), parent_element=self, object_id='#message_window_title_bar' ) self.menu_bar.set_hold_range((100, 100)) self.grabbed_window = False self.starting_grab_difference = (0, 0) self.test_slider = UIHorizontalSlider(pygame.Rect((int(self.rect.width / 2), int(self.rect.height * 0.70)), (240, 25)), 50.0, (0.0, 100.0), self.ui_manager, container=self.get_container(), parent_element=self) self.slider_label = UILabel(pygame.Rect((int(self.rect.width / 2) + 250, int(self.rect.height * 0.70)), (27, 25)), str(int(self.test_slider.get_current_value())), self.ui_manager, container=self.get_container(), parent_element=self) self.test_text_entry = UITextEntryLine(pygame.Rect((int(self.rect.width / 2), int(self.rect.height * 0.50)), (200, -1)), self.ui_manager, container=self.get_container(), parent_element=self) self.test_text_entry.set_forbidden_characters('numbers') current_resolution_string = 'Item 1' self.test_drop_down_menu = UIDropDownMenu(['Item 1', 'Item 2', 'Item 3', 'Item 4', 'Item 5', 'Item 6'], current_resolution_string, pygame.Rect((int(self.rect.width / 2), int(self.rect.height * 0.3)), (200, 25)), self.ui_manager, container=self.get_container(), parent_element=self) self.health_bar = UIScreenSpaceHealthBar(pygame.Rect((int(self.rect.width / 9), int(self.rect.height * 0.7)), (200, 20)), self.ui_manager, container=self.get_container(), parent_element=self) loaded_test_image = pygame.image.load('data/images/splat.png').convert_alpha() self.test_image = UIImage(pygame.Rect((int(self.rect.width / 9), int(self.rect.height * 0.3)), loaded_test_image.get_rect().size), loaded_test_image, self.ui_manager, container=self.get_container(), parent_element=self) self.is_selected = False def select(self): self.is_selected = True def unselect(self): self.is_selected = False def process_event(self, event): processed_event = False if self.is_selected: if event.type == pygame.KEYDOWN: if event.key == pygame.K_p: self.test_slider.set_current_value(50) if event.type == pygame.MOUSEBUTTONDOWN: if event.button == 1: mouse_x, mouse_y = event.pos if self.rect.collidepoint(mouse_x, mouse_y): processed_event = True self.window_stack.move_window_to_front(self) return processed_event def update(self, time_delta): if self.alive(): if self.test_slider.has_moved_recently: self.slider_label.set_text(str(int(self.test_slider.get_current_value()))) if self.menu_bar.held: mouse_x, mouse_y = pygame.mouse.get_pos() if not self.grabbed_window: self.window_stack.move_window_to_front(self) self.grabbed_window = True self.starting_grab_difference = (mouse_x - self.rect.x, mouse_y - self.rect.y) current_grab_difference = (mouse_x - self.rect.x, mouse_y - self.rect.y) adjustment_required = (current_grab_difference[0] - self.starting_grab_difference[0], current_grab_difference[1] - self.starting_grab_difference[1]) self.rect.x += adjustment_required[0] self.rect.y += adjustment_required[1] self.get_container().relative_rect.x += adjustment_required[0] self.get_container().relative_rect.y += adjustment_required[1] self.get_container().update_containing_rect_position() else: self.grabbed_window = False if self.close_window_button.check_pressed(): self.kill() super().update(time_delta)
class UIMessageWindow(UIWindow): """ A simple popup window for delivering text-only messages to users. :param message_window_rect: The size and position of the window, includes the menu bar across the top. :param message_title: The title of the message window. :param html_message: The message itself. Can make use of HTML (a subset of) to style the text. :param manager: The UIManager that manages this UIElement. :param object_id: A custom defined ID for fine tuning of theming. """ def __init__(self, message_window_rect: pygame.Rect, message_title: str, html_message: str, manager: ui_manager.UIManager, object_id: Union[str, None] = None): new_element_ids, new_object_ids = self.create_valid_ids( parent_element=None, object_id=object_id, element_id='message_window') super().__init__(message_window_rect, manager, new_element_ids, new_object_ids) self.done_button_vertical_start = 30 self.done_button_vertical_space = 40 self.menu_bar_height = 20 self.close_button_width = 20 self.grabbed_window = False self.starting_grab_difference = (0, 0) self.shadow_width = None # type: Union[None, int] self.border_width = None # type: Union[None, int] self.background_colour = None self.border_colour = None self.border_rect = None self.background_rect = None self.text_block_rect = None # type: Union[None, pygame.Rect] self.menu_bar = None self.close_window_button = None self.dismiss_button = None self.text_block = None self.drawable_shape = None self.shape_type = 'rectangle' self.shape_corner_radius = None self.rebuild_from_changed_theme_data() self.menu_bar = UIButton(relative_rect=pygame.Rect( (0, 0), ((self.rect.width - (self.shadow_width * 2)) - self.close_button_width, self.menu_bar_height)), text=message_title, manager=manager, container=self.get_container(), parent_element=self, object_id='#message_window_title_bar') self.menu_bar.set_hold_range((100, 100)) self.close_window_button = UIButton(relative_rect=pygame.Rect( ((self.rect.width - self.shadow_width * 2) - self.close_button_width, 0), (self.close_button_width, self.menu_bar_height)), text='╳', manager=manager, container=self.get_container(), parent_element=self, object_id='#close_button') self.dismiss_button = UIButton( relative_rect=pygame.Rect( (int(self.rect.width / 2) + 45, (self.border_rect.height - self.done_button_vertical_start)), (70, 20)), text="Dismiss", manager=manager, container=self.get_container(), tool_tip_text="<font face=fira_code color=normal_text size=2>" "Click to get rid of this message.</font>", parent_element=self, object_id='#dismiss_button') self.text_block = UITextBox(html_message, self.text_block_rect, manager=manager, container=self.get_container(), parent_element=self) def rebuild(self): """ """ border_rect_width = self.rect.width - (self.shadow_width * 2) border_rect_height = self.rect.height - (self.shadow_width * 2) self.border_rect = pygame.Rect((self.shadow_width, self.shadow_width), (border_rect_width, border_rect_height)) background_rect_width = border_rect_width - (self.border_width * 2) background_rect_height = border_rect_height - (self.border_width * 2) self.background_rect = pygame.Rect( (self.shadow_width + self.border_width, self.shadow_width + self.border_width), (background_rect_width, background_rect_height)) self.text_block_rect = pygame.Rect( (self.border_width, self.menu_bar_height), (self.border_rect.width - self.border_width, (self.border_rect.height - self.menu_bar_height - self.done_button_vertical_space))) theming_parameters = { 'normal_bg': self.background_colour, 'normal_border': self.border_colour, 'border_width': self.border_width, 'shadow_width': self.shadow_width, 'shape_corner_radius': self.shape_corner_radius } if self.shape_type == 'rectangle': self.drawable_shape = RectDrawableShape(self.rect, theming_parameters, ['normal'], self.ui_manager) elif self.shape_type == 'rounded_rectangle': self.drawable_shape = RoundedRectangleShape( self.rect, theming_parameters, ['normal'], self.ui_manager) self.image = self.drawable_shape.get_surface('normal') self.get_container( ).relative_rect.width = self.rect.width - self.shadow_width * 2 self.get_container( ).relative_rect.height = self.rect.height - self.shadow_width * 2 self.get_container( ).relative_rect.x = self.relative_rect.x + self.shadow_width self.get_container( ).relative_rect.y = self.relative_rect.y + self.shadow_width self.get_container().update_containing_rect_position() if self.menu_bar is not None: self.menu_bar.set_dimensions( ((self.rect.width - (self.shadow_width * 2)) - self.close_button_width, self.menu_bar_height)) if self.close_window_button is not None: self.close_window_button.set_relative_position( ((self.rect.width - self.shadow_width * 2) - self.close_button_width, 0)) if self.dismiss_button is not None: self.dismiss_button.set_relative_position( ((self.rect.width / 2) + 45, (self.border_rect.height - self.done_button_vertical_start))) if self.text_block is not None: self.text_block.set_relative_position(self.text_block_rect.topleft) self.text_block.set_dimensions(self.text_block_rect.size) def update(self, time_delta: float): """ Called every update loop of our UI manager. Handles moving and closing the window. :param time_delta: The time in seconds between calls to this function. """ if self.alive(): if self.dismiss_button.check_pressed(): self.kill() if self.menu_bar.held: mouse_x, mouse_y = self.ui_manager.get_mouse_position() if not self.grabbed_window: self.window_stack.move_window_to_front(self) self.grabbed_window = True self.starting_grab_difference = (mouse_x - self.rect.x, mouse_y - self.rect.y) current_grab_difference = (mouse_x - self.rect.x, mouse_y - self.rect.y) adjustment_required = (current_grab_difference[0] - self.starting_grab_difference[0], current_grab_difference[1] - self.starting_grab_difference[1]) self.rect.x += adjustment_required[0] self.rect.y += adjustment_required[1] self.relative_rect.x = self.rect.x - self.ui_container.rect.x self.relative_rect.y = self.rect.y - self.ui_container.rect.y self.get_container().relative_rect.x += adjustment_required[0] self.get_container().relative_rect.y += adjustment_required[1] self.get_container().update_containing_rect_position() else: self.grabbed_window = False if self.close_window_button.check_pressed(): self.kill() super().update(time_delta) def rebuild_from_changed_theme_data(self): """ Called by the UIManager to check the theming data and rebuild whatever needs rebuilding for this element when the theme data has changed. """ has_any_changed = False shape_type = 'rectangle' shape_type_string = self.ui_theme.get_misc_data( self.object_ids, self.element_ids, 'shape') if shape_type_string is not None: if shape_type_string in ['rectangle', 'rounded_rectangle']: shape_type = shape_type_string if shape_type != self.shape_type: self.shape_type = shape_type has_any_changed = True corner_radius = 2 shape_corner_radius_string = self.ui_theme.get_misc_data( self.object_ids, self.element_ids, 'shape_corner_radius') if shape_corner_radius_string is not None: try: corner_radius = int(shape_corner_radius_string) except ValueError: corner_radius = 2 if corner_radius != self.shape_corner_radius: self.shape_corner_radius = corner_radius has_any_changed = True border_width = 1 border_width_string = self.ui_theme.get_misc_data( self.object_ids, self.element_ids, 'border_width') if border_width_string is not None: try: border_width = int(border_width_string) except ValueError: border_width = 1 if border_width != self.border_width: self.border_width = border_width has_any_changed = True shadow_width = 2 shadow_width_string = self.ui_theme.get_misc_data( self.object_ids, self.element_ids, 'shadow_width') if shadow_width_string is not None: try: shadow_width = int(shadow_width_string) except ValueError: shadow_width = 2 if shadow_width != self.shadow_width: self.shadow_width = shadow_width has_any_changed = True background_colour = self.ui_theme.get_colour_or_gradient( self.object_ids, self.element_ids, 'dark_bg') if background_colour != self.background_colour: self.background_colour = background_colour has_any_changed = True border_colour = self.ui_theme.get_colour_or_gradient( self.object_ids, self.element_ids, 'normal_border') if border_colour != self.border_colour: self.border_colour = border_colour has_any_changed = True if has_any_changed: self.rebuild()
class UIMessageWindow(UIWindow): """ A simple popup window for delivering text-only messages to users. :param message_window_rect: The size and position of the window, includes the menu bar across the top. :param message_title: The title of the message window. :param html_message: The message itself. Can make use of HTML (a subset of) to style the text. :param manager: The UIManager that manages this UIElement. :param object_id: A custom defined ID for fine tuning of theming. """ def __init__(self, message_window_rect: pygame.Rect, message_title: str, html_message: str, manager: ui_manager.UIManager, object_id: Union[str, None] = None): new_element_ids, new_object_ids = self.create_valid_ids( parent_element=None, object_id=object_id, element_id='message_window') super().__init__(message_window_rect, manager, new_element_ids, new_object_ids) self.bg_colour = self.ui_manager.get_theme().get_colour( self.object_ids, self.element_ids, 'dark_bg') self.border_colour = self.ui_manager.get_theme().get_colour( self.object_ids, self.element_ids, 'normal_border') self.border_width = 1 border_width_string = self.ui_theme.get_misc_data( self.object_ids, self.element_ids, 'border_width') if border_width_string is not None: self.border_width = int(border_width_string) self.shadow_width = 1 shadow_width_string = self.ui_theme.get_misc_data( self.object_ids, self.element_ids, 'shadow_width') if shadow_width_string is not None: self.shadow_width = int(shadow_width_string) border_rect_width = self.rect.width - (self.shadow_width * 2) border_rect_height = self.rect.height - (self.shadow_width * 2) self.border_rect = pygame.Rect((self.shadow_width, self.shadow_width), (border_rect_width, border_rect_height)) background_rect_width = border_rect_width - (self.border_width * 2) background_rect_height = border_rect_height - (self.border_width * 2) self.background_rect = pygame.Rect( (self.shadow_width + self.border_width, self.shadow_width + self.border_width), (background_rect_width, background_rect_height)) if self.shadow_width > 0: self.image = self.ui_manager.get_shadow(self.rect.size, self.shadow_width) else: self.image = pygame.Surface(self.rect.size, flags=pygame.SRCALPHA) self.image.fill(self.border_colour, self.border_rect) self.image.fill(self.bg_colour, self.background_rect) self.get_container( ).relative_rect.width = self.rect.width - self.shadow_width * 2 self.get_container( ).relative_rect.height = self.rect.height - self.shadow_width * 2 self.get_container().relative_rect.x = self.get_container( ).relative_rect.x + self.shadow_width self.get_container().relative_rect.y = self.get_container( ).relative_rect.y + self.shadow_width self.get_container().update_containing_rect_position() menu_bar_height = 20 close_button_width = 20 self.menu_bar = UIButton(relative_rect=pygame.Rect( (0, 0), ((self.rect.width - (self.shadow_width * 2)) - close_button_width, menu_bar_height)), text=message_title, manager=manager, container=self.get_container(), parent_element=self, object_id='#message_window_title_bar') self.menu_bar.set_hold_range((100, 100)) self.grabbed_window = False self.starting_grab_difference = (0, 0) self.close_window_button = UIButton(relative_rect=pygame.Rect( ((self.rect.width - self.shadow_width * 2) - close_button_width, 0), (close_button_width, menu_bar_height)), text='╳', manager=manager, container=self.get_container(), parent_element=self, object_id='#close_button') done_button_vertical_start = 30 done_button_vertical_space = 40 self.dismiss_button = UIButton( relative_rect=pygame.Rect( ((self.rect.width / 2) + 45, (border_rect_height - done_button_vertical_start)), (70, 20)), text="Dismiss", manager=manager, container=self.get_container(), tool_tip_text="<font face=fira_code color=normal_text size=2>" "Click to get rid of this message.</font>", parent_element=self, object_id='#dismiss_button') text_block_rect = pygame.Rect( (self.border_width, menu_bar_height), (self.border_rect.width - self.border_width, (border_rect_height - menu_bar_height - done_button_vertical_space))) self.text_block = UITextBox(html_message, text_block_rect, manager=manager, container=self.get_container(), parent_element=self) def update(self, time_delta: float): """ Called every update loop of our UI manager. Handles moving and closing the window. :param time_delta: The time in seconds between calls to this function. """ if self.alive(): if self.dismiss_button.check_pressed(): self.kill() if self.menu_bar.held: mouse_x, mouse_y = pygame.mouse.get_pos() if not self.grabbed_window: self.window_stack.move_window_to_front(self) self.grabbed_window = True self.starting_grab_difference = (mouse_x - self.rect.x, mouse_y - self.rect.y) current_grab_difference = (mouse_x - self.rect.x, mouse_y - self.rect.y) adjustment_required = (current_grab_difference[0] - self.starting_grab_difference[0], current_grab_difference[1] - self.starting_grab_difference[1]) self.rect.x += adjustment_required[0] self.rect.y += adjustment_required[1] self.get_container().relative_rect.x += adjustment_required[0] self.get_container().relative_rect.y += adjustment_required[1] self.get_container().update_containing_rect_position() else: self.grabbed_window = False if self.close_window_button.check_pressed(): self.kill() super().update(time_delta)
class PongWindow(UIWindow): def __init__(self, ui_manager): super().__init__(pygame.Rect((25, 25), (320, 240)), ui_manager, ['pong_window']) self.bg_colour = self.ui_manager.get_theme().get_colour(self.object_ids, self.element_ids, 'dark_bg') # create shadow shadow_padding = (2, 2) background_surface = pygame.Surface((self.rect.width - shadow_padding[0] * 2, self.rect.height - shadow_padding[1] * 2)) background_surface.fill(self.bg_colour) self.image = self.ui_manager.get_shadow(self.rect.size) self.image.blit(background_surface, shadow_padding) self.get_container().relative_rect.width = self.rect.width - shadow_padding[0] * 2 self.get_container().relative_rect.height = self.rect.height - shadow_padding[1] * 2 self.get_container().relative_rect.x = self.get_container().relative_rect.x + shadow_padding[0] self.get_container().relative_rect.y = self.get_container().relative_rect.y + shadow_padding[1] self.get_container().update_containing_rect_position() self.menu_bar = UIButton(relative_rect=pygame.Rect((0, 0), ((self.rect.width - shadow_padding[0] * 2) - 20, 20)), text='Super Awesome Pong!', manager=ui_manager, container=self.get_container(), parent_element=self ) self.menu_bar.set_hold_range((100, 100)) self.grabbed_window = False self.starting_grab_difference = (0, 0) self.close_window_button = UIButton(relative_rect=pygame.Rect(((self.rect.width - shadow_padding[0] * 2) - 20, 0), (20, 20)), text='╳', manager=ui_manager, container=self.get_container(), parent_element=self ) game_surface_size = (self.get_container().rect.width - 4, self.get_container().rect.height - 24) self.game_surface_element = UIImage(pygame.Rect((2, 22), game_surface_size), pygame.Surface(game_surface_size).convert(), manager=ui_manager, container=self.get_container(), parent_element=self) self.pong_game = PongGame(game_surface_size) def process_event(self, event): self.pong_game.process_event(event) def update(self, time_delta): if self.alive(): if self.menu_bar.held: mouse_x, mouse_y = pygame.mouse.get_pos() if not self.grabbed_window: self.window_stack.move_window_to_front(self) self.grabbed_window = True self.starting_grab_difference = (mouse_x - self.rect.x, mouse_y - self.rect.y) current_grab_difference = (mouse_x - self.rect.x, mouse_y - self.rect.y) adjustment_required = (current_grab_difference[0] - self.starting_grab_difference[0], current_grab_difference[1] - self.starting_grab_difference[1]) self.rect.x += adjustment_required[0] self.rect.y += adjustment_required[1] self.get_container().relative_rect.x += adjustment_required[0] self.get_container().relative_rect.y += adjustment_required[1] self.get_container().update_containing_rect_position() else: self.grabbed_window = False if not self.grabbed_window: self.pong_game.update(time_delta) if self.close_window_button.check_pressed(): self.kill() super().update(time_delta) self.pong_game.draw(self.game_surface_element.image)
class UIClosedDropDownState: """ The closed state of the drop down just displays the currently chosen option and a button that will switch the menu to the expanded state. """ def __init__(self, drop_down_menu_ui, selected_option, base_position_rect, open_button_width, manager, container, element_ids, object_ids): self.drop_down_menu_ui = drop_down_menu_ui self.selected_option_button = None self.open_button = None self.selected_option = selected_option self.base_position_rect = base_position_rect self.ui_manager = manager self.ui_container = container self.element_ids = element_ids self.object_ids = object_ids self.open_button_width = open_button_width self.should_transition = False self.target_state = 'expanded' def start(self): """ Called each time we enter the closed state. It creates the necessary elements, the selected option and the open button. """ # First handle the background if self.drop_down_menu_ui.shadow_width > 0: self.drop_down_menu_ui.image = self.ui_manager.get_shadow( self.drop_down_menu_ui.rect.size) else: self.drop_down_menu_ui.image = pygame.Surface( self.drop_down_menu_ui.rect.size, flags=pygame.SRCALPHA) border_rect = pygame.Rect((self.drop_down_menu_ui.shadow_width, self.drop_down_menu_ui.shadow_width), (self.drop_down_menu_ui.rect.width - (2 * self.drop_down_menu_ui.shadow_width), self.drop_down_menu_ui.rect.height - (2 * self.drop_down_menu_ui.shadow_width))) if self.drop_down_menu_ui.border_width > 0: self.drop_down_menu_ui.image.fill( self.drop_down_menu_ui.border_colour, border_rect) relative_background_rect = pygame.Rect( (self.drop_down_menu_ui.border_width + self.drop_down_menu_ui.shadow_width, self.drop_down_menu_ui.border_width + self.drop_down_menu_ui.shadow_width), (border_rect.width - (2 * self.drop_down_menu_ui.border_width), border_rect.height - (2 * self.drop_down_menu_ui.border_width))) self.drop_down_menu_ui.image.fill( self.drop_down_menu_ui.background_colour, relative_background_rect) self.should_transition = False self.selected_option_button = UIButton( pygame.Rect( (self.base_position_rect.x, self.base_position_rect.y), (self.base_position_rect.width - self.open_button_width, self.base_position_rect.height)), self.selected_option, self.ui_manager, self.ui_container, starting_height=2, parent_element=self.drop_down_menu_ui, object_id='#selected_option') open_button_x = self.base_position_rect.x + self.base_position_rect.width - self.open_button_width expand_direction = self.ui_manager.get_theme().get_misc_data( self.object_ids, self.element_ids, 'expand_direction') expand_button_symbol = '▼' if expand_direction is not None: if expand_direction == 'up': expand_button_symbol = '▲' elif expand_direction == 'down': expand_button_symbol = '▼' self.open_button = UIButton(pygame.Rect( (open_button_x, self.base_position_rect.y), (self.open_button_width, self.base_position_rect.height)), expand_button_symbol, self.ui_manager, self.ui_container, starting_height=2, parent_element=self.drop_down_menu_ui, object_id='#expand_button') def finish(self): """ Called when we leave the closed state. Kills the open button and the selected option button. """ self.selected_option_button.kill() self.open_button.kill() def update(self): if self.open_button.check_pressed(): self.should_transition = True
class UIExpandedDropDownState: """ The expanded state of the drop down displays the currently chosen option, all the available options and a button to close the menu and return to the closed state. Picking an option will also close the menu. """ def __init__(self, drop_down_menu_ui, options_list, selected_option, base_position_rect, close_button_width, manager, container, element_ids, object_ids): self.drop_down_menu_ui = drop_down_menu_ui self.should_transition = False self.options_list = options_list self.selected_option = selected_option self.base_position_rect = base_position_rect self.ui_manager = manager self.ui_container = container self.element_ids = element_ids self.object_ids = object_ids self.close_button_width = close_button_width self.selected_option_button = None self.close_button = None self.menu_buttons = [] self.should_transition = False self.target_state = 'closed' def start(self): """ Called each time we enter the expanded state. It creates the necessary elements, the selected option, all the other available options and the close button. """ self.should_transition = False option_y_pos = self.base_position_rect.y self.selected_option_button = UIButton( pygame.Rect( self.base_position_rect.topleft, (self.base_position_rect.width - self.close_button_width, self.base_position_rect.height)), self.selected_option, self.ui_manager, self.ui_container, starting_height=2, parent_element=self.drop_down_menu_ui, object_id='#selected_option') expand_direction = self.ui_manager.get_theme().get_misc_data( self.object_ids, self.element_ids, 'expand_direction') expand_button_symbol = '▼' select_button_dist_to_move = self.selected_option_button.rect.height option_button_dist_to_move = self.base_position_rect.height if expand_direction is not None: if expand_direction == 'up': expand_button_symbol = '▲' select_button_dist_to_move = -self.selected_option_button.rect.height option_button_dist_to_move = -self.base_position_rect.height elif expand_direction == 'down': expand_button_symbol = '▼' select_button_dist_to_move = self.selected_option_button.rect.height option_button_dist_to_move = self.base_position_rect.height close_button_x = self.base_position_rect.x + self.base_position_rect.width - self.close_button_width self.close_button = UIButton(pygame.Rect( (close_button_x, self.base_position_rect.y), (self.close_button_width, self.base_position_rect.height)), expand_button_symbol, self.ui_manager, self.ui_container, starting_height=2, parent_element=self.drop_down_menu_ui, object_id='#expand_button') option_y_pos += select_button_dist_to_move for option in self.options_list: new_button = UIButton( pygame.Rect( (self.base_position_rect.x, option_y_pos), (self.base_position_rect.width - self.close_button_width, self.base_position_rect.height)), option, self.ui_manager, self.ui_container, starting_height= 3, # height allows options to overlap other UI elements parent_element=self.drop_down_menu_ui, object_id='#option') option_y_pos += option_button_dist_to_move self.menu_buttons.append(new_button) overall_background_rect = pygame.Rect( self.drop_down_menu_ui.rect.topleft, (self.drop_down_menu_ui.rect.width + 50, self.base_position_rect.height * (1 + len(self.options_list)) + 2 * self.drop_down_menu_ui.shadow_width + 2 * self.drop_down_menu_ui.border_width)) options_background_rect = pygame.Rect( self.drop_down_menu_ui.rect.topleft, (self.base_position_rect.width - self.close_button_width + 2 * self.drop_down_menu_ui.shadow_width + 2 * self.drop_down_menu_ui.border_width, self.base_position_rect.height * (1 + len(self.options_list)) + 2 * self.drop_down_menu_ui.shadow_width + 2 * self.drop_down_menu_ui.border_width)) self.drop_down_menu_ui.image = pygame.Surface( overall_background_rect.size, flags=pygame.SRCALPHA) self.drop_down_menu_ui.image.fill(pygame.Color('#00000000')) if self.drop_down_menu_ui.shadow_width > 0: self.drop_down_menu_ui.image.blit( self.ui_manager.get_shadow(self.drop_down_menu_ui.rect.size), (0, 0)) border_rect = pygame.Rect((self.drop_down_menu_ui.shadow_width, self.drop_down_menu_ui.shadow_width), (self.drop_down_menu_ui.rect.width - (2 * self.drop_down_menu_ui.shadow_width), self.drop_down_menu_ui.rect.height - (2 * self.drop_down_menu_ui.shadow_width))) if self.drop_down_menu_ui.border_width > 0: self.drop_down_menu_ui.image.fill( self.drop_down_menu_ui.border_colour, border_rect) relative_background_rect = pygame.Rect( (self.drop_down_menu_ui.border_width + self.drop_down_menu_ui.shadow_width, self.drop_down_menu_ui.border_width + self.drop_down_menu_ui.shadow_width), (border_rect.width - (2 * self.drop_down_menu_ui.border_width), border_rect.height - (2 * self.drop_down_menu_ui.border_width))) self.drop_down_menu_ui.image.fill( self.drop_down_menu_ui.background_colour, relative_background_rect) if self.drop_down_menu_ui.shadow_width > 0: self.drop_down_menu_ui.image.fill( pygame.Color('#00000000'), pygame.Rect((0, 0), (options_background_rect.width - self.drop_down_menu_ui.shadow_width - self.drop_down_menu_ui.border_width, options_background_rect.height))) self.drop_down_menu_ui.image.blit( self.ui_manager.get_shadow(options_background_rect.size), (0, 0)) options_border_rect = pygame.Rect( (self.drop_down_menu_ui.shadow_width, self.drop_down_menu_ui.shadow_width), (options_background_rect.width - (2 * self.drop_down_menu_ui.shadow_width), options_background_rect.height - (2 * self.drop_down_menu_ui.shadow_width))) if self.drop_down_menu_ui.border_width > 0: self.drop_down_menu_ui.image.fill( self.drop_down_menu_ui.border_colour, options_border_rect) options_background_rect = pygame.Rect( (self.drop_down_menu_ui.border_width + self.drop_down_menu_ui.shadow_width, self.drop_down_menu_ui.border_width + self.drop_down_menu_ui.shadow_width), (options_border_rect.width - (2 * self.drop_down_menu_ui.border_width), options_border_rect.height - (2 * self.drop_down_menu_ui.border_width))) self.drop_down_menu_ui.image.fill( self.drop_down_menu_ui.background_colour, options_background_rect) def finish(self): """ cleans everything up upon exiting the expanded menu state. """ for button in self.menu_buttons: button.kill() self.menu_buttons.clear() self.selected_option_button.kill() self.close_button.kill() def update(self): if self.close_button is not None and self.close_button.check_pressed(): self.should_transition = True for button in self.menu_buttons: if button.check_pressed(): self.drop_down_menu_ui.selected_option = button.text self.should_transition = True drop_down_changed_event = pygame.event.Event( pygame.USEREVENT, { 'user_type': 'ui_drop_down_menu_changed', 'text': button.text, 'ui_element': self.drop_down_menu_ui, 'ui_object_id': self.object_ids[-1] }) pygame.event.post(drop_down_changed_event)