Ejemplo n.º 1
0
class SpeakSpell(pygame_gui.elements.UIWindow):
    speakthrd = None

    def __init__(self, pos, manager):
        super().__init__(
            pygame.Rect(pos, (400,128)),
            manager=manager,
            window_display_title='speaknspell',
            object_id='#speaknspell'
        )

        self.label = UILabel(
            relative_rect=pygame.Rect(-20, 10, 400, 20),
            text='',
            manager=manager,
            container=self
        )

        self.input = UITextEntryLine(
            relative_rect=pygame.Rect(0, 40, 368, 30),
            manager=manager,
            container=self
        )

        self.engine = pyttsx3.init()
        self.engine.setProperty('rate', 150)
        self.speakthrd = None

        self.speak('Hello, thank you for using snakeware!')

    def speak(self, text):
        if self.speakthrd is not None and self.speakthrd.is_alive():
            return

        if text == '':
            return

        self.engine.say(text)
        self.speakthrd = threading.Thread(target=self.engine.runAndWait, args=())
        self.speakthrd.start()
        self.label.set_text(text)
        self.input.set_text('')

    def process_event(self, event):
        super().process_event(event)
        if event.type == pygame.USEREVENT and event.ui_element == self.input:
            if event.user_type == pygame_gui.UI_TEXT_ENTRY_FINISHED:
                self.speak(self.input.get_text())
