Publish 3MF: support mixed filaments and per-extruder slot selection

- Publish mixed-filament slots as whole units: serialize the filament_mixed_* definition into project_config on import, grow the receiver's parallel arrays in lockstep, and report unappliable definitions as skipped instead of dropping them silently

- Per-extruder printer selection: one inner tab per extruder, rows keyed by full "#N" ids; single-extruder receivers collapse variants onto their slot (first applied, rest skipped), multi-extruder receivers override element-wise

- New per-slot "Enable" toggle gating what gets published; enabling a mix auto-enables + Full Publishes its components

- Mixed page previews: fixed-size ratio bar, ternary triangle (3 components) and Material Ratio vs Model Height graph (gradients), always visible regardless of Enable

- Tab strip shows full swatch compositions with adjustable spacing; barycentric helpers shared via FilamentBitmapUtils
This commit is contained in:
Lam Wei Lun
2026-08-27 13:17:19 +08:00
parent ce277ebbf5
commit 76d9b8bac0
12 changed files with 1474 additions and 236 deletions
+40 -1
View File
@@ -11,6 +11,45 @@
namespace Slic3r { namespace GUI {
// Barycentric utilities for a ternary (triangle) ratio picker.
double tri_signed_area2(TriPoint a, TriPoint b, TriPoint c)
{
return (b.x - a.x) * (c.y - a.y) - (c.x - a.x) * (b.y - a.y);
}
bool tri_contains(TriPoint p, TriPoint v0, TriPoint v1, TriPoint v2)
{
double total = tri_signed_area2(v0, v1, v2);
if (std::abs(total) < 1e-9) return false;
double s0 = tri_signed_area2(p, v1, v2) / total;
double s1 = tri_signed_area2(v0, p, v2) / total;
double s2 = 1.0 - s0 - s1;
return s0 >= -0.001 && s1 >= -0.001 && s2 >= -0.001;
}
void tri_barycentric(TriPoint p, TriPoint v0, TriPoint v1, TriPoint v2,
double& w0, double& w1, double& w2)
{
double total = std::abs(tri_signed_area2(v0, v1, v2));
if (total < 1e-9) { w0 = w1 = w2 = 1.0 / 3.0; return; }
w0 = std::abs(tri_signed_area2(p, v1, v2)) / total;
w1 = std::abs(tri_signed_area2(v0, p, v2)) / total;
w2 = 1.0 - w0 - w1;
w0 = std::clamp(w0, 0.0, 1.0);
w1 = std::clamp(w1, 0.0, 1.0);
w2 = std::clamp(w2, 0.0, 1.0);
double s = w0 + w1 + w2;
if (s > 0) { w0 /= s; w1 /= s; w2 /= s; }
}
TriPoint tri_clamp(TriPoint p, TriPoint v0, TriPoint v1, TriPoint v2)
{
double w0, w1, w2;
tri_barycentric(p, v0, v1, v2, w0, w1, w2);
return {w0 * v0.x + w1 * v1.x + w2 * v2.x,
w0 * v0.y + w1 * v1.y + w2 * v2.y};
}
void fill_gradient_rect_east(wxDC& dc, const wxRect& rect, const wxColour& from, const wxColour& to)
{
if (rect.width <= 0 || rect.height <= 0) return;
@@ -73,7 +112,7 @@ std::vector<wxColour> sample_gradient_ramp(const wxColour& first,
// Resolve the curve a gradient slot is sampled with, mirroring the slicer's fallback in
// ToolOrdering: a custom curve wins, otherwise a straight line between gradient_range's
// endpoints, otherwise the 0.10 -> 0.90 default.
static Slic3r::GradientCurve mixed_gradient_curve(const Slic3r::DynamicPrintConfig& cfg, size_t slot)
Slic3r::GradientCurve mixed_gradient_curve(const Slic3r::DynamicPrintConfig& cfg, size_t slot)
{
const auto* curve_opt = cfg.option<ConfigOptionStrings>("filament_mixed_gradient_curve");
if (curve_opt && slot < curve_opt->values.size() && !curve_opt->values[slot].empty()) {
+16
View File
@@ -13,6 +13,16 @@ namespace Slic3r { class DynamicPrintConfig; struct GradientCurve; }
namespace Slic3r { namespace GUI {
// Barycentric utilities for a ternary (triangle) ratio picker, shared by the mixed-filament
// editor and the Publish dialog's read-only definition preview.
struct TriPoint { double x, y; };
double tri_signed_area2(TriPoint a, TriPoint b, TriPoint c);
bool tri_contains(TriPoint p, TriPoint v0, TriPoint v1, TriPoint v2);
void tri_barycentric(TriPoint p, TriPoint v0, TriPoint v1, TriPoint v2,
double& w0, double& w1, double& w2);
TriPoint tri_clamp(TriPoint p, TriPoint v0, TriPoint v1, TriPoint v2);
// Fills a rect with a west->east linear gradient by drawing solid 1px columns.
// Use instead of wxDC::GradientFillLinear, whose CoreGraphics (CGShading) backend
// fails to render on some macOS builds; solid fills are unaffected.
@@ -51,6 +61,12 @@ std::vector<wxColour> sample_gradient_ramp(const wxColour& first,
// destination's height in pixels.
std::vector<wxColour> mixed_gradient_ramp(const Slic3r::DynamicPrintConfig& cfg, size_t slot, int steps);
// Resolve the curve a gradient slot is sampled with: the custom curve wins when it has at
// least two points, otherwise a straight line between gradient_range's endpoints, otherwise
// the 0.10 -> 0.90 default. Mirrors the slicer's ToolOrdering fallback so every preview
// agrees with what gets sliced. Always returns a two-point curve.
Slic3r::GradientCurve mixed_gradient_curve(const Slic3r::DynamicPrintConfig& cfg, size_t slot);
// Fill rect with a ramp, ramp.front() along the bottom edge.
void fill_gradient_ramp_rect(wxDC& dc, const wxRect& rect, const std::vector<wxColour>& ramp);
+2 -46
View File
@@ -919,52 +919,8 @@ wxBoxSizer* MixedFilamentDialog::create_ratio_slider()
}
// ---- Triangle (ternary) ratio picker ----
// Barycentric coordinate utilities
struct TriPoint { double x, y; };
static double tri_signed_area2(TriPoint a, TriPoint b, TriPoint c)
{
return (b.x - a.x) * (c.y - a.y) - (c.x - a.x) * (b.y - a.y);
}
static bool tri_contains(TriPoint p, TriPoint v0, TriPoint v1, TriPoint v2)
{
double total = tri_signed_area2(v0, v1, v2);
if (std::abs(total) < 1e-9) return false;
double s0 = tri_signed_area2(p, v1, v2) / total;
double s1 = tri_signed_area2(v0, p, v2) / total;
double s2 = 1.0 - s0 - s1;
return s0 >= -0.001 && s1 >= -0.001 && s2 >= -0.001;
}
static void tri_barycentric(TriPoint p, TriPoint v0, TriPoint v1, TriPoint v2,
double& w0, double& w1, double& w2)
{
double total = std::abs(tri_signed_area2(v0, v1, v2));
if (total < 1e-9) { w0 = w1 = w2 = 1.0 / 3.0; return; }
w0 = std::abs(tri_signed_area2(p, v1, v2)) / total;
w1 = std::abs(tri_signed_area2(v0, p, v2)) / total;
w2 = 1.0 - w0 - w1;
w0 = std::clamp(w0, 0.0, 1.0);
w1 = std::clamp(w1, 0.0, 1.0);
w2 = std::clamp(w2, 0.0, 1.0);
double s = w0 + w1 + w2;
if (s > 0) { w0 /= s; w1 /= s; w2 /= s; }
}
static TriPoint tri_clamp(TriPoint p, TriPoint v0, TriPoint v1, TriPoint v2)
{
double w0, w1, w2;
tri_barycentric(p, v0, v1, v2, w0, w1, w2);
w0 = std::clamp(w0, 0.0, 1.0);
w1 = std::clamp(w1, 0.0, 1.0);
w2 = std::clamp(w2, 0.0, 1.0);
double s = w0 + w1 + w2;
if (s > 0) { w0 /= s; w1 /= s; w2 /= s; }
return {w0 * v0.x + w1 * v1.x + w2 * v2.x,
w0 * v0.y + w1 * v1.y + w2 * v2.y};
}
// The barycentric utilities (TriPoint, tri_contains, tri_barycentric, tri_clamp) live in
// FilamentBitmapUtils so the Publish dialog can mirror this picker read-only.
wxBoxSizer* MixedFilamentDialog::create_triangle_picker()
{
File diff suppressed because it is too large Load Diff
+50 -2
View File
@@ -7,8 +7,10 @@
#include "libslic3r/PublishSettings.hpp"
#include <wx/wx.h>
#include <wx/colour.h>
#include <wx/scrolwin.h>
#include <wx/menu.h>
#include <utility>
#include <vector>
#include <string>
@@ -105,9 +107,18 @@ private:
wxPoint scroll_pos{0, 0};
wxStaticBitmap* filament_color_chip{nullptr};
wxStaticText* title_label{nullptr}; // material title (static text; Full Publish carries the label elsewhere)
// "Enable": while unchecked nothing of this slot is exported and everything below the
// 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};
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};
// 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};
// Material identity, only for Section::Material categories.
std::string filament_type;
std::string filament_vendor;
@@ -118,6 +129,21 @@ private:
std::vector<size_t> rows; // flattened rows of this category
};
// Frozen snapshot of a mixed filament slot's definition for the read-only visualization
// painted on the slot's page. Plain data only: the paint handler must never touch the
// config. For gradient slots the curve is pre-sampled (t, ratio) pairs, where ratio is the
// first component's share over model height; anchors carry the raw control points.
struct MixedVisualSpec
{
bool valid{false};
bool is_gradient{false};
std::vector<wxColour> component_colours; // colour per component, in config order
std::vector<double> ratios; // sublayer shares summing to ~1 (non-gradient)
std::vector<double> tri_weights; // 3-component mixes: barycentric shares
std::vector<std::pair<double, double>> gradient_samples;
std::vector<std::pair<double, double>> gradient_anchors;
};
// One outer TabCtrl page. Category entries are its inner tabs.
struct SectionGroup
{
@@ -127,13 +153,24 @@ private:
ScalableBitmap icon_bmp; // tab icon next to the title; rescaled on DPI change
wxPanel* page{nullptr};
TabCtrl* tabs{nullptr};
// Second tab strip, below the main one, listing only the mixed-color filament slots.
// Present on the Material section only (null elsewhere).
TabCtrl* mixed_tabs{nullptr};
wxPanel* page_host{nullptr};
wxBoxSizer* page_host_sizer{nullptr};
int selected_inner{-1};
std::vector<size_t> categories; // indices into m_categories
// Selected mixed tab (index into mixed_categories), valid while a mixed slot page is shown.
int selected_mixed{-1};
std::vector<size_t> categories; // indices into m_categories (physical slots)
std::vector<size_t> mixed_categories; // indices into m_categories (mixed slots)
};
void build_option_model();
// Frozen snapshot of a mixed slot's definition for the page visualization, resolved from
// the full config once at dialog-build time. Gradient slots pre-sample exactly what the
// slicer will print: the custom curve wins over the gradient_range endpoints over the
// 0.10 -> 0.90 default (the resolution FilamentBitmapUtils::mixed_gradient_curve mirrors).
static MixedVisualSpec make_mixed_visual_spec(const Slic3r::DynamicPrintConfig& full, size_t slot);
void apply_filter(const wxString& filter_text);
// Menu-only pseudo filters: show only the checked ("Filter selected") or only the
// unchecked ("Filter non-selected") rows. The search box keeps the user's text.
@@ -147,13 +184,21 @@ private:
void set_row_bold(Row& row, bool bold);
// "Full Publish" toggled: disables/enables the material's rows.
void on_full_toggle(size_t category_index);
// "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);
// Read-only visualization of a mixed slot's definition (a stacked ratio bar, or the
// Material Ratio vs Model Height graph for a gradient), inserted above the info hint
// inside the category's scroll area.
void add_mixed_visual(size_t category_index, const MixedVisualSpec& spec);
// Return/create the fixed outer page for a Section kind.
size_t section_group_for(Section kind);
size_t category_index_for(const wxString& title,
Section section,
size_t group,
size_t source_index,
const PublishMaterialIdentity& identity = PublishMaterialIdentity());
const PublishMaterialIdentity& identity = PublishMaterialIdentity(),
bool is_mixed = false);
size_t subcategory_index_for(size_t category_index, const wxString& title, const wxString& icon);
void add_row_ui(const std::string& key,
const wxString& label,
@@ -167,8 +212,10 @@ private:
void save_scroll_position(Category& category);
void show_outer_page(size_t section_index);
void show_inner_page(size_t section_index, int inner_index);
void show_mixed_page(size_t section_index, int mixed_index);
void on_outer_tab_changed(wxCommandEvent& event);
void on_inner_tab_changed(size_t section_index, wxCommandEvent& event);
void on_mixed_tab_changed(size_t section_index, wxCommandEvent& event);
bool row_is_visible(const Row& row) const;
void apply_visibility();
void bind_tab_events();
@@ -190,6 +237,7 @@ private:
wxString m_info_nonsel;
wxString m_info_allsel;
wxString m_info_empty;
wxString m_info_mix; // body hint shown for a mixed slot (published as a whole)
ScalableBitmap m_search;
ScalableBitmap m_menu;
+67 -71
View File
@@ -2,8 +2,8 @@
#include <wx/dc.h>
wxDEFINE_EVENT( wxEVT_TAB_SEL_CHANGING, wxCommandEvent );
wxDEFINE_EVENT( wxEVT_TAB_SEL_CHANGED, wxCommandEvent );
wxDEFINE_EVENT(wxEVT_TAB_SEL_CHANGING, wxCommandEvent);
wxDEFINE_EVENT(wxEVT_TAB_SEL_CHANGED, wxCommandEvent);
BEGIN_EVENT_TABLE(TabCtrl, StaticBox)
@@ -22,11 +22,7 @@ END_EVENT_TABLE()
#define TAB_BUTTON_PADDING_Y 2
#define TAB_BUTTON_PADDING TAB_BUTTON_PADDING_X, TAB_BUTTON_PADDING_Y
TabCtrl::TabCtrl(wxWindow * parent,
wxWindowID id,
const wxPoint & pos,
const wxSize & size,
long style)
TabCtrl::TabCtrl(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style)
: StaticBox(parent, id, pos, size, style)
{
#if 0
@@ -42,14 +38,11 @@ TabCtrl::TabCtrl(wxWindow * parent,
hsizer->Add(sizer, 0, wxEXPAND | wxBOTTOM, border_width * 4);
SetSizer(hsizer);
Bind(wxEVT_COMMAND_BUTTON_CLICKED, &TabCtrl::buttonClicked, this);
//wxString reason;
//IsTransparentBackgroundSupported(&reason);
// wxString reason;
// IsTransparentBackgroundSupported(&reason);
}
TabCtrl::~TabCtrl()
{
delete images;
}
TabCtrl::~TabCtrl() { delete images; }
int TabCtrl::GetSelection() const { return sel; }
@@ -75,14 +68,11 @@ void TabCtrl::SelectItem(int item)
Refresh();
}
void TabCtrl::Unselect()
{
SelectItem(-1);
}
void TabCtrl::Unselect() { SelectItem(-1); }
void TabCtrl::Rescale()
{
for (auto & b : btns)
for (auto& b : btns)
b->Rescale();
relayout();
}
@@ -96,23 +86,20 @@ bool TabCtrl::SetFont(wxFont const& font)
return true;
}
int TabCtrl::AppendItem(const wxString &item,
int image, int selImage,
void * clientData)
int TabCtrl::AppendItem(const wxString& item, int image, int selImage, void* clientData)
{
Button * btn = new Button();
Button* btn = new Button();
btn->Create(this, item, "", wxBORDER_NONE);
btn->SetFont(GetFont());
btn->SetTextColor(StateColor(
std::make_pair(0x6B6B6C, (int) StateColor::NotChecked),
std::make_pair(*wxLIGHT_GREY, (int) StateColor::Normal)));
btn->SetTextColor(
StateColor(std::make_pair(0x6B6B6C, (int) StateColor::NotChecked), std::make_pair(*wxLIGHT_GREY, (int) StateColor::Normal)));
btn->SetBackgroundColor(StateColor());
btn->SetCornerRadius(0);
btn->SetPaddingSize({TAB_BUTTON_PADDING});
btns.push_back(btn);
if (btns.size() > 1)
sizer->GetItem(sizer->GetItemCount() - 1)->SetMinSize({0, 0});
sizer->Add(btn, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, TAB_BUTTON_SPACE * 2);
sizer->Add(btn, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, item_space * 2);
sizer->AddStretchSpacer(1);
relayout();
return btns.size() - 1;
@@ -144,7 +131,7 @@ bool TabCtrl::DeleteItem(int item)
sizer->GetItem(sizer->GetItemCount() - 1)->SetMinSize({0, 0});
if (selection_changed) {
sel--; // `relayout()` uses `sel` so we need to update this before calling `relayout()`
sel--; // `relayout()` uses `sel` so we need to update this before calling `relayout()`
}
relayout();
if (selection_changed) {
@@ -167,14 +154,12 @@ void TabCtrl::DeleteAllItems()
unsigned int TabCtrl::GetCount() const { return btns.size(); }
wxString TabCtrl::GetItemText(unsigned int item) const
{
return item < btns.size() ? btns[item]->GetLabel() : wxString{};
}
wxString TabCtrl::GetItemText(unsigned int item) const { return item < btns.size() ? btns[item]->GetLabel() : wxString{}; }
void TabCtrl::SetItemText(unsigned int item, wxString const &value)
void TabCtrl::SetItemText(unsigned int item, wxString const& value)
{
if (item >= btns.size()) return;
if (item >= btns.size())
return;
btns[item]->SetLabel(value);
}
@@ -188,61 +173,59 @@ void TabCtrl::SetItemBitmap(unsigned int item, const wxBitmap& bitmap)
bool TabCtrl::GetItemBold(unsigned int item) const
{
if (item >= btns.size()) return false;
if (item >= btns.size())
return false;
return btns[item]->GetFont() == bold;
}
void TabCtrl::SetItemBold(unsigned int item, bool bold)
{
if (item >= btns.size()) return;
if (item >= btns.size())
return;
btns[item]->SetFont(bold ? this->bold : GetFont());
btns[item]->Rescale();
}
void* TabCtrl::GetItemData(unsigned int item) const
{
if (item >= btns.size()) return nullptr;
if (item >= btns.size())
return nullptr;
return btns[item]->GetClientData();
}
void TabCtrl::SetItemData(unsigned int item, void* clientData)
{
if (item >= btns.size()) return;
if (item >= btns.size())
return;
btns[item]->SetClientData(clientData);
}
void TabCtrl::AssignImageList(wxImageList* imageList)
{
if (images == imageList) return;
if (images == imageList)
return;
delete images;
images = imageList;
}
void TabCtrl::SetItemTextColour(unsigned int item, const StateColor &col)
void TabCtrl::SetItemTextColour(unsigned int item, const StateColor& col)
{
if (item >= btns.size()) return;
if (item >= btns.size())
return;
btns[item]->SetTextColor(col);
}
int TabCtrl::GetFirstVisibleItem() const
{
return btns.size() == 0 ? -1 : 0;
}
int TabCtrl::GetFirstVisibleItem() const { return btns.size() == 0 ? -1 : 0; }
int TabCtrl::GetNextVisible(int item) const
{
return ++item < btns.size() ? item : -1;
}
int TabCtrl::GetNextVisible(int item) const { return ++item < btns.size() ? item : -1; }
bool TabCtrl::IsVisible(unsigned int item) const
{
return true;
}
bool TabCtrl::IsVisible(unsigned int item) const { return true; }
void TabCtrl::DoSetSize(int x, int y, int width, int height, int sizeFlags)
{
wxWindow::DoSetSize(x, y, width, height, sizeFlags);
if (sizeFlags & wxSIZE_USE_EXISTING) return;
if (sizeFlags & wxSIZE_USE_EXISTING)
return;
relayout();
}
@@ -250,7 +233,9 @@ void TabCtrl::DoSetSize(int x, int y, int width, int height, int sizeFlags)
WXLRESULT TabCtrl::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam)
{
if (nMsg == WM_GETDLGCODE) { return DLGC_WANTARROWS; }
if (nMsg == WM_GETDLGCODE) {
return DLGC_WANTARROWS;
}
return wxWindow::MSWWindowProc(nMsg, wParam, lParam);
}
@@ -259,15 +244,15 @@ WXLRESULT TabCtrl::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam)
void TabCtrl::relayout()
{
int offset = 10;
int item = sel + 1;
int first = 0;
int item = sel + 1;
int first = 0;
for (int i = 0; i < item; ++i)
offset += btns[i]->GetMinSize().x + TAB_BUTTON_SPACE * 2;
offset += btns[i]->GetMinSize().x + item_space * 2;
if (item < btns.size())
offset += btns[item]->GetMinSize().x + TAB_BUTTON_SPACE * 2;
int width = GetSize().x;
offset += btns[item]->GetMinSize().x + item_space * 2;
int width = GetSize().x;
for (int i = 0; i < btns.size(); ++i) {
auto size = btns[i]->GetMinSize().x + TAB_BUTTON_SPACE * 2;
auto size = btns[i]->GetMinSize().x + item_space * 2;
if (i < sel && offset > width) {
sizer->Show(i * 2 + 1, false);
sizer->Show(i * 2 + 2, false);
@@ -288,23 +273,32 @@ void TabCtrl::relayout()
sizer->GetItem(i * 2 + 2)->SetMinSize({0, 0});
}
if (item >= btns.size())
-- item;
--item;
// Keep spacing 2 ~ 10 TAB_BUTTON_SPACE
int b = GetSize().x - offset - 10 - (item + 1 - first) * TAB_BUTTON_SPACE * 8;
int b = GetSize().x - offset - 10 - (item + 1 - first) * item_space * 8;
sizer->GetItem(item * 2 + 2)->SetMinSize({b > 0 ? b : 0, 0});
Layout();
}
int TabCtrl::buttons_best_width() const
void TabCtrl::SetItemSpace(int space)
{
if (space < 0 || space == item_space)
return;
item_space = space;
relayout();
Refresh();
}
int TabCtrl::GetFullSize() const
{
// Mirrors relayout(): a 10px leading spacer plus every button's min width and spacing.
int width = 10;
for (const Button *btn : btns)
width += btn->GetMinSize().x + TAB_BUTTON_SPACE * 2;
for (const Button* btn : btns)
width += btn->GetMinSize().x + item_space * 2;
return width;
}
void TabCtrl::buttonClicked(wxCommandEvent &event)
void TabCtrl::buttonClicked(wxCommandEvent& event)
{
SetFocus();
auto btn = event.GetEventObject();
@@ -312,7 +306,7 @@ void TabCtrl::buttonClicked(wxCommandEvent &event)
SelectItem(iter == btns.end() ? -1 : iter - btns.begin());
}
void TabCtrl::keyDown(wxKeyEvent &event)
void TabCtrl::keyDown(wxKeyEvent& event)
{
switch (event.GetKeyCode()) {
case WXK_UP:
@@ -331,11 +325,13 @@ void TabCtrl::keyDown(wxKeyEvent &event)
void TabCtrl::doRender(wxDC& dc)
{
wxSize size = GetSize();
int states = state_handler.states();
if (sel < 0) { return; }
int states = state_handler.states();
if (sel < 0) {
return;
}
auto x1 = btns[sel]->GetPosition().x;
auto x2 = x1 + btns[sel]->GetSize().x;
auto x1 = btns[sel]->GetPosition().x;
auto x2 = x1 + btns[sel]->GetSize().x;
const int BS2 = (1 + border_width) / 2;
#if 0
const int BS = border_width / 2;
+25 -25
View File
@@ -3,33 +3,30 @@
#include "Button.hpp"
wxDECLARE_EVENT( wxEVT_TAB_SEL_CHANGING, wxCommandEvent );
wxDECLARE_EVENT( wxEVT_TAB_SEL_CHANGED, wxCommandEvent );
wxDECLARE_EVENT(wxEVT_TAB_SEL_CHANGING, wxCommandEvent);
wxDECLARE_EVENT(wxEVT_TAB_SEL_CHANGED, wxCommandEvent);
class TabCtrl : public StaticBox
{
std::vector<Button*> btns;
wxImageList* images = nullptr;
wxBoxSizer * sizer = nullptr;
wxBoxSizer* sizer = nullptr;
int sel = -1;
wxFont bold;
int item_space = 2; // space around each button, both sides (SetItemSpace)
public:
TabCtrl(wxWindow * parent,
wxWindowID id,
const wxPoint & pos = wxDefaultPosition,
const wxSize & size = wxDefaultSize,
long style = 0);
TabCtrl(wxWindow* parent, wxWindowID id, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = 0);
~TabCtrl();
public:
virtual bool SetFont(wxFont const & font) override;
virtual bool SetFont(wxFont const& font) override;
public:
int AppendItem(const wxString &item, int image = -1, int selImage = -1, void *clientData = nullptr);
int AppendItem(const wxString &item, const wxBitmap& bitmap, void *clientData = nullptr);
int AppendItem(const wxString& item, int image = -1, int selImage = -1, void* clientData = nullptr);
int AppendItem(const wxString& item, const wxBitmap& bitmap, void* clientData = nullptr);
bool DeleteItem(int item);
@@ -37,7 +34,7 @@ public:
unsigned int GetCount() const;
int GetSelection() const;
int GetSelection() const;
void SelectItem(int item);
@@ -46,16 +43,16 @@ public:
virtual void Rescale();
wxString GetItemText(unsigned int item) const;
void SetItemText(unsigned int item, wxString const &value);
void SetItemBitmap(unsigned int item, const wxBitmap& bitmap);
void SetItemText(unsigned int item, wxString const& value);
void SetItemBitmap(unsigned int item, const wxBitmap& bitmap);
bool GetItemBold(unsigned int item) const;
void SetItemBold(unsigned int item, bool bold);
bool GetItemBold(unsigned int item) const;
void SetItemBold(unsigned int item, bool bold);
void* GetItemData(unsigned int item) const;
void SetItemData(unsigned int item, void *clientData);
void AssignImageList(wxImageList *imageList);
void* GetItemData(unsigned int item) const;
void SetItemData(unsigned int item, void* clientData);
void AssignImageList(wxImageList* imageList);
void SetItemTextColour(unsigned int item, const StateColor& col);
@@ -64,8 +61,11 @@ public:
int GetNextVisible(int item) const;
bool IsVisible(unsigned int item) const;
// Width of the tab strip that keeps every button visible (used to size the Publish dialog).
int buttons_best_width() const;
// Extra space around each tab button (in px on both sides). Defaults to the control-wide
// standard; call before appending items so every button picks it up.
void SetItemSpace(int space);
int GetFullSize() const;
private:
virtual void DoSetSize(int x, int y, int width, int height, int sizeFlags = wxSIZE_AUTO) override;
@@ -76,10 +76,10 @@ private:
void relayout();
void buttonClicked(wxCommandEvent & event);
void keyDown(wxKeyEvent &event);
void buttonClicked(wxCommandEvent& event);
void keyDown(wxKeyEvent& event);
void doRender(wxDC & dc) override;
void doRender(wxDC& dc) override;
// some useful events
bool sendTabCtrlEvent(bool changing = false);