From ada1fc37ff11a7f18969705973f09058dfa18ff6 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 26 Jul 2023 16:31:15 +0200 Subject: [PATCH] WIP: Experiments with New simple top bar --- src/slic3r/CMakeLists.txt | 2 + src/slic3r/GUI/GUI_App.cpp | 34 ++- src/slic3r/GUI/GUI_App.hpp | 10 +- src/slic3r/GUI/MainFrame.cpp | 84 +++--- src/slic3r/GUI/Tab.cpp | 25 +- src/slic3r/GUI/Tab.hpp | 9 +- src/slic3r/GUI/TopBar.cpp | 308 +++++++++++++++++++++ src/slic3r/GUI/TopBar.hpp | 459 ++++++++++++++++++++++++++++++++ src/slic3r/GUI/wxExtensions.cpp | 2 + 9 files changed, 861 insertions(+), 72 deletions(-) create mode 100644 src/slic3r/GUI/TopBar.cpp create mode 100644 src/slic3r/GUI/TopBar.hpp diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 4c297c93dd..db1ac120f3 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -252,6 +252,8 @@ set(SLIC3R_GUI_SOURCES GUI/DoubleSlider.hpp GUI/Notebook.cpp GUI/Notebook.hpp + GUI/TopBar.cpp + GUI/TopBar.hpp GUI/ObjectDataViewModel.cpp GUI/ObjectDataViewModel.hpp GUI/InstanceCheck.cpp diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 7880d07606..99ee684a0e 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -103,7 +103,8 @@ #include "WebViewDialog.hpp" #include "BitmapCache.hpp" -#include "Notebook.hpp" +//#include "Notebook.hpp" +#include "TopBar.hpp" #ifdef __WXMSW__ #include @@ -1519,16 +1520,16 @@ void GUI_App::init_ui_colours() m_mode_palette = get_mode_default_palette(); bool is_dark_mode = dark_mode(); -#ifdef _WIN32 +//#ifdef _WIN32 m_color_label_default = is_dark_mode ? wxColour(250, 250, 250): wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); m_color_highlight_label_default = is_dark_mode ? wxColour(230, 230, 230): wxSystemSettings::GetColour(/*wxSYS_COLOUR_HIGHLIGHTTEXT*/wxSYS_COLOUR_WINDOWTEXT); m_color_highlight_default = is_dark_mode ? wxColour(78, 78, 78) : wxSystemSettings::GetColour(wxSYS_COLOUR_3DLIGHT); m_color_hovered_btn_label = is_dark_mode ? wxColour(253, 111, 40) : wxColour(252, 77, 1); m_color_default_btn_label = is_dark_mode ? wxColour(255, 181, 100): wxColour(203, 61, 0); m_color_selected_btn_bg = is_dark_mode ? wxColour(95, 73, 62) : wxColour(228, 220, 216); -#else - m_color_label_default = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); -#endif +//#else +// m_color_label_default = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); +//#endif m_color_window_default = is_dark_mode ? wxColour(43, 43, 43) : wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); } @@ -1564,12 +1565,13 @@ void GUI_App::update_label_colours() tab->update_label_colours(); } -#ifdef _WIN32 +#if 0//def _WIN32 static bool is_focused(HWND hWnd) { HWND hFocusedWnd = ::GetFocus(); return hFocusedWnd && hWnd == hFocusedWnd; } +#endif static bool is_default(wxWindow* win) { @@ -1579,7 +1581,6 @@ static bool is_default(wxWindow* win) return win == tlw->GetDefaultItem(); } -#endif void GUI_App::UpdateDarkUI(wxWindow* window, bool highlited/* = false*/, bool just_font/* = false*/) { @@ -1592,6 +1593,7 @@ void GUI_App::UpdateDarkUI(wxWindow* window, bool highlited/* = false*/, bool ju highlited = true; } // button marking + if (!dynamic_cast(window->GetParent())) // don't marking the button if it is from TopBar { auto mark_button = [this, btn, highlited](const bool mark) { if (btn->GetLabel().IsEmpty()) @@ -1604,12 +1606,12 @@ void GUI_App::UpdateDarkUI(wxWindow* window, bool highlited/* = false*/, bool ju // hovering btn->Bind(wxEVT_ENTER_WINDOW, [mark_button](wxMouseEvent& event) { mark_button(true); event.Skip(); }); - btn->Bind(wxEVT_LEAVE_WINDOW, [mark_button, btn](wxMouseEvent& event) { mark_button(is_focused(btn->GetHWND())); event.Skip(); }); + btn->Bind(wxEVT_LEAVE_WINDOW, [mark_button, btn](wxMouseEvent& event) { mark_button(btn->HasFocus()); event.Skip(); }); // focusing btn->Bind(wxEVT_SET_FOCUS, [mark_button](wxFocusEvent& event) { mark_button(true); event.Skip(); }); btn->Bind(wxEVT_KILL_FOCUS, [mark_button](wxFocusEvent& event) { mark_button(false); event.Skip(); }); - is_focused_button = is_focused(btn->GetHWND()); + is_focused_button = btn->HasFocus();// is_focused(btn->GetHWND()); is_default_button = is_default(btn); if (is_focused_button || is_default_button) mark_button(is_focused_button); @@ -2471,10 +2473,8 @@ void GUI_App::update_mode() { sidebar().update_mode(); -#ifdef _WIN32 //_MSW_DARK_MODE if (!wxGetApp().tabs_as_menu()) - dynamic_cast(mainframe->m_tabpanel)->UpdateMode(); -#endif + dynamic_cast(mainframe->m_tabpanel)->UpdateMode(); for (auto tab : tabs_list) tab->update_mode(); @@ -2483,7 +2483,8 @@ void GUI_App::update_mode() plater()->canvas3D()->update_gizmos_on_off_state(); } -void GUI_App::add_config_menu(wxMenuBar *menu) +//void GUI_App::add_config_menu(wxMenuBar *menu) +wxMenu* GUI_App::get_config_menu() { auto local_menu = new wxMenu(); wxWindowID config_id_base = wxWindow::NewControlId(int(ConfigMenuCnt)); @@ -2518,6 +2519,7 @@ void GUI_App::add_config_menu(wxMenuBar *menu) "\tCtrl+P", #endif _L("Application preferences")); + /* wxMenu* mode_menu = nullptr; if (is_editor()) { local_menu->AppendSeparator(); @@ -2532,6 +2534,7 @@ void GUI_App::add_config_menu(wxMenuBar *menu) local_menu->AppendSubMenu(mode_menu, _L("Mode"), wxString::Format(_L("%s View Mode"), SLIC3R_APP_NAME)); } + */ local_menu->AppendSeparator(); local_menu->Append(config_id_base + ConfigMenuLanguage, _L("&Language")); if (is_editor()) { @@ -2677,6 +2680,10 @@ void GUI_App::add_config_menu(wxMenuBar *menu) } }); +#if 1 + return local_menu; +#else + using std::placeholders::_1; if (mode_menu != nullptr) { @@ -2687,6 +2694,7 @@ void GUI_App::add_config_menu(wxMenuBar *menu) } menu->Append(local_menu, _L("&Configuration")); +#endif } void GUI_App::update_config_menu() { diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 007ba7f8d9..81782dc27c 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -146,12 +146,13 @@ private: wxColour m_color_label_sys; wxColour m_color_label_default; wxColour m_color_window_default; -#ifdef _WIN32 +//#ifdef _WIN32 wxColour m_color_highlight_label_default; wxColour m_color_hovered_btn_label; wxColour m_color_default_btn_label; wxColour m_color_highlight_default; wxColour m_color_selected_btn_bg; +#ifdef _WIN32 bool m_force_colors_update { false }; #endif std::vector m_mode_palette; @@ -251,7 +252,7 @@ public: std::vector get_mode_palette(); void set_mode_palette(const std::vector &palette); -#ifdef _WIN32 +//#ifdef _WIN32 const wxColour& get_label_highlight_clr() { return m_color_highlight_label_default; } const wxColour& get_highlight_default_clr() { return m_color_highlight_default; } const wxColour& get_color_hovered_btn_label() { return m_color_hovered_btn_label; } @@ -260,7 +261,7 @@ public: #ifdef _MSW_DARK_MODE void force_menu_update(); #endif //_MSW_DARK_MODE -#endif +//#endif const wxFont& small_font() { return m_small_font; } const wxFont& bold_font() { return m_bold_font; } @@ -297,7 +298,8 @@ public: bool save_mode(const /*ConfigOptionMode*/int mode) ; void update_mode(); - void add_config_menu(wxMenuBar *menu); +// void add_config_menu(wxMenuBar *menu); + wxMenu* get_config_menu(); void update_config_menu(); bool has_unsaved_preset_changes() const; bool has_current_preset_changes() const; diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 23b9b83a92..4314296b32 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -56,7 +56,8 @@ #include "GUI_App.hpp" #include "UnsavedChangesDialog.hpp" #include "MsgDialog.hpp" -#include "Notebook.hpp" +//#include "Notebook.hpp" +#include "TopBar.hpp" #include "GUI_Factories.hpp" #include "GUI_ObjectList.hpp" #include "GalleryDialog.hpp" @@ -457,13 +458,15 @@ void MainFrame::update_layout() case ESettingsLayout::Old: { m_plater->Reparent(m_tabpanel); -#ifdef _MSW_DARK_MODE m_plater->Layout(); +#ifdef _WIN32 if (!wxGetApp().tabs_as_menu()) - dynamic_cast(m_tabpanel)->InsertPage(0, m_plater, _L("Plater"), std::string("plater"), true); - else #endif + dynamic_cast(m_tabpanel)->InsertPage(0, m_plater, _L("Plater"), std::string("plater"), true); +#ifdef _WIN32 + else m_tabpanel->InsertPage(0, m_plater, _L("Plater")); +#endif m_main_sizer->Add(m_tabpanel, 1, wxEXPAND | wxTOP, 1); m_plater->Show(); m_tabpanel->Show(); @@ -483,12 +486,14 @@ void MainFrame::update_layout() m_tabpanel->Hide(); m_main_sizer->Add(m_tabpanel, 1, wxEXPAND); m_plater_page = new wxPanel(m_tabpanel); -#ifdef _MSW_DARK_MODE +#ifdef _WIN32 if (!wxGetApp().tabs_as_menu()) - dynamic_cast(m_tabpanel)->InsertPage(0, m_plater_page, _L("Plater"), std::string("plater"), true); - else #endif + dynamic_cast(m_tabpanel)->InsertPage(0, m_plater_page, _L("Plater"), std::string("plater"), true); +#ifdef _WIN32 + else m_tabpanel->InsertPage(0, m_plater_page, _L("Plater")); // empty panel just for Plater tab */ +#endif m_plater->Show(); break; } @@ -500,7 +505,7 @@ void MainFrame::update_layout() m_tabpanel->Show(); m_plater->Show(); -#ifdef _MSW_DARK_MODE +#ifdef _WIN32 if (wxGetApp().tabs_as_menu()) show_tabs_menu(false); #endif @@ -697,22 +702,14 @@ void MainFrame::init_tabpanel() // wxGetApp().UpdateDarkUI(m_tabpanel); } else - m_tabpanel = new Notebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL | wxNB_NOPAGETHEME, true); -#else - m_tabpanel = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL | wxNB_NOPAGETHEME); #endif - - wxGetApp().UpdateDarkUI(m_tabpanel); + m_tabpanel = new TopBar(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL | wxNB_NOPAGETHEME, true); m_tabpanel->SetFont(Slic3r::GUI::wxGetApp().normal_font()); m_tabpanel->Hide(); m_settings_dialog.set_tabpanel(m_tabpanel); -#ifdef __WXMSW__ m_tabpanel->Bind(wxEVT_BOOKCTRL_PAGE_CHANGED, [this](wxBookCtrlEvent& e) { -#else - m_tabpanel->Bind(wxEVT_NOTEBOOK_PAGE_CHANGED, [this](wxBookCtrlEvent& e) { -#endif if (int old_selection = e.GetOldSelection(); old_selection != wxNOT_FOUND && old_selection < static_cast(m_tabpanel->GetPageCount())) { Tab* old_tab = dynamic_cast(m_tabpanel->GetPage(old_selection)); @@ -853,12 +850,14 @@ void MainFrame::add_created_tab(Tab* panel, const std::string& bmp_name /*= ""* const auto printer_tech = wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology(); if (panel->supports_printer_technology(printer_tech)) -#ifdef _MSW_DARK_MODE +#ifdef _WIN32 if (!wxGetApp().tabs_as_menu()) - dynamic_cast(m_tabpanel)->AddPage(panel, panel->title(), bmp_name); - else #endif + dynamic_cast(m_tabpanel)->AddPage(panel, panel->title(), bmp_name); +#ifdef _WIN32 + else m_tabpanel->AddPage(panel, panel->title()); +#endif } bool MainFrame::is_active_and_shown_tab(Tab* tab) @@ -1041,10 +1040,10 @@ void MainFrame::on_dpi_changed(const wxRect& suggested_rect) wxGetApp().update_fonts(this); this->SetFont(this->normal_font()); -#ifdef _MSW_DARK_MODE +#ifdef _WIN32 // update common mode sizer if (!wxGetApp().tabs_as_menu()) - dynamic_cast(m_tabpanel)->Rescale(); + dynamic_cast(m_tabpanel)->Rescale(); #endif // update Plater @@ -1089,12 +1088,9 @@ void MainFrame::on_sys_color_changed() wxGetApp().update_ui_colours_from_appconfig(); #ifdef __WXMSW__ wxGetApp().UpdateDarkUI(m_tabpanel); -#ifdef _MSW_DARK_MODE - // update common mode sizer if (!wxGetApp().tabs_as_menu()) - dynamic_cast(m_tabpanel)->OnColorsChanged(); -#endif #endif + dynamic_cast(m_tabpanel)->OnColorsChanged(); // update Plater wxGetApp().plater()->sys_color_changed(); @@ -1112,13 +1108,9 @@ void MainFrame::on_sys_color_changed() void MainFrame::update_mode_markers() { -#ifdef __WXMSW__ -#ifdef _MSW_DARK_MODE // update markers in common mode sizer if (!wxGetApp().tabs_as_menu()) - dynamic_cast(m_tabpanel)->UpdateModeMarkers(); -#endif -#endif + dynamic_cast(m_tabpanel)->UpdateModeMarkers(); // update mode markers on side_bar wxGetApp().sidebar().update_mode_markers(); @@ -1547,6 +1539,28 @@ void MainFrame::init_menubar_as_editor() // Help menu auto helpMenu = generate_help_menu(); +#if 1 + // append menus for Menu button from TopBar + + TopBar* top_bar = dynamic_cast(m_tabpanel); + top_bar->AppendMenuItem(fileMenu, _L("&File")); + if (editMenu) + top_bar->AppendMenuItem(editMenu, _L("&Edit")); + + top_bar->AppendMenuSeparaorItem(); + + top_bar->AppendMenuItem(windowMenu, _L("&Window")); + if (viewMenu) + top_bar->AppendMenuItem(viewMenu, _L("&View")); + + top_bar->AppendMenuItem(wxGetApp().get_config_menu(), _L("&Configuration")); + + top_bar->AppendMenuSeparaorItem(); + + top_bar->AppendMenuItem(helpMenu, _L("&Help")); + +#else + // menubar // assign menubar to frame after appending items, otherwise special items // will not be handled correctly @@ -1569,6 +1583,8 @@ void MainFrame::init_menubar_as_editor() #endif SetMenuBar(m_menubar); +#endif + #ifdef _MSW_DARK_MODE if (wxGetApp().tabs_as_menu()) m_menubar->EnableTop(6, false); @@ -1662,7 +1678,7 @@ void MainFrame::init_menubar_as_gcodeviewer() m_menubar->Append(fileMenu, _L("&File")); if (viewMenu != nullptr) m_menubar->Append(viewMenu, _L("&View")); // Add additional menus from C++ - wxGetApp().add_config_menu(m_menubar); +// wxGetApp().add_config_menu(m_menubar); m_menubar->Append(helpMenu, _L("&Help")); SetMenuBar(m_menubar); @@ -2243,10 +2259,10 @@ void SettingsDialog::on_dpi_changed(const wxRect& suggested_rect) const int& em = em_unit(); const wxSize& size = wxSize(85 * em, 50 * em); -#ifdef _MSW_DARK_MODE +#ifdef _WIN32 // update common mode sizer if (!wxGetApp().tabs_as_menu()) - dynamic_cast(m_tabpanel)->Rescale(); + dynamic_cast(m_tabpanel)->Rescale(); #endif // update Tabs diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 3950d5a140..053e17b161 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -67,7 +67,8 @@ #include "SavePresetDialog.hpp" #include "EditGCodeDialog.hpp" #include "MsgDialog.hpp" -#include "Notebook.hpp" +//#include "Notebook.hpp" +#include "TopBar.hpp" #include "Widgets/CheckBox.hpp" @@ -3648,13 +3649,6 @@ void Tab::load_current_preset() // m_undo_to_sys_btn->Enable(!preset.is_default); -#if 0 - // use CallAfter because some field triggers schedule on_change calls using CallAfter, - // and we don't want them to be called after this update_dirty() as they would mark the - // preset dirty again - // (not sure this is true anymore now that update_dirty is idempotent) - wxTheApp->CallAfter([this] -#endif { // checking out if this Tab exists till this moment if (!wxGetApp().checked_tab(this)) @@ -3682,16 +3676,15 @@ void Tab::load_current_preset() } if (tab->supports_printer_technology(printer_technology)) { -#ifdef _MSW_DARK_MODE +#ifdef _WIN32 if (!wxGetApp().tabs_as_menu()) { - std::string bmp_name = tab->type() == Slic3r::Preset::TYPE_FILAMENT ? "spool" : - tab->type() == Slic3r::Preset::TYPE_SLA_MATERIAL ? "resin" : "cog"; - tab->Hide(); // #ys_WORKAROUND : Hide tab before inserting to avoid unwanted rendering of the tab - dynamic_cast(wxGetApp().tab_panel())->InsertPage(wxGetApp().tab_panel()->FindPage(this), tab, tab->title(), bmp_name); +#endif + dynamic_cast(wxGetApp().tab_panel())->InsertPage(wxGetApp().tab_panel()->FindPage(this), tab, tab->title(),""); +#ifdef _WIN32 } else -#endif wxGetApp().tab_panel()->InsertPage(wxGetApp().tab_panel()->FindPage(this), tab, tab->title()); +#endif #ifdef __linux__ // the tabs apparently need to be explicitly shown on Linux (pull request #1563) int page_id = wxGetApp().tab_panel()->FindPage(tab); wxGetApp().tab_panel()->GetPage(page_id)->Show(true); @@ -3705,10 +3698,6 @@ void Tab::load_current_preset() } static_cast(this)->m_printer_technology = printer_technology; m_active_page = tmp_page; -#ifdef _MSW_DARK_MODE - if (!wxGetApp().tabs_as_menu()) - dynamic_cast(wxGetApp().tab_panel())->SetPageImage(wxGetApp().tab_panel()->FindPage(this), printer_technology == ptFFF ? "printer" : "sla_printer"); -#endif } on_presets_changed(); if (printer_technology == ptFFF) { diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index 505c47b199..444e1ecf3f 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -478,7 +478,8 @@ class TabFilament : public Tab std::map m_overrides_options; public: TabFilament(wxBookCtrlBase* parent) : - Tab(parent, _(L("Filament Settings")), Slic3r::Preset::TYPE_FILAMENT) {} +// Tab(parent, _(L("Filament Settings")), Slic3r::Preset::TYPE_FILAMENT) {} + Tab(parent, _L("Filaments"), Slic3r::Preset::TYPE_FILAMENT) {} ~TabFilament() {} void build() override; @@ -536,7 +537,8 @@ public: PrinterTechnology m_printer_technology = ptFFF; TabPrinter(wxBookCtrlBase* parent) : - Tab(parent, _L("Printer Settings"), Slic3r::Preset::TYPE_PRINTER) {} +// Tab(parent, _L("Printer Settings"), Slic3r::Preset::TYPE_PRINTER) {} + Tab(parent, _L("Printers"), Slic3r::Preset::TYPE_PRINTER) {} ~TabPrinter() {} void build() override; @@ -574,7 +576,8 @@ class TabSLAMaterial : public Tab std::map m_overrides_options; public: TabSLAMaterial(wxBookCtrlBase* parent) : - Tab(parent, _(L("Material Settings")), Slic3r::Preset::TYPE_SLA_MATERIAL) {} +// Tab(parent, _(L("Material Settings")), Slic3r::Preset::TYPE_SLA_MATERIAL) {} + Tab(parent, _L("Materials"), Slic3r::Preset::TYPE_SLA_MATERIAL) {} ~TabSLAMaterial() {} void build() override; diff --git a/src/slic3r/GUI/TopBar.cpp b/src/slic3r/GUI/TopBar.cpp new file mode 100644 index 0000000000..6e46780f7c --- /dev/null +++ b/src/slic3r/GUI/TopBar.cpp @@ -0,0 +1,308 @@ +#include "TopBar.hpp" + +#include "GUI_App.hpp" +#include "Plater.hpp" +//#include "wxExtensions.hpp" +#include "format.hpp" +#include "I18N.hpp" + +#include +#include + +wxDEFINE_EVENT(wxCUSTOMEVT_TOPBAR_SEL_CHANGED, wxCommandEvent); + +using namespace Slic3r::GUI; + +#define down_arrow L"\u23f7"; + + +TopBarItemsCtrl::Button::Button(wxWindow* parent, const wxString& label, const std::string& icon_name, const int px_cnt) +:ScalableButton(parent, wxID_ANY, icon_name, label, wxDefaultSize, wxDefaultPosition, wxNO_BORDER, px_cnt) +{ + int btn_margin = em_unit(this); + wxSize size = GetTextExtent(label) + wxSize(6 * btn_margin, int(1.5 * btn_margin)); + if (icon_name.empty()) + this->SetMinSize(size); + else + this->SetMinSize(wxSize(-1, size.y)); + + const wxColour& selected_btn_bg = wxGetApp().get_label_clr_default(); + const wxColour& default_btn_bg = wxGetApp().get_window_default_clr(); + + this->Bind(wxEVT_SET_FOCUS, [this, selected_btn_bg, default_btn_bg](wxFocusEvent& event) { + this->SetBackgroundColour(selected_btn_bg); + this->SetForegroundColour(default_btn_bg); + event.Skip(); + }); + this->Bind(wxEVT_KILL_FOCUS, [this, selected_btn_bg, default_btn_bg](wxFocusEvent& event) { + if (!m_is_selected) { + this->SetBackgroundColour(default_btn_bg); + this->SetForegroundColour(selected_btn_bg); + } + event.Skip(); + }); +} + +TopBarItemsCtrl::ButtonWithPopup::ButtonWithPopup(wxWindow* parent, const wxString& label, const std::string& icon_name) + :TopBarItemsCtrl::Button(parent, label, icon_name, 24) +{ + this->SetLabel(label); +} + +void TopBarItemsCtrl::ButtonWithPopup::SetLabel(const wxString& label) +{ + wxString full_label = " " + label + " " + down_arrow; + ScalableButton::SetLabel(full_label); +} + +static wxString get_workspace_name(Slic3r::ConfigOptionMode mode) +{ + return mode == Slic3r::ConfigOptionMode::comSimple ? _L("Beginner workspace") : + mode == Slic3r::ConfigOptionMode::comAdvanced ? _L("Regular workspace") : _L("Josef Prusa's workspace"); +} + +TopBarItemsCtrl::TopBarItemsCtrl(wxWindow *parent, bool add_mode_buttons/* = false*/) : + wxControl(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBORDER_NONE | wxTAB_TRAVERSAL) +{ +#ifdef __WINDOWS__ + SetDoubleBuffered(true); +#endif //__WINDOWS__ + + int em = em_unit(this);// Slic3r::GUI::wxGetApp().em_unit(); + m_btn_margin = std::lround(0.9 * em); + m_line_margin = std::lround(0.1 * em); + + m_sizer = new wxBoxSizer(wxHORIZONTAL); + this->SetSizer(m_sizer); + + m_menu_btn = new ButtonWithPopup(this, _L("Menu"), wxGetApp().logo_name()); + m_sizer->Add(m_menu_btn, 1, wxALIGN_CENTER_VERTICAL | wxALL, m_btn_margin); + + m_buttons_sizer = new wxFlexGridSizer(1, m_btn_margin, m_btn_margin); + m_sizer->Add(m_buttons_sizer, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, 2 * m_btn_margin); + + if (add_mode_buttons) { + m_mode_sizer = new ModeSizer(this, m_btn_margin); + m_sizer->AddStretchSpacer(20); + m_sizer->Add(m_mode_sizer, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT | wxBOTTOM, m_btn_margin); + + m_mode_sizer->ShowItems(false); + } + + m_workspace_btn = new ButtonWithPopup(this, _L("Workspace"), "mode_simple"); + m_sizer->AddStretchSpacer(20); + m_sizer->Add(m_workspace_btn, 1, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT | wxRIGHT, 2 * m_btn_margin); + + // create modes menu + { + for (const Slic3r::ConfigOptionMode& mode : { Slic3r::ConfigOptionMode::comSimple, Slic3r::ConfigOptionMode::comAdvanced, Slic3r::ConfigOptionMode::comExpert } ) { + const wxString label = get_workspace_name(mode); + append_menu_item(&m_workspace_modes, wxID_ANY, label, label, + [this, mode](wxCommandEvent&) { + if (wxGetApp().get_mode() != mode) + wxGetApp().save_mode(mode); + }, get_bmp_bundle("mode", 16, -1, wxGetApp().get_mode_btn_color(mode)), nullptr, []() { return true; }, this); + + if (mode < Slic3r::ConfigOptionMode::comExpert) + m_workspace_modes.AppendSeparator(); + } + } + + + this->Bind(wxEVT_PAINT, &TopBarItemsCtrl::OnPaint, this); + + m_menu_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent& event) { + wxPoint pos = m_menu_btn->GetPosition(); + wxGetApp().plater()->PopupMenu(&m_menu, pos); + }); + + m_menu.Bind(wxEVT_MENU_CLOSE, [this](wxMenuEvent&) { + if (m_menu_btn->HasFocus()) { + wxPostEvent(m_menu_btn->GetEventHandler(), wxFocusEvent(wxEVT_KILL_FOCUS)); + } + }); + + m_workspace_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent& event) { + wxPoint pos = m_workspace_btn->GetPosition(); + wxGetApp().plater()->PopupMenu(&m_workspace_modes, pos); + }); + + m_workspace_modes.Bind(wxEVT_MENU_CLOSE, [this](wxMenuEvent&) { + if (m_workspace_btn->HasFocus()) { + wxPostEvent(m_workspace_btn->GetEventHandler(), wxFocusEvent(wxEVT_KILL_FOCUS)); + } + }); +} + +void TopBarItemsCtrl::OnPaint(wxPaintEvent&) +{ + wxGetApp().UpdateDarkUI(this); + const wxSize sz = GetSize(); + wxPaintDC dc(this); + + if (m_selection < 0 || m_selection >= (int)m_pageButtons.size()) + return; + + const wxColour& selected_btn_bg = wxGetApp().get_label_clr_default(); + const wxColour& default_btn_bg = wxGetApp().get_window_default_clr(); + const wxColour& btn_marker_color = wxGetApp().get_highlight_default_clr(); + + // highlight selected notebook button + + for (int idx = 0; idx < int(m_pageButtons.size()); idx++) { + wxButton* btn = m_pageButtons[idx]; + + btn->SetBackgroundColour(idx == m_selection ? selected_btn_bg : default_btn_bg); + btn->SetForegroundColour(idx == m_selection ? default_btn_bg : selected_btn_bg); + } + + // highlight selected mode button + + //bool mode_is_focused = m_workspace_btn->HasFocus(); + + //m_workspace_btn->SetBackgroundColour(mode_is_focused ? selected_btn_bg : default_btn_bg); + //m_workspace_btn->SetForegroundColour(mode_is_focused ? default_btn_bg : selected_btn_bg); + + //if (m_mode_sizer) { + // const std::vector& mode_btns = m_mode_sizer->get_btns(); + // for (int idx = 0; idx < int(mode_btns.size()); idx++) { + // ModeButton* btn = mode_btns[idx]; + // btn->SetBackgroundColour(btn->is_selected() ? selected_btn_bg : default_btn_bg); + + // //wxPoint pos = btn->GetPosition(); + // //wxSize size = btn->GetSize(); + // //const wxColour& clr = btn->is_selected() ? btn_marker_color : default_btn_bg; + // //dc.SetPen(clr); + // //dc.SetBrush(clr); + // //dc.DrawRectangle(pos.x, pos.y + size.y, size.x, sz.y - size.y); + // } + //} + + // Draw orange bottom line + + dc.SetPen(btn_marker_color); + dc.SetBrush(btn_marker_color); + dc.DrawRectangle(1, sz.y - m_line_margin, sz.x, m_line_margin); +} + +void TopBarItemsCtrl::UpdateMode() +{ + auto mode = wxGetApp().get_mode(); + m_mode_sizer->SetMode(mode); + + + auto m_bmp = *get_bmp_bundle("mode", 16, -1, wxGetApp().get_mode_btn_color(mode)); + + m_workspace_btn->SetBitmap(m_bmp); + m_workspace_btn->SetBitmapCurrent(m_bmp); + m_workspace_btn->SetBitmapPressed(m_bmp); + m_workspace_btn->SetLabel(get_workspace_name(mode)); + + m_workspace_btn->SetBitmapMargins(int(0.5 * em_unit(this)), 0); + this->Layout(); +} + +void TopBarItemsCtrl::Rescale() +{ + int em = em_unit(this); + m_btn_margin = std::lround(0.3 * em); + m_line_margin = std::lround(0.1 * em); + m_buttons_sizer->SetVGap(m_btn_margin); + m_buttons_sizer->SetHGap(m_btn_margin); + + m_sizer->Layout(); +} + +void TopBarItemsCtrl::OnColorsChanged() +{ + m_menu_btn->sys_color_changed(); + + for (ScalableButton* btn : m_pageButtons) + btn->sys_color_changed(); + + m_mode_sizer->sys_color_changed(); + + m_sizer->Layout(); +} + +void TopBarItemsCtrl::UpdateModeMarkers() +{ + m_mode_sizer->update_mode_markers(); +} + +void TopBarItemsCtrl::UpdateSelection() +{ + for (Button* btn : m_pageButtons) + btn->set_selected(false); + + if (m_selection >= 0) + m_pageButtons[m_selection]->set_selected(true); + + Refresh(); +} + +void TopBarItemsCtrl::SetSelection(int sel) +{ + if (m_selection == sel) + return; + m_selection = sel; + UpdateSelection(); +} + +bool TopBarItemsCtrl::InsertPage(size_t n, const wxString& text, bool bSelect/* = false*/, const std::string& bmp_name/* = ""*/) +{ + Button* btn = new Button(this, text); + btn->Bind(wxEVT_BUTTON, [this, btn](wxCommandEvent& event) { + if (auto it = std::find(m_pageButtons.begin(), m_pageButtons.end(), btn); it != m_pageButtons.end()) { + m_selection = it - m_pageButtons.begin(); + wxCommandEvent evt = wxCommandEvent(wxCUSTOMEVT_TOPBAR_SEL_CHANGED); + evt.SetId(m_selection); + wxPostEvent(this->GetParent(), evt); + UpdateSelection(); + } + }); + + m_pageButtons.insert(m_pageButtons.begin() + n, btn); + m_buttons_sizer->Insert(n, new wxSizerItem(btn, 0, wxALIGN_CENTER_VERTICAL)); + m_buttons_sizer->SetCols(m_buttons_sizer->GetCols() + 1); + m_sizer->Layout(); + return true; +} + +void TopBarItemsCtrl::RemovePage(size_t n) +{ + ScalableButton* btn = m_pageButtons[n]; + m_pageButtons.erase(m_pageButtons.begin() + n); + m_buttons_sizer->Remove(n); + btn->Reparent(nullptr); + btn->Destroy(); + m_sizer->Layout(); +} + +void TopBarItemsCtrl::SetPageText(size_t n, const wxString& strText) +{ + ScalableButton* btn = m_pageButtons[n]; + btn->SetLabel(strText); +} + +wxString TopBarItemsCtrl::GetPageText(size_t n) const +{ + ScalableButton* btn = m_pageButtons[n]; + return btn->GetLabel(); +} + +void TopBarItemsCtrl::AppendMenuItem(wxMenu* menu, const wxString& title) +{ + append_submenu(&m_menu, menu, wxID_ANY, title, "cog"); +} + +void TopBarItemsCtrl::AppendMenuSeparaorItem() +{ + m_menu.AppendSeparator(); +} + +void TopBarItemsCtrl::ShowMenu() +{ + wxPoint pos = m_menu_btn->GetPosition(); + wxGetApp().plater()->PopupMenu(&m_menu, pos); +} diff --git a/src/slic3r/GUI/TopBar.hpp b/src/slic3r/GUI/TopBar.hpp new file mode 100644 index 0000000000..1812b08180 --- /dev/null +++ b/src/slic3r/GUI/TopBar.hpp @@ -0,0 +1,459 @@ +#ifndef slic3r_TopBar_hpp_ +#define slic3r_TopBar_hpp_ + +//#ifdef _WIN32 + +#include +#include "wxExtensions.hpp" + +class ModeSizer; +//class ScalableButton; + +// custom message the TopBarItemsCtrl sends to its parent (TopBar) to notify a selection change: +wxDECLARE_EVENT(wxCUSTOMEVT_TOPBAR_SEL_CHANGED, wxCommandEvent); + +class TopBarItemsCtrl : public wxControl +{ + class Button : public ScalableButton + { + bool m_is_selected{ false }; + public: + Button() {}; + Button( wxWindow* parent, + const wxString& label, + const std::string& icon_name = "", + const int px_cnt = 16); + + ~Button() {} + + void set_selected(bool selected) { m_is_selected = selected; } + }; + + class ButtonWithPopup : public Button + { + public: + ButtonWithPopup() {}; + ButtonWithPopup(wxWindow* parent, + const wxString& label, + const std::string& icon_name = ""); + + ~ButtonWithPopup() {} + + void SetLabel(const wxString& label) override; + }; + + MenuWithSeparators m_menu; + MenuWithSeparators m_workspace_modes; + +public: + TopBarItemsCtrl(wxWindow* parent, bool add_mode_buttons = false); + ~TopBarItemsCtrl() {} + + void OnPaint(wxPaintEvent&); + void SetSelection(int sel); + void UpdateMode(); + void Rescale(); + void OnColorsChanged(); + void UpdateModeMarkers(); + void UpdateSelection(); + bool InsertPage(size_t n, const wxString& text, bool bSelect = false, const std::string& bmp_name = ""); + void RemovePage(size_t n); + void SetPageText(size_t n, const wxString& strText); + wxString GetPageText(size_t n) const; + + void AppendMenuItem(wxMenu* menu, const wxString& title); + void AppendMenuSeparaorItem(); + void ShowMenu(); + void AddModeItem(); + void ShowModes(); + +private: + wxWindow* m_parent; + wxFlexGridSizer* m_buttons_sizer; + wxBoxSizer* m_sizer; + ButtonWithPopup* m_menu_btn {nullptr}; + ButtonWithPopup* m_workspace_btn {nullptr}; + std::vector m_pageButtons; + int m_selection {-1}; + int m_btn_margin; + int m_line_margin; + ModeSizer* m_mode_sizer {nullptr}; +}; + +class TopBar : public wxBookCtrlBase +{ +public: + TopBar(wxWindow * parent, + wxWindowID winid = wxID_ANY, + const wxPoint & pos = wxDefaultPosition, + const wxSize & size = wxDefaultSize, + long style = 0, + bool add_mode_buttons = false) + { + Init(); + Create(parent, winid, pos, size, style, add_mode_buttons); + } + + bool Create(wxWindow * parent, + wxWindowID winid = wxID_ANY, + const wxPoint & pos = wxDefaultPosition, + const wxSize & size = wxDefaultSize, + long style = 0, + bool add_mode_buttons = false) + { + if (!wxBookCtrlBase::Create(parent, winid, pos, size, style | wxBK_TOP)) + return false; + + m_bookctrl = new TopBarItemsCtrl(this, add_mode_buttons); + + wxSizer* mainSizer = new wxBoxSizer(IsVertical() ? wxVERTICAL : wxHORIZONTAL); + + if (style & wxBK_RIGHT || style & wxBK_BOTTOM) + mainSizer->Add(0, 0, 1, wxEXPAND, 0); + + m_controlSizer = new wxBoxSizer(IsVertical() ? wxHORIZONTAL : wxVERTICAL); + m_controlSizer->Add(m_bookctrl, wxSizerFlags(1).Expand()); + wxSizerFlags flags; + if (IsVertical()) + flags.Expand(); + else + flags.CentreVertical(); + mainSizer->Add(m_controlSizer, flags.Border(wxALL, m_controlMargin)); + SetSizer(mainSizer); + + this->Bind(wxCUSTOMEVT_TOPBAR_SEL_CHANGED, [this](wxCommandEvent& evt) + { + if (int page_idx = evt.GetId(); page_idx >= 0) + SetSelection(page_idx); + }); + + this->Bind(wxEVT_NAVIGATION_KEY, &TopBar::OnNavigationKey, this); + + return true; + } + + + // Methods specific to this class. + + // A method allowing to add a new page without any label (which is unused + // by this control) and show it immediately. + bool ShowNewPage(wxWindow * page) + { + return AddPage(page, wxString(), ""/*true *//* select it */); + } + + + // Set effect to use for showing/hiding pages. + void SetEffects(wxShowEffect showEffect, wxShowEffect hideEffect) + { + m_showEffect = showEffect; + m_hideEffect = hideEffect; + } + + // Or the same effect for both of them. + void SetEffect(wxShowEffect effect) + { + SetEffects(effect, effect); + } + + // And the same for time outs. + void SetEffectsTimeouts(unsigned showTimeout, unsigned hideTimeout) + { + m_showTimeout = showTimeout; + m_hideTimeout = hideTimeout; + } + + void SetEffectTimeout(unsigned timeout) + { + SetEffectsTimeouts(timeout, timeout); + } + + + // Implement base class pure virtual methods. + + // adds a new page to the control + bool AddPage(wxWindow* page, + const wxString& text, + const std::string& bmp_name, + bool bSelect = false) + { + DoInvalidateBestSize(); + return InsertPage(GetPageCount(), page, text, bmp_name, bSelect); + } + + // Page management + virtual bool InsertPage(size_t n, + wxWindow * page, + const wxString & text, + bool bSelect = false, + int imageId = NO_IMAGE) override + { + if (!wxBookCtrlBase::InsertPage(n, page, text, bSelect, imageId)) + return false; + + GetTopBarItemsCtrl()->InsertPage(n, text, bSelect); + + if (!DoSetSelectionAfterInsertion(n, bSelect)) + page->Hide(); + + return true; + } + + bool InsertPage(size_t n, + wxWindow * page, + const wxString & text, + const std::string& bmp_name = "", + bool bSelect = false) + { + if (!wxBookCtrlBase::InsertPage(n, page, text, bSelect)) + return false; + + GetTopBarItemsCtrl()->InsertPage(n, text, bSelect, bmp_name); + + if (bSelect) + SetSelection(n); + + return true; + } + + virtual int SetSelection(size_t n) override + { + GetTopBarItemsCtrl()->SetSelection(n); + int ret = DoSetSelection(n, SetSelection_SendEvent); + + // check that only the selected page is visible and others are hidden: + for (size_t page = 0; page < m_pages.size(); page++) + if (page != n) + m_pages[page]->Hide(); + + return ret; + } + + virtual int ChangeSelection(size_t n) override + { + GetTopBarItemsCtrl()->SetSelection(n); + return DoSetSelection(n); + } + + // Neither labels nor images are supported but we still store the labels + // just in case the user code attaches some importance to them. + virtual bool SetPageText(size_t n, const wxString & strText) override + { + wxCHECK_MSG(n < GetPageCount(), false, wxS("Invalid page")); + + GetTopBarItemsCtrl()->SetPageText(n, strText); + + return true; + } + + virtual wxString GetPageText(size_t n) const override + { + wxCHECK_MSG(n < GetPageCount(), wxString(), wxS("Invalid page")); + return GetTopBarItemsCtrl()->GetPageText(n); + } + + virtual bool SetPageImage(size_t WXUNUSED(n), int WXUNUSED(imageId)) override + { + return false; + } + + virtual int GetPageImage(size_t WXUNUSED(n)) const override + { + return NO_IMAGE; + } + + // Override some wxWindow methods too. + virtual void SetFocus() override + { + wxWindow* const page = GetCurrentPage(); + if (page) + page->SetFocus(); + } + + TopBarItemsCtrl* GetTopBarItemsCtrl() const { return static_cast(m_bookctrl); } + + void UpdateMode() + { + GetTopBarItemsCtrl()->UpdateMode(); + } + + void Rescale() + { + GetTopBarItemsCtrl()->Rescale(); + } + + void OnColorsChanged() + { + GetTopBarItemsCtrl()->OnColorsChanged(); + } + + void UpdateModeMarkers() + { + GetTopBarItemsCtrl()->UpdateModeMarkers(); + } + + void OnNavigationKey(wxNavigationKeyEvent& event) + { + if (event.IsWindowChange()) { + // change pages + AdvanceSelection(event.GetDirection()); + } + else { + // we get this event in 3 cases + // + // a) one of our pages might have generated it because the user TABbed + // out from it in which case we should propagate the event upwards and + // our parent will take care of setting the focus to prev/next sibling + // + // or + // + // b) the parent panel wants to give the focus to us so that we + // forward it to our selected page. We can't deal with this in + // OnSetFocus() because we don't know which direction the focus came + // from in this case and so can't choose between setting the focus to + // first or last panel child + // + // or + // + // c) we ourselves (see MSWTranslateMessage) generated the event + // + wxWindow* const parent = GetParent(); + + // the wxObject* casts are required to avoid MinGW GCC 2.95.3 ICE + const bool isFromParent = event.GetEventObject() == (wxObject*)parent; + const bool isFromSelf = event.GetEventObject() == (wxObject*)this; + const bool isForward = event.GetDirection(); + + if (isFromSelf && !isForward) + { + // focus is currently on notebook tab and should leave + // it backwards (Shift-TAB) + event.SetCurrentFocus(this); + parent->HandleWindowEvent(event); + } + else if (isFromParent || isFromSelf) + { + // no, it doesn't come from child, case (b) or (c): forward to a + // page but only if entering notebook page (i.e. direction is + // backwards (Shift-TAB) comething from out-of-notebook, or + // direction is forward (TAB) from ourselves), + if (m_selection != wxNOT_FOUND && + (!event.GetDirection() || isFromSelf)) + { + // so that the page knows that the event comes from it's parent + // and is being propagated downwards + event.SetEventObject(this); + + wxWindow* page = m_pages[m_selection]; + if (!page->HandleWindowEvent(event)) + { + page->SetFocus(); + } + //else: page manages focus inside it itself + } + else // otherwise set the focus to the notebook itself + { + SetFocus(); + } + } + else + { + // it comes from our child, case (a), pass to the parent, but only + // if the direction is forwards. Otherwise set the focus to the + // notebook itself. The notebook is always the 'first' control of a + // page. + if (!isForward) + { + SetFocus(); + } + else if (parent) + { + event.SetCurrentFocus(this); + parent->HandleWindowEvent(event); + } + } + } + } + + // Methods for extensions of this class + + void AppendMenuItem(wxMenu* menu, const wxString& title) { + GetTopBarItemsCtrl()->AppendMenuItem(menu, title); + } + + void AppendMenuSeparaorItem() { + GetTopBarItemsCtrl()->AppendMenuSeparaorItem(); + } + + +protected: + virtual void UpdateSelectedPage(size_t WXUNUSED(newsel)) override + { + // Nothing to do here, but must be overridden to avoid the assert in + // the base class version. + } + + virtual wxBookCtrlEvent * CreatePageChangingEvent() const override + { + return new wxBookCtrlEvent(wxEVT_BOOKCTRL_PAGE_CHANGING, + GetId()); + } + + virtual void MakeChangedEvent(wxBookCtrlEvent & event) override + { + event.SetEventType(wxEVT_BOOKCTRL_PAGE_CHANGED); + } + + virtual wxWindow * DoRemovePage(size_t page) override + { + wxWindow* const win = wxBookCtrlBase::DoRemovePage(page); + if (win) + { + GetTopBarItemsCtrl()->RemovePage(page); + DoSetSelectionAfterRemoval(page); + } + + return win; + } + + virtual void DoSize() override + { + wxWindow* const page = GetCurrentPage(); + if (page) + page->SetSize(GetPageRect()); + } + + virtual void DoShowPage(wxWindow * page, bool show) override + { + if (show) + page->ShowWithEffect(m_showEffect, m_showTimeout); + else + page->HideWithEffect(m_hideEffect, m_hideTimeout); + } + +private: + void Init() + { + // We don't need any border as we don't have anything to separate the + // page contents from. + SetInternalBorder(0); + + // No effects by default. + m_showEffect = + m_hideEffect = wxSHOW_EFFECT_NONE; + + m_showTimeout = + m_hideTimeout = 0; + } + + wxShowEffect m_showEffect, + m_hideEffect; + + unsigned m_showTimeout, + m_hideTimeout; + + TopBarItemsCtrl* m_ctrl{ nullptr }; + +}; +//#endif // _WIN32 +#endif // slic3r_TopBar_hpp_ diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 71ace3dbe0..157418dd75 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -906,6 +906,8 @@ int ScalableButton::GetBitmapHeight() void ScalableButton::sys_color_changed() { + if (m_current_icon_name.empty()) + return; Slic3r::GUI::wxGetApp().UpdateDarkUI(this, m_has_border); wxBitmapBundle bmp = *get_bmp_bundle(m_current_icon_name, m_bmp_width, m_bmp_height);