Skip to content

Instantly share code, notes, and snippets.

@meow464
Last active November 22, 2025 21:47
Show Gist options
  • Select an option

  • Save meow464/3603ed3dceb13ca02c0877caaaad3e5c to your computer and use it in GitHub Desktop.

Select an option

Save meow464/3603ed3dceb13ca02c0877caaaad3e5c to your computer and use it in GitHub Desktop.
Gtk.ListBox programmatically scroll to Gtk.ListBoxRow
# Copyright (c) 2021 CEO of Programming <ceo_of_email@protonmail.com>
# GPG Fingerprint: 0D0B9511F2B562DC59A2BB3538A6B4C850773FAC
# All rights reserved.
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk
# The license is the two clause BSD, this means you can re-license.
# This class was made in the course of re-implementing the gtk implementation of
# `DetailedList` for the beeware project. Check out beeware if you are
# interested in native multi-platform (desktop, mobile and web) develpment in
# python.
# If this was useful to you please let me know with a comment.
class ScrollableRow(Gtk.ListBoxRow):
"""
You can use and inherit from this class as if it were Gtk.ListBoxRow,
nothing from the original implementation is changed.
There are three new public methods:
scroll_to_top(), scroll_to_center() and scroll_to_bottom(). 'top', 'center'
and 'bottom' are with respect to where in the visible region the row will
move to.
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# We need to wait until this widget is allocated to scroll it in,
# for that we use signals and callbacks. The handler_is of the
# signal is used to disconnect and we store it here.
self._scroll_handler_id_value = None
def scroll_to_top(self):
self.scroll_to_position("TOP")
def scroll_to_center(self):
self.scroll_to_position("CENTER")
def scroll_to_bottom(self):
self.scroll_to_position("BOTTOM")
def scroll_to_position(self, position):
"""
Scrolls the parent Gtk.ListBox until child is in the center of the
view.
`position` is one of "TOP", "CENTER" or "BOTTOM"
"""
if position not in ("TOP", "CENTER", "BOTTOM"):
return False
# Test whether the widget has already been allocated.
list_box = self.get_parent()
_, y = self.translate_coordinates(list_box, 0, 0)
if y >= 0:
self._do_scroll_to_position(position)
else:
# Wait for 'size-allocate' because we will need the
# dimensions of the widget. At this point
# widget.size_request is already available but that's
# only the requested size, not the size it will get.
self._scroll_handler_id = self.connect(
'size-allocate',
# We don't need 'wdiget' and 'gpointer'
lambda widget, gpointer: self._do_scroll_to_position(position)
)
return True
def _do_scroll_to_position(self, position):
# Disconnect the from the signal that called us
self._scroll_handler_id = None
list_box = self.get_parent()
adj = list_box.get_adjustment()
page_size = adj.get_page_size()
# 'height' and 'y' are always valid because we are
# being called after 'size-allocate'
height = self.get_allocation().height
# `y` is the position of the top of the row in the frame of
# reference of the parent Gtk.ListBox
_, y = self.translate_coordinates(list_box, 0, 0)
# `offset` is the remaining space in the visible region
offset = page_size - height
top = y
center = top - offset/2
bottom = top - offset
# `value` is the position the parent Gtk.ListBox will put at the
# top of the visible region.
value = 0.0
if position == "TOP":
value = top
if position == "CENTER":
value = center
if position == "BOTTOM":
value = bottom
if value > 0:
adj.set_value(value)
@property
def _scroll_handler_id(self):
return self._scroll_handler_id_value
@_scroll_handler_id.setter
def _scroll_handler_id(self, value):
if self._scroll_handler_id_value is not None:
self.disconnect(self._scroll_handler_id_value)
self._scroll_handler_id_value = value
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment