Skip to content

Commit

Permalink
text align
Browse files Browse the repository at this point in the history
  • Loading branch information
willmcgugan committed Dec 24, 2024
1 parent ae5c4a0 commit 9dd32eb
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 42 deletions.
37 changes: 30 additions & 7 deletions src/textual/content.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,9 +284,11 @@ def render_strips(

else:
selection_style = None

align = self._align
lines = self.wrap(
width,
align=self._align,
align=align,
overflow=(
("ellipsis" if self._ellipsis else "crop") if self._no_wrap else "fold"
),
Expand All @@ -299,7 +301,17 @@ def render_strips(
if height is not None:
lines = lines[:height]

return [Strip(line.render_segments(style), line.cell_length) for line in lines]
strip_lines = [
Strip(line.render_segments(style), line.cell_length) for line in lines
]

print("--")
print(width, [line.cell_length for line in strip_lines])
if align in ("left", "right", "center"):
strip_lines = [strip.text_align(width, align) for strip in strip_lines]
print(width, [line.cell_length for line in strip_lines])

return strip_lines

def get_height(self, width: int) -> int:
lines = self.wrap(width)
Expand Down Expand Up @@ -559,6 +571,8 @@ def pad_left(self, count: int, character: str = " ") -> Content:
text,
spans,
None if self._cell_length is None else self._cell_length + count,
x=self._x - count,
y=self._y,
)
return self

Expand All @@ -582,6 +596,8 @@ def extend_right(self, count: int, character: str = " ") -> Content:
for span in self._spans
],
None if self._cell_length is None else self._cell_length + count,
x=self._x,
y=self._y,
)
return self

Expand All @@ -598,6 +614,8 @@ def pad_right(self, count: int, character: str = " ") -> Content:
f"{self.plain}{character * count}",
self._spans,
None if self._cell_length is None else self._cell_length + count,
x=self._x,
y=self._y,
)
return self

Expand All @@ -614,8 +632,8 @@ def center(self, width: int, ellipsis: bool = False) -> Content:
content = self.rstrip().truncate(
width, overflow="ellipsis" if ellipsis else "fold"
)
left = (content.cell_length - width) // 2
right = width = left
left = (width - content.cell_length) // 2
right = width - left
content = content.pad_left(left).pad_right(right)
return content

Expand Down Expand Up @@ -799,7 +817,9 @@ def render_segments(self, base_style: Style, end: str = "") -> list[Segment]:
return segments

