Example #1
0
 def _render_child(self,
                   screen: DOMScreen,
                   child_element: DOMElement,
                   child_position: int,
                   child_desired_size: DOMSize,
                   force: bool = False) -> int:
     length = min(
         child_desired_size[self.orientation] + min(child_position, 0),
         screen.size[self.orientation] - max(child_position, 0))
     self._children_positions.append(child_position -
                                     screen.offset[self.orientation])
     screen_size = list(screen.size)
     screen_size[self.orientation] = max(length, 0)
     position = list(screen.position)
     # Min is for making sure the position does not overflow the screen
     position[self.orientation] = min(
         # The max is for making the position does not underflow the screen
         screen.position[self.orientation] + max(child_position, 0),
         screen.size[self.orientation] + screen.position[self.orientation])
     offset = list(screen.offset)
     offset[self.orientation] = min(child_position, 0)
     offset[self.opposite_orientation] = screen.offset[
         self.opposite_orientation]
     child_screen = DOMScreen(
         size=(max(
             0, screen_size[0] - child_element.style.margin.left -
             child_element.style.margin.right),
               max(
                   0, screen_size[1] - child_element.style.margin.top -
                   child_element.style.margin.bottom)),
         position=(position[0] + child_element.style.margin.left,
                   position[1] + child_element.style.margin.top),
         offset=(offset[0], offset[1]))
     # Clear the margins
     child_element._render(child_screen, force)
     if child_element.is_displayed():
         left_screen = DOMScreen(size=(child_element.style.margin.left,
                                       screen_size[1]),
                                 position=(position[0], position[1]))
         right_screen = DOMScreen(
             size=(child_element.style.margin.right, screen_size[1]),
             position=(position[0] + screen_size[0] -
                       child_element.style.margin.right, position[1]))
         top_screen = DOMScreen(size=(screen_size[1],
                                      child_element.style.margin.top),
                                position=(position[0], position[1]))
         bottom_screen = DOMScreen(
             size=(screen_size[1], child_element.style.margin.bottom),
             position=(position[0], position[1] + screen_size[1] -
                       child_element.style.margin.bottom))
         self._clear(left_screen)
         self._clear(right_screen)
         self._clear(top_screen)
         self._clear(bottom_screen)
     return child_desired_size[self.orientation]
Example #2
0
    def _ensure_focus_visible(self, index: int):
        if not self._render_screen:
            return
        if index == self._focused_child_index:
            return

        self._focused_child_index = index
        child = self._children[index]
        child_desired_size = child.get_desired_size()
        top_max_offset = -self._children_positions[index]
        bottom_min_offset = self._render_screen.size[self.orientation] - \
            (self._children_positions[index] + child_desired_size[self.orientation])

        offset = None
        self.logger.debug("Child screen: %s" % child._render_screen)
        self.logger.debug(
            "Setting selection from %s to %s. Bottom min: %s, Top max %s" %
            (self._focused_child_index, index, bottom_min_offset,
             top_max_offset))
        if bottom_min_offset < self._render_screen.offset[self.orientation]:
            offset = list(self._render_screen.offset)
            offset[self.orientation] = bottom_min_offset
        elif top_max_offset > self._render_screen.offset[self.orientation]:
            offset = list(self._render_screen.offset)
            offset[self.orientation] = top_max_offset

        if offset is not None:
            self.logger.debug("Updating the offset to scroll. Offset: %s, %s" %
                              (offset[0], offset[1]))
            self._render(
                DOMScreen(self._render_screen.size,
                          self._render_screen.position,
                          (offset[0], offset[1])))
            self.window.screen.refresh()
Example #3
0
 def _render_move(self, screen_offset: Tuple[int, int]):
     self.logger.debug(
         f"The screen offset was {self._render_screen.offset}. New offset: {screen_offset}"
     )
     self._render(
         DOMScreen(self._render_screen.size, self._render_screen.position,
                   (screen_offset[0], screen_offset[1])))
     self.window.screen.refresh()
Example #4
0
    def _render(self, screen: DOMScreen, force: bool = False):
        if not self._should_render(screen,
                                   force) or not self._can_display(screen):
            super()._render(screen)
            return

        for i in range(0, screen.height()):
            line_index = i - screen.offset[VERTICAL]
            value_start = line_index * screen.width()
            line_length = 0
            if line_index == 0:
                self._render_static_prefix(screen)
                value_end = value_start + screen.width(
                ) - self._static_prefix_length
                line_length += self._static_prefix_length
            else:
                value_start -= self._static_prefix_length
                value_end = value_start + screen.width()
            value = self._value[value_start:value_end]
            line_length += len(value)
            self.window.screen.print_at(
                value,
                screen.position[HORIZONTAL] if line_index != 0 else
                screen.position[HORIZONTAL] + self._static_prefix_length,
                screen.position[VERTICAL] + i,
                colour=self._computed_style.color.value
                if self._computed_style.color else Color.WHITE.value,
                bg=self._computed_style.background.value
                if self._computed_style.background else Color.BLACK.value)
            padding_length = screen.width() - line_length
            self.window.screen.print_at(
                " " * padding_length,
                screen.position[HORIZONTAL] + line_length,
                screen.position[VERTICAL] + i,
            )
        self._render_value = self._value
        self._render_full_size = self._desired_size
        super()._render(screen)
