This commit is contained in:
N0rmalUser 2025-08-18 00:17:59 +03:00 committed by GitHub
commit fb970a4f1b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 85 additions and 16 deletions

28
CHANGES/1522.feature.rst Normal file
View file

@ -0,0 +1,28 @@
====
Added functionality for negative numbers in keyboard adjust.
====
If the function gets a negative number in `adjust()`, it applies them to the lower buttons in the same way that positive numbers affect the upper buttons.
Negative numbers have a higher priority in formatting.
You can make that:
.. image:: https://github.com/aiogram/aiogram/assets/104172267/4d55a4c6-23fa-4a73-824b-0f2d7c879431
With this code:
.. code-block:: python
def test_markup():
builder = ReplyKeyboardBuilder()
builder.row(*(KeyboardButton(text=f"test-{index}") for index in range(6)))
builder.button(text="Add", callback_data="add")
builder.button(text="Cancel", callback_data="cancel")
builder.adjust(3, -1, -1)
return builder.as_markup()
@router.message(Command('test'))
async def test_handler(msg: Message) -> None:
await msg.answer("This is TEST!!!", reply_markup=test_markup())
...

View file

@ -211,12 +211,39 @@ class KeyboardBuilder(Generic[ButtonType], ABC):
)
return self
def _markup_constructor(self, sizes: list, buttons: list, repeat: bool) -> list:
"""
Constructs the keyboard according to the specified parameters.
:param sizes:
:param buttons:
:param repeat:
:return:
"""
markup = []
row: List[ButtonType] = []
validated_sizes = map(self._validate_size, sizes)
sizes_iter = repeat_all(validated_sizes) if repeat else repeat_last(validated_sizes)
size = next(sizes_iter)
for button in buttons:
if len(row) >= size:
markup.append(row)
size = next(sizes_iter)
row = []
row.append(button)
if row:
markup.append(row)
return markup
def adjust(self, *sizes: int, repeat: bool = False) -> "KeyboardBuilder[ButtonType]":
"""
Adjust previously added buttons to specific row sizes.
By default, when the sum of passed sizes is lower than buttons count the last
one size will be used for tail of the markup.
If size is negative - it will be used for the last buttons. Negative values are more important than positive ones.
If repeat=True is passed - all sizes will be cycled when available more buttons
count than all sizes
@ -224,23 +251,31 @@ class KeyboardBuilder(Generic[ButtonType], ABC):
:param repeat:
:return:
"""
if not sizes:
sizes = (self.max_width,)
validated_sizes = map(self._validate_size, sizes)
sizes_iter = repeat_all(validated_sizes) if repeat else repeat_last(validated_sizes)
size = next(sizes_iter)
if sizes:
negative_sizes = [-size for size in sizes if size < 0]
positive_sizes = (
[size for size in sizes if size >= 0]
if len(negative_sizes) != len(sizes)
else (self.max_width,)
)
else:
positive_sizes = (self.max_width,)
negative_sizes = []
markup = []
row: List[ButtonType] = []
for button in self.buttons:
if len(row) >= size:
markup.append(row)
size = next(sizes_iter)
row = []
row.append(button)
if row:
markup.append(row)
buttons = list(self.buttons)
if positive_sizes:
markup.extend(
self._markup_constructor(
positive_sizes,
buttons[: -sum(negative_sizes)] if negative_sizes else buttons,
repeat,
)
)
if negative_sizes:
markup.extend(
self._markup_constructor(negative_sizes, buttons[-sum(negative_sizes) :], repeat)
)
self._markup = markup
return self

View file

@ -193,11 +193,17 @@ class TestKeyboardBuilder:
[
[0, False, [], []],
[0, False, [2], []],
[0, False, [-2], []],
[1, False, [2], [1]],
[1, False, [-2], [1]],
[3, False, [2], [2, 1]],
[3, False, [-2], [1, 2]],
[10, False, [3, 2, 1], [3, 2, 1, 1, 1, 1, 1]],
[10, False, [3, 2, -1], [3, 2, 2, 2, 1]],
[10, False, [3, -2, 1], [3, 1, 1, 1, 1, 1, 2]],
[12, False, [], [10, 2]],
[12, True, [3, 2, 1], [3, 2, 1, 3, 2, 1]],
[12, True, [3, -1, -4], [3, 3, 1, 1, 4]],
],
)
def test_adjust(self, count, repeat, sizes, shape):