Add a OrcaSlicer badge in the thumbnail preview for published 3MF projects. Add a visual indicator in the publish dialog to show that something in the section is toggled

This commit is contained in:
Lam Wei Lun
2026-09-02 12:38:27 +08:00
parent f5984e7523
commit 4654d24f6f
8 changed files with 114 additions and 2 deletions

View File

@@ -552,7 +552,8 @@ body
background-color: #E4E4E4; background-color: #E4E4E4;
border-radius: 8px; border-radius: 8px;
width: 184px; width: 184px;
height: 184px; height: 184px;
position: relative;
} }
.FileItem img .FileItem img
@@ -564,6 +565,19 @@ body
object-fit: cover; object-fit: cover;
} }
.FileImg .FileLogoBadge
{
position: absolute;
top: 8px;
left: 8px;
width: 26px;
height: 26px;
padding: 2px;
box-sizing: border-box;
background-color: rgba(255,255,255,0.85);
border-radius: 50%;
}
.FileName .FileName
{ {
white-space: nowrap; white-space: nowrap;

View File

@@ -226,10 +226,11 @@ function ShowRecentFileList( pList )
//let sShortName=sPath.substring(index+1,sPath.length); //let sShortName=sPath.substring(index+1,sPath.length);
let sBadge=sPublished? '<span class="FilePublishedBadge">PUB</span>':''; let sBadge=sPublished? '<span class="FilePublishedBadge">PUB</span>':'';
let sLogoBadge=sPublished? '<img class="FileLogoBadge" src="../../images/OrcaSlicer_gradient_circle.svg" alt="" />':'';
let TmpHtml='<div class="FileItem" fpath="'+sPath+'" >'+ let TmpHtml='<div class="FileItem" fpath="'+sPath+'" >'+
'<a class="FileTip" title="'+sPath+'"></a>'+ '<a class="FileTip" title="'+sPath+'"></a>'+
'<div class="FileImg" ><img src="'+sImg+'" onerror="this.onerror=null;this.src=\'img/d.png\';" alt="No Image" /></div>'+ '<div class="FileImg" ><img src="'+sImg+'" onerror="this.onerror=null;this.src=\'img/d.png\';" alt="No Image" />'+sLogoBadge+'</div>'+
'<div class="FileNamePack">'+sBadge+'<div class="FileName TextS1">'+sName+'</div></div>'+ '<div class="FileNamePack">'+sBadge+'<div class="FileName TextS1">'+sName+'</div></div>'+
'<div class="FileDate">'+sTime+'</div>'+ '<div class="FileDate">'+sTime+'</div>'+
'</div>'; '</div>';

View File

@@ -881,6 +881,7 @@ void PublishSettingsDialog::build_option_model()
m_outer_tabs->SelectItem(0); m_outer_tabs->SelectItem(0);
show_outer_page(0); show_outer_page(0);
} }
refresh_tab_indicators();
} }
size_t PublishSettingsDialog::section_group_for(Section kind) size_t PublishSettingsDialog::section_group_for(Section kind)
@@ -1127,6 +1128,7 @@ void PublishSettingsDialog::add_row_ui(const std::string& key,
Row& current = m_rows[row_index]; Row& current = m_rows[row_index];
current.check = new wxCheckBox(category.scroll, wxID_ANY, label); current.check = new wxCheckBox(category.scroll, wxID_ANY, label);
current.check->SetFont(Label::Body_13); current.check->SetFont(Label::Body_13);
current.check->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent&) { refresh_tab_indicators(); });
auto* row_sizer = new wxBoxSizer(wxHORIZONTAL); auto* row_sizer = new wxBoxSizer(wxHORIZONTAL);
row_sizer->Add(current.check, 0, wxALIGN_CENTER_VERTICAL); row_sizer->Add(current.check, 0, wxALIGN_CENTER_VERTICAL);
// The value is read-only text (incl. the Type row: the published type is the slot's // The value is read-only text (incl. the Type row: the published type is the slot's
@@ -1161,6 +1163,7 @@ void PublishSettingsDialog::on_full_toggle(size_t category_index)
const bool full = cat.full_check->GetValue(); 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->Enable(!full);
refresh_tab_indicators();
} }
void PublishSettingsDialog::on_enable_toggle(size_t category_index) void PublishSettingsDialog::on_enable_toggle(size_t category_index)
@@ -1195,6 +1198,7 @@ void PublishSettingsDialog::on_enable_toggle(size_t category_index)
apply_visibility(); apply_visibility();
if (cat.page != nullptr) if (cat.page != nullptr)
cat.page->GetSizer()->Layout(); cat.page->GetSizer()->Layout();
refresh_tab_indicators();
} }
void PublishSettingsDialog::add_mixed_visual(size_t category_index, const MixedVisualSpec& spec) void PublishSettingsDialog::add_mixed_visual(size_t category_index, const MixedVisualSpec& spec)
@@ -1606,6 +1610,7 @@ void PublishSettingsDialog::select_all(bool value)
if (m_categories[c].section == Section::Material) if (m_categories[c].section == Section::Material)
on_enable_toggle(c); on_enable_toggle(c);
apply_visibility(); apply_visibility();
refresh_tab_indicators();
} }
bool PublishSettingsDialog::row_is_visible(const Row& row) const bool PublishSettingsDialog::row_is_visible(const Row& row) const
@@ -1637,6 +1642,7 @@ void PublishSettingsDialog::select_visible(bool value)
m_filter_ctrl->ChangeValue(""); m_filter_ctrl->ChangeValue("");
apply_filter(""); // resync visibility and the All/None bar apply_filter(""); // resync visibility and the All/None bar
} }
refresh_tab_indicators();
} }
void PublishSettingsDialog::show_menu(wxMouseEvent& evt) void PublishSettingsDialog::show_menu(wxMouseEvent& evt)
@@ -1778,6 +1784,7 @@ void PublishSettingsDialog::apply_selection(const std::vector<std::string>& publ
} }
apply_visibility(); apply_visibility();
refresh_tab_indicators();
} }
std::vector<std::string> PublishSettingsDialog::GetPublishedKeys() const std::vector<std::string> PublishSettingsDialog::GetPublishedKeys() const
@@ -1932,6 +1939,38 @@ std::vector<Slic3r::PublishedMaterialEntry> PublishSettingsDialog::GetPublishedM
return out; return out;
} }
bool PublishSettingsDialog::category_has_selection(const Category& cat) const
{
// A material slot publishes (or not) as a whole, gated by "Enable".
if (cat.section == Section::Material)
return cat.enable_check != nullptr && cat.enable_check->GetValue();
// Print/Printer categories have no gate: any checked row counts.
for (const size_t r : cat.rows)
if (m_rows[r].check->GetValue())
return true;
return false;
}
void PublishSettingsDialog::refresh_tab_indicators()
{
for (size_t s = 0; s < m_sections.size(); ++s) {
SectionGroup& section = m_sections[s];
bool any = false;
for (size_t i = 0; i < section.categories.size(); ++i) {
const bool on = category_has_selection(m_categories[section.categories[i]]);
section.tabs->SetItemIndicator(static_cast<unsigned int>(i), on);
any = any || on;
}
if (section.mixed_tabs != nullptr)
for (size_t i = 0; i < section.mixed_categories.size(); ++i) {
const bool on = category_has_selection(m_categories[section.mixed_categories[i]]);
section.mixed_tabs->SetItemIndicator(static_cast<unsigned int>(i), on);
any = any || on;
}
m_outer_tabs->SetItemIndicator(static_cast<unsigned int>(s), any);
}
}
std::vector<std::string> PublishSettingsDialog::full_keys_for_slot() const std::vector<std::string> PublishSettingsDialog::full_keys_for_slot() const
{ {
// The canonical filament preset keys minus the structural keys the published overlay must // The canonical filament preset keys minus the structural keys the published overlay must

View File

@@ -213,6 +213,11 @@ private:
// "Enable" toggled on a material slot: reveals/hides everything below the header and, for a // "Enable" toggled on a material slot: reveals/hides everything below the header and, for a
// mixed slot, auto-selects its component filaments' "Enable" + "Full Publish" toggles. // mixed slot, auto-selects its component filaments' "Enable" + "Full Publish" toggles.
void on_enable_toggle(size_t category_index); void on_enable_toggle(size_t category_index);
// Whether a category currently publishes something, driving its tab's indicator dot.
// Print/Printer: any row checked. Material: the slot's "Enable" is on.
bool category_has_selection(const Category& cat) const;
// Recompute the indicator dot on every outer/inner tab from the current selection state.
void refresh_tab_indicators();
// Unmet dependencies of enabled mixed-filament slots, one record per (mix, component) pair: // Unmet dependencies of enabled mixed-filament slots, one record per (mix, component) pair:
// "Enable" not checked on the component, or enabled with neither "Full Publish" nor the // "Enable" not checked on the component, or enabled with neither "Full Publish" nor the
// "Type" requirement row checked. Colour is deliberately ignored (the receiver renders the // "Type" requirement row checked. Colour is deliberately ignored (the receiver renders the

View File

@@ -162,6 +162,15 @@ void Button::SetCenter(bool isCenter)
{ {
this->isCenter = isCenter; } this->isCenter = isCenter; }
void Button::SetIndicator(bool on)
{
if (m_show_indicator == on)
return;
m_show_indicator = on;
messureSize();
Refresh();
}
void Button::SetVertical(bool vertical) void Button::SetVertical(bool vertical)
{ {
this->vertical = vertical; this->vertical = vertical;
@@ -324,6 +333,13 @@ void Button::render(wxDC& dc)
szContent.x -= d; szContent.x -= d;
} }
} }
if (m_show_indicator) {
const int dot = FromDIP(6);
if (vertical)
szContent.y += dot + FromDIP(6);
else
szContent.x += dot + FromDIP(6);
}
// move to center // move to center
wxRect rcContent = { {0, 0}, size }; wxRect rcContent = { {0, 0}, size };
if (isCenter) { if (isCenter) {
@@ -364,6 +380,17 @@ void Button::render(wxDC& dc)
#endif #endif
dc.DrawText(text, pt); dc.DrawText(text, pt);
} }
if (m_show_indicator) {
const int dot = FromDIP(6); // diameter
wxPoint dot_pt;
dot_pt.x = pt.x + (text.IsEmpty() ? 0 : textSize.x) + FromDIP(6) + dot / 2;
// Centre on the content vertically; a bitmap-only (empty-label) tab has no text row.
dot_pt.y = text.IsEmpty() ? rcContent.y + rcContent.height / 2 : pt.y + textSize.y / 2;
const wxColour c = StateColor::darkModeColorFor(m_indicator_color);
dc.SetBrush(wxBrush(c));
dc.SetPen(wxPen(c));
dc.DrawCircle(dot_pt, dot / 2);
}
} }
void Button::messureSize() void Button::messureSize()
@@ -388,6 +415,14 @@ void Button::messureSize()
if (szIcon.y > szContent.y) szContent.y = szIcon.y; if (szIcon.y > szContent.y) szContent.y = szIcon.y;
} }
} }
if (m_show_indicator) {
// Indicator dot sits to the right of the label: its diameter plus the gap from the text.
const int dot = FromDIP(6);
if (vertical)
szContent.y += dot + FromDIP(6);
else
szContent.x += dot + FromDIP(6);
}
wxSize size = szContent + paddingSize * 2; wxSize size = szContent + paddingSize * 2;
if (minSize.GetHeight() > 0) if (minSize.GetHeight() > 0)
size.SetHeight(minSize.GetHeight()); size.SetHeight(minSize.GetHeight());