Example #5
0
    def _render(self, screen: DOMScreen, force: bool = False):
        if not self._should_render(screen,
                                   force) or not self._can_display(screen):
            super()._render(screen)
            return
        if self._render_full_size is not None and self._render_full_size != self._desired_size:
            # The desired size changed because the parent is granting more/less room
            self._value_offset = self._calculate_offset(0, self._desired_size)

        # TODO: Handle a resize event that causes the cursor to be hidden
        # Each "pixel" on the screen has to be filled up
        if self._is_short_scroll():
            display_character_count = screen.width()
        else:
            display_character_count = screen.height() * screen.width()
        is_truncated_start = self._value_offset != 0

        if is_truncated_start and not self._is_short_scroll():
            # The static prefix does not affect the number of available characters since it takes
            # up a whole line
            is_truncated_end = len(
                self._value) - self._value_offset > display_character_count
        else:
            is_truncated_end = len(self._value) - self._value_offset > \
                display_character_count - self._get_static_prefix_size()

        self._render_static_prefix(screen)

        cursor_position = self._cursor_position - self._value_offset
        if not is_truncated_start or self._is_short_scroll():
            cursor_position += self._get_static_prefix_size()
        cursor_line_index = cursor_position // screen.width()

        for vertical_offset in range(0, screen.height()):
            is_first_line = vertical_offset == 0
            is_last_line = vertical_offset == self._desired_size[VERTICAL] - 1 or \
                (
                    self._desired_size[VERTICAL] == 2 and
                    vertical_offset == self._desired_size[VERTICAL] - 2
                )

            if not self._is_short_scroll(
            ) and is_truncated_start and is_first_line:
                self.window.screen.print_at(
                    ">...." + " " *
                    (screen.width() - 5 - self._get_static_prefix_size()),
                    screen.position[HORIZONTAL] +
                    self._get_static_prefix_size(),
                    screen.position[VERTICAL],
                    colour=self._computed_style.color.value
                    if self._computed_style.color else Color.WHITE.value,
                    bg=self._computed_style.background.value
                    if self._computed_style.background else Color.BLACK.value)
                continue

            line_index = vertical_offset - screen.offset[VERTICAL]
            value_start_index = line_index * screen.width() + self._value_offset
            if not is_truncated_start or self._is_short_scroll():
                if is_first_line:
                    value_end_index = value_start_index + screen.width() \
                        - self._get_static_prefix_size()
                else:
                    value_start_index -= self._get_static_prefix_size()
                    value_end_index = value_start_index + screen.width()
            else:
                value_end_index = value_start_index + screen.width()

            # Determine the sub-string of the input value to be printed up to the cursor.
            if line_index != cursor_line_index:
                value = self._value[value_start_index:value_end_index]
                line_value_length = len(value)
            else:
                value = self._value[value_start_index:self._cursor_position]
                line_value_length = len(
                    self._value[value_start_index:value_end_index])
            # This can happen when there are 2 lines available for the short scroll
            if self._is_short_scroll() and not is_first_line:
                value = ""
                line_value_length = 0

            if is_first_line:
                if is_truncated_start:
                    # The value is truncated at the beginning
                    value = "<" + value[1:]
                line_value_length += self._get_static_prefix_size()

            self.window.screen.print_at(
                value,
                screen.position[HORIZONTAL] if not is_first_line else
                screen.position[HORIZONTAL] + self._get_static_prefix_size(),
                screen.position[VERTICAL] + vertical_offset,
                colour=self._computed_style.color.value
                if self._computed_style.color else Color.WHITE.value,
                bg=self._computed_style.background.value
                if self._computed_style.background else Color.BLACK.value)
            # Render the cursor
            if line_index == cursor_line_index:
                # Setup the cursor background
                if self.focused:
                    cursor_background = Color.WHITE.value
                else:
                    cursor_background = Color.GREY_37.value

                # Pick the character under the cursor and update the value length if it's an added
                # character at the end of the line
                if self._cursor_position == len(self._value):
                    cursor_char = " "
                    line_value_length += 1
                else:
                    cursor_char = self._value[self._cursor_position]

                cursor_index = cursor_position % screen.width()
                self.window.screen.print_at(
                    cursor_char,
                    screen.position[HORIZONTAL] + cursor_index,
                    screen.position[VERTICAL] + vertical_offset,
                    bg=cursor_background)
                self.window.screen.print_at(
                    self._value[self._cursor_position + 1:value_end_index],
                    screen.position[HORIZONTAL] + cursor_index + 1,
                    screen.position[VERTICAL] + vertical_offset,
                    colour=self._computed_style.color.value
                    if self._computed_style.color else Color.WHITE.value,
                    bg=self._computed_style.background.value
                    if self._computed_style.background else Color.BLACK.value)

            if is_truncated_end and is_last_line:
                # Add the character to indicate there is more text
                if self._is_short_scroll():
                    end_char = ">"
                else:
                    end_char = "<...."

                self.window.screen.print_at(
                    end_char,
                    screen.position[HORIZONTAL] + line_value_length -
                    len(end_char),
                    screen.position[VERTICAL] + vertical_offset,
                    colour=self._computed_style.color.value
                    if self._computed_style.color else Color.WHITE.value,
                    bg=self._computed_style.background.value
                    if self._computed_style.background else Color.BLACK.value)
            else:
                # Pad with spaces to get rid of lingering characters
                padding_size = screen.width() - line_value_length
                padding = " " * padding_size
                self.window.screen.print_at(
                    padding,
                    screen.position[HORIZONTAL] + line_value_length,
                    screen.position[VERTICAL] + vertical_offset,
                )

        # We'll take what we can size wise
        self._render_full_size = self._desired_size
        self._render_value = self._value
        self._render_cursor_position = self._cursor_position
        super()._render(screen)