class UIColourChannelEditor(UIElement):
    """
    This colour picker specific element lets us edit a single colour channel (Red, Green, Blue,
    Hue etc). It's bundled along with the colour picker class because I don't see much use for it
    outside of a colour picker, but it still seemed sensible to make a class for a pattern in the
    colour picker that is repeated six times.

    :param relative_rect: The relative rectangle for sizing and positioning the element, relative
                          to the anchors.
    :param manager: The UI manager for the UI system.
    :param name: Name for this colour channel, (e.g 'R:' or 'B:'). Used for the label.
    :param channel_index: Index for the colour channel (e.g. red is 0, blue is 1, hue is also 0,
                          saturation is 1)
    :param value_range: Range of values for this channel (0 to 255 for R,G,B - 0 to 360 for hue, 0
                        to 100 for the rest)
    :param initial_value: Starting value for this colour channel.
    :param container: UI container for this element.
    :param parent_element: An element to parent this element, used for theming hierarchies and
                           events.
    :param object_id: A specific theming/event ID for this element.
    :param anchors: A dictionary of anchors used for setting up what this element's relative_rect
                    is relative to.
    :param visible: Whether the element is visible by default. Warning - container visibility
                    may override this.
    """
    def __init__(self,
                 relative_rect: pygame.Rect,
                 manager: IUIManagerInterface,
                 name: str,
                 channel_index: int,
                 value_range: Tuple[int, int],
                 initial_value: int,
                 container: Union[IContainerLikeInterface, None] = None,
                 parent_element: UIElement = None,
                 object_id: Union[ObjectID, str, None] = None,
                 anchors: Dict[str, str] = None,
                 visible: int = 1):

        super().__init__(relative_rect,
                         manager,
                         container,
                         starting_height=1,
                         layer_thickness=1,
                         anchors=anchors,
                         visible=visible)

        self._create_valid_ids(container=container,
                               parent_element=parent_element,
                               object_id=object_id,
                               element_id='colour_channel_editor')

        self.range = value_range
        self.current_value = initial_value
        self.channel_index = channel_index

        self.set_image(self.ui_manager.get_universal_empty_surface())

        self.element_container = UIContainer(relative_rect,
                                             self.ui_manager,
                                             container=self.ui_container,
                                             parent_element=self,
                                             anchors=anchors,
                                             visible=self.visible)

        default_sizes = {
            'space_between': 3,
            'label_width': 17,
            'entry_width': 43,
            'line_height': 29,
            'slider_height': 21,
            'slider_vert_space': 4
        }

        self.label = UILabel(pygame.Rect(0, 0, -1,
                                         default_sizes['line_height']),
                             text=name,
                             manager=self.ui_manager,
                             container=self.element_container,
                             parent_element=self,
                             anchors={
                                 'left': 'left',
                                 'right': 'left',
                                 'top': 'top',
                                 'bottom': 'bottom'
                             })

        self.entry = UITextEntryLine(pygame.Rect(-default_sizes['entry_width'],
                                                 0,
                                                 default_sizes['entry_width'],
                                                 default_sizes['line_height']),
                                     manager=self.ui_manager,
                                     container=self.element_container,
                                     parent_element=self,
                                     anchors={
                                         'left': 'right',
                                         'right': 'right',
                                         'top': 'top',
                                         'bottom': 'bottom'
                                     })

        slider_width = (self.entry.rect.left - self.label.rect.right) - (
            2 * default_sizes['space_between'])

        self.slider = UIHorizontalSlider(pygame.Rect(
            (self.label.get_abs_rect().width + default_sizes['space_between']),
            default_sizes['slider_vert_space'], slider_width,
            default_sizes['slider_height']),
                                         start_value=initial_value,
                                         value_range=value_range,
                                         manager=self.ui_manager,
                                         container=self.element_container,
                                         parent_element=self,
                                         anchors={
                                             'left': 'left',
                                             'right': 'right',
                                             'top': 'top',
                                             'bottom': 'bottom'
                                         })

        self.entry.set_allowed_characters('numbers')
        self.entry.set_text(str(initial_value))
        self.entry.set_text_length_limit(3)

    def process_event(self, event: pygame.event.Event) -> bool:
        """
        Handles events that this UI element is interested in. In this case we are responding to the
        slider being moved and the user finishing entering text in the text entry element.

        :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.

        """
        consumed_event = super().process_event(event)
        if event.type == UI_TEXT_ENTRY_FINISHED and event.ui_element == self.entry:
            int_value = self.current_value
            try:
                int_value = int(self.entry.get_text())
            except ValueError:
                int_value = 0
            finally:
                self._set_value_from_entry(int_value)

        if event.type == UI_HORIZONTAL_SLIDER_MOVED and event.ui_element == self.slider:
            int_value = self.current_value
            try:
                int_value = int(self.slider.get_current_value())
            except ValueError:
                int_value = 0
            finally:
                self._set_value_from_slider(int_value)

        return consumed_event

    def _set_value_from_slider(self, new_value: int):
        """
        For updating the value in the text entry element when we've moved the slider. Also sends
        out an event for the color picker.

        :param new_value: The new value to set.

        """
        clipped_value = min(self.range[1], max(self.range[0], new_value))
        if clipped_value != self.current_value:
            self.current_value = clipped_value
            self.entry.set_text(str(self.current_value))
            # old event - to be removed in 0.8.0
            event_data = {
                'user_type': OldType(UI_COLOUR_PICKER_COLOUR_CHANNEL_CHANGED),
                'value': self.current_value,
                'channel_index': self.channel_index,
                'ui_element': self,
                'ui_object_id': self.most_specific_combined_id
            }
            pygame.event.post(pygame.event.Event(pygame.USEREVENT, event_data))

            # new event
            event_data = {
                'value': self.current_value,
                'channel_index': self.channel_index,
                'ui_element': self,
                'ui_object_id': self.most_specific_combined_id
            }
            pygame.event.post(
                pygame.event.Event(UI_COLOUR_PICKER_COLOUR_CHANNEL_CHANGED,
                                   event_data))

    def _set_value_from_entry(self, new_value: int):
        """
        For updating the value the slider element is set to when we've edited the text entry. The
        slider may have much less precision than the text entry depending on it's available width
        so we need to be careful to make the change one way. Also sends out an event for the color
        picker and clips the value to within the allowed value range.

        :param new_value: The new value to set.

        """
        clipped_value = min(self.range[1], max(self.range[0], new_value))
        if clipped_value != new_value:
            self.entry.set_text(str(clipped_value))
        if clipped_value != self.current_value:
            self.current_value = clipped_value
            self.slider.set_current_value(self.current_value)

            # old event - to be removed in 0.8.0
            event_data = {
                'user_type': OldType(UI_COLOUR_PICKER_COLOUR_CHANNEL_CHANGED),
                'value': self.current_value,
                'channel_index': self.channel_index,
                'ui_element': self,
                'ui_object_id': self.most_specific_combined_id
            }
            colour_channel_changed_event = pygame.event.Event(
                pygame.USEREVENT, event_data)
            pygame.event.post(colour_channel_changed_event)

            event_data = {
                'value': self.current_value,
                'channel_index': self.channel_index,
                'ui_element': self,
                'ui_object_id': self.most_specific_combined_id
            }
            colour_channel_changed_event = pygame.event.Event(
                UI_COLOUR_PICKER_COLOUR_CHANNEL_CHANGED, event_data)
            pygame.event.post(colour_channel_changed_event)

    def set_value(self, new_value: int):
        """
        For when we need to set the value of the colour channel from outside, usually from
        adjusting the colour elsewhere in the colour picker. Makes sure the new value is within the
        allowed range.

        :param new_value: Value to set.

        """
        clipped_value = min(self.range[1], max(self.range[0], new_value))
        if clipped_value != self.current_value:
            self.current_value = clipped_value
            self.entry.set_text(str(self.current_value))
            self.slider.set_current_value(self.current_value)

    def set_position(self, position: Union[pygame.math.Vector2,
                                           Tuple[int, int], Tuple[float,
                                                                  float]]):
        """
        Sets the absolute screen position of this channel, updating all subordinate elements at the
        same time.

        :param position: The absolute screen position to set.

        """
        super().set_position(position)
        self.element_container.set_relative_position(
            self.relative_rect.topleft)

    def set_relative_position(self, position: Union[pygame.math.Vector2,
                                                    Tuple[int, int],
                                                    Tuple[float, float]]):
        """
        Sets the relative screen position of this channel, updating all subordinate elements at the
        same time.

        :param position: The relative screen position to set.

        """
        super().set_relative_position(position)
        self.element_container.set_relative_position(
            self.relative_rect.topleft)

    def set_dimensions(self, dimensions: Union[pygame.math.Vector2,
                                               Tuple[int, int], Tuple[float,
                                                                      float]]):
        """
        Method to directly set the dimensions of an element.

        :param dimensions: The new dimensions to set.

        """
        super().set_dimensions(dimensions)
        self.element_container.set_dimensions(self.relative_rect.size)

    def show(self):
        """
        In addition to the base UIElement.show() - call show() of the element_container
        - which will propagate to the sub-elements - label, entry and slider.
        """
        super().show()

        self.element_container.show()

    def hide(self):
        """
        In addition to the base UIElement.hide() - call hide() of the element_container
        - which will propagate to the sub-elements - label, entry and slider.
        """
        super().hide()

        self.element_container.hide()
