diff --git a/src/textual/_border.py b/src/textual/_border.py index 53ed007e3..0921d8220 100644 --- a/src/textual/_border.py +++ b/src/textual/_border.py @@ -14,25 +14,55 @@ class Border: self, renderable: RenderableType, edge_styles: tuple[EdgeStyle, EdgeStyle, EdgeStyle, EdgeStyle], + outline: bool = False, ): self.renderable = renderable self.edge_styles = edge_styles + self.outline = outline - @property - def sides(self) -> tuple[str, str, str, str]: - (top, _), (right, _), (bottom, _), (left, _) = self.edge_styles - return (top or "none", right or "none", bottom or "none", left or "none") + ( + (top, top_style), + (right, right_style), + (bottom, bottom_style), + (left, left_style), + ) = edge_styles + self._sides = (top or "none", right or "none", bottom or "none", left or "none") + self._styles = (top_style, right_style, bottom_style, left_style) - @property - def styles(self) -> tuple[Style, Style, Style, Style]: - (_, top), (_, right), (_, left), (_, bottom) = self.edge_styles - return (top, right, left, bottom) + def _crop_renderable(self, lines: list[list[Segment]], width: int) -> None: + """Crops a renderable in place. + + Args: + lines (list[list[Segment]]): Segment lines. + width (int): Desired width. + """ + top, right, bottom, left = self._sides + has_left = left != "none" + has_right = right != "none" + has_top = top != "none" + has_bottom = bottom != "none" + + if has_top: + lines.pop(0) + if has_bottom: + lines.pop(-1) + + divide = Segment.divide + if has_left and has_right: + for line in lines: + _, line[:] = divide(line, [1, width - 1]) + elif has_left: + for line in lines: + _, line[:] = divide(line, [1, width]) + elif has_right: + for line in lines: + line[:], _ = divide(line, [width - 1, width]) def __rich_console__( self, console: "Console", options: "ConsoleOptions" ) -> "RenderResult": - top, right, bottom, left = self.sides - top_style, right_style, bottom_style, left_style = self.styles + top, right, bottom, left = self._sides + top_style, right_style, bottom_style, left_style = self._styles BOX = BOX_STYLES has_left = left != "none" @@ -41,13 +71,18 @@ class Border: has_bottom = bottom != "none" width = options.max_width - has_left - has_right - if options.height is None: - render_options = options.update_width(width) + if self.outline: + render_options = options else: - height = options.height - has_top - has_bottom - render_options = options.update_dimensions(width, height) + if options.height is None: + render_options = options.update_width(width) + else: + height = options.height - has_top - has_bottom + render_options = options.update_dimensions(width, height) lines = console.render_lines(self.renderable, render_options) + if self.outline: + self._crop_renderable(lines, options.max_width) _Segment = Segment new_line = _Segment.line() @@ -63,13 +98,12 @@ class Border: box_left = BOX[left][1][0] box_right = BOX[right][1][2] left_segment = _Segment(box_left, left_style) - right_segment = _Segment(box_right, right_style) + right_segment = _Segment(box_right + "\n", right_style) if has_left and has_right: for line in lines: yield left_segment yield from line yield right_segment - yield new_line elif has_left: for line in lines: yield left_segment @@ -79,7 +113,6 @@ class Border: for line in lines: yield from line yield right_segment - yield new_line else: for line in lines: yield from line @@ -100,14 +133,18 @@ if __name__ == "__main__": from rich.text import Text text = Text("Textual " * 40, style="dim") - print( - Border( - text, - ( - ("outer", Style.parse("green on red")), - ("none", Style.parse("green")), - ("double", Style.parse("green")), - ("double", Style.parse("green")), - ), - ) + border = Border( + text, + ( + ("outer", Style.parse("green")), + ("outer", Style.parse("green")), + ("outer", Style.parse("green")), + ("outer", Style.parse("green")), + ), ) + print(text) + print() + print(border) + print() + border.outline = True + print(border)