#include "TopBar.hpp" #include "TopBarMenus.hpp" #include "GUI_App.hpp" #include "Search.hpp" #include "format.hpp" #include "I18N.hpp" #include #include wxDEFINE_EVENT(wxCUSTOMEVT_TOPBAR_SEL_CHANGED, wxCommandEvent); using namespace Slic3r::GUI; #ifdef __APPLE__ #define down_arrow L"\u25BC"; #else #define down_arrow L"\u23f7"; #endif TopBarItemsCtrl::Button::Button(wxWindow* parent, const wxString& label, const std::string& icon_name, const int px_cnt, wxSize size_def) :ScalableButton(parent, wxID_ANY, icon_name, label, size_def, wxDefaultPosition, wxNO_BORDER, px_cnt) #ifdef _WIN32 ,m_background_color(wxGetApp().get_window_default_clr()) #else ,m_background_color(wxTransparentColor) #endif ,m_foreground_color(wxGetApp().get_label_clr_default()) ,m_bmp_bundle(icon_name.empty() ? wxBitmapBundle() : *get_bmp_bundle(icon_name, px_cnt)) { int btn_margin = em_unit(this); int x, y; GetTextExtent(label.IsEmpty() ? "a" : label, &x, &y); wxSize size(x + 4 * btn_margin, y + int(1.5 * btn_margin)); if (icon_name.empty()) this->SetMinSize(size); else if (label.IsEmpty()) { const int btn_side = size.y; this->SetMinSize(wxSize(btn_side, btn_side)); } else #ifdef _WIN32 this->SetMinSize(wxSize(-1, size.y)); #else this->SetMinSize(wxSize(size.x + px_cnt, size.y)); #endif //button events Bind(wxEVT_SET_FOCUS, [this](wxFocusEvent& event) { set_hovered(true ); event.Skip(); }); Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent& event) { set_hovered(false); event.Skip(); }); Bind(wxEVT_ENTER_WINDOW, [this](wxMouseEvent& event) { set_hovered(true ); event.Skip(); }); Bind(wxEVT_LEAVE_WINDOW, [this](wxMouseEvent& event) { set_hovered(false); event.Skip(); }); Bind(wxEVT_PAINT, [this](wxPaintEvent&) { render(); }); } void TopBarItemsCtrl::Button::set_selected(bool selected) { m_is_selected = selected; m_foreground_color = m_is_selected ? wxGetApp().get_window_default_clr(): wxGetApp().get_label_clr_default() ; m_background_color = m_is_selected ? wxGetApp().get_label_clr_default() : #ifdef _WIN32 wxGetApp().get_window_default_clr(); #else wxTransparentColor; #endif #ifdef __linux__ this->SetBackgroundColour(m_background_color); this->SetForegroundColour(m_foreground_color); this->Refresh(); this->Update(); #endif // __linux__ } void TopBarItemsCtrl::Button::set_hovered(bool hovered) { using namespace Slic3r::GUI; #ifdef _WIN32 this->GetParent()->Refresh(); // force redraw a background of the selected mode button #endif /* no _WIN32 */ m_background_color = m_is_selected ? wxGetApp().get_label_clr_default() : hovered ? wxGetApp().get_color_selected_btn_bg() : #ifdef _WIN32 wxGetApp().get_window_default_clr(); #else wxTransparentColor; #endif #ifdef __linux__ this->SetBackgroundColour(m_background_color); #endif // __linux__ this->Refresh(); this->Update(); } void TopBarItemsCtrl::Button::render() { const wxRect rc(GetSize()); wxPaintDC dc(this); #ifdef _WIN32 // Draw default background dc.SetPen(wxGetApp().get_window_default_clr()); dc.SetBrush(wxGetApp().get_window_default_clr()); dc.DrawRectangle(rc); #endif int em = em_unit(this); // Draw def rect with rounded corners dc.SetPen(m_background_color); dc.SetBrush(m_background_color); dc.DrawRoundedRectangle(rc, int(0.4* em)); wxPoint pt = { 0, 0 }; wxString text = GetLabelText(); if (m_bmp_bundle.IsOk()) { wxSize szIcon = get_preferred_size(m_bmp_bundle, this); pt.x = text.IsEmpty() ? ((rc.width - szIcon.x) / 2) : em; pt.y = (rc.height - szIcon.y) / 2; #ifdef __WXGTK3__ dc.DrawBitmap(m_bmp_bundle.GetBitmap(szIcon), pt, true); #else dc.DrawBitmap(m_bmp_bundle.GetBitmapFor(this), pt, true); #endif pt.x += szIcon.x; } // Draw text if (!text.IsEmpty()) { wxSize labelSize = dc.GetTextExtent(text); if (labelSize.x > rc.width) text = wxControl::Ellipsize(text, dc, wxELLIPSIZE_END, rc.width); pt.x += (rc.width - pt.x - labelSize.x) / 2; pt.y = (rc.height - labelSize.y) / 2; dc.SetTextForeground(m_foreground_color); dc.SetFont(GetFont()); dc.DrawText(text, pt); } } void TopBarItemsCtrl::Button::sys_color_changed() { ScalableButton::sys_color_changed(); #ifdef _WIN32 m_background_color = wxGetApp().get_window_default_clr(); #endif m_foreground_color = wxGetApp().get_label_clr_default(); } #ifdef __linux__ const int icon_sz = 20; #else const int icon_sz = 24; #endif TopBarItemsCtrl::ButtonWithPopup::ButtonWithPopup(wxWindow* parent, const wxString& label, const std::string& icon_name, wxSize size) :TopBarItemsCtrl::Button(parent, label, icon_name, icon_sz, size) { if (size != wxDefaultSize) m_fixed_width = size.x * 0.1; this->SetLabel(label); } TopBarItemsCtrl::ButtonWithPopup::ButtonWithPopup(wxWindow* parent, const std::string& icon_name, int icon_width/* = 20*/, int icon_height/* = 20*/) :TopBarItemsCtrl::Button(parent, "", icon_name, icon_width) { } void TopBarItemsCtrl::ButtonWithPopup::SetLabel(const wxString& label) { wxString text = label; int btn_height = GetMinSize().GetHeight(); if (label.IsEmpty()) { ScalableButton::SetLabel(label); SetMinSize(wxSize(btn_height, btn_height)); return; } const int label_width = GetTextExtent(text).GetWidth(); bool resize_and_layout{ false }; if (m_fixed_width != wxDefaultCoord) { const int text_width = m_fixed_width * em_unit(this) - 2 * btn_height; if (label_width > text_width || GetMinSize().GetWidth() <= btn_height) { wxWindowDC wdc(this); text = wxControl::Ellipsize(text, wdc, wxELLIPSIZE_END, text_width); resize_and_layout = true; } } else if (GetMinSize().GetWidth() <= btn_height) #ifdef _WIN32 this->SetMinSize(wxSize(-1, btn_height)); #elif __APPLE__ this->SetMinSize(wxSize(label_width + 3 * btn_height, btn_height)); #else this->SetMinSize(wxSize(label_width + 2 * btn_height, btn_height)); #endif wxString full_label = " " + text + " "; #ifndef __linux__ full_label += down_arrow; #endif ScalableButton::SetLabel(full_label); if (resize_and_layout) { SetMinSize(wxSize(m_fixed_width * em_unit(this), btn_height)); GetParent()->Layout(); } } void TopBarItemsCtrl::UpdateAccountButton(bool avatar/* = false*/) { TopBarMenus::UserAccountInfo user_account = m_menus->get_user_account_info(); const wxString user_name = user_account.is_logged ? from_u8(user_account.user_name) : _L("Log in"); m_account_btn->SetToolTip(user_name); #ifdef __linux__ if (avatar) { if (user_account.is_logged) { ScalableBitmap new_logo(this, user_account.avatar_path, wxSize(icon_sz, icon_sz)); if (new_logo.IsOk()) m_account_btn->SetBitmap_(new_logo); else m_account_btn->SetBitmap_("user"); } else { m_account_btn->SetBitmap_("user"); } } #else if (avatar) { if (user_account.is_logged) { ScalableBitmap new_logo(this, user_account.avatar_path, wxSize(icon_sz, icon_sz)); if (new_logo.IsOk()) m_account_btn->SetBitmapBundle(new_logo.bmp()); else m_account_btn->SetBitmapBundle(*get_bmp_bundle("user", icon_sz)); } else { m_account_btn->SetBitmapBundle(*get_bmp_bundle("user", icon_sz)); } } #endif m_account_btn->SetLabel(m_collapsed_btns ? "" : user_name); this->Layout(); // m_account_btn->Refresh(); } void TopBarItemsCtrl::UnselectPopupButtons() { if (m_menu_btn) m_menu_btn ->set_selected(false); m_workspace_btn ->set_selected(false); m_account_btn ->set_selected(false); } void TopBarItemsCtrl::CreateSearch() { // Linux specific: If wxDefaultSize is used in constructor and than set just maxSize, // than this max size will be used as a default control size and can't be resized. // So, set initial size for some minimum value m_search = new ::TextInput(this, wxGetApp().searcher().default_string, "", "search", wxDefaultPosition, wxSize(2 * em_unit(this), -1), wxTE_PROCESS_ENTER); m_search->SetMaxSize(wxSize(42*em_unit(this), -1)); wxGetApp().UpdateDarkUI(m_search); m_search->Bind(wxEVT_TEXT, [](wxEvent& e) { wxGetApp().searcher().edit_search_input(); wxGetApp().update_search_lines(); }); m_search->Bind(wxEVT_MOVE, [](wxMoveEvent& event) { event.Skip(); wxGetApp().searcher().update_dialog_position(); }); m_search->SetOnDropDownIcon([this]() { wxGetApp().searcher().set_search_input(m_search); wxGetApp().show_search_dialog(); }); m_search->Bind(wxEVT_KILL_FOCUS, [](wxFocusEvent& e) { e.Skip(); wxGetApp().searcher().check_and_hide_dialog(); }); wxTextCtrl* ctrl = m_search->GetTextCtrl(); ctrl->SetToolTip(format_wxstr(_L("Search in settings [%1%]"), "Ctrl+F")); ctrl->Bind(wxEVT_KEY_DOWN, [this](wxKeyEvent& e) { wxGetApp().searcher().set_search_input(m_search); if (e.GetKeyCode() == WXK_TAB) m_search->Navigate(e.ShiftDown() ? wxNavigationKeyEvent::IsBackward : wxNavigationKeyEvent::IsForward); else wxGetApp().searcher().process_key_down_from_input(e); e.Skip(); }); ctrl->Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent& event) { wxGetApp().searcher().set_search_input(m_search); wxGetApp().show_search_dialog(); event.Skip(); }); ctrl->Bind(wxEVT_LEFT_UP, [this](wxMouseEvent& event) { if (m_search->GetValue() == wxGetApp().searcher().default_string) m_search->SetValue(""); event.Skip(); }); } void TopBarItemsCtrl::UpdateSearchSizeAndPosition() { if (!m_workspace_btn || !m_account_btn) return; int em = em_unit(this); wxWindow* parent_win = GetParent()->GetParent(); int top_win_without_sidebar = parent_win->GetSize().GetWidth() - 42 * em; bool update_bnts{ false }; if (top_win_without_sidebar - m_btns_width < 15 * em) { if (!m_collapsed_btns) { m_sizer->SetItemMinSize(1, wxSize(20, -1)); m_collapsed_btns = update_bnts = true; } } else if (m_collapsed_btns) { m_sizer->SetItemMinSize(1, wxSize(42 * em, -1)); m_collapsed_btns = false; update_bnts = true; } if (update_bnts) { UpdateMode(); UpdateAccountButton(); } } void TopBarItemsCtrl::UpdateSearch(const wxString& search) { if (search != m_search->GetValue()) m_search->SetValue(search); } void TopBarItemsCtrl::update_margins() { int em = em_unit(this); m_btn_margin = std::lround(0.9 * em); } wxPoint TopBarItemsCtrl::ButtonWithPopup::get_popup_pos() { wxPoint pos = this->GetPosition(); pos.y += this->GetSize().GetHeight() + int(0.2 * wxGetApp().em_unit()); return pos; } void TopBarItemsCtrl::update_btns_width() { int em = em_unit(this); m_btns_width = 2 * m_btn_margin; if (m_menu_btn) m_btns_width += m_menu_btn->GetSize().GetWidth(); else m_btns_width += 4 * em; if (m_settings_btn) m_btns_width += m_settings_btn->GetSize().GetWidth() + m_btn_margin; else { for (const Button* btn : m_pageButtons) m_btns_width += btn->GetSize().GetWidth() + m_btn_margin; } // Check min width of parent and change it if needed int sizebar_w = 25; wxWindow* parent_win = GetParent()->GetParent(); int top_win_without_sidebar = parent_win->GetSize().GetWidth() - sizebar_w * em; if (top_win_without_sidebar < 0) return; wxSize min_sz = parent_win->GetMinSize(); if (m_btns_width < (76 - sizebar_w) * em) { if (min_sz.GetWidth() > 76 * em) parent_win->SetMinSize(wxSize(76 * em, 49 * em)); } else { wxSize new_size = wxSize(m_btns_width + sizebar_w * em, 49 * em); parent_win->SetMinSize(new_size); if (top_win_without_sidebar < m_btns_width) parent_win->SetSize(new_size); } } TopBarItemsCtrl::TopBarItemsCtrl(wxWindow *parent, TopBarMenus* menus/* = nullptr*/, std::function cb_settings_btn/* = nullptr*/) : wxControl(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBORDER_NONE | wxTAB_TRAVERSAL) ,m_menus(menus) ,m_cb_settings_btn(cb_settings_btn) { wxGetApp().UpdateDarkUI(this); #ifdef _WIN32 SetDoubleBuffered(true); #endif //__WINDOWS__ update_margins(); m_sizer = new wxFlexGridSizer(2); m_sizer->AddGrowableCol(0); m_sizer->SetFlexibleDirection(wxHORIZONTAL); this->SetSizer(m_sizer); wxBoxSizer* left_sizer = new wxBoxSizer(wxHORIZONTAL); #ifdef __APPLE__ auto logo = new wxStaticBitmap(this, wxID_ANY, *get_bmp_bundle(wxGetApp().logo_name(), 40)); left_sizer->Add(logo, 0, wxALIGN_CENTER_VERTICAL | wxALL, m_btn_margin); #else m_menu_btn = new ButtonWithPopup(this, _L("Menu"), wxGetApp().logo_name()); left_sizer->Add(m_menu_btn, 0, wxALIGN_CENTER_VERTICAL | wxALL, m_btn_margin); m_menu_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent& event) { m_menu_btn->set_selected(true); m_menus->Popup(this, &m_menus->main, m_menu_btn->get_popup_pos()); }); #endif if (m_cb_settings_btn) { m_settings_btn = new Button(this, _L("Settings"/*, "settings"*/)); m_settings_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent& event) { m_cb_settings_btn(); }); left_sizer->Add(m_settings_btn, 0, wxALIGN_CENTER_VERTICAL); } m_buttons_sizer = new wxFlexGridSizer(1, m_btn_margin, m_btn_margin); left_sizer->Add(m_buttons_sizer, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, m_btn_margin); CreateSearch(); if (m_cb_settings_btn) wxGetApp().searcher().set_search_input(m_search); wxBoxSizer* search_sizer = new wxBoxSizer(wxVERTICAL); search_sizer->Add(m_search, 1, wxEXPAND | wxALIGN_RIGHT); left_sizer->Add(search_sizer, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, m_btn_margin); m_sizer->Add(left_sizer, 1, wxEXPAND); wxBoxSizer* right_sizer = new wxBoxSizer(wxHORIZONTAL); m_workspace_btn = new ButtonWithPopup(this, "Workspace", "mode_simple"); right_sizer->AddStretchSpacer(20); right_sizer->Add(m_workspace_btn, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT | wxALL, m_btn_margin); m_workspace_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent& event) { m_workspace_btn->set_selected(true); m_menus->Popup(this, &m_menus->workspaces, m_workspace_btn->get_popup_pos()); }); m_account_btn = new ButtonWithPopup(this, _L("Log in"), "user", wxSize(180, -1)); right_sizer->Add(m_account_btn, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT | wxRIGHT, m_btn_margin); m_account_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent& event) { m_account_btn->set_selected(true); m_menus->Popup(this, &m_menus->account, m_account_btn->get_popup_pos()); }); m_sizer->Add(right_sizer, 0, wxALIGN_CENTER_VERTICAL); m_sizer->SetItemMinSize(1, wxSize(42 * wxGetApp().em_unit(), -1)); update_btns_width(); } void TopBarItemsCtrl::UpdateMode() { wxBitmapBundle bmp = *m_menus->get_workspace_bitmap(); #ifdef __linux__ m_workspace_btn->SetBitmap(bmp); m_workspace_btn->SetBitmapCurrent(bmp); m_workspace_btn->SetBitmapPressed(bmp); #else m_workspace_btn->SetBitmapBundle(bmp); #endif m_workspace_btn->SetLabel(m_collapsed_btns ? "" : m_menus->get_workspace_name()); this->Layout(); } void TopBarItemsCtrl::ShowUserAccount(bool show) { m_account_btn->Show(show); this->Layout(); } void TopBarItemsCtrl::Rescale() { update_margins(); int em = em_unit(this); m_search->SetMinSize(wxSize(4 * em, -1)); m_search->SetMaxSize(wxSize(42 * em, -1)); m_search->Rescale(); m_buttons_sizer->SetVGap(m_btn_margin); m_buttons_sizer->SetHGap(m_btn_margin); // call Layout before update buttons width to process recaling of the buttons m_sizer->Layout(); update_btns_width(); UpdateSearchSizeAndPosition(); m_sizer->Layout(); } void TopBarItemsCtrl::OnColorsChanged() { wxGetApp().UpdateDarkUI(this); if (m_menus) m_menus->sys_color_changed(); if (m_menu_btn) m_menu_btn->sys_color_changed(); if (m_settings_btn) m_settings_btn->sys_color_changed(); m_workspace_btn->sys_color_changed(); m_account_btn->sys_color_changed(); UpdateAccountButton(true); m_search->SysColorsChanged(); UpdateSelection(); UpdateMode(); m_sizer->Layout(); } void TopBarItemsCtrl::UpdateModeMarkers() { UpdateMode(); m_menus->ApplyWorkspacesMenu(); } 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, bool force /*= false*/) { if (m_selection == sel && !force) 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); update_btns_width(); UpdateSearchSizeAndPosition(); 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); // Under OSX call of btn->Reparent(nullptr) causes a crash, so as a workaround use RemoveChild() instead this->RemoveChild(btn); btn->Destroy(); update_btns_width(); UpdateSearchSizeAndPosition(); m_sizer->Layout(); } void TopBarItemsCtrl::SetPageText(size_t n, const wxString& strText) { ScalableButton* btn = m_pageButtons[n]; btn->SetLabel(strText); update_btns_width(); UpdateSearchSizeAndPosition(); } wxString TopBarItemsCtrl::GetPageText(size_t n) const { ScalableButton* btn = m_pageButtons[n]; return btn->GetLabel(); } void TopBarItemsCtrl::ShowFull() { if (m_menu_btn) m_menu_btn->Show(); if (m_settings_btn) m_settings_btn->Show(); m_account_btn->Show(); update_btns_width(); UpdateSearchSizeAndPosition(); } void TopBarItemsCtrl::ShowJustMode() { if (m_menu_btn) m_menu_btn->Hide(); if (m_settings_btn) m_settings_btn->Hide(); m_account_btn->Hide(); update_btns_width(); UpdateSearchSizeAndPosition(); } void TopBarItemsCtrl::SetSettingsButtonTooltip(const wxString& tooltip) { if (m_settings_btn) m_settings_btn->SetToolTip(tooltip); }