codekingpro/portable-devtools
114k
1from __future__ import annotations2 3import typing4 5from .constants import BAR_SYMBOLS, Align, Sizing, WrapMode6from .text import Text7from .widget import Widget8 9if typing.TYPE_CHECKING:10 from collections.abc import Hashable11 12 from urwid.canvas import TextCanvas13 14 15class ProgressBar(Widget):16 _sizing = frozenset([Sizing.FLOW])17 18 eighths = BAR_SYMBOLS.HORISONTAL[:8] # Full width line is made by style19 20 text_align = Align.CENTER21 22 def __init__(23 self,24 normal: Hashable | None,25 complete: Hashable | None,26 current: int = 0,27 done: int = 100,28 satt: Hashable | None = None,29 ) -> None:30 """31 :param normal: display attribute for incomplete part of progress bar32 :param complete: display attribute for complete part of progress bar33 :param current: current progress34 :param done: progress amount at 100%35 :param satt: display attribute for smoothed part of bar where the36 foreground of satt corresponds to the normal part and the37 background corresponds to the complete part.38 If satt is ``None`` then no smoothing will be done.39 40 >>> from urwid import LineBox41 >>> pb = ProgressBar("a", "b")42 >>> pb43 <ProgressBar flow widget>44 >>> print(pb.get_text())45 0 %46 >>> pb.set_completion(34.42)47 >>> print(pb.get_text())48 34 %49 >>> class CustomProgressBar(ProgressBar):50 ... def get_text(self):51 ... return "Foobar"52 >>> cpb = CustomProgressBar("a", "b")53 >>> print(cpb.get_text())54 Foobar55 >>> for x in range(101):56 ... cpb.set_completion(x)57 ... s = cpb.render((10,))58 >>> cpb2 = CustomProgressBar("a", "b", satt="c")59 >>> for x in range(101):60 ... cpb2.set_completion(x)61 ... s = cpb2.render((10,))62 >>> pb = ProgressBar("a", "b", satt="c")63 >>> pb.set_completion(34.56)64 >>> print(LineBox(pb).render((20,)))65 ┌──────────────────┐66 │ ▏34 % │67 └──────────────────┘68 """69 super().__init__()70 self.normal = normal71 self.complete = complete72 self._current = current73 self._done = done74 self.satt = satt75 76 def set_completion(self, current: int) -> None:77 """78 current -- current progress79 """80 self._current = current81 self._invalidate()82 83 current = property(lambda self: self._current, set_completion)84 85 @property86 def done(self):87 return self._done88 89 @done.setter90 def done(self, done):91 """92 done -- progress amount at 100%93 """94 self._done = done95 self._invalidate()96 97 def rows(self, size: tuple[int], focus: bool = False) -> int:98 return 199 100 def get_text(self) -> str:101 """102 Return the progress bar percentage text.103 You can override this method to display custom text.104 """105 percent = min(100, max(0, int(self.current * 100 / self.done)))106 return f"{percent!s} %"107 108 def render(109 self,110 size: tuple[int], # type: ignore[override]111 focus: bool = False,112 ) -> TextCanvas:113 """114 Render the progress bar.115 """116 # pylint: disable=protected-access117 (maxcol,) = size118 c = Text(self.get_text(), self.text_align, WrapMode.CLIP).render((maxcol,))119 120 cf = float(self.current) * maxcol / self.done121 ccol_dirty = int(cf)122 ccol = len(c._text[0][:ccol_dirty].decode("utf-8", "ignore").encode("utf-8"))123 cs = 0124 if self.satt is not None:125 cs = int((cf - ccol) * 8)126 if ccol < 0 or (ccol == cs == 0):127 c._attr = [[(self.normal, maxcol)]]128 elif ccol >= maxcol:129 c._attr = [[(self.complete, maxcol)]]130 elif cs and c._text[0][ccol] == 32:131 t = c._text[0]132 cenc = self.eighths[cs].encode("utf-8")133 c._text[0] = t[:ccol] + cenc + t[ccol + 1 :]134 a = []135 if ccol > 0:136 a.append((self.complete, ccol))137 a.append((self.satt, len(cenc)))138 if maxcol - ccol - 1 > 0:139 a.append((self.normal, maxcol - ccol - 1))140 c._attr = [a]141 c._cs = [[(None, len(c._text[0]))]]142 else:143 c._attr = [[(self.complete, ccol), (self.normal, maxcol - ccol)]]144 return c145 