mirror of
https://github.com/rr-/pq-cli.git
synced 2026-09-29 06:44:00 -06:00
ui/curses: estimate time in progress bars
This commit is contained in:
@@ -1,8 +1,30 @@
|
|||||||
|
import datetime
|
||||||
import typing as T
|
import typing as T
|
||||||
|
|
||||||
from pqcli import random
|
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:
|
def generate_name() -> str:
|
||||||
parts = [
|
parts = [
|
||||||
"br|cr|dr|fr|gr|j|kr|l|m|n|pr||||r|sh|tr|v|wh|x|y|z".split("|"),
|
"br|cr|dr|fr|gr|j|kr|l|m|n|pr||||r|sh|tr|v|wh|x|y|z".split("|"),
|
||||||
|
|||||||
@@ -11,7 +11,14 @@ class CharacterSheetWindow(Focusable, DataTableProgressBarWindow):
|
|||||||
self, player: Player, parent: T.Any, h: int, w: int, y: int, x: int
|
self, player: Player, parent: T.Any, h: int, w: int, y: int, x: int
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(
|
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
|
self._on_focus_change += self._render
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,16 @@ class InventoryWindow(Focusable, DataTableProgressBarWindow):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self, player: Player, parent: T.Any, h: int, w: int, y: int, x: int
|
self, player: Player, parent: T.Any, h: int, w: int, y: int, x: int
|
||||||
) -> None:
|
) -> 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._on_focus_change += self._render
|
||||||
|
|
||||||
self._player = player
|
self._player = player
|
||||||
|
|||||||
@@ -14,7 +14,9 @@ class PlotWindow(Focusable, ListBoxProgressBarWindow):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self, player: Player, parent: T.Any, h: int, w: int, y: int, x: int
|
self, player: Player, parent: T.Any, h: int, w: int, y: int, x: int
|
||||||
) -> None:
|
) -> 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._on_focus_change += self._render
|
||||||
|
|
||||||
self._player = player
|
self._player = player
|
||||||
|
|||||||
@@ -13,7 +13,14 @@ from .focusable import Focusable
|
|||||||
|
|
||||||
class ProgressBarWindow(WindowWrapper):
|
class ProgressBarWindow(WindowWrapper):
|
||||||
def __init__(
|
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:
|
) -> None:
|
||||||
super().__init__(parent, h, w, y, x)
|
super().__init__(parent, h, w, y, x)
|
||||||
|
|
||||||
@@ -29,7 +36,7 @@ class ProgressBarWindow(WindowWrapper):
|
|||||||
except curses.error:
|
except curses.error:
|
||||||
self._progress_bar_win = None
|
self._progress_bar_win = None
|
||||||
self._progress_bar = ProgressBar(
|
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:
|
def stop(self) -> None:
|
||||||
@@ -87,8 +94,9 @@ class DataTableProgressBarWindow(ProgressBarWindow):
|
|||||||
x: int,
|
x: int,
|
||||||
title: str,
|
title: str,
|
||||||
align_right: bool,
|
align_right: bool,
|
||||||
|
show_time: bool,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(parent, h, w, y, x, title)
|
super().__init__(parent, h, w, y, x, title, show_time)
|
||||||
|
|
||||||
self._data_table = DataTable(
|
self._data_table = DataTable(
|
||||||
self._win, h - 4, w - 2, 1, 1, align_right
|
self._win, h - 4, w - 2, 1, 1, align_right
|
||||||
@@ -119,9 +127,16 @@ class DataTableProgressBarWindow(ProgressBarWindow):
|
|||||||
|
|
||||||
class ListBoxProgressBarWindow(ProgressBarWindow):
|
class ListBoxProgressBarWindow(ProgressBarWindow):
|
||||||
def __init__(
|
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:
|
) -> 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)
|
self._list_box = ListBox(self._win, h - 4, w - 2, 1, 1)
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ class QuestBookWindow(Focusable, ListBoxProgressBarWindow):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self, player: Player, parent: T.Any, h: int, w: int, y: int, x: int
|
self, player: Player, parent: T.Any, h: int, w: int, y: int, x: int
|
||||||
) -> None:
|
) -> 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._on_focus_change += self._render
|
||||||
|
|
||||||
self._player = player
|
self._player = player
|
||||||
|
|||||||
@@ -13,7 +13,9 @@ class TaskProgressWindow(Focusable, WindowWrapper):
|
|||||||
super().__init__(parent, h, w, y, x)
|
super().__init__(parent, h, w, y, x)
|
||||||
|
|
||||||
self._desc_win = Label(self._win, 1, w, 0, 0)
|
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 = player
|
||||||
self._player.connect("new_task", self._sync_task_name)
|
self._player.connect("new_task", self._sync_task_name)
|
||||||
|
|||||||
@@ -1,17 +1,51 @@
|
|||||||
import curses
|
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):
|
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:
|
if not self._win:
|
||||||
return
|
return
|
||||||
self._win.erase()
|
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)
|
x = max(0, (self.getmaxyx()[1] - len(text)) // 2)
|
||||||
self._win.addnstr(0, x, text, min(len(text), self.getmaxyx()[1] - 1))
|
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:
|
if x > 0:
|
||||||
self._win.chgat(0, 0, curses.A_REVERSE)
|
self._win.chgat(0, 0, curses.A_REVERSE)
|
||||||
if x < self.getmaxyx()[1]:
|
if x < self.getmaxyx()[1]:
|
||||||
|
|||||||
@@ -3,26 +3,7 @@ import typing as T
|
|||||||
|
|
||||||
import urwid
|
import urwid
|
||||||
|
|
||||||
|
from pqcli.lingo import format_timespan
|
||||||
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"
|
|
||||||
|
|
||||||
|
|
||||||
class CustomProgressBar(urwid.Widget):
|
class CustomProgressBar(urwid.Widget):
|
||||||
@@ -37,7 +18,7 @@ class CustomProgressBar(urwid.Widget):
|
|||||||
self.normal = "progressbar-normal"
|
self.normal = "progressbar-normal"
|
||||||
self.complete = "progressbar-done"
|
self.complete = "progressbar-done"
|
||||||
self.smooth = "progressbar-smooth"
|
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
|
@property
|
||||||
def time_left(self) -> T.Optional[datetime.timedelta]:
|
def time_left(self) -> T.Optional[datetime.timedelta]:
|
||||||
|
|||||||
Reference in New Issue
Block a user