class UIFileDialog(UIWindow): """ A dialog window for handling file selection operations. The dialog will let you pick a file from a file system but won't do anything with it once you have, the path will just be returned leaving it up to the rest of the application to decide what to do with it. TODO: This works fine for loading files, but can it be adjusted to allow for saving files? :param rect: The size and position of the file dialog window. Includes the size of shadow, border and title bar. :param manager: The manager for the whole of the UI. :param window_title: The title for the window, defaults to 'File Dialog' :param initial_file_path: The initial path to open the file dialog at. :param object_id: The object ID for the window, used for theming - defaults to '#file_dialog' """ def __init__(self, rect: pygame.Rect, manager: IUIManagerInterface, window_title: str = 'File Dialog', initial_file_path: Union[str, None] = None, object_id: str = '#file_dialog'): super().__init__(rect, manager, window_display_title=window_title, object_id=object_id, resizable=True) minimum_dimensions = (260, 300) if rect.width < minimum_dimensions[ 0] or rect.height < minimum_dimensions[1]: warn_string = ("Initial size: " + str(rect.size) + " is less than minimum dimensions: " + str(minimum_dimensions)) warnings.warn(warn_string, UserWarning) self.set_minimum_dimensions(minimum_dimensions) self.delete_confirmation_dialog = None # type: Union[None, UIConfirmationDialog] if initial_file_path is not None: if exists(initial_file_path): self.current_directory_path = abspath(initial_file_path) else: self.current_directory_path = abspath('.') self.last_valid_path = None self.selected_file_path = None self.current_file_list = None # type: Union[None, List[str]] self.update_current_file_list() self.ok_button = UIButton(relative_rect=pygame.Rect( -220, -40, 100, 30), text='OK', manager=self.ui_manager, container=self, object_id='#ok_button', anchors={ 'left': 'right', 'right': 'right', 'top': 'bottom', 'bottom': 'bottom' }) self.ok_button.disable() self.cancel_button = UIButton(relative_rect=pygame.Rect( -110, -40, 100, 30), text='Cancel', manager=self.ui_manager, container=self, object_id='#cancel_button', anchors={ 'left': 'right', 'right': 'right', 'top': 'bottom', 'bottom': 'bottom' }) self.home_button = UIButton(relative_rect=pygame.Rect(10, 10, 20, 20), text='⌂', tool_tip_text='Home Directory', manager=self.ui_manager, container=self, object_id='#home_icon_button', anchors={ 'left': 'left', 'right': 'left', 'top': 'top', 'bottom': 'top' }) self.delete_button = UIButton(relative_rect=pygame.Rect( 32, 10, 20, 20), text='⌧', tool_tip_text='Delete', manager=self.ui_manager, container=self, object_id='#delete_icon_button', anchors={ 'left': 'left', 'right': 'left', 'top': 'top', 'bottom': 'top' }) self.delete_button.disable() self.parent_directory_button = UIButton( relative_rect=pygame.Rect(54, 10, 20, 20), text='↑', tool_tip_text='Parent Directory', manager=self.ui_manager, container=self, object_id='#parent_icon_button', anchors={ 'left': 'left', 'right': 'left', 'top': 'top', 'bottom': 'top' }) self.refresh_button = UIButton(relative_rect=pygame.Rect( 76, 10, 20, 20), text='⇪', tool_tip_text='Refresh Directory', manager=self.ui_manager, container=self, object_id='#refresh_icon_button', anchors={ 'left': 'left', 'right': 'left', 'top': 'top', 'bottom': 'top' }) text_line_rect = pygame.Rect( 10, 40, self.get_container().relative_rect.width - 20, 25) self.file_path_text_line = UITextEntryLine( relative_rect=text_line_rect, manager=self.ui_manager, container=self, object_id='#file_path_text_line', anchors={ 'left': 'left', 'right': 'right', 'top': 'top', 'bottom': 'top' }) self.file_path_text_line.set_text(self.current_directory_path) file_selection_rect = pygame.Rect( 10, 80, self.get_container().relative_rect.width - 20, self.get_container().relative_rect.height - 130) self.file_selection_list = UISelectionList( relative_rect=file_selection_rect, item_list=self.current_file_list, manager=self.ui_manager, container=self, object_id='#file_display_list', anchors={ 'left': 'left', 'right': 'right', 'top': 'top', 'bottom': 'bottom' }) def update_current_file_list(self): """ Updates the currently displayed list of files and directories. Usually called when the directory path has changed. """ try: directories_on_path = [ f for f in listdir(self.current_directory_path) if not isfile(join(self.current_directory_path, f)) ] directories_on_path = sorted(directories_on_path, key=str.casefold) directories_on_path_tuples = [(f, '#directory_list_item') for f in directories_on_path] files_on_path = [ f for f in listdir(self.current_directory_path) if isfile(join(self.current_directory_path, f)) ] files_on_path = sorted(files_on_path, key=str.casefold) files_on_path_tuples = [(f, '#file_list_item') for f in files_on_path] self.current_file_list = directories_on_path_tuples + files_on_path_tuples except (PermissionError, FileNotFoundError): self.current_directory_path = self.last_valid_path self.update_current_file_list() else: self.last_valid_path = self.current_directory_path def _validate_selected_path(self) -> bool: """ Checks the selected path is valid. :return: True if valid. """ if self.selected_file_path is None: return False return exists(self.selected_file_path) and isfile( self.selected_file_path) def process_event(self, event: pygame.event.Event) -> bool: """ Handles events that this UI element is interested in. There are a lot of buttons in the file dialog. :param event: The pygame Event to process. :return: True if event is consumed by this element and should not be passed on to other elements. """ handled = super().process_event(event) self._process_ok_cancel_events(event) self._process_confirmation_dialog_events(event) self._process_mini_file_operation_button_events(event) self._process_file_path_entry_events(event) self._process_file_list_events(event) return handled def _process_file_path_entry_events(self, event): """ Handle events coming from text entry element which displays the current file path. :param event: event to check. """ if (event.type == pygame.USEREVENT and event.user_type == UI_TEXT_ENTRY_FINISHED and event.ui_element == self.file_path_text_line): entered_file_path = self.file_path_text_line.get_text() if exists(entered_file_path): self.current_directory_path = abspath(entered_file_path) self.update_current_file_list() self.file_path_text_line.set_text(self.current_directory_path) self.file_selection_list.set_item_list(self.current_file_list) self.delete_button.disable() self.ok_button.disable() def _process_file_list_events(self, event): """ Handle events coming from the file/folder list. :param event: event to check. """ if (event.type == pygame.USEREVENT and event.user_type == UI_SELECTION_LIST_NEW_SELECTION and event.ui_element == self.file_selection_list): new_selection_file_path = join(self.current_directory_path, event.text) if exists(new_selection_file_path) and isfile( new_selection_file_path): self.selected_file_path = new_selection_file_path self.ok_button.enable() self.delete_button.enable() else: self.ok_button.disable() self.delete_button.disable() if (event.type == pygame.USEREVENT and event.user_type == UI_SELECTION_LIST_DOUBLE_CLICKED_SELECTION and event.ui_element == self.file_selection_list): new_directory_file_path = join(self.current_directory_path, event.text) if exists(new_directory_file_path ) and not isfile(new_directory_file_path): self.current_directory_path = abspath(new_directory_file_path) self.update_current_file_list() self.file_path_text_line.set_text(self.current_directory_path) self.file_selection_list.set_item_list(self.current_file_list) self.delete_button.disable() self.ok_button.disable() def _process_confirmation_dialog_events(self, event): """ Handle any events coming from the confirmation dialog if that's up. :param event: event to check. """ if (event.type == pygame.USEREVENT and event.user_type == UI_CONFIRMATION_DIALOG_CONFIRMED and event.ui_element == self.delete_confirmation_dialog): try: Path(self.selected_file_path).unlink() self.delete_button.disable() self.update_current_file_list() self.file_path_text_line.set_text(self.current_directory_path) self.file_selection_list.set_item_list(self.current_file_list) except (PermissionError, FileNotFoundError): pass def _process_mini_file_operation_button_events(self, event): """ Handle what happens when you press one of the tiny file/folder operation buttons. :param event: event to check. """ if (event.type == pygame.USEREVENT and event.user_type == UI_BUTTON_PRESSED and event.ui_element == self.delete_button): confirmation_rect = pygame.Rect(0, 0, 300, 200) confirmation_rect.center = self.rect.center selected_file_name = Path(self.selected_file_path).name long_desc = "Delete " + str(selected_file_name) + "?" self.delete_confirmation_dialog = UIConfirmationDialog( rect=confirmation_rect, manager=self.ui_manager, action_long_desc=long_desc, action_short_name='Delete', window_title='Delete') if (event.type == pygame.USEREVENT and event.user_type == UI_BUTTON_PRESSED and event.ui_element == self.parent_directory_button): self.current_directory_path = str( Path(self.current_directory_path).parent) self.update_current_file_list() self.file_path_text_line.set_text(self.current_directory_path) self.file_selection_list.set_item_list(self.current_file_list) self.delete_button.disable() self.ok_button.disable() if (event.type == pygame.USEREVENT and event.user_type == UI_BUTTON_PRESSED and event.ui_element == self.refresh_button): self.update_current_file_list() self.file_path_text_line.set_text(self.current_directory_path) self.file_selection_list.set_item_list(self.current_file_list) self.delete_button.disable() self.ok_button.disable() if (event.type == pygame.USEREVENT and event.user_type == UI_BUTTON_PRESSED and event.ui_element == self.home_button): self.current_directory_path = str(Path.home()) self.update_current_file_list() self.file_path_text_line.set_text(self.current_directory_path) self.file_selection_list.set_item_list(self.current_file_list) self.delete_button.disable() self.ok_button.disable() def _process_ok_cancel_events(self, event): """ Handle what happens when you press OK and Cancel. :param event: event to check. """ if (event.type == pygame.USEREVENT and event.user_type == UI_BUTTON_PRESSED and event.ui_element == self.cancel_button): self.kill() if (event.type == pygame.USEREVENT and event.user_type == UI_BUTTON_PRESSED and event.ui_element == self.ok_button and self._validate_selected_path()): event_data = { 'user_type': UI_FILE_DIALOG_PATH_PICKED, 'text': self.selected_file_path, 'ui_element': self, 'ui_object_id': self.most_specific_combined_id } new_file_chosen_event = pygame.event.Event(pygame.USEREVENT, event_data) pygame.event.post(new_file_chosen_event) self.kill()
class UIFileDialog(UIWindow): """ A dialog window for handling file selection operations. The dialog will let you pick a file from a file system but won't do anything with it once you have, the path will just be returned leaving it up to the rest of the application to decide what to do with it. :param rect: The size and position of the file dialog window. Includes the size of shadow, border and title bar. :param manager: The manager for the whole of the UI. :param window_title: The title for the window, defaults to 'File Dialog' :param initial_file_path: The initial path to open the file dialog at. :param object_id: The object ID for the window, used for theming - defaults to '#file_dialog' :param visible: Whether the element is visible by default. """ def __init__(self, rect: pygame.Rect, manager: IUIManagerInterface, window_title: str = 'File Dialog', initial_file_path: Union[str, None] = None, object_id: Union[ObjectID, str] = ObjectID('#file_dialog', None), allow_existing_files_only: bool = False, allow_picking_directories: bool = False, visible: int = 1): super().__init__(rect, manager, window_display_title=window_title, object_id=object_id, resizable=True, visible=visible) minimum_dimensions = (260, 300) if rect.width < minimum_dimensions[ 0] or rect.height < minimum_dimensions[1]: warn_string = ("Initial size: " + str(rect.size) + " is less than minimum dimensions: " + str(minimum_dimensions)) warnings.warn(warn_string, UserWarning) self.set_minimum_dimensions(minimum_dimensions) self.allow_existing_files_only = allow_existing_files_only self.allow_picking_directories = allow_picking_directories self.delete_confirmation_dialog = None # type: Union[UIConfirmationDialog, None] self.current_file_path = None # type: Union[Path, None] if initial_file_path is not None: pathed_initial_file_path = Path(initial_file_path) if pathed_initial_file_path.exists( ) and not pathed_initial_file_path.is_file(): self.current_directory_path = str( pathed_initial_file_path.resolve()) if self.allow_picking_directories: self.current_file_path = self.current_directory_path elif pathed_initial_file_path.exists( ) and pathed_initial_file_path.is_file(): self.current_file_path = pathed_initial_file_path.resolve() self.current_directory_path = str( pathed_initial_file_path.parent.resolve()) elif pathed_initial_file_path.parent.exists(): self.current_directory_path = str( pathed_initial_file_path.parent.resolve()) self.current_file_path = ( Path(initial_file_path).parent.resolve() / Path(initial_file_path).name) else: self.current_directory_path = str(Path('.').resolve()) self.last_valid_directory_path = self.current_directory_path self.current_file_list = None # type: Union[List[str], None] self.update_current_file_list() self.ok_button = UIButton(relative_rect=pygame.Rect( -220, -40, 100, 30), text='OK', manager=self.ui_manager, container=self, object_id='#ok_button', anchors={ 'left': 'right', 'right': 'right', 'top': 'bottom', 'bottom': 'bottom' }) if not self._validate_file_path(self.current_file_path): self.ok_button.disable() self.cancel_button = UIButton(relative_rect=pygame.Rect( -110, -40, 100, 30), text='Cancel', manager=self.ui_manager, container=self, object_id='#cancel_button', anchors={ 'left': 'right', 'right': 'right', 'top': 'bottom', 'bottom': 'bottom' }) self.home_button = UIButton(relative_rect=pygame.Rect(10, 10, 20, 20), text='⌂', tool_tip_text='Home Directory', manager=self.ui_manager, container=self, object_id='#home_icon_button', anchors={ 'left': 'left', 'right': 'left', 'top': 'top', 'bottom': 'top' }) self.delete_button = UIButton(relative_rect=pygame.Rect( 32, 10, 20, 20), text='⌧', tool_tip_text='Delete', manager=self.ui_manager, container=self, object_id='#delete_icon_button', anchors={ 'left': 'left', 'right': 'left', 'top': 'top', 'bottom': 'top' }) if not self._validate_path_exists_and_of_allowed_type( self.current_file_path, allow_directories=False): self.delete_button.disable() self.parent_directory_button = UIButton( relative_rect=pygame.Rect(54, 10, 20, 20), text='↑', tool_tip_text='Parent Directory', manager=self.ui_manager, container=self, object_id='#parent_icon_button', anchors={ 'left': 'left', 'right': 'left', 'top': 'top', 'bottom': 'top' }) self.refresh_button = UIButton(relative_rect=pygame.Rect( 76, 10, 20, 20), text='⇪', tool_tip_text='Refresh Directory', manager=self.ui_manager, container=self, object_id='#refresh_icon_button', anchors={ 'left': 'left', 'right': 'left', 'top': 'top', 'bottom': 'top' }) text_line_rect = pygame.Rect(10, 40, self.get_container().get_size()[0] - 20, 25) self.file_path_text_line = UITextEntryLine( relative_rect=text_line_rect, manager=self.ui_manager, container=self, object_id='#file_path_text_line', anchors={ 'left': 'left', 'right': 'right', 'top': 'top', 'bottom': 'top' }) if self.current_file_path is not None: self.file_path_text_line.set_text(str(self.current_file_path)) self._highlight_file_name_for_editing() else: self.file_path_text_line.set_text(str(self.current_directory_path)) file_selection_rect = pygame.Rect( 10, 80, self.get_container().get_size()[0] - 20, self.get_container().get_size()[1] - 130) self.file_selection_list = UISelectionList( relative_rect=file_selection_rect, item_list=self.current_file_list, manager=self.ui_manager, container=self, object_id='#file_display_list', anchors={ 'left': 'left', 'right': 'right', 'top': 'top', 'bottom': 'bottom' }) def _highlight_file_name_for_editing(self): # try highlighting the file name if self.current_file_path is None or self.allow_existing_files_only: return highlight_start = self.file_path_text_line.get_text().find( self.current_file_path.stem) highlight_end = highlight_start + len(self.current_file_path.stem) self.file_path_text_line.select_range[0] = highlight_start self.file_path_text_line.select_range[1] = highlight_end self.file_path_text_line.cursor_has_moved_recently = True self.file_path_text_line.edit_position = highlight_end text_clip_width = (self.file_path_text_line.rect.width - (self.file_path_text_line.padding[0] * 2) - (self.file_path_text_line.shape_corner_radius * 2) - (self.file_path_text_line.border_width * 2) - (self.file_path_text_line.shadow_width * 2)) text_width = self.file_path_text_line.font.size( self.file_path_text_line.get_text())[0] self.file_path_text_line.start_text_offset = max( 0, text_width - text_clip_width) if not self.file_path_text_line.is_focused: self.file_path_text_line.focus() def update_current_file_list(self): """ Updates the currently displayed list of files and directories. Usually called when the directory path has changed. """ try: directories_on_path = [ f.name for f in Path(self.current_directory_path).iterdir() if not f.is_file() ] directories_on_path = sorted(directories_on_path, key=str.casefold) directories_on_path_tuples = [(f, '#directory_list_item') for f in directories_on_path] files_on_path = [ f.name for f in Path(self.current_directory_path).iterdir() if f.is_file() ] files_on_path = sorted(files_on_path, key=str.casefold) files_on_path_tuples = [(f, '#file_list_item') for f in files_on_path] self.current_file_list = directories_on_path_tuples + files_on_path_tuples except (PermissionError, FileNotFoundError): self.current_directory_path = self.last_valid_directory_path self.update_current_file_list() else: self.last_valid_directory_path = self.current_directory_path def _validate_file_path(self, path_to_validate: Path) -> bool: if self.allow_existing_files_only: return self._validate_path_exists_and_of_allowed_type( path_to_validate, self.allow_picking_directories) else: return self._validate_path_in_existing_directory(path_to_validate) @staticmethod def _validate_path_in_existing_directory(path_to_validate: Path) -> bool: """ Checks the selected path is valid. :return: True if valid. """ if path_to_validate is None: return False return len( path_to_validate.name) > 0 and path_to_validate.parent.exists() @staticmethod def _validate_path_exists_and_of_allowed_type( path_to_validate: Path, allow_directories: bool) -> bool: """ Checks the selected path is valid. :return: True if valid. """ if path_to_validate is None: return False if allow_directories: valid_type = (path_to_validate.is_file() or path_to_validate.is_dir()) else: valid_type = path_to_validate.is_file() return path_to_validate.exists() and valid_type def process_event(self, event: pygame.event.Event) -> bool: """ Handles events that this UI element is interested in. There are a lot of buttons in the file dialog. :param event: The pygame Event to process. :return: True if event is consumed by this element and should not be passed on to other elements. """ handled = super().process_event(event) self._process_ok_cancel_events(event) self._process_confirmation_dialog_events(event) self._process_mini_file_operation_button_events(event) self._process_file_path_entry_events(event) self._process_file_list_events(event) return handled def _process_file_path_entry_events(self, event): """ Handle events coming from text entry element which displays the current file path. :param event: event to check. """ if (event.type != pygame.USEREVENT or event.user_type not in [UI_TEXT_ENTRY_FINISHED, UI_TEXT_ENTRY_CHANGED] or event.ui_element != self.file_path_text_line): return entered_file_path = Path( self.file_path_text_line.get_text()).absolute() if self._validate_file_path(entered_file_path): if len(entered_file_path.name) > 0 and ( entered_file_path.is_file() or not entered_file_path.exists()): self.current_file_path = entered_file_path if self._validate_path_exists_and_of_allowed_type( self.current_file_path, allow_directories=False): self.delete_button.enable() else: self.delete_button.disable() self.ok_button.enable() else: self.current_directory_path = str(entered_file_path) self.current_file_path = None self.delete_button.disable() self.ok_button.disable() if event.user_type == UI_TEXT_ENTRY_FINISHED: if len(entered_file_path.name ) > 0 and entered_file_path.is_dir(): self.current_directory_path = str(entered_file_path) elif len(entered_file_path.name) > 0 and ( entered_file_path.is_file() or not entered_file_path.exists()): self.current_directory_path = str( entered_file_path.parent.absolute()) else: self.current_directory_path = str(entered_file_path) self.update_current_file_list() self.file_selection_list.set_item_list(self.current_file_list) if self.current_file_path is not None: self.file_path_text_line.set_text( str(self.current_file_path)) else: self.file_path_text_line.set_text( self.current_directory_path) else: self.current_directory_path = self.last_valid_directory_path self.current_file_path = None self.delete_button.disable() self.ok_button.disable() def _process_file_list_events(self, event): """ Handle events coming from the file/folder list. :param event: event to check. """ if (event.type == pygame.USEREVENT and event.user_type == UI_SELECTION_LIST_NEW_SELECTION and event.ui_element == self.file_selection_list): new_selection_file_path = Path( self.current_directory_path) / event.text if self._validate_path_exists_and_of_allowed_type( new_selection_file_path, self.allow_picking_directories): self.current_file_path = new_selection_file_path self.file_path_text_line.set_text(str(self.current_file_path)) self._highlight_file_name_for_editing() self.ok_button.enable() if self._validate_path_exists_and_of_allowed_type( self.current_file_path, allow_directories=False): self.delete_button.enable() else: self.delete_button.disable() else: self.ok_button.disable() self.delete_button.disable() if (event.type == pygame.USEREVENT and event.user_type == UI_SELECTION_LIST_DOUBLE_CLICKED_SELECTION and event.ui_element == self.file_selection_list): new_directory_file_path = Path( self.current_directory_path) / event.text self._change_directory_path(new_directory_file_path) def _change_directory_path(self, new_directory_path: Path): """ Change the current directory path and update everything that needs to update when that happens. :param new_directory_path: The new path to change to. """ if not new_directory_path.exists() or new_directory_path.is_file(): return self.current_directory_path = str(new_directory_path.resolve()) self.update_current_file_list() self.file_selection_list.set_item_list(self.current_file_list) if self.current_file_path is not None and not self.allow_existing_files_only: self.current_file_path = (new_directory_path / self.current_file_path.name).resolve() self.file_path_text_line.set_text(str(self.current_file_path)) self._highlight_file_name_for_editing() self.ok_button.enable() if self._validate_path_exists_and_of_allowed_type( self.current_file_path, allow_directories=False): self.delete_button.enable() else: self.delete_button.disable() else: self.current_file_path = None self.file_path_text_line.set_text(self.current_directory_path) self.delete_button.disable() self.ok_button.disable() def _process_confirmation_dialog_events(self, event): """ Handle any events coming from the confirmation dialog if that's up. :param event: event to check. """ if (event.type != pygame.USEREVENT or event.user_type != UI_CONFIRMATION_DIALOG_CONFIRMED or event.ui_element != self.delete_confirmation_dialog): return try: self.current_file_path.unlink() except (PermissionError, FileNotFoundError): pass else: self.current_file_path = None self.delete_button.disable() self.ok_button.disable() self.update_current_file_list() self.file_path_text_line.set_text(self.current_directory_path) self.file_selection_list.set_item_list(self.current_file_list) def _process_mini_file_operation_button_events(self, event): """ Handle what happens when you press one of the tiny file/folder operation buttons. :param event: event to check. """ if (event.type == pygame.USEREVENT and event.user_type == UI_BUTTON_PRESSED and event.ui_element == self.delete_button): confirmation_rect = pygame.Rect(0, 0, 300, 200) confirmation_rect.center = self.rect.center selected_file_name = self.current_file_path.name long_desc = "Delete " + str(selected_file_name) + "?" self.delete_confirmation_dialog = UIConfirmationDialog( rect=confirmation_rect, manager=self.ui_manager, action_long_desc=long_desc, action_short_name='Delete', window_title='Delete') if (event.type == pygame.USEREVENT and event.user_type == UI_BUTTON_PRESSED and event.ui_element == self.parent_directory_button): self._change_directory_path( Path(self.current_directory_path).parent) if (event.type == pygame.USEREVENT and event.user_type == UI_BUTTON_PRESSED and event.ui_element == self.refresh_button): self._change_directory_path(Path(self.current_directory_path)) if (event.type == pygame.USEREVENT and event.user_type == UI_BUTTON_PRESSED and event.ui_element == self.home_button): self._change_directory_path(Path.home()) def _process_ok_cancel_events(self, event): """ Handle what happens when you press OK and Cancel. :param event: event to check. """ if (event.type == pygame.USEREVENT and event.user_type == UI_BUTTON_PRESSED and event.ui_element == self.cancel_button): self.kill() if (event.type == pygame.USEREVENT and event.user_type == UI_BUTTON_PRESSED and event.ui_element == self.ok_button and self._validate_file_path(self.current_file_path)): event_data = { 'user_type': UI_FILE_DIALOG_PATH_PICKED, 'text': str(self.current_file_path), 'ui_element': self, 'ui_object_id': self.most_specific_combined_id } new_file_chosen_event = pygame.event.Event(pygame.USEREVENT, event_data) pygame.event.post(new_file_chosen_event) self.kill()
class UrizenGuiApp: def __init__(self): pygame.init() pygame.display.set_caption('Urizen 0.2.5') self.opt = GUIOptions() self.gen_state = GeneratorsState() if self.opt.fullscreen: self.window_surface = pygame.display.set_mode( self.opt.resolution, pygame.FULLSCREEN) else: self.window_surface = pygame.display.set_mode( self.opt.resolution, pygame.RESIZABLE) self.background_surface = None self.ui_manager = UIManager( self.opt.resolution, PackageResource('urizen.data.themes', 'gui_theme.json')) self.ui_manager.preload_fonts([{ 'name': 'fira_code', 'point_size': 10, 'style': 'bold' }, { 'name': 'fira_code', 'point_size': 10, 'style': 'regular' }, { 'name': 'fira_code', 'point_size': 10, 'style': 'italic' }, { 'name': 'fira_code', 'point_size': 14, 'style': 'italic' }, { 'name': 'fira_code', 'point_size': 14, 'style': 'bold' }]) self.panel = None self.message_window = None self.active_panel = None self.recreate_ui() self.clock = pygame.time.Clock() self.time_delta_stack = deque([]) self.button_response_timer = pygame.time.Clock() self.running = True def recreate_ui(self): self.ui_manager.set_window_resolution(self.opt.resolution) self.ui_manager.clear_and_reset() self.background_surface = pygame.Surface(self.opt.resolution) self.background_surface.fill( self.ui_manager.get_theme().get_colour('dark_bg')) self.btn_gen_explore = UIButton( pygame.Rect(5, 5, 48, 48), '', manager=self.ui_manager, container=None, object_id='#btn_gen_explore', ) self.btn_gen_search = UIButton( pygame.Rect(5, 58, 48, 48), '', manager=self.ui_manager, container=None, object_id='#btn_gen_search', tool_tip_text='Not yet implemented', ) self.btn_tiles_explore = UIButton( pygame.Rect(5, 111, 48, 48), '', manager=self.ui_manager, container=None, object_id='#btn_tiles_explore', tool_tip_text='Not yet implemented', ) self.btn_tiles_search = UIButton( pygame.Rect(5, 164, 48, 48), '', manager=self.ui_manager, container=None, object_id='#btn_tiles_search', tool_tip_text='Not yet implemented', ) self.main_area = pygame.Rect(5 + 48 + 5, 5, self.opt.W - (5 + 48 + 5 + 5), self.opt.H - (5 + 5)) self.pnl_empty = UIPanel(self.main_area, starting_layer_height=1, manager=self.ui_manager) self.construct_gen_explore() self.construct_gen_search() self.btn_gen_search.disable() self.construct_tiles_explore() self.btn_tiles_explore.disable() self.construct_tiles_search() self.btn_tiles_search.disable() if self.active_panel: self.active_panel.show() def construct_gen_explore(self): #self.gen_explore_bg_surface = pygame.Surface(self.opt.resolution) #self.gen_explore_bg_surface.fill(self.ui_manager.get_theme().get_colour('dark_bg')) self.pnl_gen_explore = UIPanel(self.main_area, starting_layer_height=0, manager=self.ui_manager) self.gen_explore_label = UILabel( pygame.Rect(5, 5, 350, 35), 'Explore generators', self.ui_manager, container=self.pnl_gen_explore, ) self.gen_list = UISelectionList( relative_rect=pygame.Rect(5, 75, 350, self.opt.H - 95), item_list=self.gen_state.get_current_gen_list(), manager=self.ui_manager, container=self.pnl_gen_explore, allow_multi_select=False, object_id='#gen_list', ) self.btn_gen_explore_save_as_png = None self.gen_explore_map_image = None self.gen_explore_image = None self.file_dialog_gen_explore_save_as_png = None self.pnl_gen_explore.hide() def construct_gen_search(self): self.pnl_gen_search = UIPanel(self.main_area, starting_layer_height=0, manager=self.ui_manager) self.pnl_gen_search.hide() def construct_tiles_explore(self): self.pnl_tiles_explore = UIPanel(self.main_area, starting_layer_height=0, manager=self.ui_manager) self.pnl_tiles_explore.hide() def construct_tiles_search(self): self.pnl_tiles_search = UIPanel(self.main_area, starting_layer_height=0, manager=self.ui_manager) self.pnl_tiles_search.hide() def process_events(self): for event in pygame.event.get(): if event.type == pygame.QUIT: self.running = False self.ui_manager.process_events(event) if event.type == pygame.VIDEORESIZE: self.opt.W = event.w self.opt.H = event.h self.opt.resolution = (event.w, event.h) self.recreate_ui() if event.type == pygame.USEREVENT: if event.user_type == pygame_gui.UI_BUTTON_PRESSED: if event.ui_element == self.btn_gen_explore: self.pnl_gen_explore.show() self.pnl_gen_search.hide() self.pnl_tiles_explore.hide() self.pnl_tiles_search.hide() self.active_panel = self.pnl_gen_explore elif event.ui_element == self.btn_gen_search: self.pnl_gen_explore.hide() self.pnl_gen_search.show() self.pnl_tiles_explore.hide() self.pnl_tiles_search.hide() self.active_panel = self.btn_gen_search elif event.ui_element == self.btn_tiles_explore: self.pnl_gen_explore.hide() self.pnl_gen_search.hide() self.pnl_tiles_explore.show() self.pnl_tiles_search.hide() self.active_panel = self.btn_tiles_explore elif event.ui_element == self.btn_tiles_search: self.pnl_gen_explore.hide() self.pnl_gen_search.hide() self.pnl_tiles_explore.hide() self.pnl_tiles_search.show() self.active_panel = self.pnl_tiles_search elif event.ui_element == self.btn_gen_explore_save_as_png: self.file_dialog_gen_explore_save_as_png = UIFileDialog( pygame.Rect(self.opt.W // 4, self.opt.H // 4, self.opt.W // 2, self.opt.H // 2), self.ui_manager, window_title='Save as PNG', initial_file_path='map.png', object_id='#file_dialog_gen_explore_save_as_png') if event.user_type == pygame_gui.UI_FILE_DIALOG_PATH_PICKED: if event.ui_element == self.file_dialog_gen_explore_save_as_png: self.gen_explore_image.save(event.text) if event.user_type == pygame_gui.UI_WINDOW_CLOSE: if event.ui_element == self.file_dialog_gen_explore_save_as_png: self.file_dialog_gen_explore_save_as_png = None if event.user_type == pygame_gui.UI_SELECTION_LIST_DOUBLE_CLICKED_SELECTION: if event.ui_element == self.gen_list: if self.gen_state.is_generator(event.text): M = self.gen_state.get_generator(event.text)() surface_w = self.opt.W - 435 surface_h = self.opt.H - 95 self.gen_explore_image = construct_bounded_map_image( M, surface_w, surface_h) image_bytes = self.gen_explore_image.tobytes() im_w, im_h = self.gen_explore_image.size shift_x = (surface_w - im_w) // 2 shift_y = (surface_h - im_h) // 2 if not self.gen_explore_map_image: self.gen_explore_map_image = UIImage( relative_rect=pygame.Rect( 360 + shift_x, 75 + shift_y, im_w, im_h), image_surface=pygame.image.fromstring( image_bytes, self.gen_explore_image.size, self.gen_explore_image.mode), manager=self.ui_manager, container=self.pnl_gen_explore, object_id='#gen_explore_map_image', ) else: self.gen_explore_map_image.set_relative_position( pygame.Rect(360 + shift_x, 75 + shift_y, im_w, im_h)) self.gen_explore_map_image.image = pygame.image.fromstring( image_bytes, self.gen_explore_image.size, self.gen_explore_image.mode) if not self.btn_gen_explore_save_as_png: self.btn_gen_explore_save_as_png = UIButton( pygame.Rect(self.opt.W - 265, 5, 190, 50), 'Save as PNG', manager=self.ui_manager, container=self.pnl_gen_explore, object_id='#btn_gen_explore_save_as_png', starting_height=10, ) else: self.gen_state.change_state(event.text) self.gen_list.set_item_list( self.gen_state.get_current_gen_list()) def run(self): while self.running: time_delta = self.clock.tick() / 1000.0 self.time_delta_stack.append(time_delta) if len(self.time_delta_stack) > 2000: self.time_delta_stack.popleft() # check for input self.process_events() # respond to input self.ui_manager.update(time_delta) # draw graphics self.window_surface.blit(self.background_surface, (0, 0)) self.ui_manager.draw_ui(self.window_surface) pygame.display.update()