From adfeecb568249a8e1b9ec5f4c8a6246687594c44 Mon Sep 17 00:00:00 2001 From: Robin De Schepper Date: Fri, 22 Mar 2024 17:21:36 +0100 Subject: [PATCH 1/2] add bounded _Multiline base, add DoubleColumn, clipping to Text and Log --- dashing/__init__.py | 117 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 101 insertions(+), 16 deletions(-) diff --git a/dashing/__init__.py b/dashing/__init__.py index 59afc40..3568855 100644 --- a/dashing/__init__.py +++ b/dashing/__init__.py @@ -219,53 +219,138 @@ class HSplit(Split): pass -class Text(Tile): +class _Multiline(Tile): """ - A multi-line text box. Example:: - - Text('Hello World, this is dashing.', border_color=2), - + An abstract multi-line text box. Requires to be subclassed. """ def __init__(self, text: str, color: Color = 0, **kw): super().__init__(**kw) - self.text: str = text self.color = color + @abc.abstractmethod + def _lines(self, width: int, height: int) -> Iterable[str]: + """ + Return max `height` lines to be rendered in the multiline tile. + """ + pass + def _display(self, tbox: TBox, parent: Optional[Tile]): tbox = self._draw_borders_and_title(tbox) - for dx, line in pad(self.text.splitlines()[-(tbox.h) :], tbox.h): + for dx, line in pad(self._lines(tbox.w, tbox.h), tbox.h): + if dx >= tbox.h: + break + if len(line) >= tbox.w: + line = line[: tbox.w - 1] + "…" print( tbox.t.color(self.color) + tbox.t.move(tbox.x + dx, tbox.y) - + line + + line[: tbox.w] + " " * (tbox.w - len(line)) ) -class Log(Tile): +class Text(_Multiline): + """ + A multi-line text box. Example:: + + Text('Hello World, this is dashing.', border_color=2), + + """ + + def __init__(self, text: str, clip=Clip.Head, **kw): + super().__init__(**kw) + self.text = text + self.clip = clip + + @property + def text(self): + return self._text + + @text.setter + def text(self, value: str): + self._text = value + self._textlines = value.splitlines() + + def _lines(self, width, height: int): + yield from ( + self._textlines if self.clip == Clip.Head else self._textlines[-height:] + ) + + +class Log(_Multiline): """A log pane that scrolls automatically. Add new lines with :meth:`append` """ - def __init__(self, **kw): + def __init__(self, clip=Clip.Tail, **kw): super().__init__(**kw) self.logs = deque(maxlen=50) + self.clip = clip - def _display(self, tbox: TBox, parent: Optional[Tile]): - tbox = self._draw_borders_and_title(tbox) + def _lines(self, width: int, height: int): n_logs = len(self.logs) - log_range = min(n_logs, tbox.h) + log_range = min(n_logs, height) start = n_logs - log_range - print(tbox.t.color(self.color)) - for dx, line in pad((self.logs[ln] for ln in range(start, n_logs)), tbox.h): - print(tbox.t.move(tbox.x + dx, tbox.y) + line + " " * (tbox.w - len(line))) + if self.clip == Clip.Tail: + clipped_range = range(start, n_logs) + else: + clipped_range = range(n_logs, start, -1) + yield from (self.logs[ln] for ln in clipped_range) def append(self, msg: str): """Append a new log message at the bottom""" self.logs.append(msg) +class DoubleColumn(_Multiline): + """ + A list with a label column and value column + """ + + def __init__( + self, + lines: Sequence[Tuple[str, str]], + sep=": ", + trunc_col=Side.Left, + trunc=Side.Right, + clip=Clip.Head, + **kw, + ): + super().__init__(**kw) + self.lines = lines + self.sep = sep + self.trunc = trunc + self.trunc_col = trunc_col + self.clip = clip + + def _lines(self, width: int, height: int): + if self.clip == Clip.Head: + lines = self.lines[:height] + else: + lines = self.lines[-height:] + size_col = not self.trunc_col + size = min( + width - 1 - len(self.sep), + max((len(cols[size_col]) for cols in lines), default=1), + ) + trunc_size = width - size + for cols in lines: + cols = [*cols] + trunc_text = cols[self.trunc_col] + size_text = cols[size_col] + if len(trunc_text) > trunc_size: + if self.trunc == Side.Left: + trunc_text = "…" + trunc_text[-trunc_size + 1 :] + else: + trunc_text = trunc_text[: trunc_size - 1] + "…" + cols[self.trunc_col] = trunc_text + if len(size_text) > size: + cols[size_col] = size_text[: size - 1] + "…" + + yield f"{cols[0]}{self.sep}{cols[1]}" + + class HGauge(Tile): """Horizontal gauge""" From 86f27ff1a12946fc29ca86404972291159e9fbda Mon Sep 17 00:00:00 2001 From: Robin De Schepper Date: Tue, 26 Mar 2024 13:36:13 +0100 Subject: [PATCH 2/2] fix code --- dashing/__init__.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/dashing/__init__.py b/dashing/__init__.py index 3568855..11260bd 100644 --- a/dashing/__init__.py +++ b/dashing/__init__.py @@ -60,10 +60,12 @@ __version__ = "0.1.0" +import abc import contextlib import itertools from collections import deque, namedtuple -from typing import Literal, Optional, Tuple +from enum import IntEnum +from typing import Iterable, Literal, Optional, Sequence, Tuple from blessed import Terminal @@ -87,6 +89,16 @@ Colormap = Tuple[Tuple[float, Color], ...] +class Side(IntEnum): + Left = 0 + Right = 1 + + +class Clip(IntEnum): + Head = 0 + Tail = 1 + + class Tile(object): def __init__(self, title: str = None, border_color: Color = None, color: Color = 0): self.title = title