Switch to a "tiny" progress bar
this saves horizontal space for when the screen is small.
This commit is contained in:
parent
3cff298395
commit
a1eb7e10d7
2 changed files with 91 additions and 5 deletions
85
src/ttotp/TinyProgress.py
Normal file
85
src/ttotp/TinyProgress.py
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
#!/usr/bin/python3
|
||||
|
||||
# SPDX-FileCopyrightText: 2025 Jeff Epler
|
||||
# SPDX-FileCopyrightText: 2021 Will McGugan
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
from textual.app import ComposeResult, RenderResult
|
||||
from textual.widgets._progress_bar import ProgressBar, Bar
|
||||
from rich.text import Text
|
||||
|
||||
|
||||
class OneCellBar(Bar):
|
||||
"""The bar portion of the tiny progress bar."""
|
||||
|
||||
BARS = "▁▂▃▄▅▆▇"
|
||||
SHADES = "█▓▒░▒▓"
|
||||
|
||||
DEFAULT_CSS = """
|
||||
OneCellBar {
|
||||
width: 1;
|
||||
height: 1;
|
||||
|
||||
&> .bar--bar {
|
||||
color: $primary;
|
||||
background: $surface;
|
||||
}
|
||||
&> .bar--indeterminate {
|
||||
color: $error;
|
||||
background: $surface;
|
||||
}
|
||||
&> .bar--complete {
|
||||
color: $success;
|
||||
background: $surface;
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
def render(self) -> RenderResult:
|
||||
if self.percentage is None:
|
||||
return self.render_indeterminate()
|
||||
else:
|
||||
return self.render_determinate(self.percentage)
|
||||
|
||||
def render_determinate(self, percentage: float) -> RenderResult:
|
||||
bar_style = (
|
||||
self.get_component_rich_style("bar--bar")
|
||||
if percentage < 1
|
||||
else self.get_component_rich_style("bar--complete")
|
||||
)
|
||||
i = self.percentage_to_index(percentage)
|
||||
return Text(self.BARS[i], style=bar_style)
|
||||
|
||||
def watch_percentage(self, percentage: float | None) -> None:
|
||||
"""Manage the timer that enables the indeterminate bar animation."""
|
||||
if percentage is not None:
|
||||
self.auto_refresh = None
|
||||
else:
|
||||
self.auto_refresh = 1 # every second
|
||||
|
||||
def render_indeterminate(self) -> RenderResult:
|
||||
bar_style = self.get_component_rich_style("bar--indeterminate")
|
||||
phase = round(self._clock.time) % len(self.SHADES)
|
||||
i = self.SHADES[phase]
|
||||
return Text(i, style=bar_style)
|
||||
|
||||
def percentage_to_index(self, percentage: float) -> int:
|
||||
p = max(0, min(1, percentage))
|
||||
i = round(p * (len(self.BARS) - 1))
|
||||
return i
|
||||
|
||||
def _validate_percentage(self, percentage: float | None) -> float | None:
|
||||
if percentage is None:
|
||||
return None
|
||||
return self.percentage_to_index(percentage) / (len(self.BARS) - 1)
|
||||
|
||||
|
||||
class TinyProgress(ProgressBar):
|
||||
def compose(self) -> ComposeResult:
|
||||
if self.show_bar:
|
||||
yield (
|
||||
OneCellBar(id="bar", clock=self._clock)
|
||||
.data_bind(ProgressBar.percentage)
|
||||
.data_bind(ProgressBar.gradient)
|
||||
)
|
||||
|
|
@ -20,7 +20,7 @@ from textual.fuzzy import Matcher
|
|||
from textual.app import App, ComposeResult
|
||||
from textual.events import Key, MouseDown, MouseUp, MouseScrollDown, MouseScrollUp
|
||||
from textual.widget import Widget
|
||||
from textual.widgets import Label, Footer, ProgressBar, Button, Input
|
||||
from textual.widgets import Label, Footer, Button, Input
|
||||
from textual.binding import Binding
|
||||
from textual.containers import VerticalScroll, Horizontal
|
||||
from textual.css.query import DOMQuery
|
||||
|
|
@ -31,6 +31,8 @@ import pyotp
|
|||
import platformdirs
|
||||
import tomllib
|
||||
|
||||
from .TinyProgress import TinyProgress as ProgressBar
|
||||
|
||||
from typing import TypeGuard # use `typing_extensions` for Python 3.9 and below
|
||||
|
||||
# workaround for pyperclip being un-typed
|
||||
|
|
@ -328,15 +330,13 @@ def search_preprocess(s: str) -> str:
|
|||
class TTOTP(App[None]):
|
||||
CSS = """
|
||||
VerticalScroll { min-height: 1; }
|
||||
.otp-progress { width: 12; }
|
||||
.otp-value { width: 9; }
|
||||
.otp-hidden { display: none; }
|
||||
.otp-name { text-wrap: nowrap; text-overflow: ellipsis; }
|
||||
.otp-name:focus { background: red; }
|
||||
TOTPLabel { width: 1fr; height: 1; padding: 0 1; }
|
||||
Horizontal:focus-within { background: $primary-background; }
|
||||
Bar > .bar--bar { color: $success; }
|
||||
Bar { width: 1fr; }
|
||||
OneCellBar > .bar--bar { color: $success; }
|
||||
Button { border: none; height: 1; width: 3; min-width: 4 }
|
||||
Horizontal { height: 1; }
|
||||
Input { border: none; height: 1; width: 1fr; }
|
||||
|
|
@ -527,6 +527,7 @@ multiple profiles as configuration file sections, and select one with
|
|||
profile_data = config_data.get(profile, None)
|
||||
if profile_data is None:
|
||||
config_hint(f"The profile {profile!r} file does not exist.")
|
||||
else:
|
||||
config_data.update(profile_data)
|
||||
|
||||
otp_command = config_data.get("otp-command")
|
||||
|
|
|
|||
Loading…
Reference in a new issue