diff --git a/musicbot/utils/userinteraction/navbar_range.py b/musicbot/utils/userinteraction/navbar_range.py new file mode 100644 index 0000000..d7cb6f5 --- /dev/null +++ b/musicbot/utils/userinteraction/navbar_range.py @@ -0,0 +1,28 @@ + +class NavBarRange: + def __init__(self, num_items: int, current_item: int, max_iter_length: int, add_ends: bool = True): + self.num_items = num_items + self.current_item = current_item + self.max_iter_length = max_iter_length + self.add_ends = add_ends + + def __iter__(self): + if (self.max_iter_length > self.num_items): + return iter(range(0, self.num_items)) + + # Start can not be before 0 + start = max(0, self.current_item - self.max_iter_length // 2) + # Start can not be closer to end than max_iter_length + start = min(start, self.num_items - self.max_iter_length) + end = start + self.max_iter_length + + mid_select_items = list(range(start, end)) + if self.add_ends: + mid_select_items[0] = 0 + mid_select_items[-1] = self.num_items - 1 + return iter(mid_select_items) + + def _make_truncated_range(self, start, end): + actual_start = max(start, 0) + actual_end = min(self.max_iter_length, end) + return range(actual_start, actual_end) diff --git a/musicbot/utils/userinteraction/scroller.py b/musicbot/utils/userinteraction/scroller.py index 67425d3..2f96875 100644 --- a/musicbot/utils/userinteraction/scroller.py +++ b/musicbot/utils/userinteraction/scroller.py @@ -8,8 +8,11 @@ import discord from bot import MusicBot +from .navbar_range import NavBarRange from .paginators import BasePaginator, CantScrollError +DISCORD_MAX_SELECTOR_OPTIONS = 25 + class ClearMode(Flag): Timeout = auto() @@ -109,7 +112,7 @@ class Scroller: def update_view(self): if self.use_nav_bar: - self.navigator.placeholder = f"Page: {self.page_number + 1}/{len(self.paginator.pages)}" + self._update_navbar_items() if self.is_scrolling_paginator: self.back_button.disabled = self.page_number == 0 @@ -120,10 +123,32 @@ class Scroller: self.view.add_item(item=button) if self.use_nav_bar: - self.navigator = ScrollerNav(self.navigate, placeholder="Navigate to page", row=4) + self.navigator = None + self._update_navbar_items() + + def _update_navbar_items(self): + placeholder = f"Page: {self.page_number + 1}/{len(self.paginator.pages)}" + did_exist = False + # If we have more than 25 items, the navigator needs to be re-created. + num_selectable_items = len(self.paginator.pages) + if num_selectable_items > DISCORD_MAX_SELECTOR_OPTIONS and self.navigator is not None: + self.view.remove_item(self.navigator) + self.navigator = None + did_exist = True + + if self.navigator is None: + if not did_exist: + placeholder = "Navigate to page" + self.navigator = ScrollerNav(self.navigate, placeholder=placeholder, row=4) self.view.add_item(item=self.navigator) - for (i, _) in enumerate(self.paginator.pages): - self.navigator.add_option(label=str(i+1), value=str(i)) + navigatable_items = NavBarRange(num_items=num_selectable_items, + current_item=self.page_number, + max_iter_length=DISCORD_MAX_SELECTOR_OPTIONS, + add_ends=True) + for page in navigatable_items: + self.navigator.add_option(label=str(page+1), value=str(page)) + else: + self.navigator.placeholder = placeholder async def stop(self, was_timeout: bool, clear_scroller_view: bool = True): self.is_scrolling_paginator = False diff --git a/musicbot/utils/userinteraction/test_centered_range.py b/musicbot/utils/userinteraction/test_centered_range.py new file mode 100644 index 0000000..6b1256d --- /dev/null +++ b/musicbot/utils/userinteraction/test_centered_range.py @@ -0,0 +1,64 @@ + +from .navbar_range import NavBarRange + + +class TestCenteredRange(): + def test_centered_basic_range(self): + nav_range = NavBarRange(num_items=15, current_item=6, max_iter_length=7, add_ends=False) + assert list(nav_range) == [3, 4, 5, 6, 7, 8, 9] + + def test_centered_padded_range(self): + nav_range = NavBarRange(num_items=15, current_item=6, max_iter_length=7, add_ends=True) + assert list(nav_range) == [0, 4, 5, 6, 7, 8, 14] + + def test_beginning_basic_range(self): + nav_range = NavBarRange(num_items=15, current_item=0, max_iter_length=7, add_ends=False) + assert list(nav_range) == [0, 1, 2, 3, 4, 5, 6] + nav_range = NavBarRange(num_items=15, current_item=1, max_iter_length=7, add_ends=False) + assert list(nav_range) == [0, 1, 2, 3, 4, 5, 6] + nav_range = NavBarRange(num_items=15, current_item=3, max_iter_length=7, add_ends=False) + assert list(nav_range) == [0, 1, 2, 3, 4, 5, 6] + + # Should be shifted + nav_range = NavBarRange(num_items=15, current_item=4, max_iter_length=7, add_ends=False) + assert list(nav_range) == [1, 2, 3, 4, 5, 6, 7] + + def test_beginning_padded_range(self): + nav_range = NavBarRange(num_items=15, current_item=0, max_iter_length=7, add_ends=True) + assert list(nav_range) == [0, 1, 2, 3, 4, 5, 14] + nav_range = NavBarRange(num_items=15, current_item=1, max_iter_length=7, add_ends=True) + assert list(nav_range) == [0, 1, 2, 3, 4, 5, 14] + nav_range = NavBarRange(num_items=15, current_item=3, max_iter_length=7, add_ends=True) + assert list(nav_range) == [0, 1, 2, 3, 4, 5, 14] + + # Should be shifted + nav_range = NavBarRange(num_items=15, current_item=4, max_iter_length=7, add_ends=True) + assert list(nav_range) == [0, 2, 3, 4, 5, 6, 14] + + def test_end_basic_range(self): + nav_range = NavBarRange(num_items=15, current_item=14, max_iter_length=7, add_ends=False) + assert list(nav_range) == [8, 9, 10, 11, 12, 13, 14] + nav_range = NavBarRange(num_items=15, current_item=13, max_iter_length=7, add_ends=False) + assert list(nav_range) == [8, 9, 10, 11, 12, 13, 14] + nav_range = NavBarRange(num_items=15, current_item=11, max_iter_length=7, add_ends=False) + assert list(nav_range) == [8, 9, 10, 11, 12, 13, 14] + + # Should be shifted + nav_range = NavBarRange(num_items=15, current_item=10, max_iter_length=7, add_ends=False) + assert list(nav_range) == [7, 8, 9, 10, 11, 12, 13] + + def test_end_padded_range(self): + nav_range = NavBarRange(num_items=15, current_item=14, max_iter_length=7, add_ends=True) + assert list(nav_range) == [0, 9, 10, 11, 12, 13, 14] + nav_range = NavBarRange(num_items=15, current_item=13, max_iter_length=7, add_ends=True) + assert list(nav_range) == [0, 9, 10, 11, 12, 13, 14] + nav_range = NavBarRange(num_items=15, current_item=11, max_iter_length=7, add_ends=True) + assert list(nav_range) == [0, 9, 10, 11, 12, 13, 14] + + # Should be shifted + nav_range = NavBarRange(num_items=15, current_item=10, max_iter_length=7, add_ends=True) + assert list(nav_range) == [0, 8, 9, 10, 11, 12, 14] + + def test_short_range(self): + nav_range = NavBarRange(num_items=15, current_item=7, max_iter_length=25) + assert list(nav_range) == list(range(15))