Ejemplo n.º 3
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()
Ejemplo n.º 4
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()
Ejemplo n.º 5
0
class Chat(UIContainer):
    # region Docstring
    """
    Class to display the game chat
    """

    # endregion

    def __init__(
        self,
        size: tuple[int, int],
        manager: IUIManagerInterface,
        udp: UDP_P2P,
        username: str,
    ) -> None:
        # region Docstring
        """
        Creates a `Chat` object

        ### Arguments
            `size {(int, int)}`:
                `summary`: the size of the chat window
            `manager {UIManager}`:
                `summary`: the UIManager that manages this element.
            `udp {UDP_P2P}`:
                `summary`: the udp object
            `username {str}`:
                `summary`: the username of this host
        """
        # endregion
        super().__init__(relative_rect=Rect((Game.SIZE[0], 0), size),
                         manager=manager)

        # region Element creation

        self.textbox = UITextBox(
            html_text="",
            relative_rect=Rect((0, 0), (size[0], size[1] - 25)),
            manager=manager,
            container=self,
        )

        self.entryline = UITextEntryLine(
            relative_rect=Rect((0, size[1] - 28), (size[0] - 50, 20)),
            manager=manager,
            container=self,
        )
        self.entryline.set_text_length_limit(100)

        self.enterbtn = UIButton(
            text="Enter",
            relative_rect=Rect((size[0] - 50, size[1] - 28), (50, 30)),
            manager=manager,
            container=self,
        )

        # endregion

        self.record = ""
        self.size = size
        self.udp = udp
        self.username = username

    def process_event(self, event: Event) -> Union[bool, None]:
        # region Docstring
        """
        Overridden method to handle the gui events

        ### Arguments
            `event {Event}`:
                `summary`: the fired event

        ### Returns
            `bool | None`: return if the event has been handled
        """
        # endregion

        handled = super().process_event(event)
        if event.type != USEREVENT:
            return

        if (event.user_type == UI_TEXT_ENTRY_FINISHED and event.ui_element
                == self.entryline) or (event.user_type == UI_BUTTON_PRESSED
                                       and event.ui_element == self.enterbtn):
            self.__send()
            handled = True

        return handled

    def __send(self) -> None:
        # region Docstring
        """
        Handles the press of the enter `UIButton`, sending the user message.\n
        """
        # endregion

        if len(self.entryline.get_text().strip()) > 0:
            self.udp.transmission("CHA", "01", self.username,
                                  self.entryline.get_text().strip())
            self.__addmsg(
                f"<b>(YOU): </b><br>{self.entryline.get_text().strip()}<br>")
            self.entryline.set_text("")

    def receive(self, data: Packet, addr: Tuple[str, int],
                time: datetime) -> None:
        # region Docstring
        """
        Method that handles the received data, address and time of reception

        ### Arguments
            `data {Packet}`:
                `summary`: the data received
            `addr {Tuple[str, int]}`:
                `summary`: the source address and port
                example: (192.168.0.1, 6000)
            `time {datetime}`:
                `summary`: the time of the packet reception
        """
        # endregion

        n = UDP_P2P.latency(
            datetime.strptime(time.strftime("%H%M%S%f"), "%H%M%S%f"),
            datetime.strptime(data.time + "000", "%H%M%S%f"),
        )

        self.record += f"<b>({data.nick.strip()} - {addr[0]} - {n}ms): </b><br>{data.msg.strip()}<br>"

    def update(self, time_delta: float) -> None:
        # region Docstring
        """
        Overridden method to update the element

        ### Arguments
            `time_delta {float}`:
                `summary`: the time passed between frames, measured in seconds.
        """
        # endregion

        super().update(time_delta)
        if self.record != self.textbox.html_text:
            self.textbox.kill()
            self.textbox = UITextBox(
                html_text=self.record,
                relative_rect=Rect((0, 0), (self.size[0], self.size[1] - 25)),
                container=self,
                manager=self.ui_manager,
            )

    def __addmsg(self, msg: str) -> None:
        # region Docstring
        """
        Method to insert text in the `UITextBox` element

        ### Arguments
            `msg {str}`:
                `summary`: the text to insert
        """
        # endregion
        self.record += msg
        self.textbox.kill()
        self.textbox = UITextBox(
            html_text=self.record,
            relative_rect=Rect((0, 0), (self.size[0], self.size[1] - 25)),
            container=self,
            manager=self.ui_manager,
        )
