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]
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()
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()
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)
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)
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)