From 3c8b7dfa7ace7d4071dc821f7650e31ee2138850 Mon Sep 17 00:00:00 2001 From: Ralph Sennhauser Date: Sun, 5 Jul 2026 08:27:18 +0200 Subject: [PATCH] Fix Enter key behaviour on Windows Commit fe192fe58fae7879b8bee0808c0afd31e96cc0d9 made it possible for events to properly propagate to the top window. As on Windows Enter is also used for navigation, i.e. used to trigger the dialogs default action, which in our case seems to be quit. Tell the wxSpinCtrl to fire wxEVT_TEXT_ENTER instead and handle it. The wxCommandEvent wxEVT_TEXT_ENTER doesn't carry the value, so need to use dynamic event binding (which is preferred way anyway). Also show the numPlayerSpinner as a spinner instead of a text field to match visuals to actual behaviour. Fixes: #9026 Signed-off-by: Ralph Sennhauser --- .../ScenarioEditor/Sections/Player/Player.cpp | 49 +++++++++++++------ .../ScenarioEditor/Tools/Common/Brushes.cpp | 8 +++ 2 files changed, 43 insertions(+), 14 deletions(-) diff --git a/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Player/Player.cpp b/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Player/Player.cpp index 83519fe64f..cb051c54a5 100644 --- a/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Player/Player.cpp +++ b/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Player/Player.cpp @@ -192,35 +192,35 @@ public: wxFlexGridSizer* gridSizer = new wxFlexGridSizer(3, 5, 5); gridSizer->AddGrowableCol(2); - wxSpinCtrl* foodCtrl = new wxSpinCtrl(resourceSizer->GetStaticBox(), ID_PlayerFood, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, INT_MAX); + wxSpinCtrl* foodCtrl = new wxSpinCtrl(resourceSizer->GetStaticBox(), ID_PlayerFood, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS | wxTE_PROCESS_ENTER, 0, INT_MAX); gridSizer->Add(new DefaultCheckbox(resourceSizer->GetStaticBox(), ID_DefaultFood, foodCtrl), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL)); gridSizer->Add(new wxStaticText(resourceSizer->GetStaticBox(), wxID_ANY, _("Food")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT)); gridSizer->Add(Tooltipped(foodCtrl, _("Initial value of food resource")), wxSizerFlags().Expand()); m_Controls.food = foodCtrl; - wxSpinCtrl* woodCtrl = new wxSpinCtrl(resourceSizer->GetStaticBox(), ID_PlayerWood, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, INT_MAX); + wxSpinCtrl* woodCtrl = new wxSpinCtrl(resourceSizer->GetStaticBox(), ID_PlayerWood, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS | wxTE_PROCESS_ENTER, 0, INT_MAX); gridSizer->Add(new DefaultCheckbox(resourceSizer->GetStaticBox(), ID_DefaultWood, woodCtrl), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL)); gridSizer->Add(new wxStaticText(resourceSizer->GetStaticBox(), wxID_ANY, _("Wood")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT)); gridSizer->Add(Tooltipped(woodCtrl, _("Initial value of wood resource")), wxSizerFlags().Expand()); m_Controls.wood = woodCtrl; - wxSpinCtrl* metalCtrl = new wxSpinCtrl(resourceSizer->GetStaticBox(), ID_PlayerMetal, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, INT_MAX); + wxSpinCtrl* metalCtrl = new wxSpinCtrl(resourceSizer->GetStaticBox(), ID_PlayerMetal, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS | wxTE_PROCESS_ENTER, 0, INT_MAX); gridSizer->Add(new DefaultCheckbox(resourceSizer->GetStaticBox(), ID_DefaultMetal, metalCtrl), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL)); gridSizer->Add(new wxStaticText(resourceSizer->GetStaticBox(), wxID_ANY, _("Metal")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT)); gridSizer->Add(Tooltipped(metalCtrl, _("Initial value of metal resource")), wxSizerFlags().Expand()); m_Controls.metal = metalCtrl; - wxSpinCtrl* stoneCtrl = new wxSpinCtrl(resourceSizer->GetStaticBox(), ID_PlayerStone, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, INT_MAX); + wxSpinCtrl* stoneCtrl = new wxSpinCtrl(resourceSizer->GetStaticBox(), ID_PlayerStone, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS | wxTE_PROCESS_ENTER, 0, INT_MAX); gridSizer->Add(new DefaultCheckbox(resourceSizer->GetStaticBox(), ID_DefaultStone, stoneCtrl), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL)); gridSizer->Add(new wxStaticText(resourceSizer->GetStaticBox(), wxID_ANY, _("Stone")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT)); gridSizer->Add(Tooltipped(stoneCtrl, _("Initial value of stone resource")), wxSizerFlags().Expand()); m_Controls.stone = stoneCtrl; - wxSpinCtrl* popCtrl = new wxSpinCtrl(resourceSizer->GetStaticBox(), ID_PlayerPop, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, INT_MAX); + wxSpinCtrl* popCtrl = new wxSpinCtrl(resourceSizer->GetStaticBox(), ID_PlayerPop, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS | wxTE_PROCESS_ENTER, 0, INT_MAX); gridSizer->Add(new DefaultCheckbox(resourceSizer->GetStaticBox(), ID_DefaultPop, popCtrl), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL)); gridSizer->Add(new wxStaticText(resourceSizer->GetStaticBox(), wxID_ANY, _("Pop limit")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT)); gridSizer->Add(Tooltipped(popCtrl, @@ -477,6 +477,14 @@ private: } } + void OnEditSpin(wxCommandEvent&) + { + if (!m_InGUIUpdate) + { + SendToEngine(); + } + } + void OnPlayerColor(wxCommandEvent& WXUNUSED(evt)) { if (!m_InGUIUpdate) @@ -493,22 +501,22 @@ private: // and we don't want to handle the same event twice } - void OnNumPlayersSpin(wxSpinEvent& evt) + void SetNumPlayers(int newNumPlayers) { if (!m_InGUIUpdate) { - wxASSERT(evt.GetInt() > 0); + wxASSERT(newNumPlayers > 0); // When wxMessageBox pops up, wxSpinCtrl loses focus, which // forces another EVT_SPINCTRL event, which we don't want // to handle, so we check here for a change - if (evt.GetInt() == (int)m_NumPlayers) + if (newNumPlayers == (int)m_NumPlayers) { return; // No change } size_t oldNumPlayers = m_NumPlayers; - m_NumPlayers = evt.GetInt(); + m_NumPlayers = newNumPlayers; if (m_NumPlayers < oldNumPlayers) { @@ -574,14 +582,17 @@ BEGIN_EVENT_TABLE(PlayerSettingsControl, wxPanel) EVT_BUTTON(ID_CameraClear, PlayerSettingsControl::OnEdit) EVT_CHECKBOX(wxID_ANY, PlayerSettingsControl::OnEdit) EVT_CHOICE(wxID_ANY, PlayerSettingsControl::OnEdit) - EVT_TEXT(ID_NumPlayers, PlayerSettingsControl::OnNumPlayersText) EVT_TEXT(wxID_ANY, PlayerSettingsControl::OnEdit) - EVT_SPINCTRL(ID_NumPlayers, PlayerSettingsControl::OnNumPlayersSpin) EVT_SPINCTRL(ID_PlayerFood, PlayerSettingsControl::OnEditSpin) EVT_SPINCTRL(ID_PlayerWood, PlayerSettingsControl::OnEditSpin) EVT_SPINCTRL(ID_PlayerMetal, PlayerSettingsControl::OnEditSpin) EVT_SPINCTRL(ID_PlayerStone, PlayerSettingsControl::OnEditSpin) EVT_SPINCTRL(ID_PlayerPop, PlayerSettingsControl::OnEditSpin) + EVT_TEXT_ENTER(ID_PlayerFood, PlayerSettingsControl::OnEditSpin) + EVT_TEXT_ENTER(ID_PlayerWood, PlayerSettingsControl::OnEditSpin) + EVT_TEXT_ENTER(ID_PlayerMetal, PlayerSettingsControl::OnEditSpin) + EVT_TEXT_ENTER(ID_PlayerStone, PlayerSettingsControl::OnEditSpin) + EVT_TEXT_ENTER(ID_PlayerPop, PlayerSettingsControl::OnEditSpin) END_EVENT_TABLE(); PlayerSettingsControl::PlayerSettingsControl(wxWindow* parent, ScenarioEditor& scenarioEditor) @@ -604,9 +615,19 @@ PlayerSettingsControl::PlayerSettingsControl(wxWindow* parent, ScenarioEditor& s boxSizer->AddSpacer(10); - wxSpinCtrl* numPlayersSpin = new wxSpinCtrl(topBox, ID_NumPlayers, wxEmptyString, wxDefaultPosition, wxSize(40, -1)); - numPlayersSpin->SetValue(MAX_NUM_PLAYERS); - numPlayersSpin->SetRange(1, MAX_NUM_PLAYERS); + // NOTE: on MSW pressing Enter with wxSpinCtrl is used to navigate, ie + // it triggers the default Window action which is ok / close. So we + // have to explicitly generate the wxCommandEvent wxEVT_TEXT_ENTER and + // capture it. + // https://gitea.wildfiregames.com/0ad/0ad/issues/9026 + + wxSpinCtrl* numPlayersSpin = new wxSpinCtrl(topBox, ID_NumPlayers, + wxEmptyString, wxDefaultPosition, wxDefaultSize, + wxSP_ARROW_KEYS | wxTE_PROCESS_ENTER, + 1, MAX_NUM_PLAYERS, MAX_NUM_PLAYERS); + auto numPlayerSetter = [this, numPlayersSpin](auto&){ SetNumPlayers(numPlayersSpin->GetValue()); }; + numPlayersSpin->Bind(wxEVT_SPINCTRL, numPlayerSetter); + numPlayersSpin->Bind(wxEVT_TEXT_ENTER, numPlayerSetter); boxSizer->Add(numPlayersSpin); gridSizer->Add(boxSizer); diff --git a/source/tools/atlas/AtlasUI/ScenarioEditor/Tools/Common/Brushes.cpp b/source/tools/atlas/AtlasUI/ScenarioEditor/Tools/Common/Brushes.cpp index b27c2ee650..da767ae329 100644 --- a/source/tools/atlas/AtlasUI/ScenarioEditor/Tools/Common/Brushes.cpp +++ b/source/tools/atlas/AtlasUI/ScenarioEditor/Tools/Common/Brushes.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include @@ -186,6 +187,11 @@ void Brush::CreateUI(wxWindow* parent, wxSizer* sizer) sizer->AddSpacer(5); + // NOTE: on MSW pressing Enter with wxSpinCtrl is used to navigate, ie it + // triggers the default Window action which is ok / close. So we have to + // explicitly generate the wxCommandEvent wxEVT_TEXT_ENTER and capture it. + // https://gitea.wildfiregames.com/0ad/0ad/issues/9026 + auto* brushSizeLabel = new wxStaticText(parent, wxID_ANY, _("Size")); auto* brushSizeCtrl = new wxSpinCtrl(parent, wxID_ANY, wxString::Format(_T("%d"), GetSize()), @@ -194,6 +200,7 @@ void Brush::CreateUI(wxWindow* parent, wxSizer* sizer) 0, 100, GetSize()); auto burshSizeSetter = [this, brushSizeCtrl](auto&){ SetSize(brushSizeCtrl->GetValue()); }; brushSizeCtrl->Bind(wxEVT_SPINCTRL, burshSizeSetter); + brushSizeCtrl->Bind(wxEVT_TEXT_ENTER, burshSizeSetter); auto* brushStrengthLabel = new wxStaticText(parent, wxID_ANY, _("Strength")); auto* brushStrengthCtrl = new wxSpinCtrl(parent, wxID_ANY, @@ -203,6 +210,7 @@ void Brush::CreateUI(wxWindow* parent, wxSizer* sizer) 0, 100, static_cast(10.f * GetStrength())); auto burshStrenghtSetter = [this, brushStrengthCtrl](auto&){ SetStrength(brushStrengthCtrl->GetValue() / 10.f); }; brushStrengthCtrl->Bind(wxEVT_SPINCTRL, burshStrenghtSetter); + brushStrengthCtrl->Bind(wxEVT_TEXT_ENTER, burshStrenghtSetter); wxFlexGridSizer* spinnerSizer = new wxFlexGridSizer(2, 5, 5); spinnerSizer->AddGrowableCol(1);