diff --git a/toot/tui/timeline.py b/toot/tui/timeline.py index 42299e10..c0d40266 100644 --- a/toot/tui/timeline.py +++ b/toot/tui/timeline.py @@ -8,10 +8,11 @@ from .entities import Status from .scroll import Scrollable, ScrollBar from .utils import highlight_hashtags, parse_datetime, highlight_keys -from .widgets import SelectableText, SelectableColumns +from .widgets import SelectableText, SelectableColumns, StatusList from toot.utils import format_content from toot.utils.language import language_name from toot.tui.utils import time_ago +from urwid.util import is_mouse_press logger = logging.getLogger("toot") @@ -67,18 +68,19 @@ def __init__(self, focused_status = None self.status_details = StatusDetails(self, focused_status) - status_widget = self.wrap_status_details(self.status_details) + status_widget = self.wrap_status_details(self.status_details, False) + status_list_widget = self.wrap_status_list(self.status_list, True) super().__init__([ - ("weight", 40, self.status_list), - ("weight", 0, urwid.AttrWrap(urwid.SolidFill("│"), "blue_selected")), + ("weight", 40, status_list_widget), + ("weight", 0, urwid.AttrWrap(urwid.SolidFill(" "), "blue_selected")), ("weight", 60, status_widget), ]) - def wrap_status_details(self, status_details: "StatusDetails") -> urwid.Widget: + def wrap_status_details(self, status_details: "StatusDetails", focus=False) -> urwid.Widget: """Wrap StatusDetails widget with a scrollbar and footer.""" self.status_detail_scrollable = Scrollable(urwid.Padding(status_details, right=1)) - return urwid.Padding( + wrapped_sd = urwid.Padding( urwid.Frame( body=ScrollBar( self.status_detail_scrollable, @@ -87,15 +89,29 @@ def wrap_status_details(self, status_details: "StatusDetails") -> urwid.Widget: ), footer=self.get_option_text(status_details.status), ), - left=1 - ) + left=1) + if (focus): + return urwid.LineBox(wrapped_sd) + else: + return urwid.LineBox(wrapped_sd, + tline=" ", bline=" ", rline=" ", + lline=" ", tlcorner=" ", trcorner=" ", + blcorner=" ", brcorner=" ") + + def wrap_status_list(self, status_list: urwid.ListBox, focus=True) -> urwid.Widget: + """Wrap StatusList widget with a box""" + if (focus): + return urwid.LineBox(status_list) + else: + return urwid.LineBox(status_list, tline=" ", bline=" ", rline=" ", + lline=" ", tlcorner=" ", trcorner=" ", blcorner=" ", brcorner=" ") def build_status_list(self, statuses, focus): items = [self.build_list_item(status) for status in statuses] walker = urwid.SimpleFocusListWalker(items) walker.set_focus(focus) urwid.connect_signal(walker, "modified", self.modified) - return urwid.ListBox(walker) + return StatusList(walker, self) def build_list_item(self, status): item = StatusListItem(status) @@ -158,25 +174,46 @@ def get_focused_status_with_counts(self): def modified(self): """Called when the list focus switches to a new status""" status, index, count = self.get_focused_status_with_counts() - self.draw_status_details(status) + self.draw_status_details(status, focus=False) self._emit("focus") - def refresh_status_details(self): + def refresh_same_status_details(self, focus=None): + """Redraws the details of the focused status.""" + pos = self.status_detail_scrollable.get_scrollpos() + widget = self.wrap_status_details(self.status_details, focus) + self.status_detail_scrollable.set_scrollpos(pos) + self.contents[2] = widget, ("weight", 60, False) + + def refresh_status_details(self, focus=None): """Redraws the details of the focused status.""" status = self.get_focused_status() pos = self.status_detail_scrollable.get_scrollpos() self.draw_status_details(status) self.status_detail_scrollable.set_scrollpos(pos) - def draw_status_details(self, status): + def draw_status_details(self, status, focus=None): self.status_details = StatusDetails(self, status) - widget = self.wrap_status_details(self.status_details) + widget = self.wrap_status_details(self.status_details, focus) self.contents[2] = widget, ("weight", 60, False) + def draw_status_list(self, focus=None): + widget = self.wrap_status_list(self.status_list, focus) + self.contents[0] = widget, ("weight", 40, False) + def keypress(self, size, key): status = self.get_focused_status() command = self._command_map[key] + if command in [urwid.CURSOR_RIGHT, urwid.CURSOR_MAX_RIGHT]: + if self.focus_position == 0: + self.draw_status_list(focus=False) + self.refresh_same_status_details(focus=True) + + if command in [urwid.CURSOR_LEFT, urwid.CURSOR_MAX_LEFT]: + if self.focus_position != 0: + self.draw_status_list(focus=True) + self.refresh_same_status_details(focus=False) + if not status: return super().keypress(size, key) @@ -314,7 +351,7 @@ def update_status(self, status): # Redraw status details if status is focused if index == self.status_list.body.focus: - self.draw_status_details(status) + self.draw_status_details(status, focus=False) def remove_status(self, status): index = self.get_status_index(status.id) @@ -330,7 +367,7 @@ def __init__(self, timeline: Timeline, status: Optional[Status]): self.status = status self.followed_tags = timeline.followed_tags self.followed_accounts = timeline.followed_accounts - + self.timeline = timeline reblogged_by = status.author if status and status.reblog else None widget_list = list(self.content_generator(status.original, reblogged_by) if status else ()) @@ -454,6 +491,12 @@ def poll_generator(self, poll): yield urwid.Text(("gray", status)) + def mouse_event(self, size, event, button, col, row, focus): + if is_mouse_press(event) and button == 1: + self.timeline.draw_status_list(focus=False) + self.timeline.refresh_same_status_details(focus=True) + return super().mouse_event(size, event, button, col, row, focus) + class StatusListItem(SelectableColumns): def __init__(self, status): diff --git a/toot/tui/widgets.py b/toot/tui/widgets.py index 6f46fb33..ab19f0fa 100644 --- a/toot/tui/widgets.py +++ b/toot/tui/widgets.py @@ -1,4 +1,5 @@ import urwid +from urwid.util import is_mouse_press class Clickable: @@ -48,6 +49,18 @@ def set_label(self, *args, **kwargs): self.original_widget.width = len(args[0]) + 4 +class StatusList(urwid.ListBox): + def __init__(self, *args, **kwargs): + self.timeline = args[1] + return super().__init__(args[0]) + + def mouse_event(self, size, event, button, col, row, focus): + if is_mouse_press(event) and button == 1: + self.timeline.draw_status_list(focus=True) + self.timeline.refresh_same_status_details(focus=False) + return super().mouse_event(size, event, button, col, row, focus) + + class CheckBox(urwid.AttrWrap): """Styled checkbox.""" def __init__(self, *args, **kwargs):