From 5fe465bda4dc8aa80437fbaff5fd2bbbfd7d61c5 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Mon, 3 Aug 2026 11:29:48 +0300 Subject: [PATCH] Fixed reverse focus order on Shift+Tab in input fields. --- .../SourceFiles/boxes/add_contact_box.cpp | 9 +-- .../SourceFiles/boxes/create_poll_box.cpp | 72 +++++++++++++------ .../SourceFiles/boxes/edit_todo_list_box.cpp | 30 ++++++-- .../chat_helpers/field_autocomplete.cpp | 4 +- .../chat_helpers/message_field.cpp | 9 +-- .../SourceFiles/history/history_widget.cpp | 4 +- Telegram/lib_ui | 2 +- 7 files changed, 90 insertions(+), 40 deletions(-) diff --git a/Telegram/SourceFiles/boxes/add_contact_box.cpp b/Telegram/SourceFiles/boxes/add_contact_box.cpp index 16390d109d..73f250438c 100644 --- a/Telegram/SourceFiles/boxes/add_contact_box.cpp +++ b/Telegram/SourceFiles/boxes/add_contact_box.cpp @@ -1579,15 +1579,16 @@ void EditNameBox::prepare() { _last->submits( ) | rpl::on_next([=] { submit(); }, _last->lifetime()); + using TabbedRequest = Ui::InputField::TabbedRequest; _first->tabbed( - ) | rpl::on_next([=](not_null handled) { + ) | rpl::on_next([=](not_null request) { _last->setFocus(); - *handled = true; + request->handled = true; }, _first->lifetime()); _last->tabbed( - ) | rpl::on_next([=](not_null handled) { + ) | rpl::on_next([=](not_null request) { _first->setFocus(); - *handled = true; + request->handled = true; }, _last->lifetime()); } diff --git a/Telegram/SourceFiles/boxes/create_poll_box.cpp b/Telegram/SourceFiles/boxes/create_poll_box.cpp index 5e81b12143..2f8e1ba4b8 100644 --- a/Telegram/SourceFiles/boxes/create_poll_box.cpp +++ b/Telegram/SourceFiles/boxes/create_poll_box.cpp @@ -154,6 +154,7 @@ public: bool refreshStaleMedia(crl::time threshold); [[nodiscard]] std::vector toPollAnswers() const; void focusFirst(); + void focusLast(); void enableChooseCorrect(bool enabled, bool multiCorrect = false); @@ -161,7 +162,7 @@ public: [[nodiscard]] rpl::producer usedCount() const; [[nodiscard]] rpl::producer> scrollToWidget() const; [[nodiscard]] rpl::producer<> backspaceInFront() const; - [[nodiscard]] rpl::producer<> tabbed() const; + [[nodiscard]] rpl::producer tabbed() const; void handlePaste( not_null field, @@ -280,7 +281,7 @@ private: bool _hasCorrect = false; rpl::event_stream> _scrollToWidget; rpl::event_stream<> _backspaceInFront; - rpl::event_stream<> _tabbed; + rpl::event_stream _tabbed; rpl::lifetime _emojiPanelLifetime; }; @@ -829,7 +830,7 @@ rpl::producer<> Options::backspaceInFront() const { return _backspaceInFront.events(); } -rpl::producer<> Options::tabbed() const { +rpl::producer Options::tabbed() const { return _tabbed.events(); } @@ -870,6 +871,12 @@ void Options::focusFirst() { _list.front()->setFocus(); } +void Options::focusLast() { + Expects(!_list.empty()); + + _list.back()->setFocus(); +} + std::shared_ptr Options::createChooseCorrectGroup() { auto result = std::make_shared(0); result->setChangedCallback([=](int) { @@ -1093,14 +1100,20 @@ void Options::initOptionField(not_null field) { _scrollToWidget.fire_copy(field); }, field->lifetime()); field->tabbed( - ) | rpl::on_next([=](not_null handled) { + ) | rpl::on_next([=](not_null request) { const auto index = findField(field); - if (index + 1 < _list.size()) { + if (request->backward) { + if (index > 0) { + _list[index - 1]->setFocus(); + } else { + _tabbed.fire(true); + } + } else if (index + 1 < _list.size()) { _list[index + 1]->setFocus(); } else { - _tabbed.fire({}); + _tabbed.fire(false); } - *handled = true; + request->handled = true; }, field->lifetime()); base::install_event_filter(field, [=](not_null event) { if (event->type() != QEvent::KeyPress @@ -2758,16 +2771,15 @@ object_ptr CreatePollBox::setupContent() { st::boxDividerLabel), st::createPollLimitPadding)); - question->tabbed( - ) | rpl::on_next([=](not_null handled) { - description->setFocus(); - *handled = true; - }, question->lifetime()); - + using TabbedRequest = Ui::InputField::TabbedRequest; description->tabbed( - ) | rpl::on_next([=](not_null handled) { - options->focusFirst(); - *handled = true; + ) | rpl::on_next([=](not_null request) { + if (request->backward) { + question->setFocus(); + } else { + options->focusFirst(); + } + request->handled = true; }, description->lifetime()); Ui::AddSkip(container); @@ -3061,9 +3073,23 @@ object_ptr CreatePollBox::setupContent() { rpl::single(quiz->toggled()) | rpl::then(quiz->toggledChanges())); addMediaButton(solution, state->solutionMedia); + question->tabbed( + ) | rpl::on_next([=](not_null request) { + if (!request->backward) { + description->setFocus(); + } else if (quiz->toggled()) { + solution->setFocus(); + } else { + options->focusLast(); + } + request->handled = true; + }, question->lifetime()); + options->tabbed( - ) | rpl::on_next([=] { - if (quiz->toggled()) { + ) | rpl::on_next([=](bool backward) { + if (backward) { + description->setFocus(); + } else if (quiz->toggled()) { solution->setFocus(); } else { question->setFocus(); @@ -3071,9 +3097,13 @@ object_ptr CreatePollBox::setupContent() { }, question->lifetime()); solution->tabbed( - ) | rpl::on_next([=](not_null handled) { - question->setFocus(); - *handled = true; + ) | rpl::on_next([=](not_null request) { + if (request->backward) { + options->focusLast(); + } else { + question->setFocus(); + } + request->handled = true; }, solution->lifetime()); const auto updateAddOptionsLocked = [=] { diff --git a/Telegram/SourceFiles/boxes/edit_todo_list_box.cpp b/Telegram/SourceFiles/boxes/edit_todo_list_box.cpp index 00ef62176e..9ea93e4917 100644 --- a/Telegram/SourceFiles/boxes/edit_todo_list_box.cpp +++ b/Telegram/SourceFiles/boxes/edit_todo_list_box.cpp @@ -69,6 +69,7 @@ public: [[nodiscard]] bool isValid() const; [[nodiscard]] std::vector toTodoListItems() const; void focusFirst(); + void focusLast(); [[nodiscard]] rpl::producer addedCount() const; [[nodiscard]] rpl::producer> scrollToWidget() const; @@ -579,6 +580,12 @@ void Tasks::focusFirst() { FocusAtEnd((_list.begin() + locked)->get()->field()); } +void Tasks::focusLast() { + Expects(!_list.empty()); + + _list.back()->setFocus(); +} + bool Tasks::correctShadows() const { // Last one should be without shadow. const auto noShadow = ranges::find( @@ -763,14 +770,21 @@ void Tasks::initTaskField(not_null task, TextWithEntities text) { _scrollToWidget.fire_copy(field); }, field->lifetime()); field->tabbed( - ) | rpl::on_next([=](not_null handled) { + ) | rpl::on_next([=](not_null request) { const auto index = findField(field); - if (index + 1 < _list.size()) { + if (request->backward) { + const auto locked = _existingLocked ? _existingCount : 0; + if (index > locked) { + _list[index - 1]->setFocus(); + } else { + _tabbed.fire({}); + } + } else if (index + 1 < _list.size()) { _list[index + 1]->setFocus(); } else { _tabbed.fire({}); } - *handled = true; + request->handled = true; }, field->lifetime()); base::install_event_filter(field, [=](not_null event) { if (event->type() != QEvent::KeyPress @@ -1041,9 +1055,13 @@ object_ptr EditTodoListBox::setupContent() { st::createPollLimitPadding)); title->tabbed( - ) | rpl::on_next([=](not_null handled) { - tasks->focusFirst(); - *handled = true; + ) | rpl::on_next([=](not_null request) { + if (request->backward) { + tasks->focusLast(); + } else { + tasks->focusFirst(); + } + request->handled = true; }, title->lifetime()); Ui::AddSkip(container); diff --git a/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp b/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp index 33a25f4748..918f17ce6c 100644 --- a/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp +++ b/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp @@ -1955,10 +1955,10 @@ void InitFieldAutocomplete( } field->tabbed( - ) | rpl::on_next([=](not_null handled) { + ) | rpl::on_next([=](not_null request) { if (!raw->isHidden()) { raw->chooseSelected(FieldAutocomplete::ChooseMethod::ByTab); - *handled = true; + request->handled = true; } }, raw->lifetime()); diff --git a/Telegram/SourceFiles/chat_helpers/message_field.cpp b/Telegram/SourceFiles/chat_helpers/message_field.cpp index 2023b46ea7..0382ee6768 100644 --- a/Telegram/SourceFiles/chat_helpers/message_field.cpp +++ b/Telegram/SourceFiles/chat_helpers/message_field.cpp @@ -276,21 +276,22 @@ void EditLinkBox( } }; + using TabbedRequest = Ui::InputField::TabbedRequest; url->tabbed( - ) | rpl::on_next([=](not_null handled) { + ) | rpl::on_next([=](not_null request) { clearFullSelection(url); text->setFocus(); - *handled = true; + request->handled = true; }, url->lifetime()); text->tabbed( - ) | rpl::on_next([=](not_null handled) { + ) | rpl::on_next([=](not_null request) { if (!url->empty()) { url->selectAll(); } clearFullSelection(text); url->setFocus(); - *handled = true; + request->handled = true; }, text->lifetime()); } diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index f58c42131a..1b6a78ba3a 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -458,10 +458,10 @@ HistoryWidget::HistoryWidget( escape(); }, _field->lifetime()); _field->tabbed( - ) | rpl::on_next([=](not_null handled) { + ) | rpl::on_next([=](not_null request) { if (_supportAutocomplete) { _supportAutocomplete->activate(_field.data()); - *handled = true; + request->handled = true; } }, _field->lifetime()); _field->heightChanges( diff --git a/Telegram/lib_ui b/Telegram/lib_ui index 84ab0a4bbc..3b58165640 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit 84ab0a4bbcfca5e3e966eecaf1f8c73bcc284ef0 +Subproject commit 3b5816564053ef15d70ee82a6514d9db082f13da