Align Publish 3MF dialog styling and fix tab-switch flicker (#15695)

* Fix Publish 3MF dialog styling

Use Orca's shared checkbox widget in the Publish 3MF dialog. Keep checkbox labels clickable, preserve toggle event propagation and disabled-row state, and use Windows-only double buffering on the tab page hosts to reduce flicker during page switches.

Also align the dialog's guide-link color with existing Orca dialogs.

* Fix Publish 3MF dialog styling

Use Orca's shared checkbox widget in the Publish 3MF dialog. Keep checkbox labels clickable, preserve toggle event propagation and disabled-row state, and use Windows-only double buffering on the tab page hosts to reduce flicker during page switches.

Also align the dialog's guide-link color with existing Orca dialogs.

* match font size and left margins

---------

Co-authored-by: yw4z <ywsyildiz@gmail.com>
This commit is contained in:
Kiss Lorand
2026-09-18 19:25:53 +03:00
committed by GitHub
co-authored by yw4z
parent c5f9257878
commit e573fc4680
2 changed files with 109 additions and 45 deletions
+102 -42
View File
@@ -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)
+7 -3
View File
@@ -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};