diff --git a/src/slic3r/GUI/PublishSettingsDialog.cpp b/src/slic3r/GUI/PublishSettingsDialog.cpp index 57818dca82..bd5c045053 100644 --- a/src/slic3r/GUI/PublishSettingsDialog.cpp +++ b/src/slic3r/GUI/PublishSettingsDialog.cpp @@ -8,6 +8,8 @@ #include "ConfigValueFormatter.hpp" #include "FilamentBitmapUtils.hpp" #include "Widgets/Label.hpp" +#include "Widgets/CheckBox.hpp" +#include "Widgets/HyperLink.hpp" #include "Widgets/TextInput.hpp" #include "Widgets/DialogButtons.hpp" #include "Widgets/StaticLine.hpp" @@ -39,6 +41,48 @@ namespace Slic3r { namespace GUI { namespace { +// Orca's bitmap checkbox has the established teal checked state on every platform. Keep the +// label separate so it stays clickable like a native wxCheckBox, while the control itself +// remains accessible by keyboard. +wxStaticText* add_checkbox_label(wxWindow* parent, + wxBoxSizer* sizer, + ::CheckBox* check, + const wxString& label, + const wxString& tooltip, + int label_width = 0) +{ + check->SetToolTip(tooltip); + sizer->Add(check, 0, wxALIGN_CENTER_VERTICAL | wxTOP | wxBOTTOM, parent->FromDIP(2)); + + auto* text = new wxStaticText(parent, wxID_ANY, label); + text->SetFont(Label::Body_14); + text->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#363636"))); + if (label_width > 0) { + text->SetMinSize(wxSize(label_width, -1)); + text->SetMaxSize(wxSize(label_width, -1)); + text->Wrap(label_width); + } + text->SetToolTip(tooltip); + text->SetCursor(wxCURSOR_HAND); + const auto toggle = [check]() { + if (!check->IsEnabled()) + return; + check->SetValue(!check->GetValue()); + wxCommandEvent event(wxEVT_TOGGLEBUTTON, check->GetId()); + event.SetEventObject(check); + check->GetEventHandler()->ProcessEvent(event); + }; + text->Bind(wxEVT_LEFT_DOWN, [toggle](wxMouseEvent& event) { + if (!event.LeftDClick()) + toggle(); + }); + text->Bind(wxEVT_LEFT_DCLICK, [toggle](wxMouseEvent&) { + toggle(); + }); + sizer->Add(text, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, parent->FromDIP(5)); + return text; +} + // Menu ids for show_menu(): dedicated range so the popup cannot collide with application-level // bindings (e.g. MainFrame's recent-files wxID_FILE1.. range). enum { @@ -649,6 +693,9 @@ PublishSettingsDialog::PublishSettingsDialog(wxWindow* parent, m_outer_tabs->SetBackgroundColour(GetBackgroundColour()); m_outer_host = new wxPanel(this, wxID_ANY); +#ifdef __WINDOWS__ + m_outer_host->SetDoubleBuffered(true); +#endif m_outer_host->SetBackgroundColour(GetBackgroundColour()); m_outer_host_sizer = new wxBoxSizer(wxVERTICAL); m_outer_host->SetSizer(m_outer_host_sizer); @@ -688,24 +735,22 @@ PublishSettingsDialog::PublishSettingsDialog(wxWindow* parent, dlg_btns->GetCANCEL()->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { EndModal(wxID_CANCEL); }); // Guide links, bottom-left, sharing the footer row with the OK/Cancel buttons (pushed right). - auto make_link = [this](const wxString& label, const char* url) { - wxStaticText* link = new wxStaticText(this, wxID_ANY, label); - link->SetFont(Label::Body_13); - link->SetForegroundColour(wxColour(0x1F, 0x8E, 0xEA)); - link->SetCursor(wxCURSOR_HAND); - link->Bind(wxEVT_LEFT_DOWN, [url](wxMouseEvent&) { wxLaunchDefaultBrowser(url, wxBROWSER_NEW_WINDOW); }); - return link; - }; wxBoxSizer* links_sizer = new wxBoxSizer(wxVERTICAL); - links_sizer->Add(make_link(_L("Publish 3MF Wiki"), "https://www.orcaslicer.com/wiki/publishing_3mf/publish_3mf.html"), 0, wxALIGN_LEFT); - links_sizer->Add(make_link(_L("Publish 3MF Video Guide"), "https://www.youtube.com/watch?v=-xt1N29UIOg"), 0, - wxTOP | wxALIGN_LEFT, FromDIP(4)); + auto* wiki_link = new HyperLink(this, _L("Wiki Guide"), "https://www.orcaslicer.com/wiki/publishing_3mf/publish_3mf.html"); + auto* video_link = new HyperLink(this, _L("Video Guide"), "https://www.youtube.com/watch?v=-xt1N29UIOg"); + links_sizer->Add(wiki_link , 0, wxALIGN_LEFT); + links_sizer->Add(video_link, 0, wxTOP | wxALIGN_LEFT, FromDIP(4)); wxBoxSizer* footer = new wxBoxSizer(wxHORIZONTAL); footer->Add(links_sizer, 0, wxALIGN_CENTER_VERTICAL); footer->AddStretchSpacer(); footer->Add(dlg_btns, 0, wxALIGN_CENTER_VERTICAL); - w_sizer->Add(footer, 0, wxRIGHT | wxLEFT | wxBOTTOM | wxEXPAND, FromDIP(10)); + auto* footer_line = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(-1, 1)); + footer_line->SetBackgroundColour(wxColour("#CECECE")); + footer_line->SetMinSize(wxSize(-1, 1)); + footer_line->SetMaxSize(wxSize(-1, 1)); + w_sizer->Add(footer_line, 0, wxRIGHT | wxLEFT | wxTOP | wxEXPAND, FromDIP(10)); + w_sizer->Add(footer, 0, wxRIGHT | wxLEFT | wxTOP | wxBOTTOM | wxEXPAND, FromDIP(10)); SetSizerAndFit(w_sizer); fit_to_content(); // initial size only; the dialog is resizable @@ -718,14 +763,14 @@ PublishSettingsDialog::PublishSettingsDialog(wxWindow* parent, // Size the window to its content: width follows the widest tab strip so no filament tab is // hidden (TabCtrl::relayout hides overflowing buttons), height scales proportionally. Both -// are floored at the 600x500 base and capped at hard DIP limits - deliberately not the whole +// are floored at the 530x530 base and capped at hard DIP limits - deliberately not the whole // display - with one last-resort clamp so the dialog can never open larger than the screen. // Also owns the resize floor: the window cannot be resized below what the tabs need, so // shrinking never re-hides a filament tab. void PublishSettingsDialog::fit_to_content() { - static const wxSize BASE{600, 500}; - static const wxSize CAP{1300, 850}; + static const wxSize BASE{530, 530}; // base size in DIP, the minimum the dialog can shrink to + static const wxSize CAP{1300, 850}; // hard cap in DIP, the maximum the dialog can grow to int strip = m_outer_tabs->GetFullSize(); for (const SectionGroup& section : m_sections) { @@ -793,6 +838,8 @@ void PublishSettingsDialog::build_option_model() return false; value = get_string_value(opt_id, full); unit = _(def->sidetext); + if (unit == "%" && value.EndsWith("%")) + unit.clear(); return true; }; @@ -1005,13 +1052,19 @@ void PublishSettingsDialog::build_option_model() // stays valid even if the vector is reallocated later. for (size_t c = 0; c < m_categories.size(); ++c) if (m_categories[c].enable_check != nullptr) - m_categories[c].enable_check->Bind(wxEVT_CHECKBOX, [this, c](wxCommandEvent&) { on_enable_toggle(c); }); + m_categories[c].enable_check->Bind(wxEVT_TOGGLEBUTTON, [this, c](wxCommandEvent& event) { + on_enable_toggle(c); + event.Skip(); + }); // Wire the "Full Publish" checkboxes (physical slots): toggling one disables/enables the // material's rows. for (size_t c = 0; c < m_categories.size(); ++c) if (m_categories[c].full_check != nullptr) - m_categories[c].full_check->Bind(wxEVT_CHECKBOX, [this, c](wxCommandEvent&) { on_full_toggle(c); }); + m_categories[c].full_check->Bind(wxEVT_TOGGLEBUTTON, [this, c](wxCommandEvent& event) { + on_full_toggle(c); + event.Skip(); + }); // No filter is active at startup: every row matches until the user types. for (Row& row : m_rows) @@ -1074,6 +1127,9 @@ size_t PublishSettingsDialog::section_group_for(Section kind) section.mixed_tabs->Hide(); } section.page_host = new wxPanel(section.page, wxID_ANY); +#ifdef __WINDOWS__ + section.page_host->SetDoubleBuffered(true); +#endif section.page_host->SetBackgroundColour(GetBackgroundColour()); section.page_host_sizer = new wxBoxSizer(wxVERTICAL); section.page_host->SetSizer(section.page_host_sizer); @@ -1136,10 +1192,9 @@ size_t PublishSettingsDialog::category_index_for( if (is_mixed) { // No chip/title: the lone "Enable" checkbox tops the page. auto* enable_sizer = new wxBoxSizer(wxHORIZONTAL); - category.enable_check = new wxCheckBox(category.page, wxID_ANY, _L("Enable")); - category.enable_check->SetFont(Label::Body_13); - category.enable_check->SetToolTip(_L("Publish this mixed filament and enable + Full Publish its component filaments")); - enable_sizer->Add(category.enable_check, 0, wxALIGN_CENTER_VERTICAL); + category.enable_check = new ::CheckBox(category.page, wxID_ANY); + category.enable_label = add_checkbox_label(category.page, enable_sizer, category.enable_check, _L("Enable"), + _L("Publish this mixed filament and enable + Full Publish its component filaments")); page_sizer->Add(enable_sizer, 0, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, FromDIP(6)); } else { // Line 1: [chip] [title] [Enable]. The Enable checkbox gates the whole slot: while @@ -1152,19 +1207,19 @@ size_t PublishSettingsDialog::category_index_for( category.title_label = new wxStaticText(category.page, wxID_ANY, title); category.title_label->SetFont(Label::Head_14); header_sizer->Add(category.title_label, 0, wxALIGN_CENTER_VERTICAL); - category.enable_check = new wxCheckBox(category.page, wxID_ANY, _L("Enable")); - category.enable_check->SetFont(Label::Body_13); - category.enable_check->SetToolTip(_L("Publish this filament slot in the 3MF file")); - header_sizer->Add(category.enable_check, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(10)); + auto* enable_sizer = new wxBoxSizer(wxHORIZONTAL); + category.enable_check = new ::CheckBox(category.page, wxID_ANY); + category.enable_label = add_checkbox_label(category.page, enable_sizer, category.enable_check, _L("Enable"), + _L("Publish this filament slot in the 3MF file")); + header_sizer->Add(enable_sizer, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(10)); page_sizer->Add(header_sizer, 0, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, FromDIP(6)); // Line 2: the "Full Publish" toggle, on its own line below the title (hidden until // the slot is enabled), aligned with the colour chip above it. auto* full_sizer = new wxBoxSizer(wxHORIZONTAL); - category.full_check = new wxCheckBox(category.page, wxID_ANY, _L("Full Publish")); - category.full_check->SetFont(Label::Body_13); - category.full_check->SetToolTip(_L("Embed the entire filament of this slot in the 3MF file")); - full_sizer->Add(category.full_check, 0, wxALIGN_CENTER_VERTICAL); + category.full_check = new ::CheckBox(category.page, wxID_ANY); + category.full_label = add_checkbox_label(category.page, full_sizer, category.full_check, _L("Full Publish"), + _L("Embed the entire filament of this slot in the 3MF file")); category.full_line_item = page_sizer->Add(full_sizer, 0, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, FromDIP(6)); } } @@ -1180,7 +1235,7 @@ size_t PublishSettingsDialog::category_index_for( category.info->SetFont(Label::Body_13); category.list_sizer->Add(category.info, 1, wxALIGN_CENTER_HORIZONTAL | wxALL, FromDIP(10)); category.info->Hide(); - page_sizer->Add(category.scroll, 1, wxEXPAND | wxALL, FromDIP(4)); + page_sizer->Add(category.scroll, 1, wxEXPAND | wxTOP | wxBOTTOM, FromDIP(4)); // A material slot starts disabled: its rows (and its Full Publish line) stay hidden until // "Enable" is checked. if (section == Section::Material) @@ -1241,7 +1296,7 @@ size_t PublishSettingsDialog::subcategory_index_for(size_t category_index, const sub.header->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#363636"))); auto* wrap = new wxBoxSizer(wxVERTICAL); wrap->Add(sub.header, 0, wxEXPAND | wxTOP | wxBOTTOM, FromDIP(6)); - sub.item = category.list_sizer->Add(wrap, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(22)); + sub.item = category.list_sizer->Add(wrap, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(5)); } category.subs.push_back(std::move(sub)); return category.subs.size() - 1; @@ -1271,15 +1326,18 @@ void PublishSettingsDialog::add_row_ui(const std::string& key, const size_t row_index = m_rows.size(); m_rows.push_back(std::move(row)); Row& current = m_rows[row_index]; - current.check = new wxCheckBox(category.scroll, wxID_ANY, label); - current.check->SetFont(Label::Body_13); - current.check->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent&) { refresh_tab_indicators(); }); + current.check = new ::CheckBox(category.scroll, wxID_ANY); + current.check->Bind(wxEVT_TOGGLEBUTTON, [this](wxCommandEvent& event) { + refresh_tab_indicators(); + event.Skip(); + }); auto* row_sizer = new wxBoxSizer(wxHORIZONTAL); - row_sizer->Add(current.check, 0, wxALIGN_CENTER_VERTICAL); + current.check_label = add_checkbox_label(category.scroll, row_sizer, current.check, label + ":", wxEmptyString, + 24 * wxGetApp().em_unit()); // The value is read-only text (incl. the Type row: the published type is the slot's // normalized type, not author-editable). current.value_label = new wxStaticText(category.scroll, wxID_ANY, value, wxDefaultPosition, wxDefaultSize, wxST_ELLIPSIZE_END); - current.value_label->SetFont(Label::Body_13); + current.value_label->SetFont(Label::Body_14); current.value_label->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#262E30"))); current.value_label->SetToolTip(unit.IsEmpty() ? value : value + " " + unit); if (kind == RowKind::Color && !value.IsEmpty()) { @@ -1290,14 +1348,14 @@ void PublishSettingsDialog::add_row_ui(const std::string& key, row_sizer->Add(current.color_chip, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(8)); } } - row_sizer->Add(current.value_label, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(8)); + row_sizer->Add(current.value_label, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(8)); if (!unit.IsEmpty()) { current.unit_label = new wxStaticText(category.scroll, wxID_ANY, unit); - current.unit_label->SetFont(Label::Body_13); + current.unit_label->SetFont(Label::Body_14); current.unit_label->SetForegroundColour(StateColor::darkModeColorFor(wxColour("#6B6B6B"))); row_sizer->Add(current.unit_label, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(4)); } - current.item = category.list_sizer->Add(row_sizer, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(38)); + current.item = category.list_sizer->Add(row_sizer, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(5)); category.rows.push_back(row_index); category.subs[subcategory_index].rows.push_back(row_index); } @@ -1306,8 +1364,10 @@ void PublishSettingsDialog::on_full_toggle(size_t category_index) { Category& cat = m_categories[category_index]; const bool full = cat.full_check->GetValue(); - for (size_t r : cat.rows) + for (size_t r : cat.rows) { m_rows[r].check->Enable(!full); + m_rows[r].check_label->Enable(!full); + } refresh_tab_indicators(); } @@ -1501,7 +1561,7 @@ void PublishSettingsDialog::add_mixed_visual(size_t category_index, const MixedV void PublishSettingsDialog::set_row_bold(Row& row, bool bold) { // Rebase on the dialog's body font so clearing bold restores the exact original font. - row.check->SetFont(bold ? Label::Body_13.Bold() : Label::Body_13); + row.check_label->SetFont(bold ? Label::Body_13.Bold() : Label::Body_13); } void PublishSettingsDialog::save_scroll_position(Category& category) @@ -1749,7 +1809,7 @@ void PublishSettingsDialog::select_all(bool value) for (Category& cat : m_categories) if (cat.section == Section::Material && cat.enable_check != nullptr) cat.enable_check->SetValue(value); - // wxCheckBox::SetValue does not emit wxEVT_CHECKBOX, so re-run the enable handlers to + // CheckBox::SetValue does not emit wxEVT_TOGGLEBUTTON, so re-run the enable handlers to // propagate mixed-slot components and refresh visibility as if the user had clicked. for (size_t c = 0; c < m_categories.size(); ++c) if (m_categories[c].section == Section::Material) diff --git a/src/slic3r/GUI/PublishSettingsDialog.hpp b/src/slic3r/GUI/PublishSettingsDialog.hpp index f41b3e73a3..c38d865413 100644 --- a/src/slic3r/GUI/PublishSettingsDialog.hpp +++ b/src/slic3r/GUI/PublishSettingsDialog.hpp @@ -18,6 +18,7 @@ // Widgets/StaticLine.hpp). class TextInput; class StaticLine; +class CheckBox; namespace Slic3r { namespace GUI { @@ -97,7 +98,8 @@ private: size_t inner_index{0}; bool dirty{false}; // matches a dirty base key: pre-checked + bold bool matches_filter{false}; // survives the active filter (computed by apply_filter) - wxCheckBox* check{nullptr}; + ::CheckBox* check{nullptr}; + wxStaticText* check_label{nullptr}; wxStaticText* value_label{nullptr}; wxStaticText* unit_label{nullptr}; wxStaticBitmap* color_chip{nullptr}; // Color rows only; swatch next to the value @@ -131,11 +133,13 @@ private: // header row is hidden. For physical slots the Full Publish toggle sits on a second // line (full_line_item) visible only when enabled; for mixed slots Enable alone implies // publishing the mix definition, so no Full Publish widget exists at all. - wxCheckBox* enable_check{nullptr}; + ::CheckBox* enable_check{nullptr}; + wxStaticText* enable_label{nullptr}; wxSizerItem* full_line_item{nullptr}; // sizer item of the Full Publish line (physical slots only) // "Full Publish": while checked, the whole slot preset is serialized and its rows // (incl. Color/Type) are disabled. - wxCheckBox* full_check{nullptr}; + ::CheckBox* full_check{nullptr}; + wxStaticText* full_label{nullptr}; // True for a mixed-color filament slot: no Material/Retraction rows; Enable publishes // the slot's gradient/ratio definition as a whole. bool is_mixed{false};