View File

@@ -4,6 +4,7 @@
#include "../wxExtensions.hpp" #include "../wxExtensions.hpp"
#include "StaticBox.hpp" #include "StaticBox.hpp"
#include <wx/tipwin.h> #include <wx/tipwin.h>
#include <wx/colour.h>
class ButtonProps class ButtonProps
{ {
@@ -44,6 +45,8 @@ class Button : public StaticBox
bool canFocus = true; bool canFocus = true;
bool isCenter = true; bool isCenter = true;
bool vertical = false; bool vertical = false;
bool m_show_indicator = false;
wxColour m_indicator_color = wxColour("#009688");
static const int buttonWidth = 200; static const int buttonWidth = 200;
static const int buttonHeight = 50; static const int buttonHeight = 50;
@@ -75,6 +78,10 @@ public:
void SetSelected(bool selected = true) { m_selected = selected; } void SetSelected(bool selected = true) { m_selected = selected; }
// Show a small coloured dot to the right of the label (used by TabCtrl tabs to flag that
// the tab's category has a selected/toggled setting).
void SetIndicator(bool on);
// Only meant to be used by inspector, not public API // Only meant to be used by inspector, not public API
ButtonStyle GetStyle() const { return m_style; } ButtonStyle GetStyle() const { return m_style; }
ButtonType GetType() const { return m_type; } ButtonType GetType() const { return m_type; }

View File

@@ -171,6 +171,14 @@ void TabCtrl::SetItemBitmap(unsigned int item, const wxBitmap& bitmap)
relayout(); relayout();
} }
void TabCtrl::SetItemIndicator(unsigned int item, bool on)
{
if (item >= btns.size())
return;
btns[item]->SetIndicator(on);
relayout();
}
bool TabCtrl::GetItemBold(unsigned int item) const bool TabCtrl::GetItemBold(unsigned int item) const
{ {
if (item >= btns.size()) if (item >= btns.size())

View File

@@ -46,6 +46,9 @@ public:
void SetItemText(unsigned int item, wxString const& value); void SetItemText(unsigned int item, wxString const& value);
void SetItemBitmap(unsigned int item, const wxBitmap& bitmap); void SetItemBitmap(unsigned int item, const wxBitmap& bitmap);
// Show/hide the small "has selection" dot next to a tab's text.
void SetItemIndicator(unsigned int item, bool on);
bool GetItemBold(unsigned int item) const; bool GetItemBold(unsigned int item) const;
void SetItemBold(unsigned int item, bool bold); void SetItemBold(unsigned int item, bool bold);