ai-station/.venv/lib/python3.12/site-packages/textual/renderables/bar.py

141 lines
4.5 KiB
Python
Raw Normal View History

2025-12-25 14:54:33 +00:00
from __future__ import annotations
from rich.console import Console, ConsoleOptions, RenderResult
from rich.style import Style, StyleType
from rich.text import Text
from textual.color import Gradient
class Bar:
"""Thin horizontal bar with a portion highlighted.
Args:
highlight_range: The range to highlight.
highlight_style: The style of the highlighted range of the bar.
background_style: The style of the non-highlighted range(s) of the bar.
width: The width of the bar, or `None` to fill available width.
gradient: Optional gradient object.
"""
HALF_BAR_LEFT: str = ""
BAR: str = ""
HALF_BAR_RIGHT: str = ""
def __init__(
self,
highlight_range: tuple[float, float] = (0, 0),
highlight_style: StyleType = "magenta",
background_style: StyleType = "grey37",
clickable_ranges: dict[str, tuple[int, int]] | None = None,
width: int | None = None,
gradient: Gradient | None = None,
) -> None:
self.highlight_range = highlight_range
self.highlight_style = highlight_style
self.background_style = background_style
self.clickable_ranges = clickable_ranges or {}
self.width = width
self.gradient = gradient
def __rich_console__(
self, console: Console, options: ConsoleOptions
) -> RenderResult:
highlight_style = console.get_style(self.highlight_style)
background_style = console.get_style(self.background_style)
width = self.width or options.max_width
start, end = self.highlight_range
start = max(start, 0)
end = min(end, width)
output_bar = Text("", end="")
if start == end == 0 or end < 0 or start > end:
output_bar.append(Text(self.BAR * width, style=background_style, end=""))
yield output_bar
return
# Round start and end to nearest half
start = round(start * 2) / 2
end = round(end * 2) / 2
# Check if we start/end on a number that rounds to a .5
half_start = start - int(start) > 0
half_end = end - int(end) > 0
# Initial non-highlighted portion of bar
output_bar.append(
Text(self.BAR * (int(start - 0.5)), style=background_style, end="")
)
if not half_start and start > 0:
output_bar.append(Text(self.HALF_BAR_RIGHT, style=background_style, end=""))
highlight_bar = Text("", end="")
# The highlighted portion
bar_width = int(end) - int(start)
if half_start:
highlight_bar.append(
Text(
self.HALF_BAR_LEFT + self.BAR * (bar_width - 1),
style=highlight_style,
end="",
)
)
else:
highlight_bar.append(
Text(self.BAR * bar_width, style=highlight_style, end="")
)
if half_end:
highlight_bar.append(
Text(self.HALF_BAR_RIGHT, style=highlight_style, end="")
)
if self.gradient is not None:
_apply_gradient(highlight_bar, self.gradient, width)
output_bar.append(highlight_bar)
# The non-highlighted tail
if not half_end and end - width != 0:
output_bar.append(Text(self.HALF_BAR_LEFT, style=background_style, end=""))
output_bar.append(
Text(self.BAR * (int(width) - int(end) - 1), style=background_style, end="")
)
# Fire actions when certain ranges are clicked (e.g. for tabs)
for range_name, (start, end) in self.clickable_ranges.items():
output_bar.apply_meta(
{"@click": f"range_clicked('{range_name}')"}, start, end
)
yield output_bar
def _apply_gradient(text: Text, gradient: Gradient, width: int) -> None:
"""Apply a gradient to a Rich Text instance.
Args:
text: A Text object.
gradient: A Textual gradient.
width: Width of gradient.
"""
if not width:
return
assert width > 0
from_color = Style.from_color
get_rich_color = gradient.get_rich_color
max_width = width - 1
if not max_width:
text.stylize(from_color(gradient.get_color(0).rich_color))
return
text_length = len(text)
for offset in range(text_length):
bar_offset = text_length - offset
text.stylize(
from_color(get_rich_color(bar_offset / max_width)),
offset,
offset + 1,
)