Example #6
0
    def _render(self, screen: DOMScreen, force: bool = False):
        if not self._should_render(self._render_screen, force):
            super()._render(screen)
            return
        if not self._can_display(screen):
            # Let the children know that they are no longer being displayed so they can rerender
            # properly when the layout is displayed again
            for child in self._children:
                child._render(screen)
            super()._render(screen)
            return

        self._children_positions = []
        self.logger.debug("rendering on %s" % screen)
        length = 0
        full_length_widgets = 0
        last_full_length_widget = None
        children_desired_size = []
        for child in self._children:
            child_desired_size = child.get_desired_size(screen.size)
            # Account for the child's margin when computing sizes
            children_desired_size.append(child_desired_size)
            if child_desired_size[self.orientation] is FULL_LENGTH:
                full_length_widgets += 1
                last_full_length_widget = child
            else:
                length += child_desired_size[self.orientation]
        total_shared_length = max(screen.size[self.orientation] - length, 0)
        shared_length = int(total_shared_length / (full_length_widgets or 1))
        extra_shared_length = total_shared_length - shared_length * full_length_widgets

        if length + shared_length * full_length_widgets <= screen.size[
                self.orientation]:
            # The layout element can render all the children, reset the scroll offset
            screen.offset = list(screen.offset)
            screen.offset[self.orientation] = 0
            screen.offset = (screen.offset[0], screen.offset[1])

        child_position = screen.offset[self.orientation]

        render_size = [0, 0]
        for child, child_desired_size in zip(self._children,
                                             children_desired_size):
            if child_desired_size[self.orientation] is not FULL_LENGTH:
                child_position += self._render_child(screen, child,
                                                     child_position,
                                                     child_desired_size, force)
            else:
                new_size = list(child_desired_size)
                new_size[self.orientation] = shared_length
                if child is last_full_length_widget:
                    new_size[self.orientation] += extra_shared_length
                child_position += self._render_child(
                    screen, child, child_position, (new_size[0], new_size[1]),
                    force)
            # Handle the case in which the child was not rendered
            if child._render_full_size is not None:
                render_size[self.opposite_orientation] = max(
                    child._render_full_size[self.opposite_orientation],
                    render_size[self.opposite_orientation])

        if self._children:
            render_size[self.orientation] = child_position - screen.offset[
                self.orientation]

        clear_size = [0, 0]
        clear_size[self.orientation] = max(
            0, screen.size[self.orientation] - max(0, child_position))
        clear_size[self.opposite_orientation] = screen.size[
            self.opposite_orientation]
        clear_position = [0, 0]
        clear_position[
            self.orientation] = screen.position[self.orientation] + max(
                0, child_position)
        clear_position[self.opposite_orientation] = screen.position[
            self.opposite_orientation]
        clear_screen = DOMScreen(size=(clear_size[0], clear_size[1]),
                                 position=(clear_position[0],
                                           clear_position[1]))
        self._clear(clear_screen)
        self._render_full_size = render_size
        super()._render(screen)