Exemple #1
0
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()
Exemple #2
0
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()
Exemple #3
0
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()