Skip to content

Commit

Permalink
feat: primitive autoscrolling of messages
Browse files Browse the repository at this point in the history
Doesn't work when window shrinks, but better than nothing
  • Loading branch information
thegamecracks committed Mar 22, 2024
1 parent a4bc296 commit 278a771
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 2 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

- Dumdum client improvements:
- Dynamically wrap message content for larger window sizes
- Automatically scroll message feed

## [0.2.1] - 2024-03-21

Expand Down
2 changes: 1 addition & 1 deletion src/dumdum/client/chat_frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ def __init__(self, parent: ChatFrame):

self.messages: list[MessageView] = []

self._scroll_frame = ScrollableFrame(self)
self._scroll_frame = ScrollableFrame(self, autoscroll=True)
self._scroll_frame.grid(row=0, column=0, sticky="nesw")

self._last_configured = None
Expand Down
34 changes: 33 additions & 1 deletion src/dumdum/client/scrollable_frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@


class ScrollableFrame(Frame):
def __init__(self, *args, **kwargs):
_last_scrollregion: tuple[int, int, int, int] | None

def __init__(self, *args, autoscroll: bool = False, **kwargs):
super().__init__(*args, **kwargs)

self.autoscroll = autoscroll

self.grid_columnconfigure(0, weight=1)
self.grid_rowconfigure(0, weight=1)

Expand Down Expand Up @@ -34,6 +38,7 @@ def __init__(self, *args, **kwargs):
self._canvas.bind("<Configure>", lambda event: self._update())
self.inner.bind("<Configure>", self._on_inner_configure)

self._last_scrollregion = None
self._scrolled_widgets = WeakSet()
self._style = Style(self)
self._update_rate = 125
Expand All @@ -51,6 +56,8 @@ def _update_loop(self):
self.after(self._update_rate, self._update_loop)

def _update(self):
scroll_edges = self._get_scroll_edges()

# self._canvas.bbox("all") doesn't update until window resize
# so we're relying on the inner frame's requested height instead.
new_width = max(self._canvas.winfo_width(), self.inner.winfo_reqwidth())
Expand All @@ -62,6 +69,14 @@ def _update(self):
self._update_scrollbar_visibility(self._xscrollbar)
self._update_scrollbar_visibility(self._yscrollbar)
self._propagate_scroll_binds(self.inner)
self._update_scroll_edges(bbox, *scroll_edges)

def _get_scroll_edges(self) -> tuple[bool, bool]:
xview = self._canvas.xview()
yview = self._canvas.yview()
scrolled_to_right = xview[1] == 1 and xview[0] != 0
scrolled_to_bottom = yview[1] == 1 and yview[0] != 0
return scrolled_to_right, scrolled_to_bottom

def _propagate_scroll_binds(self, parent: Widget):
if parent not in self._scrolled_widgets:
Expand All @@ -72,6 +87,23 @@ def _propagate_scroll_binds(self, parent: Widget):
for child in parent.winfo_children():
self._propagate_scroll_binds(child)

def _update_scroll_edges(
self,
bbox: tuple[int, int, int, int],
scrolled_to_right: bool,
scrolled_to_bottom: bool,
) -> None:
self._last_scrollregion, last_bbox = bbox, self._last_scrollregion
if not self.autoscroll:
return
elif bbox == last_bbox:
return

if scrolled_to_right:
self._canvas.xview_moveto(1)
if scrolled_to_bottom:
self._canvas.yview_moveto(1)

def _on_mouse_xscroll(self, event: Event):
delta = int(-event.delta / 100)
self._canvas.xview_scroll(delta, "units")
Expand Down

0 comments on commit 278a771

Please sign in to comment.