155 lines
4.8 KiB
Python
155 lines
4.8 KiB
Python
from __future__ import annotations
|
|
|
|
import typing
|
|
import warnings
|
|
|
|
from .constants import BAR_SYMBOLS, Align, Sizing, WrapMode
|
|
from .text import Text
|
|
from .widget import Widget
|
|
|
|
if typing.TYPE_CHECKING:
|
|
from collections.abc import Hashable
|
|
|
|
from urwid.canvas import TextCanvas
|
|
|
|
|
|
class ProgressBar(Widget):
|
|
_sizing = frozenset([Sizing.FLOW])
|
|
|
|
eighths = BAR_SYMBOLS.HORISONTAL[:8] # Full width line is made by style
|
|
|
|
text_align = Align.CENTER
|
|
|
|
def __init__(
|
|
self,
|
|
normal: Hashable | None,
|
|
complete: Hashable | None,
|
|
current: int = 0,
|
|
done: int = 100,
|
|
satt: Hashable | None = None,
|
|
) -> None:
|
|
"""
|
|
:param normal: display attribute for incomplete part of progress bar
|
|
:param complete: display attribute for complete part of progress bar
|
|
:param current: current progress
|
|
:param done: progress amount at 100%
|
|
:param satt: display attribute for smoothed part of bar where the
|
|
foreground of satt corresponds to the normal part and the
|
|
background corresponds to the complete part.
|
|
If satt is ``None`` then no smoothing will be done.
|
|
|
|
>>> from urwid import LineBox
|
|
>>> pb = ProgressBar('a', 'b')
|
|
>>> pb
|
|
<ProgressBar flow widget>
|
|
>>> print(pb.get_text())
|
|
0 %
|
|
>>> pb.set_completion(34.42)
|
|
>>> print(pb.get_text())
|
|
34 %
|
|
>>> class CustomProgressBar(ProgressBar):
|
|
... def get_text(self):
|
|
... return u'Foobar'
|
|
>>> cpb = CustomProgressBar('a', 'b')
|
|
>>> print(cpb.get_text())
|
|
Foobar
|
|
>>> for x in range(101):
|
|
... cpb.set_completion(x)
|
|
... s = cpb.render((10, ))
|
|
>>> cpb2 = CustomProgressBar('a', 'b', satt='c')
|
|
>>> for x in range(101):
|
|
... cpb2.set_completion(x)
|
|
... s = cpb2.render((10, ))
|
|
>>> pb = ProgressBar('a', 'b', satt='c')
|
|
>>> pb.set_completion(34.56)
|
|
>>> print(LineBox(pb).render((20,)))
|
|
┌──────────────────┐
|
|
│ ▏34 % │
|
|
└──────────────────┘
|
|
"""
|
|
super().__init__()
|
|
self.normal = normal
|
|
self.complete = complete
|
|
self._current = current
|
|
self._done = done
|
|
self.satt = satt
|
|
|
|
def set_completion(self, current: int) -> None:
|
|
"""
|
|
current -- current progress
|
|
"""
|
|
self._current = current
|
|
self._invalidate()
|
|
|
|
current = property(lambda self: self._current, set_completion)
|
|
|
|
@property
|
|
def done(self):
|
|
return self._done
|
|
|
|
@done.setter
|
|
def done(self, done):
|
|
"""
|
|
done -- progress amount at 100%
|
|
"""
|
|
self._done = done
|
|
self._invalidate()
|
|
|
|
def _set_done(self, done):
|
|
warnings.warn(
|
|
f"Method `{self.__class__.__name__}._set_done` is deprecated, "
|
|
f"please use property `{self.__class__.__name__}.done`",
|
|
DeprecationWarning,
|
|
stacklevel=2,
|
|
)
|
|
self.done = done
|
|
|
|
def rows(self, size: tuple[int], focus: bool = False) -> int:
|
|
return 1
|
|
|
|
def get_text(self) -> str:
|
|
"""
|
|
Return the progress bar percentage text.
|
|
You can override this method to display custom text.
|
|
"""
|
|
percent = min(100, max(0, int(self.current * 100 / self.done)))
|
|
return f"{percent!s} %"
|
|
|
|
def render(
|
|
self,
|
|
size: tuple[int], # type: ignore[override]
|
|
focus: bool = False,
|
|
) -> TextCanvas:
|
|
"""
|
|
Render the progress bar.
|
|
"""
|
|
# pylint: disable=protected-access
|
|
(maxcol,) = size
|
|
c = Text(self.get_text(), self.text_align, WrapMode.CLIP).render((maxcol,))
|
|
|
|
cf = float(self.current) * maxcol / self.done
|
|
ccol_dirty = int(cf)
|
|
ccol = len(c._text[0][:ccol_dirty].decode("utf-8", "ignore").encode("utf-8"))
|
|
cs = 0
|
|
if self.satt is not None:
|
|
cs = int((cf - ccol) * 8)
|
|
if ccol < 0 or (ccol == cs == 0):
|
|
c._attr = [[(self.normal, maxcol)]]
|
|
elif ccol >= maxcol:
|
|
c._attr = [[(self.complete, maxcol)]]
|
|
elif cs and c._text[0][ccol] == 32:
|
|
t = c._text[0]
|
|
cenc = self.eighths[cs].encode("utf-8")
|
|
c._text[0] = t[:ccol] + cenc + t[ccol + 1 :]
|
|
a = []
|
|
if ccol > 0:
|
|
a.append((self.complete, ccol))
|
|
a.append((self.satt, len(cenc)))
|
|
if maxcol - ccol - 1 > 0:
|
|
a.append((self.normal, maxcol - ccol - 1))
|
|
c._attr = [a]
|
|
c._cs = [[(None, len(c._text[0]))]]
|
|
else:
|
|
c._attr = [[(self.complete, ccol), (self.normal, maxcol - ccol)]]
|
|
return c
|