Ejemplo n.º 6
0
class UIConsoleWindow(UIWindow):
    """
    Create a basic console window. By default it doesn't do anything except log
    commands entered into the console in the text box log. There is an event
    and a few methods however that allow you to hook this console window up
    to do whatever you would like with the entered text commands.

    See the pygame GUI examples repository for an example using it to run the
    Python Interactive Shell.

    :param rect: A rect determining the size and position of the console window.
    :param manager: The UI manager.
    :param window_title: The title displayed in the windows title bar.
    :param object_id: The object ID for the window, used for theming - defaults to
                      '#console_window'
    :param visible: Whether the element is visible by default.
    """
    def __init__(self,
                 rect: pygame.Rect,
                 manager: IUIManagerInterface,
                 window_title: str = 'pygame-gui.console_title_bar',
                 object_id: Union[ObjectID,
                                  str] = ObjectID('#console_window', None),
                 visible: int = 1,
                 preload_bold_log_font: bool = True):
        super().__init__(rect,
                         manager,
                         window_display_title=window_title,
                         object_id=object_id,
                         resizable=True,
                         visible=visible)

        self.default_log_prefix = '> '
        self.log_prefix = self.default_log_prefix

        self.should_logged_commands_escape_html = True

        self.logged_commands_above = []
        self.current_logged_command = None
        self.logged_commands_below = []

        self.command_entry = UITextEntryLine(relative_rect=pygame.rect.Rect(
            (2, -32), (self.get_container().get_size()[0] - 4, 30)),
                                             manager=self.ui_manager,
                                             container=self,
                                             object_id='#command_entry',
                                             anchors={
                                                 'left': 'left',
                                                 'right': 'right',
                                                 'top': 'bottom',
                                                 'bottom': 'bottom'
                                             })

        self.log = UITextBox(html_text="",
                             relative_rect=pygame.rect.Rect(
                                 (2, 2),
                                 (self.get_container().get_size()[0] - 4,
                                  self.get_container().get_size()[1] - 36)),
                             manager=manager,
                             container=self,
                             object_id='#log',
                             anchors={
                                 'left': 'left',
                                 'right': 'right',
                                 'top': 'top',
                                 'bottom': 'bottom'
                             })

        if preload_bold_log_font:
            # Would be better to load this font during UIManager setup, but this is probably
            # second best place. We can't know if someone will use the console window in advance
            # but if they do and use the default bold output setup for it, it would be a good idea
            # to load the bold font at th the same time th other UI stuff is loaded.

            log_font_info = self.ui_theme.get_font_info(
                self.log.combined_element_ids)
            font_dict = self.ui_manager.get_theme().get_font_dictionary()
            bold_font_id = font_dict.create_font_id(log_font_info['size'],
                                                    log_font_info['name'],
                                                    bold=True,
                                                    italic=False)
            if not font_dict.check_font_preloaded(bold_font_id):
                font_dict.preload_font(log_font_info['size'],
                                       log_font_info['name'],
                                       bold=True)

    def set_log_prefix(self, prefix: str) -> None:
        """
        Set the prefix to add before commands when they are displayed in the log.
        This defaults to '> '.

        :param prefix: A string that is prepended to commands before they are added to the
                       log text box.
        """
        self.log_prefix = prefix

    def restore_default_prefix(self) -> None:
        """
        Restore the console log prefix to it's default value (which is: '> ')
        """
        self.log_prefix = self.default_log_prefix

    def set_logged_commands_escape_html(self, should_escape: bool) -> None:
        """
        Sets whether commands should have any HTML characters escaped before being added
        to the log. This is because the log uses an HTML Text box and most of the time we don't
        want to assume that every entered > or < is part of an HTML tag.

        By default HTML is escaped for commands added to the log.

        :param should_escape: A bool to switch escaping HTML on commands on or off.
        """
        self.should_logged_commands_escape_html = should_escape

    def add_output_line_to_log(self,
                               text_to_add: str,
                               is_bold: bool = True,
                               remove_line_break: bool = False,
                               escape_html: bool = True) -> None:
        """
        Adds a single line of text to the log text box. This is intended as a hook to add
        output/responses to commands entered into the console.

        :param text_to_add: The single line of text to add to the log.
        :param is_bold: Determines whether the output is shown in bold or not. Defaults to True.
        :param remove_line_break: Set this to True to remove the automatic line break at the end
                                  of the line of text. Sometimes you might want to add some output
                                  all on a single line (e.g. a console style 'progress bar')
        :param escape_html:  Determines whether to escape any HTML in this line of text. Defaults
                             to True, as most people won't be expecting every > or < to be
                             processed as HTML.
        """
        output_to_log = html.escape(
            text_to_add) if escape_html else text_to_add
        line_ending = '' if remove_line_break else '<br>'
        if is_bold:
            self.log.append_html_text('<b>' + output_to_log + '</b>' +
                                      line_ending)
        else:
            self.log.append_html_text(output_to_log + line_ending)

    def process_event(self, event: pygame.event.Event) -> bool:
        """
        See if we need to handle an event passed down by the UI manager.
        Returns True if the console window dealt with this event.

        :param event: The event to handle
        """
        handled = super().process_event(event)

        if (self.command_entry.is_focused and event.type == pygame.KEYDOWN):
            if event.key == pygame.K_DOWN:
                if len(self.logged_commands_below) > 0:
                    popped_command = self.logged_commands_below.pop()
                    if self.current_logged_command is not None:
                        self.logged_commands_above.append(
                            self.current_logged_command)
                    self.current_logged_command = popped_command
                    self.command_entry.set_text(self.current_logged_command)
            elif event.key == pygame.K_UP:
                if len(self.logged_commands_above) > 0:
                    popped_command = self.logged_commands_above.pop()
                    if self.current_logged_command is not None:
                        self.logged_commands_below.append(
                            self.current_logged_command)
                    self.current_logged_command = popped_command
                    self.command_entry.set_text(self.current_logged_command)

        if event.type == UI_TEXT_ENTRY_FINISHED and event.ui_element == self.command_entry:
            handled = True
            command = self.command_entry.get_text()
            command_for_log = command
            self._restore_command_log_to_end()
            self.logged_commands_above.append(command_for_log)
            if self.should_logged_commands_escape_html:
                command_for_log = html.escape(command_for_log)
            self.log.append_html_text(self.log_prefix + command_for_log +
                                      "<br>")
            self.command_entry.set_text("")

            event_data = {
                'command': command,
                'ui_element': self,
                'ui_object_id': self.most_specific_combined_id
            }
            command_entered_event = pygame.event.Event(
                UI_CONSOLE_COMMAND_ENTERED, event_data)
            pygame.event.post(command_entered_event)

        if event.type == UI_TEXT_ENTRY_CHANGED and event.ui_element == self.command_entry:
            self._restore_command_log_to_end()

        return handled

    def _restore_command_log_to_end(self):
        if self.current_logged_command is not None:
            self.logged_commands_above.append(self.current_logged_command)
        self.current_logged_command = None
        while len(self.logged_commands_below) > 0:
            self.logged_commands_above.append(self.logged_commands_below.pop())
