1from typing import Union
2
3from .color import Color
4from .console import Console, ConsoleOptions, RenderResult
5from .jupyter import JupyterMixin
6from .measure import Measurement
7from .segment import Segment
8from .style import Style
9
10# There are left-aligned characters for 1/8 to 7/8, but
11# the right-aligned characters exist only for 1/8 and 4/8.
12BEGIN_BLOCK_ELEMENTS = ["█", "█", "█", "▐", "▐", "▐", "▕", "▕"]
13END_BLOCK_ELEMENTS = [" ", "▏", "▎", "▍", "▌", "▋", "▊", "▉"]
14FULL_BLOCK = "█"
15
16
17class Bar(JupyterMixin):
18    """Renders a solid block bar.
19
20    Args:
21        size (float): Value for the end of the bar.
22        begin (float): Begin point (between 0 and size, inclusive).
23        end (float): End point (between 0 and size, inclusive).
24        width (int, optional): Width of the bar, or ``None`` for maximum width. Defaults to None.
25        color (Union[Color, str], optional): Color of the bar. Defaults to "default".
26        bgcolor (Union[Color, str], optional): Color of bar background. Defaults to "default".
27    """
28
29    def __init__(
30        self,
31        size: float,
32        begin: float,
33        end: float,
34        *,
35        width: int = None,
36        color: Union[Color, str] = "default",
37        bgcolor: Union[Color, str] = "default",
38    ):
39        self.size = size
40        self.begin = max(begin, 0)
41        self.end = min(end, size)
42        self.width = width
43        self.style = Style(color=color, bgcolor=bgcolor)
44
45    def __repr__(self) -> str:
46        return f"Bar({self.size}, {self.begin}, {self.end})"
47
48    def __rich_console__(
49        self, console: Console, options: ConsoleOptions
50    ) -> RenderResult:
51
52        width = min(
53            self.width if self.width is not None else options.max_width,
54            options.max_width,
55        )
56
57        if self.begin >= self.end:
58            yield Segment(" " * width, self.style)
59            yield Segment.line()
60            return
61
62        prefix_complete_eights = int(width * 8 * self.begin / self.size)
63        prefix_bar_count = prefix_complete_eights // 8
64        prefix_eights_count = prefix_complete_eights % 8
65
66        body_complete_eights = int(width * 8 * self.end / self.size)
67        body_bar_count = body_complete_eights // 8
68        body_eights_count = body_complete_eights % 8
69
70        # When start and end fall into the same cell, we ideally should render
71        # a symbol that's "center-aligned", but there is no good symbol in Unicode.
72        # In this case, we fall back to right-aligned block symbol for simplicity.
73
74        prefix = " " * prefix_bar_count
75        if prefix_eights_count:
76            prefix += BEGIN_BLOCK_ELEMENTS[prefix_eights_count]
77
78        body = FULL_BLOCK * body_bar_count
79        if body_eights_count:
80            body += END_BLOCK_ELEMENTS[body_eights_count]
81
82        suffix = " " * (width - len(body))
83
84        yield Segment(prefix + body[len(prefix) :] + suffix, self.style)
85        yield Segment.line()
86
87    def __rich_measure__(
88        self, console: Console, options: ConsoleOptions
89    ) -> Measurement:
90        return (
91            Measurement(self.width, self.width)
92            if self.width is not None
93            else Measurement(4, options.max_width)
94        )
95