From 0133802570c72504eef0a7de5fe924f0df27ae93 Mon Sep 17 00:00:00 2001 From: rr- Date: Fri, 4 Jan 2019 16:40:30 +0100 Subject: [PATCH] ui/curses: estimate time in progress bars --- pqcli/lingo.py | 22 ++++++++++ .../views/game_view/character_sheet_window.py | 9 +++- .../views/game_view/inventory_window.py | 11 ++++- .../ui/curses/views/game_view/plot_window.py | 4 +- .../views/game_view/progress_bar_window.py | 25 ++++++++--- .../views/game_view/quest_book_window.py | 2 +- .../views/game_view/task_progress_window.py | 4 +- pqcli/ui/curses/widgets/progress_bar.py | 42 +++++++++++++++++-- pqcli/ui/urwid/custom_progress_bar.py | 23 +--------- 9 files changed, 107 insertions(+), 35 deletions(-) diff --git a/pqcli/lingo.py b/pqcli/lingo.py index 71500b8..3573bd5 100644 --- a/pqcli/lingo.py +++ b/pqcli/lingo.py @@ -1,8 +1,30 @@ +import datetime import typing as T from pqcli import random +def format_float(num: float) -> str: + ret = f"{num:.01f}" + if ret.endswith("0"): + ret = ret[:-2] + return ret + + +def format_timespan(timespan: datetime.timedelta) -> str: + num = timespan.total_seconds() + if num < 60.0: + return f"~{int(num)}s" + num /= 60 + if num < 60.0: + return f"~{int(num)}m" + num /= 60 + if num < 24.0: + return f"~{format_float(num)}h" + num /= 24 + return f"~{format_float(num)}d" + + def generate_name() -> str: parts = [ "br|cr|dr|fr|gr|j|kr|l|m|n|pr||||r|sh|tr|v|wh|x|y|z".split("|"), diff --git a/pqcli/ui/curses/views/game_view/character_sheet_window.py b/pqcli/ui/curses/views/game_view/character_sheet_window.py index afeb3d4..9cc39d4 100644 --- a/pqcli/ui/curses/views/game_view/character_sheet_window.py +++ b/pqcli/ui/curses/views/game_view/character_sheet_window.py @@ -11,7 +11,14 @@ class CharacterSheetWindow(Focusable, DataTableProgressBarWindow): self, player: Player, parent: T.Any, h: int, w: int, y: int, x: int ) -> None: super().__init__( - parent, h, w, y, x, " Character Sheet ", align_right=False + parent, + h, + w, + y, + x, + " Character Sheet ", + align_right=False, + show_time=True, ) self._on_focus_change += self._render diff --git a/pqcli/ui/curses/views/game_view/inventory_window.py b/pqcli/ui/curses/views/game_view/inventory_window.py index ff545a6..a315b31 100644 --- a/pqcli/ui/curses/views/game_view/inventory_window.py +++ b/pqcli/ui/curses/views/game_view/inventory_window.py @@ -13,7 +13,16 @@ class InventoryWindow(Focusable, DataTableProgressBarWindow): def __init__( self, player: Player, parent: T.Any, h: int, w: int, y: int, x: int ) -> None: - super().__init__(parent, h, w, y, x, " Inventory ", align_right=True) + super().__init__( + parent, + h, + w, + y, + x, + " Inventory ", + align_right=True, + show_time=False, + ) self._on_focus_change += self._render self._player = player diff --git a/pqcli/ui/curses/views/game_view/plot_window.py b/pqcli/ui/curses/views/game_view/plot_window.py index 1a4e7e4..9cb591b 100644 --- a/pqcli/ui/curses/views/game_view/plot_window.py +++ b/pqcli/ui/curses/views/game_view/plot_window.py @@ -14,7 +14,9 @@ class PlotWindow(Focusable, ListBoxProgressBarWindow): def __init__( self, player: Player, parent: T.Any, h: int, w: int, y: int, x: int ) -> None: - super().__init__(parent, h, w, y, x, " Plot Development ") + super().__init__( + parent, h, w, y, x, " Plot Development ", show_time=True + ) self._on_focus_change += self._render self._player = player diff --git a/pqcli/ui/curses/views/game_view/progress_bar_window.py b/pqcli/ui/curses/views/game_view/progress_bar_window.py index ebab7e3..46e59a1 100644 --- a/pqcli/ui/curses/views/game_view/progress_bar_window.py +++ b/pqcli/ui/curses/views/game_view/progress_bar_window.py @@ -13,7 +13,14 @@ from .focusable import Focusable class ProgressBarWindow(WindowWrapper): def __init__( - self, parent: T.Any, h: int, w: int, y: int, x: int, title: str + self, + parent: T.Any, + h: int, + w: int, + y: int, + x: int, + title: str, + show_time: bool, ) -> None: super().__init__(parent, h, w, y, x) @@ -29,7 +36,7 @@ class ProgressBarWindow(WindowWrapper): except curses.error: self._progress_bar_win = None self._progress_bar = ProgressBar( - self._progress_bar_win, 1, w - 2, 1, 1 + self._progress_bar_win, 1, w - 2, 1, 1, show_time=show_time ) def stop(self) -> None: @@ -87,8 +94,9 @@ class DataTableProgressBarWindow(ProgressBarWindow): x: int, title: str, align_right: bool, + show_time: bool, ) -> None: - super().__init__(parent, h, w, y, x, title) + super().__init__(parent, h, w, y, x, title, show_time) self._data_table = DataTable( self._win, h - 4, w - 2, 1, 1, align_right @@ -119,9 +127,16 @@ class DataTableProgressBarWindow(ProgressBarWindow): class ListBoxProgressBarWindow(ProgressBarWindow): def __init__( - self, parent: T.Any, h: int, w: int, y: int, x: int, title: str + self, + parent: T.Any, + h: int, + w: int, + y: int, + x: int, + title: str, + show_time: bool, ) -> None: - super().__init__(parent, h, w, y, x, title) + super().__init__(parent, h, w, y, x, title, show_time) self._list_box = ListBox(self._win, h - 4, w - 2, 1, 1) diff --git a/pqcli/ui/curses/views/game_view/quest_book_window.py b/pqcli/ui/curses/views/game_view/quest_book_window.py index ed6facd..69d125c 100644 --- a/pqcli/ui/curses/views/game_view/quest_book_window.py +++ b/pqcli/ui/curses/views/game_view/quest_book_window.py @@ -13,7 +13,7 @@ class QuestBookWindow(Focusable, ListBoxProgressBarWindow): def __init__( self, player: Player, parent: T.Any, h: int, w: int, y: int, x: int ) -> None: - super().__init__(parent, h, w, y, x, " Quests ") + super().__init__(parent, h, w, y, x, " Quests ", show_time=True) self._on_focus_change += self._render self._player = player diff --git a/pqcli/ui/curses/views/game_view/task_progress_window.py b/pqcli/ui/curses/views/game_view/task_progress_window.py index 4517ef9..0fdc9bc 100644 --- a/pqcli/ui/curses/views/game_view/task_progress_window.py +++ b/pqcli/ui/curses/views/game_view/task_progress_window.py @@ -13,7 +13,9 @@ class TaskProgressWindow(Focusable, WindowWrapper): super().__init__(parent, h, w, y, x) self._desc_win = Label(self._win, 1, w, 0, 0) - self._progress_bar = ProgressBar(self._win, 1, w, 1, 0) + self._progress_bar = ProgressBar( + self._win, 1, w, 1, 0, show_time=False + ) self._player = player self._player.connect("new_task", self._sync_task_name) diff --git a/pqcli/ui/curses/widgets/progress_bar.py b/pqcli/ui/curses/widgets/progress_bar.py index d173bb3..83c543f 100644 --- a/pqcli/ui/curses/widgets/progress_bar.py +++ b/pqcli/ui/curses/widgets/progress_bar.py @@ -1,17 +1,51 @@ import curses +import datetime +import typing as T -from .base import WindowWrapper +from pqcli.lingo import format_timespan +from pqcli.ui.curses.widgets.base import WindowWrapper class ProgressBar(WindowWrapper): - def set_position(self, position: float, max_: float) -> None: + def __init__( + self, parent: T.Any, h: int, w: int, y: int, x: int, show_time: bool + ) -> None: + super().__init__(parent, h, w, y, x) + + self._cur_pos = 0.0 + self._max_pos = 1.0 + + self._show_time = show_time + self._last_tick: T.Optional[T.Tuple[datetime.datetime, float]] = None + + @property + def time_left(self) -> T.Optional[datetime.timedelta]: + if self._last_tick is None: + return None + time_then, pos_then = self._last_tick + time_now, pos_now = datetime.datetime.now(), self._cur_pos + speed = (pos_now - pos_then) / (time_now - time_then).total_seconds() + if not speed: + return None + return datetime.timedelta(seconds=(self._max_pos - pos_now) / speed) + + def set_position(self, cur_pos: float, max_pos: float) -> None: if not self._win: return self._win.erase() - text = f"{position / max_:.02%}" + + if self._last_tick is None or cur_pos == 0 or max_pos != self._max_pos: + self._last_tick = (datetime.datetime.now(), cur_pos) + self._cur_pos = cur_pos + self._max_pos = max_pos + + text = f"{cur_pos / max_pos:.02%}" + if self.time_left and self._show_time: + text += f" ({format_timespan(self.time_left)})" + x = max(0, (self.getmaxyx()[1] - len(text)) // 2) self._win.addnstr(0, x, text, min(len(text), self.getmaxyx()[1] - 1)) - x = int(position * self.getmaxyx()[1] // max_) + x = int(cur_pos * self.getmaxyx()[1] // max_pos) if x > 0: self._win.chgat(0, 0, curses.A_REVERSE) if x < self.getmaxyx()[1]: diff --git a/pqcli/ui/urwid/custom_progress_bar.py b/pqcli/ui/urwid/custom_progress_bar.py index e7ef0df..d555c4f 100644 --- a/pqcli/ui/urwid/custom_progress_bar.py +++ b/pqcli/ui/urwid/custom_progress_bar.py @@ -3,26 +3,7 @@ import typing as T import urwid - -def format_float(num: float) -> str: - ret = f"{num:.01f}" - if ret.endswith("0"): - ret = ret[:-2] - return ret - - -def format_timespan(timespan: datetime.timedelta) -> str: - num = timespan.total_seconds() - if num < 60.0: - return f"~{int(num)}s" - num /= 60 - if num < 60.0: - return f"~{int(num)}m" - num /= 60 - if num < 24.0: - return f"~{format_float(num)}h" - num /= 24 - return f"~{format_float(num)}d" +from pqcli.lingo import format_timespan class CustomProgressBar(urwid.Widget): @@ -37,7 +18,7 @@ class CustomProgressBar(urwid.Widget): self.normal = "progressbar-normal" self.complete = "progressbar-done" self.smooth = "progressbar-smooth" - self.last_tick: T.Optional[datetime.datetime, float] = None + self.last_tick: T.Optional[T.Tuple[datetime.datetime, float]] = None @property def time_left(self) -> T.Optional[datetime.timedelta]: