Fixed reverse focus order on Shift+Tab in input fields.

This commit is contained in:
23rd
2026-08-03 11:29:48 +03:00
committed by John Preston
parent d32499d089
commit 5fe465bda4
7 changed files with 90 additions and 40 deletions

View File

@@ -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<bool*> handled) {
) | rpl::on_next([=](not_null<TabbedRequest*> request) {
_last->setFocus();
*handled = true;
request->handled = true;
}, _first->lifetime());
_last->tabbed(
) | rpl::on_next([=](not_null<bool*> handled) {
) | rpl::on_next([=](not_null<TabbedRequest*> request) {
_first->setFocus();
*handled = true;
request->handled = true;
}, _last->lifetime());
}

View File

@@ -154,6 +154,7 @@ public:
bool refreshStaleMedia(crl::time threshold);
[[nodiscard]] std::vector<PollAnswer> toPollAnswers() const;
void focusFirst();
void focusLast();
void enableChooseCorrect(bool enabled, bool multiCorrect = false);
@@ -161,7 +162,7 @@ public:
[[nodiscard]] rpl::producer<int> usedCount() const;
[[nodiscard]] rpl::producer<not_null<QWidget*>> scrollToWidget() const;
[[nodiscard]] rpl::producer<> backspaceInFront() const;
[[nodiscard]] rpl::producer<> tabbed() const;
[[nodiscard]] rpl::producer<bool> tabbed() const;
void handlePaste(
not_null<Ui::InputField*> field,
@@ -280,7 +281,7 @@ private:
bool _hasCorrect = false;
rpl::event_stream<not_null<QWidget*>> _scrollToWidget;
rpl::event_stream<> _backspaceInFront;
rpl::event_stream<> _tabbed;
rpl::event_stream<bool> _tabbed;
rpl::lifetime _emojiPanelLifetime;
};
@@ -829,7 +830,7 @@ rpl::producer<> Options::backspaceInFront() const {
return _backspaceInFront.events();
}
rpl::producer<> Options::tabbed() const {
rpl::producer<bool> 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<Ui::RadiobuttonGroup> Options::createChooseCorrectGroup() {
auto result = std::make_shared<Ui::RadiobuttonGroup>(0);
result->setChangedCallback([=](int) {
@@ -1093,14 +1100,20 @@ void Options::initOptionField(not_null<Ui::InputField*> field) {
_scrollToWidget.fire_copy(field);
}, field->lifetime());
field->tabbed(
) | rpl::on_next([=](not_null<bool*> handled) {
) | rpl::on_next([=](not_null<Ui::InputField::TabbedRequest*> 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<QEvent*> event) {
if (event->type() != QEvent::KeyPress
@@ -2758,16 +2771,15 @@ object_ptr<Ui::RpWidget> CreatePollBox::setupContent() {
st::boxDividerLabel),
st::createPollLimitPadding));
question->tabbed(
) | rpl::on_next([=](not_null<bool*> handled) {
description->setFocus();
*handled = true;
}, question->lifetime());
using TabbedRequest = Ui::InputField::TabbedRequest;
description->tabbed(
) | rpl::on_next([=](not_null<bool*> handled) {
options->focusFirst();
*handled = true;
) | rpl::on_next([=](not_null<TabbedRequest*> request) {
if (request->backward) {
question->setFocus();
} else {
options->focusFirst();
}
request->handled = true;
}, description->lifetime());
Ui::AddSkip(container);
@@ -3061,9 +3073,23 @@ object_ptr<Ui::RpWidget> CreatePollBox::setupContent() {
rpl::single(quiz->toggled()) | rpl::then(quiz->toggledChanges()));
addMediaButton(solution, state->solutionMedia);
question->tabbed(
) | rpl::on_next([=](not_null<TabbedRequest*> 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<Ui::RpWidget> CreatePollBox::setupContent() {
}, question->lifetime());
solution->tabbed(
) | rpl::on_next([=](not_null<bool*> handled) {
question->setFocus();
*handled = true;
) | rpl::on_next([=](not_null<TabbedRequest*> request) {
if (request->backward) {
options->focusLast();
} else {
question->setFocus();
}
request->handled = true;
}, solution->lifetime());
const auto updateAddOptionsLocked = [=] {

View File

@@ -69,6 +69,7 @@ public:
[[nodiscard]] bool isValid() const;
[[nodiscard]] std::vector<TodoListItem> toTodoListItems() const;
void focusFirst();
void focusLast();
[[nodiscard]] rpl::producer<int> addedCount() const;
[[nodiscard]] rpl::producer<not_null<QWidget*>> 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*> task, TextWithEntities text) {
_scrollToWidget.fire_copy(field);
}, field->lifetime());
field->tabbed(
) | rpl::on_next([=](not_null<bool*> handled) {
) | rpl::on_next([=](not_null<Ui::InputField::TabbedRequest*> 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<QEvent*> event) {
if (event->type() != QEvent::KeyPress
@@ -1041,9 +1055,13 @@ object_ptr<Ui::RpWidget> EditTodoListBox::setupContent() {
st::createPollLimitPadding));
title->tabbed(
) | rpl::on_next([=](not_null<bool*> handled) {
tasks->focusFirst();
*handled = true;
) | rpl::on_next([=](not_null<Ui::InputField::TabbedRequest*> request) {
if (request->backward) {
tasks->focusLast();
} else {
tasks->focusFirst();
}
request->handled = true;
}, title->lifetime());
Ui::AddSkip(container);

View File

@@ -1955,10 +1955,10 @@ void InitFieldAutocomplete(
}
field->tabbed(
) | rpl::on_next([=](not_null<bool*> handled) {
) | rpl::on_next([=](not_null<Ui::InputField::TabbedRequest*> request) {
if (!raw->isHidden()) {
raw->chooseSelected(FieldAutocomplete::ChooseMethod::ByTab);
*handled = true;
request->handled = true;
}
}, raw->lifetime());

View File

@@ -276,21 +276,22 @@ void EditLinkBox(
}
};
using TabbedRequest = Ui::InputField::TabbedRequest;
url->tabbed(
) | rpl::on_next([=](not_null<bool*> handled) {
) | rpl::on_next([=](not_null<TabbedRequest*> request) {
clearFullSelection(url);
text->setFocus();
*handled = true;
request->handled = true;
}, url->lifetime());
text->tabbed(
) | rpl::on_next([=](not_null<bool*> handled) {
) | rpl::on_next([=](not_null<TabbedRequest*> request) {
if (!url->empty()) {
url->selectAll();
}
clearFullSelection(text);
url->setFocus();
*handled = true;
request->handled = true;
}, text->lifetime());
}

View File

@@ -458,10 +458,10 @@ HistoryWidget::HistoryWidget(
escape();
}, _field->lifetime());
_field->tabbed(
) | rpl::on_next([=](not_null<bool*> handled) {
) | rpl::on_next([=](not_null<Ui::InputField::TabbedRequest*> request) {
if (_supportAutocomplete) {
_supportAutocomplete->activate(_field.data());
*handled = true;
request->handled = true;
}
}, _field->lifetime());
_field->heightChanges(