From 872c37f1a28541c7b56cc60f87244413e559e40a Mon Sep 17 00:00:00 2001 From: Ilia Petrov-Komotskii Date: Thu, 16 Feb 2023 15:25:29 +0300 Subject: [PATCH 01/10] Made save / restore of the main screen size and position using wxConfig --- source/interface_derived.cpp | 29 +++++++++++++++++++++++++++++ source/interface_derived.hpp | 4 +--- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/source/interface_derived.cpp b/source/interface_derived.cpp index 32b20012..41751386 100644 --- a/source/interface_derived.cpp +++ b/source/interface_derived.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #if defined _WIN32 #include "dirent.h" #else @@ -29,6 +30,11 @@ using namespace std::filesystem; #define LEARN_TAB 2 #define WEBVIEW 2000 +#define CONFIG_NAME "com.nativeunityhub.config" +#define SETTING_WIDTH "W_WIDTH" +#define SETTING_HEIGHT "W_HEIGHT" +#define SETTING_POSITION_X "POSITION_X" +#define SETTING_POSITION_Y "POSITION_Y" //the web view unloads after 5 minutes of page hidden const int TIMER_LENGTH = 5 * 1000 * 60; @@ -74,6 +80,14 @@ wxEND_EVENT_TABLE() //call superclass constructor MainFrameDerived::MainFrameDerived() : MainFrame(NULL){ + // set saved position and size + wxConfig *config = new wxConfig(CONFIG_NAME); + int width, height, x, y; + if (config->Read(SETTING_WIDTH, &width) && config->Read(SETTING_HEIGHT, &height) && + config->Read(SETTING_POSITION_X, &x) && config->Read(SETTING_POSITION_Y, &y)) { + this->SetSize(x, y, width, height); + } + delete config; //set up project list columns { string cols[] = {"Project Name","Unity Version","Last Modified","Path"}; @@ -109,6 +123,21 @@ MainFrameDerived::MainFrameDerived() : MainFrame(NULL){ projSearchCtrl->SetFocus(); } +void MainFrameDerived::OnQuit(wxCommandEvent&){ + // save window size and position + wxConfig *config = new wxConfig(CONFIG_NAME); + int width, height, x, y; + this->GetSize(&width, &height); + this->GetPosition(&x, &y); + config->Write(SETTING_WIDTH, width); + config->Write(SETTING_HEIGHT, height); + config->Write(SETTING_POSITION_X, x); + config->Write(SETTING_POSITION_Y, y); + delete config; + + Close(); +} + void MainFrameDerived::OnSelectProject(wxListEvent&){ for (auto ptr : projectActionItems){ ptr->Enable(); diff --git a/source/interface_derived.hpp b/source/interface_derived.hpp index d1b694e8..f3c86ecd 100644 --- a/source/interface_derived.hpp +++ b/source/interface_derived.hpp @@ -49,9 +49,7 @@ class MainFrameDerived : public MainFrame{ void OnSelectEditor(wxCommandEvent&); void OnSelectEditorPath(wxCommandEvent&); - void OnQuit(wxCommandEvent&) { - Close(); - } + void OnQuit(wxCommandEvent&); wxWindow* const projectActionItems[3]{ revealProjBtn, From c1c6c7c2db15e3c0afd08d65e1e21d300ad9644c Mon Sep 17 00:00:00 2001 From: Ilia Petrov-Komotskii Date: Fri, 30 Oct 2026 00:34:30 +0100 Subject: [PATCH 02/10] fix: Aftermerge issues --- source/interface_derived.cpp | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/source/interface_derived.cpp b/source/interface_derived.cpp index 7a1e88d0..4c99901c 100644 --- a/source/interface_derived.cpp +++ b/source/interface_derived.cpp @@ -12,11 +12,6 @@ #include #include #include -#if defined _WIN32 -#include "dirent.h" -#else -#include -#endif #include @@ -85,13 +80,13 @@ wxEND_EVENT_TABLE() //call superclass constructor MainFrameDerived::MainFrameDerived() : MainFrame(NULL){ // set saved position and size - wxConfig *config = new wxConfig(CONFIG_NAME); + wxConfig *windowConfig = new wxConfig(CONFIG_NAME); int width, height, x, y; - if (config->Read(SETTING_WIDTH, &width) && config->Read(SETTING_HEIGHT, &height) && - config->Read(SETTING_POSITION_X, &x) && config->Read(SETTING_POSITION_Y, &y)) { + if (windowConfig->Read(SETTING_WIDTH, &width) && windowConfig->Read(SETTING_HEIGHT, &height) && + windowConfig->Read(SETTING_POSITION_X, &x) && windowConfig->Read(SETTING_POSITION_Y, &y)) { this->SetSize(x, y, width, height); } - delete config; + delete windowConfig; // load prefs wxConfig config(config_name.data()); @@ -191,11 +186,6 @@ void MainFrameDerived::OnSelectEditorPath(wxCommandEvent&){ } } -void MainFrameDerived::OnQuit(wxCommandEvent&) -{ - Close(); -} - /** Loads the data in the main view. If anything is currently loaded, it will be cleared and re-loaded */ From d4b045a687e725c496487effcb6bd05de83f8dd8 Mon Sep 17 00:00:00 2001 From: Ilia Petrov-Komotskii Date: Fri, 30 Oct 2026 22:27:36 +0100 Subject: [PATCH 03/10] fix: Save window geometry when closing with the close button --- source/interface_derived.cpp | 8 +++++++- source/interface_derived.hpp | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/source/interface_derived.cpp b/source/interface_derived.cpp index 4c99901c..a896a4e4 100644 --- a/source/interface_derived.cpp +++ b/source/interface_derived.cpp @@ -48,6 +48,7 @@ EVT_MENU(wxID_REFRESH, MainFrameDerived::OnReloadData) EVT_MENU(wxID_UNINDENT, MainFrameDerived::OnSource) EVT_MENU(wxID_TOP, MainFrameDerived::OnUpdate) EVT_MENU(wxID_EXIT, MainFrameDerived::OnQuit) +EVT_CLOSE(MainFrameDerived::OnClose) EVT_BUTTON(wxID_ADD,MainFrameDerived::OnAddProject) EVT_BUTTON(wxID_NEW,MainFrameDerived::OnCreateProject) EVT_BUTTON(wxID_FIND,MainFrameDerived::OnLocateInstall) @@ -128,6 +129,10 @@ MainFrameDerived::MainFrameDerived() : MainFrame(NULL){ } void MainFrameDerived::OnQuit(wxCommandEvent&){ + Close(); +} + +void MainFrameDerived::OnClose(wxCloseEvent& event){ // save window size and position wxConfig *config = new wxConfig(CONFIG_NAME); int width, height, x, y; @@ -139,7 +144,8 @@ void MainFrameDerived::OnQuit(wxCommandEvent&){ config->Write(SETTING_POSITION_Y, y); delete config; - Close(); + // let the default handler destroy the window + event.Skip(); } void MainFrameDerived::OnSelectProject(wxListEvent&){ diff --git a/source/interface_derived.hpp b/source/interface_derived.hpp index 9eb67783..503e142c 100644 --- a/source/interface_derived.hpp +++ b/source/interface_derived.hpp @@ -61,6 +61,7 @@ class MainFrameDerived : public MainFrame{ void OnSelectEditorPath(wxCommandEvent&); void OnQuit(wxCommandEvent&); + void OnClose(wxCloseEvent&); wxWindow* const projectActionItems[3]{ revealProjBtn, From f9cfa4c9c0d9d7e3d06c7ec68cd4bfac1df78de1 Mon Sep 17 00:00:00 2001 From: Ilia Petrov-Komotskii Date: Fri, 30 Oct 2026 22:28:36 +0100 Subject: [PATCH 04/10] refactor: Store window geometry in the app's existing config --- source/interface_derived.cpp | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/source/interface_derived.cpp b/source/interface_derived.cpp index a896a4e4..65b6f28a 100644 --- a/source/interface_derived.cpp +++ b/source/interface_derived.cpp @@ -24,17 +24,16 @@ using namespace std::filesystem; #define LEARN_TAB 2 #define WEBVIEW 2000 -#define CONFIG_NAME "com.nativeunityhub.config" -#define SETTING_WIDTH "W_WIDTH" -#define SETTING_HEIGHT "W_HEIGHT" -#define SETTING_POSITION_X "POSITION_X" -#define SETTING_POSITION_Y "POSITION_Y" //the web view unloads after 5 minutes of page hidden const int TIMER_LENGTH = 5 * 1000 * 60; constexpr std::string_view config_name = "com.ravbug.unityhubnative.config"; constexpr std::string_view config_sortCol = "sortColIndex"; constexpr std::string_view config_sortAscending = "sortAscending"; +constexpr std::string_view config_windowWidth = "windowWidth"; +constexpr std::string_view config_windowHeight = "windowHeight"; +constexpr std::string_view config_windowX = "windowX"; +constexpr std::string_view config_windowY = "windowY"; //Declare events here wxBEGIN_EVENT_TABLE(MainFrameDerived, wxFrame) @@ -80,17 +79,16 @@ wxEND_EVENT_TABLE() //call superclass constructor MainFrameDerived::MainFrameDerived() : MainFrame(NULL){ + // load prefs + wxConfig config(config_name.data()); + // set saved position and size - wxConfig *windowConfig = new wxConfig(CONFIG_NAME); int width, height, x, y; - if (windowConfig->Read(SETTING_WIDTH, &width) && windowConfig->Read(SETTING_HEIGHT, &height) && - windowConfig->Read(SETTING_POSITION_X, &x) && windowConfig->Read(SETTING_POSITION_Y, &y)) { + if (config.Read(config_windowWidth.data(), &width) && config.Read(config_windowHeight.data(), &height) && + config.Read(config_windowX.data(), &x) && config.Read(config_windowY.data(), &y)) { this->SetSize(x, y, width, height); } - delete windowConfig; - // load prefs - wxConfig config(config_name.data()); sortColumn = std::clamp(config.ReadLong(config_sortCol.data(), 0), 0, 3); sortAscending = config.ReadBool(config_sortAscending.data(), false); @@ -134,15 +132,14 @@ void MainFrameDerived::OnQuit(wxCommandEvent&){ void MainFrameDerived::OnClose(wxCloseEvent& event){ // save window size and position - wxConfig *config = new wxConfig(CONFIG_NAME); + wxConfig config(config_name.data()); int width, height, x, y; this->GetSize(&width, &height); this->GetPosition(&x, &y); - config->Write(SETTING_WIDTH, width); - config->Write(SETTING_HEIGHT, height); - config->Write(SETTING_POSITION_X, x); - config->Write(SETTING_POSITION_Y, y); - delete config; + config.Write(config_windowWidth.data(), width); + config.Write(config_windowHeight.data(), height); + config.Write(config_windowX.data(), x); + config.Write(config_windowY.data(), y); // let the default handler destroy the window event.Skip(); From abc2c4ff2f14d12acbaae630dfe8a807ddff9a27 Mon Sep 17 00:00:00 2001 From: Ilia Petrov-Komotskii Date: Fri, 30 Oct 2026 22:29:14 +0100 Subject: [PATCH 05/10] fix: Skip saving window geometry when the window is minimized --- source/interface_derived.cpp | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/source/interface_derived.cpp b/source/interface_derived.cpp index 65b6f28a..0b9a2363 100644 --- a/source/interface_derived.cpp +++ b/source/interface_derived.cpp @@ -131,15 +131,17 @@ void MainFrameDerived::OnQuit(wxCommandEvent&){ } void MainFrameDerived::OnClose(wxCloseEvent& event){ - // save window size and position - wxConfig config(config_name.data()); - int width, height, x, y; - this->GetSize(&width, &height); - this->GetPosition(&x, &y); - config.Write(config_windowWidth.data(), width); - config.Write(config_windowHeight.data(), height); - config.Write(config_windowX.data(), x); - config.Write(config_windowY.data(), y); + // save window size and position, unless minimized (its geometry is not meaningful then) + if (!this->IsIconized()) { + wxConfig config(config_name.data()); + int width, height, x, y; + this->GetSize(&width, &height); + this->GetPosition(&x, &y); + config.Write(config_windowWidth.data(), width); + config.Write(config_windowHeight.data(), height); + config.Write(config_windowX.data(), x); + config.Write(config_windowY.data(), y); + } // let the default handler destroy the window event.Skip(); From 1878abf5211db7678cda1ba2a2740a2795b84aa2 Mon Sep 17 00:00:00 2001 From: Ilia Petrov-Komotskii Date: Fri, 30 Oct 2026 23:40:11 +0100 Subject: [PATCH 06/10] feat: Save window rect on minimize, so if app quit minimized it can save the latest visible state --- source/interface_derived.cpp | 39 ++++++++++++++++++++++++++---------- source/interface_derived.hpp | 6 ++++++ 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/source/interface_derived.cpp b/source/interface_derived.cpp index 0b9a2363..0fc05ee9 100644 --- a/source/interface_derived.cpp +++ b/source/interface_derived.cpp @@ -48,6 +48,8 @@ EVT_MENU(wxID_UNINDENT, MainFrameDerived::OnSource) EVT_MENU(wxID_TOP, MainFrameDerived::OnUpdate) EVT_MENU(wxID_EXIT, MainFrameDerived::OnQuit) EVT_CLOSE(MainFrameDerived::OnClose) +EVT_SIZE(MainFrameDerived::OnSize) +EVT_MOVE(MainFrameDerived::OnMove) EVT_BUTTON(wxID_ADD,MainFrameDerived::OnAddProject) EVT_BUTTON(wxID_NEW,MainFrameDerived::OnCreateProject) EVT_BUTTON(wxID_FIND,MainFrameDerived::OnLocateInstall) @@ -88,6 +90,7 @@ MainFrameDerived::MainFrameDerived() : MainFrame(NULL){ config.Read(config_windowX.data(), &x) && config.Read(config_windowY.data(), &y)) { this->SetSize(x, y, width, height); } + windowRect = this->GetRect(); sortColumn = std::clamp(config.ReadLong(config_sortCol.data(), 0), 0, 3); sortAscending = config.ReadBool(config_sortAscending.data(), false); @@ -131,22 +134,36 @@ void MainFrameDerived::OnQuit(wxCommandEvent&){ } void MainFrameDerived::OnClose(wxCloseEvent& event){ - // save window size and position, unless minimized (its geometry is not meaningful then) - if (!this->IsIconized()) { - wxConfig config(config_name.data()); - int width, height, x, y; - this->GetSize(&width, &height); - this->GetPosition(&x, &y); - config.Write(config_windowWidth.data(), width); - config.Write(config_windowHeight.data(), height); - config.Write(config_windowX.data(), x); - config.Write(config_windowY.data(), y); - } + // save the last window size and position from before it was minimized (if it is) + wxConfig config(config_name.data()); + config.Write(config_windowWidth.data(), windowRect.width); + config.Write(config_windowHeight.data(), windowRect.height); + config.Write(config_windowX.data(), windowRect.x); + config.Write(config_windowY.data(), windowRect.y); // let the default handler destroy the window event.Skip(); } +void MainFrameDerived::OnSize(wxSizeEvent& event){ + RememberWindowRect(); + event.Skip(); +} + +void MainFrameDerived::OnMove(wxMoveEvent& event){ + RememberWindowRect(); + event.Skip(); +} + +/** + Stores the current window geometry, unless the window is minimized (its geometry is not meaningful then) + */ +void MainFrameDerived::RememberWindowRect(){ + if (!this->IsIconized()) { + windowRect = this->GetRect(); + } +} + void MainFrameDerived::OnSelectProject(wxListEvent&){ for (auto ptr : projectActionItems){ ptr->Enable(); diff --git a/source/interface_derived.hpp b/source/interface_derived.hpp index 503e142c..19fe9111 100644 --- a/source/interface_derived.hpp +++ b/source/interface_derived.hpp @@ -62,6 +62,9 @@ class MainFrameDerived : public MainFrame{ void OnQuit(wxCommandEvent&); void OnClose(wxCloseEvent&); + void OnSize(wxSizeEvent&); + void OnMove(wxMoveEvent&); + void RememberWindowRect(); wxWindow* const projectActionItems[3]{ revealProjBtn, @@ -93,6 +96,9 @@ class MainFrameDerived : public MainFrame{ // Sorting state int sortColumn = 0; // Default sort by name (column 0) bool sortAscending = true; // Default ascending + + // Last window geometry while not minimized, saved on close + wxRect windowRect; //window events void OnAbout(wxCommandEvent& event); From 1a6d3a1a80437cdadbee368aa363e2a57ec0bd11 Mon Sep 17 00:00:00 2001 From: Ilia Petrov-Komotskii Date: Sat, 31 Oct 2026 01:05:55 +0100 Subject: [PATCH 07/10] feat: Save main projects table parameters as well --- source/interface_derived.cpp | 51 ++++++++++++++++++++++++++++++------ source/interface_derived.hpp | 3 +++ 2 files changed, 46 insertions(+), 8 deletions(-) diff --git a/source/interface_derived.cpp b/source/interface_derived.cpp index 0fc05ee9..59386163 100644 --- a/source/interface_derived.cpp +++ b/source/interface_derived.cpp @@ -34,6 +34,7 @@ constexpr std::string_view config_windowWidth = "windowWidth"; constexpr std::string_view config_windowHeight = "windowHeight"; constexpr std::string_view config_windowX = "windowX"; constexpr std::string_view config_windowY = "windowY"; +constexpr std::string_view config_columnWidth = "projectColumnWidth"; //Declare events here wxBEGIN_EVENT_TABLE(MainFrameDerived, wxFrame) @@ -68,6 +69,7 @@ EVT_LIST_ITEM_SELECTED(wxID_HARDDISK, MainFrameDerived::OnSelectProject) EVT_LIST_ITEM_DESELECTED(wxID_HARDDISK, MainFrameDerived::OnDeselectProject) EVT_LIST_DELETE_ITEM(wxID_HARDDISK, MainFrameDerived::OnDeselectProject) EVT_LIST_COL_CLICK(wxID_HARDDISK, MainFrameDerived::OnColumnClick) +EVT_LIST_COL_END_DRAG(wxID_HARDDISK, MainFrameDerived::OnColumnResized) EVT_LISTBOX(wxID_FLOPPY, MainFrameDerived::OnSelectEditor) EVT_LISTBOX(wxID_HOME, MainFrameDerived::OnSelectEditorPath) @@ -93,7 +95,7 @@ MainFrameDerived::MainFrameDerived() : MainFrame(NULL){ windowRect = this->GetRect(); sortColumn = std::clamp(config.ReadLong(config_sortCol.data(), 0), 0, 3); - sortAscending = config.ReadBool(config_sortAscending.data(), false); + sortAscending = config.ReadBool(config_sortAscending.data(), true); //set up project list columns { @@ -102,6 +104,22 @@ MainFrameDerived::MainFrameDerived() : MainFrame(NULL){ projectsList->AppendColumn(str,wxLIST_FORMAT_LEFT); } } + //restore column widths if the user resized them in a previous session + { + const int cols = projectsList->GetColumnCount(); + std::vector widths(cols); + bool allRead = true; + for (int i = 0; i < cols && allRead; i++){ + allRead = config.Read(std::format("{}{}", config_columnWidth, i), &widths[i]); + } + if (allRead){ + for (int i = 0; i < cols; i++){ + projectsList->SetColumnWidth(i, widths[i]); + } + customColumnWidths = true; + } + } + projectsList->ShowSortIndicator(sortColumn, sortAscending); //make the data folder if it does not already exist (with readwrite for all groups) #if defined __APPLE__ || defined __linux__ int status = mkdir(datapath.c_str(),S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); @@ -141,6 +159,17 @@ void MainFrameDerived::OnClose(wxCloseEvent& event){ config.Write(config_windowX.data(), windowRect.x); config.Write(config_windowY.data(), windowRect.y); + // save column widths only if the user resized them, otherwise keep autosizing + for (int i = 0; i < projectsList->GetColumnCount(); i++){ + const auto key = std::format("{}{}", config_columnWidth, i); + if (customColumnWidths){ + config.Write(key, projectsList->GetColumnWidth(i)); + } + else{ + config.DeleteEntry(key); + } + } + // let the default handler destroy the window event.Skip(); } @@ -652,10 +681,12 @@ void MainFrameDerived::AddProject(const project& p, const std::string& filter, b projectsList->SetItem(itemIndex, 2, p.modifyDateString()); projectsList->SetItem(itemIndex, 3, p.path.string()); - //resize columns - int cols = projectsList->GetColumnCount(); - for (int j = 0; j < cols; j++){ - projectsList->SetColumnWidth(j, wxLIST_AUTOSIZE_USEHEADER); + //resize columns, unless the user has set their widths + if (!customColumnWidths){ + int cols = projectsList->GetColumnCount(); + for (int j = 0; j < cols; j++){ + projectsList->SetColumnWidth(j, wxLIST_AUTOSIZE_USEHEADER); + } } // Sort the list to place the new item in the correct position @@ -792,9 +823,13 @@ void MainFrameDerived::OnColumnClick(wxListEvent& event) { } SortProjects(); - // update UI - const bool ascending = projectsList->GetUpdatedAscendingSortIndicator(col); - projectsList->ShowSortIndicator(col, ascending); + // update UI + projectsList->ShowSortIndicator(sortColumn, sortAscending); +} + +void MainFrameDerived::OnColumnResized(wxListEvent& event) { + customColumnWidths = true; + event.Skip(); } // Sort comparison function diff --git a/source/interface_derived.hpp b/source/interface_derived.hpp index 19fe9111..44355dee 100644 --- a/source/interface_derived.hpp +++ b/source/interface_derived.hpp @@ -96,6 +96,8 @@ class MainFrameDerived : public MainFrame{ // Sorting state int sortColumn = 0; // Default sort by name (column 0) bool sortAscending = true; // Default ascending + // Set once the user resizes a column; disables autosizing and persists the widths + bool customColumnWidths = false; // Last window geometry while not minimized, saved on close wxRect windowRect; @@ -184,6 +186,7 @@ class MainFrameDerived : public MainFrame{ this->LoadEditorVersions(); } void OnColumnClick(wxListEvent& event); + void OnColumnResized(wxListEvent& event); static int wxCALLBACK CompareItems(wxIntPtr item1, wxIntPtr item2, wxIntPtr sortData); void SortProjects(); wxDECLARE_EVENT_TABLE(); From a2acbc1e3cdcb200afb72af00cb79338db1db88e Mon Sep 17 00:00:00 2001 From: Ilia Petrov-Komotskii Date: Mon, 2 Nov 2026 12:25:15 +0100 Subject: [PATCH 08/10] fix: Fixed some window size saving on Windows --- source/interface_derived.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/source/interface_derived.cpp b/source/interface_derived.cpp index 59386163..ab4063c7 100644 --- a/source/interface_derived.cpp +++ b/source/interface_derived.cpp @@ -86,14 +86,6 @@ MainFrameDerived::MainFrameDerived() : MainFrame(NULL){ // load prefs wxConfig config(config_name.data()); - // set saved position and size - int width, height, x, y; - if (config.Read(config_windowWidth.data(), &width) && config.Read(config_windowHeight.data(), &height) && - config.Read(config_windowX.data(), &x) && config.Read(config_windowY.data(), &y)) { - this->SetSize(x, y, width, height); - } - windowRect = this->GetRect(); - sortColumn = std::clamp(config.ReadLong(config_sortCol.data(), 0), 0, 3); sortAscending = config.ReadBool(config_sortAscending.data(), true); @@ -135,6 +127,16 @@ MainFrameDerived::MainFrameDerived() : MainFrame(NULL){ //set reveal label menuReveal->SetItemLabel("Reveal In File Explorer\tCtrl-F"); #endif + + // set saved position and size. This must come after dpi_scale(), which calls Fit() on Windows + // and would otherwise shrink the window back to its default size + int width, height, x, y; + if (config.Read(config_windowWidth.data(), &width) && config.Read(config_windowHeight.data(), &height) && + config.Read(config_windowX.data(), &x) && config.Read(config_windowY.data(), &y)) { + this->SetSize(x, y, width, height); + } + windowRect = this->GetRect(); + if (status != 0){ ReloadData(); } From 0d7fb72733eed8fe59b01b65074548c29ef312e5 Mon Sep 17 00:00:00 2001 From: Ilia Petrov-Komotskii Date: Tue, 3 Nov 2026 11:48:53 +0100 Subject: [PATCH 09/10] feat: Introduced Window > Center Window on Current Display option to cover the cases of out of screen window --- source/interface_derived.cpp | 43 ++++++++++++++++++++++++++++++++++++ source/interface_derived.hpp | 1 + 2 files changed, 44 insertions(+) diff --git a/source/interface_derived.cpp b/source/interface_derived.cpp index ab4063c7..6dc8d940 100644 --- a/source/interface_derived.cpp +++ b/source/interface_derived.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include @@ -36,6 +37,9 @@ constexpr std::string_view config_windowX = "windowX"; constexpr std::string_view config_windowY = "windowY"; constexpr std::string_view config_columnWidth = "projectColumnWidth"; +// menu command ids that have no fitting stock id +constexpr int ID_CENTER_ON_DISPLAY = wxID_HIGHEST + 1; + //Declare events here wxBEGIN_EVENT_TABLE(MainFrameDerived, wxFrame) EVT_MENU(wxID_ABOUT, MainFrameDerived::OnAbout) @@ -48,6 +52,7 @@ EVT_MENU(wxID_REFRESH, MainFrameDerived::OnReloadData) EVT_MENU(wxID_UNINDENT, MainFrameDerived::OnSource) EVT_MENU(wxID_TOP, MainFrameDerived::OnUpdate) EVT_MENU(wxID_EXIT, MainFrameDerived::OnQuit) +EVT_MENU(ID_CENTER_ON_DISPLAY, MainFrameDerived::OnCenterOnDisplay) EVT_CLOSE(MainFrameDerived::OnClose) EVT_SIZE(MainFrameDerived::OnSize) EVT_MOVE(MainFrameDerived::OnMove) @@ -128,6 +133,14 @@ MainFrameDerived::MainFrameDerived() : MainFrame(NULL){ menuReveal->SetItemLabel("Reveal In File Explorer\tCtrl-F"); #endif + // recovery command for a window that ended up off screen (e.g. its saved display was disconnected). + // Added here rather than in the generated base class so regenerating interface.cpp does not drop it. + // The shortcut matters on Windows and Linux: the menu bar is part of the window there, so when the + // window is off screen the menu is unreachable, but the shortcut still works once the window is focused + if (const int windowMenuIndex = menubar->FindMenu("Window"); windowMenuIndex != wxNOT_FOUND) { + menubar->GetMenu(windowMenuIndex)->Insert(1, ID_CENTER_ON_DISPLAY, "Center Window on Current Display\tCtrl-Shift-M"); + } + // set saved position and size. This must come after dpi_scale(), which calls Fit() on Windows // and would otherwise shrink the window back to its default size int width, height, x, y; @@ -186,6 +199,36 @@ void MainFrameDerived::OnMove(wxMoveEvent& event){ event.Skip(); } +/** + Moves the window to the center of the display the user is working on (the one under the mouse pointer), + shrinking it to fit if needed. Recovers a window that is off screen or on a display that is gone + */ +void MainFrameDerived::OnCenterOnDisplay(wxCommandEvent&){ + int displayIndex = wxDisplay::GetFromPoint(wxGetMousePosition()); + if (displayIndex == wxNOT_FOUND) { + displayIndex = 0; // the primary display + } + // the client area excludes the taskbar, the Dock and the macOS menu bar + const wxRect area = wxDisplay(static_cast(displayIndex)).GetClientArea(); + + // leave the states whose geometry is managed by the OS, so the window can be moved + if (IsFullScreen()) { + ShowFullScreen(false); + } + if (IsIconized()) { + Iconize(false); + } + if (IsMaximized()) { + Maximize(false); + } + + wxRect rect = GetRect(); + rect.width = std::min(rect.width, area.width); + rect.height = std::min(rect.height, area.height); + SetSize(rect.CenterIn(area)); + Raise(); +} + /** Stores the current window geometry, unless the window is minimized (its geometry is not meaningful then) */ diff --git a/source/interface_derived.hpp b/source/interface_derived.hpp index 44355dee..609b2d0e 100644 --- a/source/interface_derived.hpp +++ b/source/interface_derived.hpp @@ -65,6 +65,7 @@ class MainFrameDerived : public MainFrame{ void OnSize(wxSizeEvent&); void OnMove(wxMoveEvent&); void RememberWindowRect(); + void OnCenterOnDisplay(wxCommandEvent&); wxWindow* const projectActionItems[3]{ revealProjBtn, From 2a49000f8fc7ad6770843c329a4478673850053a Mon Sep 17 00:00:00 2001 From: Ilia Petrov-Komotskii Date: Tue, 3 Nov 2026 12:00:29 +0100 Subject: [PATCH 10/10] fix: Bring the hub window back to the visible viewport if it was saved outside --- source/interface_derived.cpp | 68 +++++++++++++++++++++++++++++++++--- source/interface_derived.hpp | 1 + 2 files changed, 65 insertions(+), 4 deletions(-) diff --git a/source/interface_derived.cpp b/source/interface_derived.cpp index 6dc8d940..0e204333 100644 --- a/source/interface_derived.cpp +++ b/source/interface_derived.cpp @@ -15,6 +15,7 @@ #include #include +#include #include #if __APPLE__ @@ -147,6 +148,8 @@ MainFrameDerived::MainFrameDerived() : MainFrame(NULL){ if (config.Read(config_windowWidth.data(), &width) && config.Read(config_windowHeight.data(), &height) && config.Read(config_windowX.data(), &x) && config.Read(config_windowY.data(), &y)) { this->SetSize(x, y, width, height); + // the saved geometry may no longer fit the displays, e.g. its monitor was disconnected or the layout changed + KeepOnScreen(); } windowRect = this->GetRect(); @@ -199,6 +202,21 @@ void MainFrameDerived::OnMove(wxMoveEvent& event){ event.Skip(); } +/** + Shrinks the rect to fit into the area if it is larger, then moves it the shortest distance that puts it fully inside + */ +static wxRect FitInto(wxRect rect, const wxRect& area){ + rect.width = std::min(rect.width, area.width); + rect.height = std::min(rect.height, area.height); + rect.x = std::clamp(rect.x, area.x, area.x + area.width - rect.width); + rect.y = std::clamp(rect.y, area.y, area.y + area.height - rect.height); + return rect; +} + +static long long AreaOf(const wxRect& rect){ + return rect.IsEmpty() ? 0 : static_cast(rect.width) * rect.height; +} + /** Moves the window to the center of the display the user is working on (the one under the mouse pointer), shrinking it to fit if needed. Recovers a window that is off screen or on a display that is gone @@ -222,13 +240,55 @@ void MainFrameDerived::OnCenterOnDisplay(wxCommandEvent&){ Maximize(false); } - wxRect rect = GetRect(); - rect.width = std::min(rect.width, area.width); - rect.height = std::min(rect.height, area.height); - SetSize(rect.CenterIn(area)); + SetSize(FitInto(GetRect(), area).CenterIn(area)); Raise(); } +/** + If part of the window is outside every display, snaps it back to the nearest edge of the display it overlaps the most + (or the closest display, if it overlaps none), shrinking it if it does not fit there. + A window spanning several displays is left alone as long as all of it is visible + */ +void MainFrameDerived::KeepOnScreen(){ + const unsigned displayCount = wxDisplay::GetCount(); + if (displayCount == 0) { + return; + } + const wxRect rect = GetRect(); + + // The client areas exclude the taskbar, the Dock and the macOS menu bar, and do not overlap each other, so the + // window is fully visible exactly when their overlaps with it add up to its whole area. The visibility test + // ignores a margin around the window: on Windows 10/11 GetRect() includes the invisible resize borders, so a + // window snapped to a screen edge sticks out by a few pixels and must not be treated as off screen + // (const matters: the non-const wxRect::Intersect() overload intersects in place instead of returning a copy) + const wxRect deflated = wxRect(rect).Deflate(FromDIP(16)); + const wxRect tested = deflated.IsEmpty() ? rect : deflated; + + long long visibleArea = 0; + long long bestOverlap = 0; + long long bestDistance = std::numeric_limits::max(); + wxRect target; + for (unsigned i = 0; i < displayCount; i++){ + const wxRect area = wxDisplay(i).GetClientArea(); + visibleArea += AreaOf(tested.Intersect(area)); + + // prefer the display holding most of the window; failing that, the one with the closest edge + const long long overlap = AreaOf(rect.Intersect(area)); + const long long dx = std::max({0, area.x - rect.GetRight(), rect.x - area.GetRight()}); + const long long dy = std::max({0, area.y - rect.GetBottom(), rect.y - area.GetBottom()}); + const long long distance = dx * dx + dy * dy; + if (overlap > bestOverlap || (bestOverlap == 0 && overlap == 0 && distance < bestDistance)) { + bestOverlap = overlap; + bestDistance = distance; + target = area; + } + } + + if (visibleArea < AreaOf(tested)) { + SetSize(FitInto(rect, target)); + } +} + /** Stores the current window geometry, unless the window is minimized (its geometry is not meaningful then) */ diff --git a/source/interface_derived.hpp b/source/interface_derived.hpp index 609b2d0e..d9b956a1 100644 --- a/source/interface_derived.hpp +++ b/source/interface_derived.hpp @@ -66,6 +66,7 @@ class MainFrameDerived : public MainFrame{ void OnMove(wxMoveEvent&); void RememberWindowRect(); void OnCenterOnDisplay(wxCommandEvent&); + void KeepOnScreen(); wxWindow* const projectActionItems[3]{ revealProjBtn,