Skip to content

feat(bar): Add support for custom icons #5963

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 18 additions & 12 deletions src/textual/renderables/bar.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ class Bar:
gradient: Optional gradient object.
"""

HALF_BAR_LEFT: str = "╺"
BAR: str = "━"
HALF_BAR_RIGHT: str = "╸"

def __init__(
self,
highlight_range: tuple[float, float] = (0, 0),
Expand All @@ -40,10 +44,6 @@ def __rich_console__(
highlight_style = console.get_style(self.highlight_style)
background_style = console.get_style(self.background_style)

half_bar_right = "╸"
half_bar_left = "╺"
bar = "━"

width = self.width or options.max_width
start, end = self.highlight_range

Expand All @@ -53,7 +53,7 @@ def __rich_console__(
output_bar = Text("", end="")

if start == end == 0 or end < 0 or start > end:
output_bar.append(Text(bar * width, style=background_style, end=""))
output_bar.append(Text(self.BAR * width, style=background_style, end=""))
yield output_bar
return

Expand All @@ -67,34 +67,40 @@ def __rich_console__(

# Initial non-highlighted portion of bar
output_bar.append(
Text(bar * (int(start - 0.5)), style=background_style, end="")
Text(self.BAR * (int(start - 0.5)), style=background_style, end="")
)
if not half_start and start > 0:
output_bar.append(Text(half_bar_right, style=background_style, end=""))
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(
half_bar_left + bar * (bar_width - 1), style=highlight_style, end=""
self.HALF_BAR_LEFT + self.BAR * (bar_width - 1),
style=highlight_style,
end="",
)
)
else:
highlight_bar.append(Text(bar * bar_width, style=highlight_style, end=""))
highlight_bar.append(
Text(self.BAR * bar_width, style=highlight_style, end="")
)
if half_end:
highlight_bar.append(Text(half_bar_right, style=highlight_style, 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(half_bar_left, style=background_style, end=""))
output_bar.append(Text(self.HALF_BAR_LEFT, style=background_style, end=""))
output_bar.append(
Text(bar * (int(width) - int(end) - 1), style=background_style, end="")
Text(self.BAR * (int(width) - int(end) - 1), style=background_style, end="")
)

# Fire actions when certain ranges are clicked (e.g. for tabs)
Expand Down
13 changes: 9 additions & 4 deletions src/textual/widgets/_progress_bar.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from __future__ import annotations

from typing import Optional
from typing import Optional, Type

from rich.style import Style

Expand Down Expand Up @@ -72,11 +72,13 @@ def __init__(
disabled: bool = False,
clock: Clock | None = None,
gradient: Gradient | None = None,
bar_renderable: Type[BarRenderable] = BarRenderable,
):
"""Create a bar for a [`ProgressBar`][textual.widgets.ProgressBar]."""
self._clock = (clock or Clock()).clone()
super().__init__(name=name, id=id, classes=classes, disabled=disabled)
self.set_reactive(Bar.gradient, gradient)
self.bar_renderable = bar_renderable

def _validate_percentage(self, percentage: float | None) -> float | None:
"""Avoid updating the bar, if the percentage increase is too small to render."""
Expand Down Expand Up @@ -104,7 +106,7 @@ def render(self) -> RenderResult:
if self.percentage < 1
else self.get_component_rich_style("bar--complete")
)
return BarRenderable(
return self.bar_renderable(
highlight_range=(0, self.size.width * self.percentage),
highlight_style=Style.from_color(bar_style.color),
background_style=Style.from_color(bar_style.bgcolor),
Expand Down Expand Up @@ -133,7 +135,7 @@ def render_indeterminate(self) -> RenderResult:
end = start + highlighted_bar_width

bar_style = self.get_component_rich_style("bar--indeterminate")
return BarRenderable(
return self.bar_renderable(
highlight_range=(max(0, start), min(end, width)),
highlight_style=Style.from_color(bar_style.color),
background_style=Style.from_color(bar_style.bgcolor),
Expand Down Expand Up @@ -226,6 +228,9 @@ class ProgressBar(Widget, can_focus=False):
gradient: reactive[Gradient | None] = reactive(None)
"""Optional gradient object (will replace CSS styling in bar)."""

BAR_RENDERABLE: Type[BarRenderable] = BarRenderable
"""BarRenderable to use for rendering the bar-part of the ProgressBar"""

def __init__(
self,
total: float | None = None,
Expand Down Expand Up @@ -283,7 +288,7 @@ def on_mount(self) -> None:
def compose(self) -> ComposeResult:
if self.show_bar:
yield (
Bar(id="bar", clock=self._clock)
Bar(id="bar", clock=self._clock, bar_renderable=self.BAR_RENDERABLE)
.data_bind(ProgressBar.percentage)
.data_bind(ProgressBar.gradient)
)
Expand Down