126 lines
3.7 KiB
Python
126 lines
3.7 KiB
Python
from abc import ABC, abstractmethod
|
|
from typing import Any, Generator, Iterable, List, Type, TypeVar
|
|
from typing_extensions import Literal
|
|
|
|
from rich.color import Color
|
|
from rich.console import (
|
|
Console,
|
|
ConsoleOptions,
|
|
ConsoleRenderable,
|
|
RenderableType,
|
|
RenderResult,
|
|
)
|
|
from rich.segment import Segment
|
|
from rich.text import Text
|
|
|
|
|
|
from rich_toolkit.utils.colors import lighten
|
|
|
|
|
|
ConsoleRenderableClass = TypeVar(
|
|
"ConsoleRenderableClass", bound=Type[ConsoleRenderable]
|
|
)
|
|
|
|
|
|
ANIMATION_STATUS = Literal["started", "stopped", "error", "no_animation"]
|
|
|
|
|
|
class BaseStyle(ABC):
|
|
result_color: Color
|
|
decoration_size: int
|
|
|
|
def __init__(self) -> None:
|
|
self.padding = 2
|
|
self.cursor_offset = 0
|
|
|
|
self._animation_counter = 0
|
|
self.decoration_size = 2
|
|
|
|
def empty_line(self) -> Text:
|
|
return Text(" ")
|
|
|
|
def with_decoration(
|
|
self,
|
|
*renderables: RenderableType,
|
|
animation_status: ANIMATION_STATUS = "no_animation",
|
|
**metadata: Any,
|
|
) -> ConsoleRenderable:
|
|
class WithDecoration:
|
|
@staticmethod
|
|
def __rich_console__(
|
|
console: Console, options: ConsoleOptions
|
|
) -> RenderResult:
|
|
for content in renderables:
|
|
# our styles are potentially adding some paddings on the left
|
|
# and right sides, so we need to adjust the max_width to
|
|
# make sure that rich takes that into account
|
|
options = options.update(
|
|
max_width=console.width - self.decoration_size
|
|
)
|
|
|
|
lines = console.render_lines(content, options, pad=False)
|
|
|
|
for line in Segment.split_lines(
|
|
self.decorate(
|
|
lines=lines,
|
|
console=console,
|
|
animation_status=animation_status,
|
|
**metadata,
|
|
)
|
|
):
|
|
yield from line
|
|
yield Segment.line()
|
|
|
|
return WithDecoration()
|
|
|
|
def decorate_class(
|
|
self, klass: ConsoleRenderableClass, **metadata: Any
|
|
) -> ConsoleRenderableClass:
|
|
style = self
|
|
|
|
class Decorated(klass): # type: ignore[valid-type,misc]
|
|
def __rich_console__(
|
|
self, console: Console, options: ConsoleOptions
|
|
) -> RenderResult:
|
|
lines = Segment.split_lines(super().__rich_console__(console, options)) # type: ignore
|
|
|
|
yield from style.decorate(lines=lines, console=console, **metadata)
|
|
|
|
return Decorated # type: ignore
|
|
|
|
@abstractmethod
|
|
def decorate(
|
|
self,
|
|
console: Console,
|
|
lines: Iterable[List[Segment]],
|
|
animation_status: ANIMATION_STATUS = "no_animation",
|
|
**kwargs: Any,
|
|
) -> Generator[Segment, None, None]:
|
|
raise NotImplementedError()
|
|
|
|
def _get_animation_colors(
|
|
self,
|
|
console: Console,
|
|
steps: int = 5,
|
|
animation_status: ANIMATION_STATUS = "started",
|
|
**metadata: Any,
|
|
) -> List[Color]:
|
|
animated = animation_status == "started"
|
|
|
|
if animation_status == "error":
|
|
base_color = console.get_style("error").color
|
|
|
|
if base_color is None:
|
|
base_color = Color.parse("red")
|
|
|
|
else:
|
|
base_color = console.get_style("progress").bgcolor
|
|
|
|
if not base_color:
|
|
base_color = Color.parse("white")
|
|
|
|
if animated and base_color.triplet is not None:
|
|
return [lighten(base_color, 0.1 * i) for i in range(0, steps)]
|
|
|
|
return [base_color] * steps
|