diff --git a/resources/web/homepage/css/home.css b/resources/web/homepage/css/home.css
index ff04d3ba3d..f4ef1d5420 100644
--- a/resources/web/homepage/css/home.css
+++ b/resources/web/homepage/css/home.css
@@ -552,7 +552,8 @@ body
background-color: #E4E4E4;
border-radius: 8px;
width: 184px;
- height: 184px;
+ height: 184px;
+ position: relative;
}
.FileItem img
@@ -564,6 +565,19 @@ body
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
{
white-space: nowrap;
diff --git a/resources/web/homepage/js/home.js b/resources/web/homepage/js/home.js
index 3cb5ff4fb3..20eb96474b 100644
--- a/resources/web/homepage/js/home.js
+++ b/resources/web/homepage/js/home.js
@@ -226,10 +226,11 @@ function ShowRecentFileList( pList )
//let sShortName=sPath.substring(index+1,sPath.length);
let sBadge=sPublished? 'PUB':'';
+ let sLogoBadge=sPublished? '
':'';
let TmpHtml='
'+
'
'+
- '
'+
+ '

'+sLogoBadge+'
'+
'
'+
'
'+sTime+'
'+
'
';
diff --git a/src/slic3r/GUI/PublishSettingsDialog.cpp b/src/slic3r/GUI/PublishSettingsDialog.cpp
index d4d9b5d5d2..340ca8d02e 100644
--- a/src/slic3r/GUI/PublishSettingsDialog.cpp
+++ b/src/slic3r/GUI/PublishSettingsDialog.cpp
@@ -881,6 +881,7 @@ void PublishSettingsDialog::build_option_model()
m_outer_tabs->SelectItem(0);
show_outer_page(0);
}
+ refresh_tab_indicators();
}
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];
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(); });
auto* row_sizer = new wxBoxSizer(wxHORIZONTAL);
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
@@ -1161,6 +1163,7 @@ void PublishSettingsDialog::on_full_toggle(size_t category_index)
const bool full = cat.full_check->GetValue();
for (size_t r : cat.rows)
m_rows[r].check->Enable(!full);
+ refresh_tab_indicators();
}
void PublishSettingsDialog::on_enable_toggle(size_t category_index)
@@ -1195,6 +1198,7 @@ void PublishSettingsDialog::on_enable_toggle(size_t category_index)
apply_visibility();
if (cat.page != nullptr)
cat.page->GetSizer()->Layout();
+ refresh_tab_indicators();
}
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)
on_enable_toggle(c);
apply_visibility();
+ refresh_tab_indicators();
}
bool PublishSettingsDialog::row_is_visible(const Row& row) const
@@ -1637,6 +1642,7 @@ void PublishSettingsDialog::select_visible(bool value)
m_filter_ctrl->ChangeValue("");
apply_filter(""); // resync visibility and the All/None bar
}
+ refresh_tab_indicators();
}
void PublishSettingsDialog::show_menu(wxMouseEvent& evt)
@@ -1778,6 +1784,7 @@ void PublishSettingsDialog::apply_selection(const std::vector& publ
}
apply_visibility();
+ refresh_tab_indicators();
}
std::vector PublishSettingsDialog::GetPublishedKeys() const
@@ -1932,6 +1939,38 @@ std::vector PublishSettingsDialog::GetPublishedM
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(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(i), on);
+ any = any || on;
+ }
+ m_outer_tabs->SetItemIndicator(static_cast(s), any);
+ }
+}
+
std::vector PublishSettingsDialog::full_keys_for_slot() const
{
// The canonical filament preset keys minus the structural keys the published overlay must
diff --git a/src/slic3r/GUI/PublishSettingsDialog.hpp b/src/slic3r/GUI/PublishSettingsDialog.hpp
index 8d9342fadb..f41b3e73a3 100644
--- a/src/slic3r/GUI/PublishSettingsDialog.hpp
+++ b/src/slic3r/GUI/PublishSettingsDialog.hpp
@@ -213,6 +213,11 @@ private:
// "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.
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:
// "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
diff --git a/src/slic3r/GUI/Widgets/Button.cpp b/src/slic3r/GUI/Widgets/Button.cpp
index 5a5bd89403..22b1c34cab 100644
--- a/src/slic3r/GUI/Widgets/Button.cpp
+++ b/src/slic3r/GUI/Widgets/Button.cpp
@@ -162,6 +162,15 @@ void Button::SetCenter(bool 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)
{
this->vertical = vertical;
@@ -324,6 +333,13 @@ void Button::render(wxDC& dc)
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
wxRect rcContent = { {0, 0}, size };
if (isCenter) {
@@ -364,6 +380,17 @@ void Button::render(wxDC& dc)
#endif
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()
@@ -388,6 +415,14 @@ void Button::messureSize()
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;
if (minSize.GetHeight() > 0)
size.SetHeight(minSize.GetHeight());
diff --git a/src/slic3r/GUI/Widgets/Button.hpp b/src/slic3r/GUI/Widgets/Button.hpp
index 19dcd24938..758a9a4009 100644
--- a/src/slic3r/GUI/Widgets/Button.hpp
+++ b/src/slic3r/GUI/Widgets/Button.hpp
@@ -4,6 +4,7 @@
#include "../wxExtensions.hpp"
#include "StaticBox.hpp"
#include
+#include
class ButtonProps
{
@@ -44,6 +45,8 @@ class Button : public StaticBox
bool canFocus = true;
bool isCenter = true;
bool vertical = false;
+ bool m_show_indicator = false;
+ wxColour m_indicator_color = wxColour("#009688");
static const int buttonWidth = 200;
static const int buttonHeight = 50;
@@ -75,6 +78,10 @@ public:
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
ButtonStyle GetStyle() const { return m_style; }
ButtonType GetType() const { return m_type; }
diff --git a/src/slic3r/GUI/Widgets/TabCtrl.cpp b/src/slic3r/GUI/Widgets/TabCtrl.cpp
index 4090bf9c1b..34de109b8f 100644
--- a/src/slic3r/GUI/Widgets/TabCtrl.cpp
+++ b/src/slic3r/GUI/Widgets/TabCtrl.cpp
@@ -171,6 +171,14 @@ void TabCtrl::SetItemBitmap(unsigned int item, const wxBitmap& bitmap)
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
{
if (item >= btns.size())
diff --git a/src/slic3r/GUI/Widgets/TabCtrl.hpp b/src/slic3r/GUI/Widgets/TabCtrl.hpp
index 0d3606aca7..493c4edee5 100644
--- a/src/slic3r/GUI/Widgets/TabCtrl.hpp
+++ b/src/slic3r/GUI/Widgets/TabCtrl.hpp
@@ -46,6 +46,9 @@ public:
void SetItemText(unsigned int item, wxString const& value);
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;
void SetItemBold(unsigned int item, bool bold);