mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-18 22:42:37 +00:00
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:
@@ -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)
|
||||
|
||||
@@ -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};
|
||||
|
||||
Reference in New Issue
Block a user