Last active
November 22, 2025 21:47
-
-
Save meow464/3603ed3dceb13ca02c0877caaaad3e5c to your computer and use it in GitHub Desktop.
Gtk.ListBox programmatically scroll to Gtk.ListBoxRow
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # 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