Ejemplo n.º 7
0
class SpeakSpell(pygame_gui.elements.UIWindow):
    speakthrd = None

    def __init__(self, pos, manager):
        super().__init__(
            pygame.Rect(pos, (400, 200)),
            manager=manager,
            window_display_title="speaknspell",
            object_id="#speaknspell",
            resizable=True,
        )

        self.box = UITextBox(
            "",
            relative_rect=pygame.Rect(0, 0, 368, 100),
            manager=manager,
            container=self,
            anchors={
                "left": "left",
                "right": "right",
                "top": "top",
                "bottom": "bottom",
            },
        )

        self.input = UITextEntryLine(
            relative_rect=pygame.Rect(0, -35, 368, 30),
            manager=manager,
            container=self,
            anchors={
                "left": "left",
                "right": "right",
                "top": "bottom",
                "bottom": "bottom",
            },
        )

        self.engine = pyttsx3.init()
        self.engine.setProperty("rate", 150)
        self.speakthrd = None

        self.speak("Hello, thank you for using snakeware!")
        self.input.focus()

    def speak(self, text):
        if self.speakthrd is not None and self.speakthrd.is_alive():
            return

        if text == "":
            return

        text = text.replace("\n", "<br>")

        spoken = re.sub(r"<(.*?)>", "", text)
        self.engine.say(spoken)
        self.speakthrd = threading.Thread(target=self.engine.runAndWait, args=())
        self.speakthrd.start()
        self.box.html_text = text
        self.box.rebuild()
        self.input.set_text("")

    def process_event(self, event):
        super().process_event(event)
        if event.type == pygame.USEREVENT and event.ui_element == self.input:
            if event.user_type == pygame_gui.UI_TEXT_ENTRY_FINISHED:
                self.speak(self.input.get_text())
Ejemplo n.º 8
0
class SpeakSpell(pygame_gui.elements.UIWindow):
    speakthrd = None

    def __init__(self, pos, manager):
        super().__init__(
            pygame.Rect(pos, (400, 200)),
            manager=manager,
            window_display_title="speaknspell",
            object_id="#speaknspell",
            resizable=True,
        )

        self.box = UITextBox(
            "",
            relative_rect=pygame.Rect(0, 0, 368, 100),
            manager=manager,
            container=self,
            anchors={
                "left": "left",
                "right": "right",
                "top": "top",
                "bottom": "bottom",
            },
        )

        self.input = UITextEntryLine(
            relative_rect=pygame.Rect(0, -35, 368, 30),
            manager=manager,
            container=self,
            anchors={
                "left": "left",
                "right": "right",
                "top": "bottom",
                "bottom": "bottom",
            },
        )

        self.engine = pyttsx3.init()
        self.engine.setProperty("rate", 150)
        self.speakthrd = None

        self.speak("Hello, thank you for using snakeware!")
        self.input.focus()

        # history attributes
        self.histsize = 100
        self.histindex = -1
        self.history = ["Hello, thank you for using snakeware!"]
        self.cached_command = ""

    def speak(self, text):
        if self.speakthrd is not None and self.speakthrd.is_alive():
            return

        if text == "":
            return

        text = re.sub(r"(\\r)?\\n", "<br>", text)
        spoken = re.sub(r"<(.*?)>", "", text)
        self.engine.say(spoken)
        self.speakthrd = threading.Thread(target=self.engine.runAndWait,
                                          args=())
        self.speakthrd.start()
        self.box.html_text = text
        self.box.rebuild()
        self.input.set_text("")

    def cache_command(self):
        self.cached_command = self.input.get_text()

    def flush_command_cache(self):
        self.cached_command = ""

    def set_histindex(self, increment):
        try:
            # self.history[self.histindex + increment]
            self.histindex += increment
        except IndexError:
            pass
        return self.histindex

    def set_from_history(self):
        if self.histindex > -1:
            self.input.set_text(self.history[self.histindex])
        else:
            self.input.set_text(self.cached_command)
        self.input.edit_position = len(self.input.get_text())

    def add_to_history(self, text):
        self.history = [text] + self.history
        if len(self.history) > self.histsize:
            del self.history[-1]

    def process_event(self, event):
        super().process_event(event)
        if event.type == pygame.USEREVENT and event.ui_element == self.input:
            if event.user_type == pygame_gui.UI_TEXT_ENTRY_FINISHED:
                text = self.input.get_text()
                self.add_to_history(text)
                self.histindex = -1
                self.flush_command_cache()
                self.speak(text)
        elif event.type == pygame.KEYUP and event.key in (pygame.K_UP,
                                                          pygame.K_DOWN):
            increment = 1 if event.key == pygame.K_UP else -1
            if self.histindex == -1:
                self.cache_command()
            self.set_histindex(increment)
            self.set_from_history()