def divide(
self, offsets: Sequence[int], axis: Literal["x", "y"] | None = "y"
self,
offsets: Sequence[int],
axis: Literal["x", "y"] | None = "y",
) -> list[Content]:
if not offsets:
return [self]
Expand Down Expand Up @@ -994,7 +1014,7 @@ def expand_tabs(self, tab_size: int = 8) -> Content:
def wrap(
self,
width: int,
align: TextAlign = "left",
align: TextAlign = "center",
overflow: OverflowMethod = "fold",
no_wrap: bool = False,
tab_size: int = 8,
Expand Down Expand Up @@ -1027,7 +1047,10 @@ def get_span(y: int) -> tuple[int, int] | None:
new_lines = line.divide(offsets, axis="x")

new_lines = [line.rstrip_end(width) for line in new_lines]
new_lines = _align_lines(new_lines, width, align=align, overflow=overflow)
if align in ("left", "full"):
new_lines = _align_lines(
new_lines, width, align=align, overflow=overflow
)
new_lines = [line.truncate(width, overflow=overflow) for line in new_lines]
lines.extend(new_lines)

Expand Down
6 changes: 2 additions & 4 deletions src/textual/screen.py
Original file line number Diff line number Diff line change
Expand Up @@ -1509,7 +1509,7 @@ def _watch__select_end(
return

select_start, select_end = sorted(
[select_start, select_end], key=lambda selection: selection[1].y
[select_start, select_end], key=lambda selection: (selection[1].transpose)
)

start_widget, screen_start, start_offset = select_start
Expand Down Expand Up @@ -1578,9 +1578,7 @@ def _watch__select_end(
if region.overlaps(widget.content_region)
]
highlighted_widgets.update(covered_widgets)
highlighted_widgets.discard(start_widget)
highlighted_widgets.discard(end_widget)
highlighted_widgets.discard(self)
highlighted_widgets -= {self, start_widget, end_widget}

select_all = SELECT_ALL
self.selections = {
Expand Down
57 changes: 46 additions & 11 deletions src/textual/strip.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ def align(
strips: list[Strip],
style: Style,
width: int,
height: int,
height: int | None,
horizontal: AlignHorizontal,
vertical: AlignVertical,
) -> Iterable[Strip]:
Expand Down Expand Up @@ -202,18 +202,19 @@ def blank_lines(count: int) -> Iterable[Strip]:
yield blank

top_blank_lines = bottom_blank_lines = 0
vertical_excess_space = max(0, height - shape_height)
if height is not None:
vertical_excess_space = max(0, height - shape_height)

if vertical == "top":
bottom_blank_lines = vertical_excess_space
elif vertical == "middle":
top_blank_lines = vertical_excess_space // 2
bottom_blank_lines = vertical_excess_space - top_blank_lines
elif vertical == "bottom":
top_blank_lines = vertical_excess_space
if vertical == "top":
bottom_blank_lines = vertical_excess_space
elif vertical == "middle":
top_blank_lines = vertical_excess_space // 2
bottom_blank_lines = vertical_excess_space - top_blank_lines
elif vertical == "bottom":
top_blank_lines = vertical_excess_space

if top_blank_lines:
yield from blank_lines(top_blank_lines)
if top_blank_lines:
yield from blank_lines(top_blank_lines)

if horizontal == "left":
for strip in strips:
Expand Down Expand Up @@ -609,3 +610,37 @@ def crop_pad(self, cell_length: int, left: int, right: int, style: Style) -> Str
if right:
segments.append(Segment(" " * right, style))
return Strip(segments, cell_length + left + right)

def text_align(self, width: int, align: AlignHorizontal) -> Strip:
if align == "left":
if self.cell_length == width:
return self
else:
return Strip(
line_pad(self._segments, 0, width - self.cell_length, Style.null()),
width,
)
elif align == "center":
left_space = max(0, width - self.cell_length) // 2

if self.cell_length == width:
return self
else:
return Strip(
line_pad(
self._segments,
left_space,
width - self.cell_length - left_space,
Style.null(),
),
width,
)

elif align == "right":
if self.cell_length == width:
return self
else:
return Strip(
line_pad(self._segments, width - self.cell_length, 0, Style.null()),
width,
)
40 changes: 28 additions & 12 deletions src/textual/visual.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
from textual._context import active_app
from textual.color import TRANSPARENT, Color
from textual.css.styles import StylesBase
from textual.css.types import AlignHorizontal, AlignVertical
from textual.geometry import Spacing
from textual.render import measure
from textual.strip import Strip
Expand Down Expand Up @@ -85,13 +84,10 @@ def visualize(widget: Widget, obj: object) -> Visual:
if is_renderable(obj):
# If it is a string, render it to Text
if isinstance(obj, str):
# obj = widget.render_str(obj)
obj = widget.render_str(obj)

print(obj)

return Content(obj)
if isinstance(obj, Text):
return Content.from_rich_text(obj)
if isinstance(obj, Text) and widget.ALLOW_SELECT:
return Content.from_rich_text(obj, align=widget.styles.text_align)

# If its is a Rich renderable, wrap it with a RichVisual
return RichVisual(widget, rich_cast(obj))
Expand Down Expand Up @@ -276,7 +272,11 @@ class Visual(ABC):

@abstractmethod
def render_strips(
self, widget: Widget, width: int, height: int | None, style: Style
self,
widget: Widget,
width: int,
height: int | None,
style: Style,
) -> list[Strip]:
"""Render the visual in to an iterable of strips.
Expand Down Expand Up @@ -320,7 +320,6 @@ def to_strips(
style: Style,
*,
pad: bool = False,
align: tuple[AlignHorizontal, AlignVertical] = ("left", "top"),
) -> list[Strip]:
"""High level function to render a visual to strips.
Expand All @@ -331,7 +330,6 @@ def to_strips(
height: Desired height (in lines) or `None` for no limit.
style: A (Visual) Style instance.
pad: Pad to desired width?
align: Tuple of horizontal and vertical alignment.
Returns:
A list of Strips containing the render.
Expand All @@ -342,8 +340,9 @@ def to_strips(
rich_style = style.rich_style
if pad:
strips = [strip.extend_cell_length(width, rich_style) for strip in strips]
if align != ("left", "top"):
align_horizontal, align_vertical = align
content_align = widget.styles.content_align
if content_align != ("left", "top"):
align_horizontal, align_vertical = content_align
strips = list(
Strip.align(
strips,
Expand Down Expand Up @@ -440,6 +439,7 @@ def render_strips(
height,
)
]

return strips


Expand Down Expand Up @@ -500,3 +500,19 @@ def render_strips(
]

return strips


def pick_bool(*values: bool | None) -> bool:
"""Pick the first non-none bool or return the last value.
Args:
*values (bool): Any number of boolean or None values.
Returns:
bool: First non-none boolean.
"""
assert values, "1 or more values required"
for value in values:
if value is not None:
return value
return bool(value)
9 changes: 1 addition & 8 deletions src/textual/widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -3799,14 +3799,7 @@ def _render_content(self) -> None:
"""Render all lines."""
width, height = self.size
visual = self._render()
strips = Visual.to_strips(
self,
visual,
width,
height,
self.visual_style,
align=self.styles.content_align,
)
strips = Visual.to_strips(self, visual, width, height, self.visual_style)
self._render_cache = _RenderCache(self.size, strips)
self._dirty_regions.clear()

Expand Down

0 comments on commit 9dd32eb

Please sign in to comment.