Resync the device tab widgets and add the AMS best-position popup

This commit is contained in:
SoftFever
2026-07-17 03:37:18 +08:00
parent 0feca8d442
commit d4e95e3269
17 changed files with 1975 additions and 517 deletions

View File

@@ -2,10 +2,10 @@
list(APPEND SLIC3R_GUI_SOURCES
GUI/DeviceTab/uiAmsHumidityPopup.h
GUI/DeviceTab/uiAmsHumidityPopup.cpp
GUI/DeviceTab/uiAMSBestPositionPopup.hpp
GUI/DeviceTab/uiAMSBestPositionPopup.cpp
GUI/DeviceTab/uiDeviceUpdateVersion.h
GUI/DeviceTab/uiDeviceUpdateVersion.cpp
GUI/DeviceTab/wgtDeviceNozzleRackNozzleItem.h
GUI/DeviceTab/wgtDeviceNozzleRackNozzleItem.cpp
GUI/DeviceTab/wgtDeviceNozzleRack.h
GUI/DeviceTab/wgtDeviceNozzleRack.cpp
GUI/DeviceTab/wgtDeviceNozzleRackUpdate.h
@@ -14,5 +14,7 @@ list(APPEND SLIC3R_GUI_SOURCES
GUI/DeviceTab/wgtDeviceNozzleSelect.cpp
GUI/DeviceTab/wgtMsgBox.h
GUI/DeviceTab/wgtMsgBox.cpp
GUI/DeviceTab/wgtMsgPanel.h
GUI/DeviceTab/wgtMsgPanel.cpp
)
set(SLIC3R_GUI_SOURCES ${SLIC3R_GUI_SOURCES} PARENT_SCOPE)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,224 @@
//**********************************************************/
/* File: uiAMSBestPositionPopup.hpp
* Description: The popup with suggest best ams position
*
//**********************************************************/
#pragma once
#include "slic3r/GUI/Widgets/AMSItem.hpp"
#include "slic3r/GUI/Widgets/Label.hpp"
#include "slic3r/GUI/Widgets/PopupWindow.hpp"
#include "slic3r/GUI/wxExtensions.hpp"
#include "slic3r/GUI/Widgets/Button.hpp"
#include "slic3r/GUI/DeviceCore/DevFilaSystem.h"
#include "slic3r/GUI/DeviceCore/DevFilaSwitch.h"
#include <algorithm>
#include <tuple>
namespace Slic3r { namespace GUI {
class UiStyledAMSPanel : public wxPanel
{
public:
UiStyledAMSPanel(wxWindow* parent,
wxWindowID id = wxID_ANY,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
const wxColour& borderColor = wxColour(200, 200, 200),
const wxColour& bgColor = wxColour(255, 255, 255),
bool borderDashed = true,
wxString name = "",
bool isTop = false);
protected:
void OnPaint(wxPaintEvent& event);
private:
bool m_borderDashed;
int m_borderWidth;
int m_radius;
wxColour m_borderColor;
wxColour m_bgColor;
wxString m_name;
bool m_isTop;
};
class UiStyledSwitchPanel : public wxPanel
{
public:
UiStyledSwitchPanel(wxWindow* parent,
wxWindowID id,
const wxPoint& pos,
const wxSize& size,
const wxColour& borderColor,
const wxColour& bgColor,
bool borderDashed,
int borderWidth,
int radius,
bool isTop);
void AddToLeft(wxWindow* window, int proportion = 0, int flag = wxEXPAND, int border = 0);
void AddToRight(wxWindow* window, int proportion = 0, int flag = wxEXPAND, int border = 0);
void Clear(bool deleteWindows);
wxSizer* GetLeftSizer() { return m_leftSizer; }
wxSizer* GetRightSizer() { return m_rightSizer; }
void LayoutAndFit()
{
m_leftSizer->Layout();
// m_leftSizer->Fit();
m_rightSizer->Layout();
// m_rightSizer->Fit();
m_splitSizer->Layout();
// m_splitSizer->Fit();
m_contentSizer->Layout();
// m_contentSizer->Fit();
m_mainSizer->Layout();
// m_mainSizer->Fit();
Fit();
}
protected:
void OnPaint(wxPaintEvent& event);
private:
bool m_borderDashed;
int m_borderWidth;
int m_radius;
wxColour m_borderColor;
wxColour m_bgColor;
bool m_isTop;
wxBoxSizer* m_mainSizer{nullptr};
wxBoxSizer* m_contentSizer{nullptr};
wxBoxSizer* m_splitSizer{nullptr};
wxSizer* m_leftSizer{nullptr};
wxSizer* m_rightSizer{nullptr};
static constexpr int labelHeight = 30;
};
enum DataStatusType {
ADJUST,
OK,
UNMATCHED
};
class UiAMSSlot : public wxPanel
{
public:
UiAMSSlot(wxWindow* parent,
const std::vector<wxColour>& bgColours,
const wxString& text,
DataStatusType status,
wxWindowID id = wxID_ANY,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
double colourFactor = 1.0,
double scaleFactor = 1.0);
private:
void OnPaint(wxPaintEvent&);
void DrawRectangle(wxPaintDC& dc, const wxSize& cli);
void DrawLine(wxPaintDC& dc, const wxSize& cli);
wxColour LightenColour(const wxColour& original);
bool IsDark(const wxColour& c)
{
int brightness = (c.Red() * 299 + c.Green() * 587 + c.Blue() * 114) / 1000;
return brightness < 128; // 0-255 range128 mid
}
private:
const std::vector<wxColour>& m_bgColours;
wxString m_text;
DataStatusType m_status;
wxSize m_size;
ScalableBitmap *m_ams_slot_readonly{nullptr};
int m_rectangleW = 44;
int m_rectangleH = 62;
int rectangleW = 44;
int rectangleH = 62;
double m_colourFactor = 1.0f;
double m_scaleFactor = 1.0f;
// wxDECLARE_EVENT_TABLE();
};
struct DataAmsSlotInfo
{
wxString amsName;
wxString name;
std::vector<wxColour> colours;
double colourFactor;
double scaleFactor;
DataStatusType status;
};
class UiAMS : public UiStyledAMSPanel
{
public:
UiAMS(wxWindow* parent,
const std::vector<DataAmsSlotInfo>& amsInfo,
wxWindowID id,
const wxPoint& pos,
const wxSize& minSize);
private:
void init();
std::vector<DataAmsSlotInfo> m_amsInfo;
wxString m_amsTitle;
wxSize m_minSize;
};
struct DataStatusParam {
// int count = 0;
int width = 0;
int height = 0;
std::vector<DataAmsSlotInfo> slots;
};
class ReselectMachineDialog : public wxDialog
{
public:
ReselectMachineDialog(wxWindow* parent);
~ReselectMachineDialog();
void Update(MachineObject* obj,
const std::map<int, int>& best_pos_map,
const std::vector<FilamentInfo>& ams_mapping,
wxString save_time);
private:
int CaculateSwitcherDistribution(MachineObject* obj, const std::map<int, int>& best_pos_map, const std::vector<FilamentInfo>& ams_mapping);
wxString getTrayID(MachineObject* obj, const std::string& amsID, const std::string& slotID);
void OnRefreshButton(wxCommandEvent& event);
private:
int saveTimes{0};
wxBoxSizer* mainSizer{nullptr};
wxPanel* textPanel{nullptr};
wxBoxSizer* textSizer{nullptr};
Label* suggestText{nullptr};
// wxHyperlinkCtrl* linkwiki{nullptr};
Label* linkwiki{nullptr};
Label* summaryText{nullptr};
UiStyledSwitchPanel* filamentSwitch{nullptr};
wxStaticText* filamentTips{nullptr};
std::vector<std::vector<DataAmsSlotInfo>> inAAMS{};
std::vector<std::vector<DataAmsSlotInfo>> inBAMS{};
wxPanel* statusBar{nullptr};
wxBoxSizer* btnSizer{nullptr};
Button* m_buttonClose{nullptr };
Button* m_buttonRefresh{ nullptr };
std::vector<wxColour>colourAdjust{wxColour("#675AFF")};
std::vector<wxColour>colourOK{wxColour("#FF8181")};
std::vector<wxColour>colourUnused{wxColour("#FF818140")};
// wxStaticBitmap* m_bitmapSelectMachine{nullptr};
};
wxDECLARE_EVENT(wxEVT_REFRESH_DATA, wxCommandEvent);
}} // namespace Slic3r::GUI

View File

@@ -23,16 +23,16 @@ uiAmsPercentHumidityDryPopup::uiAmsPercentHumidityDryPopup(wxWindow *parent)
: wxDialog(parent, wxID_ANY, "")
{
Create();
wxGetApp().UpdateDlgDarkUI(this);
wxGetApp().UpdateDlgDarkUI(this); // Orca: apply dark-mode theming to the popup
}
void uiAmsPercentHumidityDryPopup::Create()
{
// create images
idle_img = ScalableBitmap(this, "ams_drying", 16);
drying_img = ScalableBitmap(this, "ams_is_drying", 16);
drying_img = ScalableBitmap(this, "ams_is_drying", 16); // Orca: use the drying-state icon
// background
// background
SetBackgroundColour(*wxWHITE);
// create title sizer
@@ -147,7 +147,8 @@ void uiAmsPercentHumidityDryPopup::UpdateContents()
// table grid
const wxString& humidity_str = wxString::Format("%d%%", m_humidity_percent);
m_humidity_label->SetLabel(humidity_str);
const wxString& temp_str = wxString::Format(wxString::FromUTF8(u8"%d\u2103" /* °C */), (int)std::round(m_current_temperature));
// Orca: format the temperature with an explicit UTF-8 \u2103 (no translation of a units-only format)
const wxString& temp_str = wxString::Format(wxString::FromUTF8(u8"%d\u2103" /* \u00b0C */), (int)std::round(m_current_temperature));
m_temperature_label->SetLabel(temp_str);
if (m_left_dry_time > 0)

View File

@@ -20,7 +20,7 @@ namespace Slic3r { namespace GUI {
struct uiAmsHumidityInfo
{
std::string ams_id;
AMSModel ams_type;
AMSModel ams_type; // Orca: GUI-layer AMS enum; consumer (AMSItem.cpp) assigns an AMSModel
int humidity_display_idx = -1;
int humidity_percent = -1;
float current_temperature;

View File

@@ -9,7 +9,7 @@
#include "slic3r/GUI/I18N.hpp"
#include "slic3r/GUI/wxExtensions.hpp"
#include "slic3r/GUI/Widgets/Label.hpp"
#include "slic3r/GUI/Widgets/Label.hpp" // Orca: explicit Label include
#include <wx/stattext.h>
@@ -81,7 +81,6 @@ void uiDeviceUpdateVersion::CreateWidgets()
serial_text->SetFont(font);
version_text->SetFont(font);
model_text->SetFont(font);
// The grid sizer
wxFlexGridSizer* grid_sizer = new wxFlexGridSizer(0, 2, 0, 0);
//grid_sizer->AddGrowableCol(1);
@@ -103,10 +102,10 @@ void uiDeviceUpdateVersion::CreateWidgets()
grid_sizer->Add(version_hsizer, 0, wxEXPAND, 0);
grid_sizer->Add(m_dev_version, 0, wxEXPAND | wxALL, FromDIP(5));
// Updating
wxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
main_sizer->AddSpacer(FromDIP(40));
main_sizer->AddSpacer(FromDIP(40)); // Orca: top spacing above the firmware-info grid
main_sizer->Add(grid_sizer, 0, wxALIGN_LEFT, FromDIP(5));
SetSizer(main_sizer);

View File

@@ -1,14 +1,11 @@
//**********************************************************/
/* File: wgtDeviceNozzleRack.cpp
* Description: The Device-tab panel with the toolhead nozzle and the H2C induction hotend rack.
* Description: The panel with rack info
*
* \n class wgtDeviceNozzleRack;
* \n class wgtDeviceNozzleRackToolHead;
* \n class wgtDeviceNozzleRackArea;
* \n class wgtDeviceNozzleRackNozzleItem;
* \n class wgtDeviceNozzleRackToolHead;
* \n class wgtDeviceNozzleRackPos;
*
* The wgtDeviceNozzleRackNozzleItem tile and the EVT_NOZZLE_RACK_NOZZLE_ITEM_SELECTED event live in
* wgtDeviceNozzleRackNozzleItem.{h,cpp}, so they are not redefined here.
//**********************************************************/
#include "wgtDeviceNozzleRack.h"
@@ -17,29 +14,41 @@
#include "slic3r/GUI/DeviceCore/DevNozzleSystem.h"
#include "slic3r/GUI/I18N.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/MainFrame.hpp"
#include "slic3r/GUI/MsgDialog.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/wxExtensions.hpp"
#include "slic3r/GUI/Widgets/Button.hpp"
#include "slic3r/GUI/Widgets/Label.hpp"
#include <unordered_set>
#define WX_DIP_SIZE_18 wxSize(FromDIP(18), FromDIP(18))
#define WX_DIP_SIZE_46 wxSize(FromDIP(46), FromDIP(46))
#define WX_DIP_SIZE(x, y) wxSize(FromDIP(x), FromDIP(y))
#define WGT_RACK_NOZZLE_SIZE WX_DIP_SIZE(88, 100)
#define L_RAW_A_STR _L("Row A")
#define L_RAW_B_STR _L("Row B")
static wxColour s_gray_clr("#B0B0B0");
static wxColour s_hgreen_clr("#009688"); // Orca: accent green
static wxColour s_red_clr("#D01B1B");
// Orca: StateColor lacks these grey constants, so mirror the values here.
static const wxColour WGT_GREY200 = wxColour(248, 248, 248);
static const wxColour WGT_GREY300 = wxColour(238, 238, 238);
static wxColour s_hgreen_clr("#009688");
static std::vector<int> a_nozzle_seq = { 0, 2, 4, 1, 3, 5 };
static std::vector<int> b_nozzle_seq = { 1, 3, 5, 0, 2, 4 };
wxDEFINE_EVENT(EVT_NOZZLE_RACK_NOZZLE_ITEM_SELECTED, wxCommandEvent);
namespace Slic3r::GUI
{
// Tints the "yellow" template pixels of a nozzle bitmap with the loaded filament colour.
// Duplicates the file-local helper of the same purpose in wgtDeviceNozzleRackNozzleItem.cpp.
static wxBitmap SetNozzleBmpColor(const wxBitmap& bmp, const std::string& color_str) {
if(color_str.empty()) return bmp;
@@ -353,6 +362,36 @@ StaticBox* wgtDeviceNozzleRackArea::CreateNozzleBox(const std::vector<int> nozzl
return nozzle_box;
}
static void s_update_title(const std::shared_ptr<DevNozzleRack> rack, wgtDeviceNozzleRackTitle* title_label)
{
wxString title = _L("Induction Hotend Rack");
if (rack && (rack->GetReadingCount() > 0))
{
wxString pending = ": " + _L("Reading") + wxString::Format(" %d/%d", rack->GetReadingIdx(), rack->GetReadingCount());
title += pending;
}
title_label->SetLabel(title);
}
#if 0
static void s_update_readall_btn(const std::shared_ptr<DevNozzleRack> rack, Button* btn)
{
wxString label = _L("Read All");
if (rack && rack->HasUnreliableNozzles())
{
label += "*";
}
if (btn->GetLabel() != label)
{
btn->SetLabel(label);
}
btn->Enable(rack->CtrlCanReadAll());
};
#endif
void wgtDeviceNozzleRackArea::UpdateNozzleItems(const std::unordered_map<int, wgtDeviceNozzleRackNozzleItem*>& nozzle_items,
std::shared_ptr<DevNozzleRack> nozzle_rack)
{
@@ -409,6 +448,7 @@ void wgtDeviceNozzleRackArea::UpdateRackInfo(std::weak_ptr<DevNozzleRack> rack)
const auto& nozzle_rack = rack.lock();
if (nozzle_rack)
{
// s_update_title(nozzle_rack, m_title_nozzle_rack);
UpdateNozzleItems(m_nozzle_items, nozzle_rack);
m_rack_pos_panel->UpdateRackPos(nozzle_rack);
m_btn_read_all->Enable(nozzle_rack->CtrlCanReadAll());
@@ -571,7 +611,7 @@ static void s_show_label(Label* label, const wxString& text, const wxColour& tex
void wgtDeviceNozzleRackPos::UpdateRackPos(DevNozzleRack::RackPos new_pos,
DevNozzleRack::RackStatus new_status, bool is_reading)
{
// While reading, both rows show a "Running..." status and the move buttons are hidden.
/* di*/
if (is_reading)
{
s_show_label(m_label_rowup, L_RAW_A_STR, *wxBLACK);
@@ -697,4 +737,298 @@ void wgtDeviceNozzleRackPos::Rescale()
m_btn_homing->msw_rescale();
}
};// end of namespace Slic3r::GUI
wgtDeviceNozzleRackNozzleItem::wgtDeviceNozzleRackNozzleItem(wxWindow* parent, int nozzle_id)
: StaticBox(parent, wxID_ANY), m_nozzle_id(nozzle_id)
{
CreateGui();
}
void wgtDeviceNozzleRackNozzleItem::CreateGui()
{
// Background
SetCornerRadius(FromDIP(5));
SetBackgroundColor(*wxWHITE);
// Top H
wxSizer *top_h_sizer = new wxBoxSizer(wxHORIZONTAL);
m_nozzle_label_id = new Label(this);
m_nozzle_label_id->SetFont(Label::Body_12);
m_nozzle_label_id->SetBackgroundColour(*wxWHITE);
m_nozzle_label_id->SetLabel(wxString::Format("%d", m_nozzle_id + 1));
m_status = NOZZLE_STATUS::NOZZLE_EMPTY;
m_nozzle_empty_image = new ScalableBitmap(this, "dev_rack_nozzle_empty", 46);
m_nozzle_icon = new wxStaticBitmap(this, wxID_ANY, m_nozzle_empty_image->bmp(), wxDefaultPosition, WX_DIP_SIZE_46);
m_nozzle_icon->SetBackgroundColour(*wxWHITE);
m_nozzle_selected_bitmap = new wxStaticBitmap(this, wxID_ANY, wxNullBitmap, wxDefaultPosition, WX_DIP_SIZE(20, 20));
m_nozzle_selected_bitmap->SetBackgroundColour(*wxWHITE);
top_h_sizer->Add(m_nozzle_label_id, 0, wxTOP | wxLEFT, FromDIP(6));
top_h_sizer->AddStretchSpacer(1);
top_h_sizer->Add(m_nozzle_icon, 0, wxTOP, FromDIP(10));
top_h_sizer->AddStretchSpacer(1);
top_h_sizer->Add(m_nozzle_selected_bitmap, 0, wxTOP | wxRIGHT, FromDIP(2));
// Bottom V
wxBoxSizer* bottom_v = new wxBoxSizer(wxVERTICAL);
wxSizer* label_h_sizer = new wxBoxSizer(wxHORIZONTAL);
m_nozzle_label_1 = new Label(this);
m_nozzle_label_1->SetFont(Label::Body_12);
m_nozzle_label_1->SetBackgroundColour(*wxWHITE);
m_nozzle_label_1->SetLabel(_L("Empty"));
label_h_sizer->Add(m_nozzle_label_1, 0, wxALIGN_LEFT);
auto status_icon = create_scaled_bitmap("dev_rack_nozzle_error_icon", this, 14);
m_nozzle_status_icon = new wxStaticBitmap(this, wxID_ANY, status_icon, wxDefaultPosition, WX_DIP_SIZE(14, 14));
m_nozzle_status_icon->Bind(wxEVT_LEFT_DOWN, &wgtDeviceNozzleRackNozzleItem::OnBtnNozzleStatus, this);
m_nozzle_status_icon->Bind(wxEVT_ENTER_WINDOW, [this](auto&) { SetCursor(wxCURSOR_HAND); });
m_nozzle_status_icon->Bind(wxEVT_LEAVE_WINDOW, [this](auto&) { SetCursor(wxCURSOR_ARROW); });
m_nozzle_status_icon->SetBackgroundColour(*wxWHITE);
m_nozzle_status_icon->Show(false);
label_h_sizer->Add(m_nozzle_status_icon, 0, wxALIGN_CENTER | wxLEFT, FromDIP(2));
bottom_v->Add(label_h_sizer, 0, wxALIGN_CENTER_HORIZONTAL | wxTOP, FromDIP(2));
m_nozzle_label_2 = new Label(this);
m_nozzle_label_2->SetFont(Label::Body_12);
m_nozzle_label_2->SetBackgroundColour(*wxWHITE);
bottom_v->Add(m_nozzle_label_2, 0, wxALIGN_CENTER_HORIZONTAL);
// Main sizer
wxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
main_sizer->Add(top_h_sizer, 0, wxEXPAND);
main_sizer->Add(bottom_v, 0, wxALIGN_CENTER_HORIZONTAL);
SetSizer(main_sizer);
SetMinSize(WGT_RACK_NOZZLE_SIZE);
SetMaxSize(WGT_RACK_NOZZLE_SIZE);
SetSize(WGT_RACK_NOZZLE_SIZE);
Layout();
};
void wgtDeviceNozzleRackNozzleItem::SetSelected(bool selected)
{
if (!m_enable_select){
assert(false && "not support select");
return;
}
if (m_is_selected != selected) {
m_is_selected = selected;
if (selected) {
if (!m_nozzle_selected_image) {
m_nozzle_selected_image = new ScalableBitmap(this, "dev_rack_nozzle_selected", 20);
}
m_nozzle_selected_bitmap->SetBitmap(m_nozzle_selected_image->bmp());
SetBorderColor(StateColor::darkModeColorFor(s_hgreen_clr));
} else {
m_nozzle_selected_bitmap->SetBitmap(wxNullBitmap);
SetBorderColor(StateColor::darkModeColorFor(s_gray_clr));
}
Refresh();
}
}
void wgtDeviceNozzleRackNozzleItem::Update(const std::shared_ptr<DevNozzleRack> rack, bool on_rack /*= true*/)
{
m_rack = rack;
if (rack) {
const auto &nozzle_info = on_rack ? rack->GetNozzle(m_nozzle_id) : rack->GetNozzleSystem()->GetExtNozzle(m_nozzle_id);
const wxString &diameter_str = nozzle_info.GetNozzleDiameterStr();
const wxString &flowtype_str = nozzle_info.GetNozzleFlowTypeStr();
const std::string &color = nozzle_info.GetFilamentColor();
/*check empty first*/
if (nozzle_info.IsEmpty()) {
SetNozzleStatus(NOZZLE_STATUS::NOZZLE_EMPTY, _L("Empty"), wxEmptyString, color);
} else if (nozzle_info.IsNormal()) {
SetNozzleStatus(NOZZLE_STATUS::NOZZLE_NORMAL, diameter_str, flowtype_str, color);
} else if (nozzle_info.IsAbnormal()) {
SetNozzleStatus(NOZZLE_STATUS::NOZZLE_ERROR, _L("Error"), wxEmptyString, color);
} else if (nozzle_info.IsUnknown()) {
SetNozzleStatus(NOZZLE_STATUS::NOZZLE_UNKNOWN, _L("Unknown"), wxEmptyString, color);
}
}
}
void wgtDeviceNozzleRackNozzleItem::SetNozzleStatus(NOZZLE_STATUS status, const wxString& str1, const wxString& str2, const std::string& color)
{
if (m_status != status || m_filament_color != color)
{
m_status = status;
m_filament_color = color;
switch (status)
{
case Slic3r::GUI::wgtDeviceNozzleRackNozzleItem::NOZZLE_EMPTY:
{
if (!m_nozzle_empty_image) { m_nozzle_empty_image = new ScalableBitmap(this, "dev_rack_nozzle_empty", 46);}
m_nozzle_icon->SetBitmap(m_nozzle_empty_image->bmp());
break;
}
case Slic3r::GUI::wgtDeviceNozzleRackNozzleItem::NOZZLE_NORMAL:
{
if (!m_nozzle_normal_image) { m_nozzle_normal_image = new ScalableBitmap(this, "dev_rack_nozzle_normal", 46);}
m_nozzle_icon->SetBitmap(SetNozzleBmpColor(m_nozzle_normal_image->bmp(), m_filament_color));
break;
}
case Slic3r::GUI::wgtDeviceNozzleRackNozzleItem::NOZZLE_UNKNOWN:
{
if (!m_nozzle_unknown_image) { m_nozzle_unknown_image = new ScalableBitmap(this, "dev_rack_nozzle_unknown", 46);}
m_nozzle_icon->SetBitmap(m_nozzle_unknown_image->bmp());
break;
}
case Slic3r::GUI::wgtDeviceNozzleRackNozzleItem::NOZZLE_ERROR:
{
if (!m_nozzle_error_image) { m_nozzle_error_image = new ScalableBitmap(this, "dev_rack_nozzle_error", 46);}
m_nozzle_icon->SetBitmap(m_nozzle_error_image->bmp());
break;
}
default:
{
break;
}
}
if (status == wgtDeviceNozzleRackNozzleItem::NOZZLE_ERROR)
{
m_nozzle_label_1->SetForegroundColour(StateColor::darkModeColorFor(s_red_clr));
m_nozzle_status_icon->Show(true);
}
else
{
m_nozzle_label_1->SetForegroundColour(StateColor::darkModeColorFor(*wxBLACK));
m_nozzle_status_icon->Show(false);
}
}
bool update_layout = (m_nozzle_label_1->GetLabel() != str1 || m_nozzle_label_2->GetLabel() != str2);
m_nozzle_label_1->SetLabel(str1);
m_nozzle_label_2->SetLabel(str2);
if (update_layout) {
Layout();
}
}
void wgtDeviceNozzleRackNozzleItem::OnBtnNozzleStatus(wxMouseEvent& evt)
{
if (m_is_disabled) {
return;
}
auto rack = m_rack.lock();
if (rack && m_status == wgtDeviceNozzleRackNozzleItem::NOZZLE_ERROR)
{
MessageDialog dlg(nullptr, _L("The hotend is in an abnormal state and currently unavailable. "
"Please go to 'Device -> Upgrade' to upgrade firmware."), _L("Abnormal Hotend"), wxICON_WARNING);
dlg.AddButton(wxID_CANCEL, _L("Cancel"), false);
dlg.AddButton(wxID_OK,_L("Jump to the upgrade page"), true);
if (dlg.ShowModal() == wxID_OK) {
wxGetApp().mainframe->m_monitor->jump_to_Upgrade();
};
}
}
void wgtDeviceNozzleRackNozzleItem::Rescale()
{
if (m_nozzle_normal_image) { m_nozzle_normal_image->msw_rescale(); }
if (m_nozzle_empty_image) { m_nozzle_empty_image->msw_rescale(); }
if (m_nozzle_unknown_image) { m_nozzle_unknown_image->msw_rescale(); }
if (m_nozzle_error_image) { m_nozzle_error_image->msw_rescale(); }
auto status_icon = create_scaled_bitmap("dev_rack_nozzle_error_icon", this, 14);
m_nozzle_status_icon->SetBitmap(status_icon);
m_nozzle_status_icon->Refresh();
if (m_nozzle_selected_image) {
m_nozzle_selected_image->msw_rescale();
if (m_is_selected) {
m_nozzle_selected_bitmap->SetBitmap(m_nozzle_selected_image->bmp());
}
};
switch (m_status)
{
case Slic3r::GUI::wgtDeviceNozzleRackNozzleItem::NOZZLE_EMPTY:
{
m_nozzle_icon->SetBitmap(m_nozzle_empty_image->bmp());
break;
}
case Slic3r::GUI::wgtDeviceNozzleRackNozzleItem::NOZZLE_NORMAL:
{
m_nozzle_icon->SetBitmap(SetNozzleBmpColor(m_nozzle_normal_image->bmp(), m_filament_color));
break;
}
case Slic3r::GUI::wgtDeviceNozzleRackNozzleItem::NOZZLE_UNKNOWN:
{
m_nozzle_icon->SetBitmap(m_nozzle_unknown_image->bmp());
break;
}
case Slic3r::GUI::wgtDeviceNozzleRackNozzleItem::NOZZLE_ERROR:
{
m_nozzle_icon->SetBitmap(m_nozzle_error_image->bmp());
break;
}
default:
{
break;
}
};
};
void wgtDeviceNozzleRackNozzleItem::EnableSelect()
{
if (m_enable_select == true) {
return;
};
m_enable_select = true;
m_nozzle_icon->Bind(wxEVT_LEFT_DOWN, [this](auto& evt) { OnItemSelected(evt); });
m_nozzle_label_id->Bind(wxEVT_LEFT_DOWN, [this](auto& evt) { OnItemSelected(evt); });
m_nozzle_label_1->Bind(wxEVT_LEFT_DOWN, [this](auto& evt) { OnItemSelected(evt); });
m_nozzle_label_2->Bind(wxEVT_LEFT_DOWN, [this](auto& evt) { OnItemSelected(evt); });
Bind(wxEVT_LEFT_DOWN, [this](auto& evt) { OnItemSelected(evt); });
}
void wgtDeviceNozzleRackNozzleItem::OnItemSelected(wxMouseEvent& evt)
{
if (m_enable_select && !m_is_disabled){
SetSelected(true);
wxCommandEvent command_evt(EVT_NOZZLE_RACK_NOZZLE_ITEM_SELECTED, GetId());
command_evt.SetEventObject(this);
ProcessEvent(command_evt);
}
evt.Skip();
}
void wgtDeviceNozzleRackNozzleItem::SetDisable(bool disabled)
{
if (m_is_disabled == disabled) {
return;
}
m_is_disabled = disabled;
auto bg_clr = disabled ? StateColor::darkModeColorFor("#E5E7EB") : StateColor::darkModeColorFor(*wxWHITE);
m_nozzle_icon->SetBackgroundColour(bg_clr);
m_nozzle_label_id->SetBackgroundColour(bg_clr);
m_nozzle_label_1->SetBackgroundColour(bg_clr);
m_nozzle_status_icon->SetBackgroundColour(bg_clr);
m_nozzle_label_2->SetBackgroundColour(bg_clr);
m_nozzle_selected_bitmap->SetBackgroundColour(bg_clr);
SetBackgroundColor(bg_clr);
Refresh();
};
};

View File

@@ -1,31 +1,22 @@
//**********************************************************/
/* File: wgtDeviceNozzleRack.h
* Description: The Device-tab panel with the toolhead nozzle and the H2C induction hotend rack.
* Description: The panel with rack and nozzles
*
* \n class wgtDeviceNozzleRack; // toolhead panel + rack area, side by side
* \n class wgtDeviceNozzleRackToolHead;
* \n class wgtDeviceNozzleRackArea;
* \n class wgtDeviceNozzleRackNozzleItem;
* \n class wgtDeviceNozzleRackToolHead;
* \n class wgtDeviceNozzleRackPos;
*
* The single-nozzle tile class wgtDeviceNozzleRackNozzleItem lives in its own translation unit
* (wgtDeviceNozzleRackNozzleItem.{h,cpp}), so it is included here rather than redeclared.
* The Hotends-Info upgrade dialog (wgtDeviceNozzleRackUpgradeDlg) is intentionally not wired yet
* (see the TODO markers in the .cpp).
//**********************************************************/
#pragma once
#include "slic3r/GUI/DeviceCore/DevNozzleRack.h"
#include "wgtDeviceNozzleRackNozzleItem.h"
#include "slic3r/GUI/Widgets/StaticBox.hpp"
#include "slic3r/GUI/Widgets/AnimaController.hpp"
#include <wx/panel.h>
#include <wx/simplebook.h>
#include <memory>
#include <unordered_map>
#include <vector>
// Previous definitions
class Button;
@@ -47,6 +38,9 @@ namespace GUI
}
};
// Events
wxDECLARE_EVENT(EVT_NOZZLE_RACK_NOZZLE_ITEM_SELECTED, wxCommandEvent);
namespace Slic3r::GUI
{
class wgtDeviceNozzleRack : public wxPanel
@@ -148,8 +142,6 @@ private:
Label* m_progress_refresh{ nullptr };
AnimaIcon* m_refresh_icon{ nullptr };
// "Hotends Info" upgrade dialog. Owned for its ShowModal lifetime by
// OnBtnHotendsInfos; UpdateRackInfo forwards live device pushes to it while it is shown.
wgtDeviceNozzleRackUpgradeDlg* m_rack_upgrade_dlg = nullptr;
};
@@ -193,4 +185,71 @@ private:
ScalableButton* m_btn_homing{ nullptr };
};
};// end of namespace Slic3r::GUI
class wgtDeviceNozzleRackNozzleItem : public StaticBox
{
public:
enum NOZZLE_STATUS
{
NOZZLE_EMPTY,
NOZZLE_NORMAL,
NOZZLE_UNKNOWN,
NOZZLE_ERROR
};
public:
wgtDeviceNozzleRackNozzleItem(wxWindow* parent, int nozzle_id);
public:
void Update(const std::shared_ptr<DevNozzleRack> rack, bool on_rack = true); // on_rack is false means extruder nozzle
int GetNozzleId() const { return m_nozzle_id; }
void SetDisplayIdText(const wxString& text) { m_nozzle_label_id->SetLabel(text);};
void EnableSelect();;
void SetSelected(bool selected);
bool IsSelected() const { return m_is_selected; }
bool IsDisabled() const { return m_is_disabled; }
void SetDisable(bool disabled);
void Rescale();
private:
void CreateGui();
void SetNozzleStatus(NOZZLE_STATUS status, const wxString& str1, const wxString& str2, const std::string& color);
void OnBtnNozzleStatus(wxMouseEvent& evt);
void OnItemSelected(wxMouseEvent& evt);
private:
std::weak_ptr<DevNozzleRack> m_rack;
int m_nozzle_id; // internal id, from 0 to 5
std::string m_filament_color;
NOZZLE_STATUS m_status = NOZZLE_STATUS::NOZZLE_EMPTY;
// select
bool m_is_selected = false;
bool m_enable_select = false;
ScalableBitmap* m_nozzle_selected_image{ nullptr };
wxStaticBitmap* m_nozzle_selected_bitmap{ nullptr };
// enable or disable
bool m_is_disabled = false;
// Images
ScalableBitmap* m_nozzle_normal_image{ nullptr };
ScalableBitmap* m_nozzle_empty_image{ nullptr };
ScalableBitmap* m_nozzle_unknown_image{ nullptr };
ScalableBitmap* m_nozzle_error_image{ nullptr };
// GUI
wxStaticBitmap* m_nozzle_icon{ nullptr };
Label* m_nozzle_label_id { nullptr };
Label* m_nozzle_label_1{ nullptr };
wxStaticBitmap* m_nozzle_status_icon = nullptr;
Label* m_nozzle_label_2{ nullptr };
};
};// end of namespace Slic3r::GUI

View File

@@ -1,347 +0,0 @@
//**********************************************************/
/* File: wgtDeviceNozzleRackNozzleItem.cpp
* Description: One nozzle cell of the H2C hotend rack view. Holds the
* wgtDeviceNozzleRackNozzleItem widget, the SetNozzleBmpColor helper and the layout
* constants it needs.
//**********************************************************/
#include "wgtDeviceNozzleRackNozzleItem.h"
#include "slic3r/GUI/DeviceCore/DevNozzleSystem.h"
#include "slic3r/GUI/I18N.hpp"
#include "slic3r/GUI/MsgDialog.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/wxExtensions.hpp"
#include "slic3r/GUI/Widgets/Label.hpp"
#define WX_DIP_SIZE_46 wxSize(FromDIP(46), FromDIP(46))
#define WX_DIP_SIZE(x, y) wxSize(FromDIP(x), FromDIP(y))
#define WGT_RACK_NOZZLE_SIZE WX_DIP_SIZE(88, 100)
static wxColour s_gray_clr("#B0B0B0");
static wxColour s_hgreen_clr("#009688");
static wxColour s_red_clr("#D01B1B");
wxDEFINE_EVENT(EVT_NOZZLE_RACK_NOZZLE_ITEM_SELECTED, wxCommandEvent);
namespace Slic3r::GUI
{
static wxBitmap SetNozzleBmpColor(const wxBitmap& bmp, const std::string& color_str) {
if(color_str.empty()) return bmp;
wxImage img = bmp.ConvertToImage();
wxColour color("#" + color_str);
for (int y = 0; y < img.GetHeight(); ++y) {
for (int x = 0; x < img.GetWidth(); ++x) {
unsigned char r = img.GetRed(x, y);
unsigned char g = img.GetGreen(x, y);
unsigned char b = img.GetBlue(x, y);
/*replace yellow with color*/
if ( r >= 180 && g >= 180 && b <= 150) {
img.SetRGB(x, y, color.Red(), color.Green(), color.Blue());
}
}
}
return wxBitmap(img, -1, bmp.GetScaleFactor());
}
wgtDeviceNozzleRackNozzleItem::wgtDeviceNozzleRackNozzleItem(wxWindow* parent, int nozzle_id)
: StaticBox(parent, wxID_ANY), m_nozzle_id(nozzle_id)
{
CreateGui();
}
void wgtDeviceNozzleRackNozzleItem::CreateGui()
{
// Background
SetCornerRadius(FromDIP(5));
SetBackgroundColor(*wxWHITE);
// Top H
wxSizer *top_h_sizer = new wxBoxSizer(wxHORIZONTAL);
m_nozzle_label_id = new Label(this);
m_nozzle_label_id->SetFont(Label::Body_12);
m_nozzle_label_id->SetBackgroundColour(*wxWHITE);
m_nozzle_label_id->SetLabel(wxString::Format("%d", m_nozzle_id + 1));
m_status = NOZZLE_STATUS::NOZZLE_EMPTY;
m_nozzle_empty_image = new ScalableBitmap(this, "dev_rack_nozzle_empty", 46);
m_nozzle_icon = new wxStaticBitmap(this, wxID_ANY, m_nozzle_empty_image->bmp(), wxDefaultPosition, WX_DIP_SIZE_46);
m_nozzle_icon->SetBackgroundColour(*wxWHITE);
m_nozzle_selected_bitmap = new wxStaticBitmap(this, wxID_ANY, wxNullBitmap, wxDefaultPosition, WX_DIP_SIZE(20, 20));
m_nozzle_selected_bitmap->SetBackgroundColour(*wxWHITE);
top_h_sizer->Add(m_nozzle_label_id, 0, wxTOP | wxLEFT, FromDIP(6));
top_h_sizer->AddStretchSpacer(1);
top_h_sizer->Add(m_nozzle_icon, 0, wxTOP, FromDIP(10));
top_h_sizer->AddStretchSpacer(1);
top_h_sizer->Add(m_nozzle_selected_bitmap, 0, wxTOP | wxRIGHT, FromDIP(2));
// Bottom V
wxBoxSizer* bottom_v = new wxBoxSizer(wxVERTICAL);
wxSizer* label_h_sizer = new wxBoxSizer(wxHORIZONTAL);
m_nozzle_label_1 = new Label(this);
m_nozzle_label_1->SetFont(Label::Body_12);
m_nozzle_label_1->SetBackgroundColour(*wxWHITE);
m_nozzle_label_1->SetLabel(_L("Empty"));
label_h_sizer->Add(m_nozzle_label_1, 0, wxALIGN_LEFT);
auto status_icon = create_scaled_bitmap("dev_rack_nozzle_error_icon", this, 14);
m_nozzle_status_icon = new wxStaticBitmap(this, wxID_ANY, status_icon, wxDefaultPosition, WX_DIP_SIZE(14, 14));
m_nozzle_status_icon->Bind(wxEVT_LEFT_DOWN, &wgtDeviceNozzleRackNozzleItem::OnBtnNozzleStatus, this);
m_nozzle_status_icon->Bind(wxEVT_ENTER_WINDOW, [this](auto&) { SetCursor(wxCURSOR_HAND); });
m_nozzle_status_icon->Bind(wxEVT_LEAVE_WINDOW, [this](auto&) { SetCursor(wxCURSOR_ARROW); });
m_nozzle_status_icon->SetBackgroundColour(*wxWHITE);
m_nozzle_status_icon->Show(false);
label_h_sizer->Add(m_nozzle_status_icon, 0, wxALIGN_CENTER | wxLEFT, FromDIP(2));
bottom_v->Add(label_h_sizer, 0, wxALIGN_CENTER_HORIZONTAL | wxTOP, FromDIP(2));
m_nozzle_label_2 = new Label(this);
m_nozzle_label_2->SetFont(Label::Body_12);
m_nozzle_label_2->SetBackgroundColour(*wxWHITE);
bottom_v->Add(m_nozzle_label_2, 0, wxALIGN_CENTER_HORIZONTAL);
// Main sizer
wxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
main_sizer->Add(top_h_sizer, 0, wxEXPAND);
main_sizer->Add(bottom_v, 0, wxALIGN_CENTER_HORIZONTAL);
SetSizer(main_sizer);
SetMinSize(WGT_RACK_NOZZLE_SIZE);
SetMaxSize(WGT_RACK_NOZZLE_SIZE);
SetSize(WGT_RACK_NOZZLE_SIZE);
Layout();
};
void wgtDeviceNozzleRackNozzleItem::SetSelected(bool selected)
{
if (!m_enable_select){
assert(false && "not support select");
return;
}
if (m_is_selected != selected) {
m_is_selected = selected;
if (selected) {
if (!m_nozzle_selected_image) {
m_nozzle_selected_image = new ScalableBitmap(this, "dev_rack_nozzle_selected", 20);
}
m_nozzle_selected_bitmap->SetBitmap(m_nozzle_selected_image->bmp());
SetBorderColor(StateColor::darkModeColorFor(s_hgreen_clr));
} else {
m_nozzle_selected_bitmap->SetBitmap(wxNullBitmap);
SetBorderColor(StateColor::darkModeColorFor(s_gray_clr));
}
Refresh();
}
}
void wgtDeviceNozzleRackNozzleItem::Update(const std::shared_ptr<DevNozzleRack> rack, bool on_rack /*= true*/)
{
m_rack = rack;
if (rack) {
const auto &nozzle_info = on_rack ? rack->GetNozzle(m_nozzle_id) : rack->GetNozzleSystem()->GetExtNozzle(m_nozzle_id);
const wxString &diameter_str = nozzle_info.GetNozzleDiameterStr();
const wxString &flowtype_str = nozzle_info.GetNozzleFlowTypeStr();
const std::string &color = nozzle_info.GetFilamentColor();
/*check empty first*/
if (nozzle_info.IsEmpty()) {
SetNozzleStatus(NOZZLE_STATUS::NOZZLE_EMPTY, _L("Empty"), wxEmptyString, color);
} else if (nozzle_info.IsNormal()) {
SetNozzleStatus(NOZZLE_STATUS::NOZZLE_NORMAL, diameter_str, flowtype_str, color);
} else if (nozzle_info.IsAbnormal()) {
SetNozzleStatus(NOZZLE_STATUS::NOZZLE_ERROR, _L("Error"), wxEmptyString, color);
} else if (nozzle_info.IsUnknown()) {
SetNozzleStatus(NOZZLE_STATUS::NOZZLE_UNKNOWN, _L("Unknown"), wxEmptyString, color);
}
}
}
void wgtDeviceNozzleRackNozzleItem::SetNozzleStatus(NOZZLE_STATUS status, const wxString& str1, const wxString& str2, const std::string& color)
{
if (m_status != status || m_filament_color != color)
{
m_status = status;
m_filament_color = color;
switch (status)
{
case Slic3r::GUI::wgtDeviceNozzleRackNozzleItem::NOZZLE_EMPTY:
{
if (!m_nozzle_empty_image) { m_nozzle_empty_image = new ScalableBitmap(this, "dev_rack_nozzle_empty", 46);}
m_nozzle_icon->SetBitmap(m_nozzle_empty_image->bmp());
break;
}
case Slic3r::GUI::wgtDeviceNozzleRackNozzleItem::NOZZLE_NORMAL:
{
if (!m_nozzle_normal_image) { m_nozzle_normal_image = new ScalableBitmap(this, "dev_rack_nozzle_normal", 46);}
m_nozzle_icon->SetBitmap(SetNozzleBmpColor(m_nozzle_normal_image->bmp(), m_filament_color));
break;
}
case Slic3r::GUI::wgtDeviceNozzleRackNozzleItem::NOZZLE_UNKNOWN:
{
if (!m_nozzle_unknown_image) { m_nozzle_unknown_image = new ScalableBitmap(this, "dev_rack_nozzle_unknown", 46);}
m_nozzle_icon->SetBitmap(m_nozzle_unknown_image->bmp());
break;
}
case Slic3r::GUI::wgtDeviceNozzleRackNozzleItem::NOZZLE_ERROR:
{
if (!m_nozzle_error_image) { m_nozzle_error_image = new ScalableBitmap(this, "dev_rack_nozzle_error", 46);}
m_nozzle_icon->SetBitmap(m_nozzle_error_image->bmp());
break;
}
default:
{
break;
}
}
if (status == wgtDeviceNozzleRackNozzleItem::NOZZLE_ERROR)
{
m_nozzle_label_1->SetForegroundColour(StateColor::darkModeColorFor(s_red_clr));
m_nozzle_status_icon->Show(true);
}
else
{
m_nozzle_label_1->SetForegroundColour(StateColor::darkModeColorFor(*wxBLACK));
m_nozzle_status_icon->Show(false);
}
}
bool update_layout = (m_nozzle_label_1->GetLabel() != str1 || m_nozzle_label_2->GetLabel() != str2);
m_nozzle_label_1->SetLabel(str1);
m_nozzle_label_2->SetLabel(str2);
if (update_layout) {
Layout();
}
}
void wgtDeviceNozzleRackNozzleItem::OnBtnNozzleStatus(wxMouseEvent& evt)
{
if (m_is_disabled) {
return;
}
auto rack = m_rack.lock();
if (rack && m_status == wgtDeviceNozzleRackNozzleItem::NOZZLE_ERROR)
{
// Orca: show the abnormal-hotend warning as an informational dialog only, with no
// "Jump to the upgrade page" button, since Orca's MessageDialog and device Upgrade UI
// have no such entry point. Fires when tapping an error-state rack nozzle's status icon.
MessageDialog dlg(nullptr, _L("The hotend is in an abnormal state and currently unavailable. "
"Please go to 'Device -> Upgrade' to upgrade firmware."), _L("Abnormal Hotend"), wxICON_WARNING | wxOK);
dlg.ShowModal();
}
}
void wgtDeviceNozzleRackNozzleItem::Rescale()
{
if (m_nozzle_normal_image) { m_nozzle_normal_image->msw_rescale(); }
if (m_nozzle_empty_image) { m_nozzle_empty_image->msw_rescale(); }
if (m_nozzle_unknown_image) { m_nozzle_unknown_image->msw_rescale(); }
if (m_nozzle_error_image) { m_nozzle_error_image->msw_rescale(); }
auto status_icon = create_scaled_bitmap("dev_rack_nozzle_error_icon", this, 14);
m_nozzle_status_icon->SetBitmap(status_icon);
m_nozzle_status_icon->Refresh();
if (m_nozzle_selected_image) {
m_nozzle_selected_image->msw_rescale();
if (m_is_selected) {
m_nozzle_selected_bitmap->SetBitmap(m_nozzle_selected_image->bmp());
}
};
switch (m_status)
{
case Slic3r::GUI::wgtDeviceNozzleRackNozzleItem::NOZZLE_EMPTY:
{
m_nozzle_icon->SetBitmap(m_nozzle_empty_image->bmp());
break;
}
case Slic3r::GUI::wgtDeviceNozzleRackNozzleItem::NOZZLE_NORMAL:
{
m_nozzle_icon->SetBitmap(SetNozzleBmpColor(m_nozzle_normal_image->bmp(), m_filament_color));
break;
}
case Slic3r::GUI::wgtDeviceNozzleRackNozzleItem::NOZZLE_UNKNOWN:
{
m_nozzle_icon->SetBitmap(m_nozzle_unknown_image->bmp());
break;
}
case Slic3r::GUI::wgtDeviceNozzleRackNozzleItem::NOZZLE_ERROR:
{
m_nozzle_icon->SetBitmap(m_nozzle_error_image->bmp());
break;
}
default:
{
break;
}
};
};
void wgtDeviceNozzleRackNozzleItem::EnableSelect()
{
if (m_enable_select == true) {
return;
};
m_enable_select = true;
m_nozzle_icon->Bind(wxEVT_LEFT_DOWN, [this](auto& evt) { OnItemSelected(evt); });
m_nozzle_label_id->Bind(wxEVT_LEFT_DOWN, [this](auto& evt) { OnItemSelected(evt); });
m_nozzle_label_1->Bind(wxEVT_LEFT_DOWN, [this](auto& evt) { OnItemSelected(evt); });
m_nozzle_label_2->Bind(wxEVT_LEFT_DOWN, [this](auto& evt) { OnItemSelected(evt); });
Bind(wxEVT_LEFT_DOWN, [this](auto& evt) { OnItemSelected(evt); });
}
void wgtDeviceNozzleRackNozzleItem::OnItemSelected(wxMouseEvent& evt)
{
if (m_enable_select && !m_is_disabled){
SetSelected(true);
wxCommandEvent command_evt(EVT_NOZZLE_RACK_NOZZLE_ITEM_SELECTED, GetId());
command_evt.SetEventObject(this);
ProcessEvent(command_evt);
}
evt.Skip();
}
void wgtDeviceNozzleRackNozzleItem::SetDisable(bool disabled)
{
if (m_is_disabled == disabled) {
return;
}
m_is_disabled = disabled;
auto bg_clr = disabled ? StateColor::darkModeColorFor("#E5E7EB") : StateColor::darkModeColorFor(*wxWHITE);
m_nozzle_icon->SetBackgroundColour(bg_clr);
m_nozzle_label_id->SetBackgroundColour(bg_clr);
m_nozzle_label_1->SetBackgroundColour(bg_clr);
m_nozzle_status_icon->SetBackgroundColour(bg_clr);
m_nozzle_label_2->SetBackgroundColour(bg_clr);
m_nozzle_selected_bitmap->SetBackgroundColour(bg_clr);
SetBackgroundColor(bg_clr);
Refresh();
};
};// end of namespace Slic3r::GUI

View File

@@ -1,99 +1,13 @@
//**********************************************************/
/* File: wgtDeviceNozzleRackNozzleItem.h
* Description: One nozzle cell of the H2C hotend rack view.
*
* This header defines only the single-nozzle-cell widget (and the selection event it emits), which
* is the one dependency MultiNozzleSync's HotEndTable needs. The rest of the Device-tab rack panel
* (wgtDeviceNozzleRack / ...Area / ...ToolHead / ...Pos) is not provided here.
* Description: Compatibility header for the single-nozzle-cell widget.
//**********************************************************/
#pragma once
#include "slic3r/GUI/DeviceCore/DevNozzleRack.h"
#include "slic3r/GUI/Widgets/StaticBox.hpp"
#include "slic3r/GUI/Widgets/Label.hpp" // complete type required by the inline SetDisplayIdText below
#include <wx/panel.h>
#include <memory>
// Previous definitions
class ScalableBitmap;
namespace Slic3r
{
struct DevNozzle;
class DevNozzleRack;
};
// Events
wxDECLARE_EVENT(EVT_NOZZLE_RACK_NOZZLE_ITEM_SELECTED, wxCommandEvent);
namespace Slic3r::GUI
{
class wgtDeviceNozzleRackNozzleItem : public StaticBox
{
public:
enum NOZZLE_STATUS
{
NOZZLE_EMPTY,
NOZZLE_NORMAL,
NOZZLE_UNKNOWN,
NOZZLE_ERROR
};
public:
wgtDeviceNozzleRackNozzleItem(wxWindow* parent, int nozzle_id);
public:
void Update(const std::shared_ptr<DevNozzleRack> rack, bool on_rack = true); // on_rack is false means extruder nozzle
int GetNozzleId() const { return m_nozzle_id; }
void SetDisplayIdText(const wxString& text) { m_nozzle_label_id->SetLabel(text);};
void EnableSelect();;
void SetSelected(bool selected);
bool IsSelected() const { return m_is_selected; }
bool IsDisabled() const { return m_is_disabled; }
void SetDisable(bool disabled);
void Rescale();
private:
void CreateGui();
void SetNozzleStatus(NOZZLE_STATUS status, const wxString& str1, const wxString& str2, const std::string& color);
void OnBtnNozzleStatus(wxMouseEvent& evt);
void OnItemSelected(wxMouseEvent& evt);
private:
std::weak_ptr<DevNozzleRack> m_rack;
int m_nozzle_id; // internal id, from 0 to 5
std::string m_filament_color;
NOZZLE_STATUS m_status = NOZZLE_STATUS::NOZZLE_EMPTY;
// select
bool m_is_selected = false;
bool m_enable_select = false;
ScalableBitmap* m_nozzle_selected_image{ nullptr };
wxStaticBitmap* m_nozzle_selected_bitmap{ nullptr };
// enable or disable
bool m_is_disabled = false;
// Images
ScalableBitmap* m_nozzle_normal_image{ nullptr };
ScalableBitmap* m_nozzle_empty_image{ nullptr };
ScalableBitmap* m_nozzle_unknown_image{ nullptr };
ScalableBitmap* m_nozzle_error_image{ nullptr };
// GUI
wxStaticBitmap* m_nozzle_icon{ nullptr };
Label* m_nozzle_label_id { nullptr };
Label* m_nozzle_label_1{ nullptr };
wxStaticBitmap* m_nozzle_status_icon = nullptr;
Label* m_nozzle_label_2{ nullptr };
};
};// end of namespace Slic3r::GUI
// Orca: transitional shim (removed in resync cluster 8)
// The wgtDeviceNozzleRackNozzleItem class and its EVT_NOZZLE_RACK_NOZZLE_ITEM_SELECTED event now
// live inline in wgtDeviceNozzleRack.{h,cpp} (folded back to the reference shape). This header is
// kept only so the not-yet-resynced GUI/Widgets/MultiNozzleSync includer keeps resolving; it is
// deleted once that consumer is resynced to include wgtDeviceNozzleRack.h directly.
#include "wgtDeviceNozzleRack.h"

View File

@@ -1,6 +1,6 @@
//**********************************************************/
/* File: wgtDeviceNozzleRackUpdate.cpp
* Description: The dialog for reading/upgrading the H2C induction-hotend-rack firmware.
* Description: The panel with rack updating
*
* \n class wgtDeviceNozzleRackUpdate
//**********************************************************/
@@ -8,6 +8,7 @@
#include "wgtDeviceNozzleRackUpdate.h"
#include "slic3r/GUI/DeviceCore/DevNozzleSystem.h"
#include "slic3r/GUI/DeviceCore/DevUpgrade.h"
#include "slic3r/GUI/MainFrame.hpp"
#include "slic3r/GUI/GUI_App.hpp"
@@ -103,7 +104,7 @@ void wgtDeviceNozzleRackUprade::CreateGui()
wxPanel* separator = new wxPanel(this);
separator->SetMaxSize(wxSize(-1, FromDIP(1)));
separator->SetMinSize(wxSize(-1, FromDIP(1)));
separator->SetBackgroundColour(wxColour(238, 238, 238));// Orca: grey300 spelled out as a literal RGB, since Orca has no equivalent grey-300 colour macro
separator->SetBackgroundColour(wxColour(238, 238, 238)); // Orca: grey300 as a literal, no equivalent colour macro in Orca
main_sizer->Add(separator, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(12));
}
}
@@ -215,10 +216,13 @@ void wgtDeviceNozzleRackHotendUpdate::CreateGui()
m_colour_box->SetMinSize(WX_DIP_SIZE(16, 16));
m_colour_box->SetCornerRadius(FromDIP(2));
m_colour_box->SetSize(wxSize(FromDIP(16), FromDIP(16)));
// m_colour_box->SetBackgroundColour(*wxRED);
// type_sizer_row_1->AddStretchSpacer();
type_sizer_row_1->Add(m_colour_box, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT);
type_sizer_row_1->AddStretchSpacer(1);
type_sizer_row_1->Add(m_material_label, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(2));
// type_sizer_row_1->AddStretchSpacer();
m_diameter_label = new Label(type_panel);
m_diameter_label->SetFont(Label::Body_12);
@@ -277,19 +281,20 @@ void wgtDeviceNozzleRackHotendUpdate::CreateGui()
m_used_time->SetBackgroundColour(WGT_DEVICE_NOZZLE_RACK_HOTEND_UPDATE_DEFAULT_BG);
m_refresh_icon = new ScalableBitmap(this, "refresh_printer", 12);
// m_in_refreh_icon = new ScalableBitmap(this, "refresh_nozzle", 12);
m_error_icon = new ScalableBitmap(this, "error", 14);
m_status_bitmap = new wxStaticBitmap(this, wxID_ANY, m_refresh_icon->bmp());
m_status_bitmap->Bind(wxEVT_LEFT_UP, &wgtDeviceNozzleRackHotendUpdate::OnStatusIconClick, this);
std::vector<std::string> list{"refresh_nozzle_1", "refresh_nozzle_2", "refresh_nozzle_3", "refresh_nozzle_4"};
// Orca: AnimaIcon has no per-instance size argument (it fixes 25px internally), so no size is
// passed here.
// Orca: AnimaIcon has no per-instance size argument (it fixes the size internally), so none is passed.
m_refreshing_icon = new AnimaIcon(this, wxID_ANY, list, "refresh_nozzle", 100);
m_refreshing_icon->Show(false);
m_status_label = new Label(this);
m_status_label->SetFont(Label::Body_12);
// m_status_label->SetForegroundColour(wxColour("#00AE42"));
content_sizer->Add(info_panel, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(20));
content_sizer->Add(m_used_time, 1, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(20));
@@ -312,7 +317,8 @@ void wgtDeviceNozzleRackHotendUpdate::OnStatusIconClick(wxMouseEvent& event)
m_status_label->SetForegroundColour(wxColour("#A3A3A3"));
m_status_label->SetLabel(_L("Refreshing"));
m_status_bitmap->Show(false);
if(!m_refreshing_icon->IsPlaying())
// m_status_bitmap->Refresh();
if(!m_refreshing_icon->IsPlaying())
{
m_refreshing_icon->Play();
m_refreshing_icon->Show();
@@ -333,17 +339,17 @@ void wgtDeviceNozzleRackHotendUpdate::OnStatusIconClick(wxMouseEvent& event)
dlg.AddButton(wxID_CANCEL, _L("Cancel"), false);
dlg.AddButton(wxID_OK,_L("Jump to the upgrade page"), true);
if (dlg.ShowModal() == wxID_OK)
if (dlg.ShowModal() == wxID_OK)
{
wxGetApp().mainframe->m_monitor->jump_to_Upgrade();
wxCommandEvent evt(wxEVT_NOZZLE_JUMP_UPGRADE, GetId());
evt.SetEventObject(this);
wxWindow* target = GetParent();
if (target)
if (target)
{
target = target->GetParent();
if (target)
if (target)
{
wxPostEvent(target, evt);
}
@@ -370,8 +376,8 @@ void wgtDeviceNozzleRackHotendUpdate::OnBitmapHoverEnter(wxMouseEvent& event)
scaledBmp = m_scaled_nozzle_image;
}
m_hoverFrame = new wxFrame(nullptr, wxID_ANY, "",
wxDefaultPosition, wxDefaultSize,
m_hoverFrame = new wxFrame(nullptr, wxID_ANY, "",
wxDefaultPosition, wxDefaultSize,
wxFRAME_NO_TASKBAR | wxBORDER_NONE | wxTRANSPARENT_WINDOW);
m_hoverFrame->SetBackgroundColour(WGT_DEVICE_NOZZLE_RACK_HOTEND_UPDATE_DEFAULT_BG);
m_hoverFrame->SetSize(scaledW, scaledH);
@@ -506,11 +512,11 @@ void wgtDeviceNozzleRackHotendUpdate::UpdateInfo(const DevNozzle& nozzle)
}
wxString filamentDisplayName{};
for (auto iter = GUI::wxGetApp().preset_bundle->filaments.begin(); iter != GUI::wxGetApp().preset_bundle->filaments.end(); ++iter)
for (auto iter = GUI::wxGetApp().preset_bundle->filaments.begin(); iter != GUI::wxGetApp().preset_bundle->filaments.end(); ++iter)
{
const Preset& filament_preset = *iter;
// const auto& config = filament_preset.config;
if (filament_preset.filament_id == nozzle.GetFilamentId())
if (filament_preset.filament_id == nozzle.GetFilamentId())
{
filamentDisplayName = wxString(filament_preset.alias);
}
@@ -612,7 +618,7 @@ void wgtDeviceNozzleRackHotendUpdate::UpdateInfo(const DevNozzle& nozzle)
m_used_time->Show(true);
m_status_label->Show(true);
m_status_bitmap->Show(true);
m_status_label->SetForegroundColour(wxColour("#009688"));
m_status_label->SetForegroundColour(wxColour("#009688")); // Orca: accent green
m_status_label->SetLabel(_L("Refresh"));
m_status_bitmap->SetBitmap(m_refresh_icon->bmp());
m_status_bitmap->Refresh();

View File

@@ -1,16 +1,8 @@
//**********************************************************/
/* File: wgtDeviceNozzleRackUpdate.h
* Description: The dialog for reading/upgrading the H2C induction-hotend-rack firmware.
* Description: The panel for updating hotends
*
* \n class wgtDeviceNozzleRackUpgradeDlg; // modal "Hotends Info" dialog
* \n class wgtDeviceNozzleRackUprade; // the dialog's content panel (per-nozzle rows)
* \n class wgtDeviceNozzleRackHotendUpdate; // one hotend row (toolhead "R" + rack 1..6)
*
* Opened from the Device-tab rack panel's "Hotends Info" button (wgtDeviceNozzleRack.cpp) and
* reused by the Device->Upgrade page "Hotends on Rack" section
* (UpgradePanel::createNozzleRackWidgets). All device data + MQTT commands live in
* DevNozzleRack / DevNozzleSystem, so this is additive GUI gated behind
* DevNozzleRack::IsSupported() — dormant for every non-rack printer.
* \n class wgtDeviceNozzleRackUpdate
//**********************************************************/
#pragma once
@@ -22,8 +14,6 @@
#include <wx/panel.h>
#include <memory>
#include <unordered_map>
#include <vector>
// Previous definitions
class Button;
@@ -49,7 +39,7 @@ public:
wgtDeviceNozzleRackUpgradeDlg(wxWindow* parent, const std::shared_ptr<DevNozzleRack> rack);
public:
void UpdateRackInfo(const std::shared_ptr<DevNozzleRack> rack);
void UpdateRackInfo(const std::shared_ptr<DevNozzleRack> rack);;
public:
void on_dpi_changed(const wxRect& suggested_rect) override;
@@ -146,6 +136,7 @@ private:
ScalableBitmap* m_scaled_nozzle_image = nullptr;
ScalableBitmap* m_scaled_nozzle_empty_image = nullptr;
ScalableBitmap* m_refresh_icon = nullptr;
// ScalableBitmap* m_in_refreh_icon = nullptr;
AnimaIcon* m_refreshing_icon = nullptr;
ScalableBitmap* m_error_icon = nullptr;
@@ -159,7 +150,7 @@ private:
Label* m_status_label{ nullptr };
Label* m_used_time{ nullptr };
Label* m_diameter_label;
Label* m_flowtype_label;
Label* m_type_label;
@@ -172,4 +163,4 @@ private:
wxDECLARE_EVENT(wxEVT_NOZZLE_JUMP_UPGRADE, wxCommandEvent);
};// end of namespace Slic3r::GUI
};// end of namespace Slic3r::GUI

View File

@@ -6,14 +6,14 @@
//**********************************************************/
#include "wgtDeviceNozzleSelect.h"
#include "wgtDeviceNozzleRackNozzleItem.h" // the nozzle-item widget lives in its own header
#include "wgtDeviceNozzleRack.h"
#include "slic3r/GUI/I18N.hpp"
#include "slic3r/GUI/DeviceTab/wgtMsgBox.h"
#include "slic3r/GUI/Widgets/Label.hpp"
#include "slic3r/GUI/Widgets/Label.hpp" // Orca: explicit Label include
static wxColour s_gray_clr("#B0B0B0");
static wxColour s_hgreen_clr("#009688");
static wxColour s_hgreen_clr("#009688"); // Orca: accent green
static wxColour s_red_clr("#D01B1B");
static std::vector<int> a_nozzle_seq = {16, 18, 20, 17, 19, 21};
@@ -132,7 +132,7 @@ void wgtDeviceNozzleRackSelect::UpdateNozzleInfos(std::shared_ptr<DevNozzleRack>
}
}
static void s_enable_item_if_match(wgtDeviceNozzleRackNozzleItem* item,
static void s_enable_item_if_match(wgtDeviceNozzleRackNozzleItem* item,
const DevNozzle& nozzle_info,
const DevNozzle& selected_nozzle)
{
@@ -214,7 +214,7 @@ void wgtDeviceNozzleRackSelect::UpdatSelectedNozzles(std::shared_ptr<DevNozzleRa
}
}
void wgtDeviceNozzleRackSelect::ClearSelection()
void wgtDeviceNozzleRackSelect::ClearSelection()
{
m_selected_nozzle = DevNozzle();
m_toolhead_nozzle_l->SetSelected(false);

View File

@@ -7,14 +7,11 @@
#pragma once
#include "slic3r/GUI/DeviceCore/DevNozzleSystem.h" // DevNozzle (value member) + DevDefs.h (PrintFromType, MAIN/DEPUTY_EXTRUDER_ID)
#include "slic3r/GUI/DeviceCore/DevNozzleSystem.h"
#include <wx/panel.h>
#include <memory>
#include <optional>
#include <unordered_map>
#include <vector>
// Previous definitions
class Label;
@@ -71,4 +68,4 @@ private:
std::unordered_map<int, wgtDeviceNozzleRackNozzleItem *> m_nozzle_items; // from 16 to 21
};
};// end of namespace Slic3r::GUI
};// end of namespace Slic3r::GUI

View File

@@ -28,4 +28,4 @@ private:
};
}
#endif // WGTMSGBOX_H
#endif // WGTMSGBOX_H

View File

@@ -0,0 +1,163 @@
#include "wgtMsgPanel.h"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/Widgets/Label.hpp"
#include "slic3r/GUI/Widgets/StateColor.hpp"
#include "slic3r/GUI/wxExtensions.hpp"
#include <wx/sizer.h>
namespace Slic3r::GUI
{
// ===== wgtMsgPanelItem ======================================================
wgtMsgPanelItem::wgtMsgPanelItem(wxWindow* parent,
const wxColour& colour,
const wxString& text,
int max_width,
const wxString& wiki_url)
: wxPanel(parent, wxID_ANY)
, m_max_width(max_width)
, m_colour(colour)
, m_text(text)
, m_wiki_url(wiki_url)
{
CreateGui();
}
void wgtMsgPanelItem::CreateGui()
{
SetBackgroundColour(StateColor::darkModeColorFor(wxColour(*wxWHITE)));
wxBoxSizer* main_sizer = new wxBoxSizer(wxHORIZONTAL);
// Color bar on the left
//wxPanel* color_bar = new wxPanel(this, wxID_ANY);
//color_bar->SetMinSize(wxSize(FromDIP(4), -1));
//color_bar->SetBackgroundColour(m_colour);
//main_sizer->Add(color_bar, 0, wxEXPAND | wxRIGHT, FromDIP(4));
// Text and optional wiki link
wxBoxSizer* text_sizer = new wxBoxSizer(wxHORIZONTAL);
m_text_label = new ::Label(this, m_text);
m_text_label->SetFont(::Label::Body_13);
m_text_label->SetBackgroundColour(StateColor::darkModeColorFor(wxColour(*wxWHITE)));
m_text_label->SetForegroundColour(m_colour);
if (!m_wiki_url.IsEmpty()) {
m_text_label->Wrap(m_max_width - FromDIP(20)); // Wrap text to fit within a reasonable width
} else {
m_text_label->Wrap(m_max_width - FromDIP(10)); // Wrap text to fit within a reasonable width
}
text_sizer->Add(m_text_label, 0, wxALIGN_CENTER_VERTICAL);
if (!m_wiki_url.IsEmpty()) {
m_wiki_link = new wxHyperlinkCtrl(this, wxID_ANY, "Wiki->", m_wiki_url);
m_wiki_link->SetNormalColour(m_colour);
m_wiki_link->SetHoverColour(wxColour(0, 0, 200));
m_wiki_link->SetVisitedColour(*wxBLUE);
Bind(wxEVT_HYPERLINK, &wgtMsgPanelItem::OnClickWiki, this, m_wiki_link->GetId());
text_sizer->AddSpacer(FromDIP(4));
text_sizer->Add(m_wiki_link, 0, wxALIGN_CENTER_VERTICAL | wxEXPAND);
}
main_sizer->Add(text_sizer, 0, wxALIGN_LEFT | wxEXPAND);
SetSizer(main_sizer);
Layout();
Fit();
}
void wgtMsgPanelItem::SetColour(const wxColour& colour)
{
m_colour = colour;
// Simple handling: refresh the whole control to redraw the color bar
Refresh();
}
void wgtMsgPanelItem::SetText(const wxString& text)
{
m_text = text;
if (m_text_label) {
m_text_label->SetLabel(m_text);
}
}
void wgtMsgPanelItem::SetWiki(const wxString& wiki_url)
{
m_wiki_url = wiki_url;
if (m_wiki_link) {
m_wiki_link->SetURL(m_wiki_url);
}
}
void wgtMsgPanelItem::OnClickWiki(wxHyperlinkEvent& evt)
{
if (!m_wiki_url.IsEmpty()) {
wxLaunchDefaultBrowser(m_wiki_url);
}
}
// ===== wgtMsgPanel ==========================================================
wgtMsgPanel::wgtMsgPanel(wxWindow* parent)
: wxPanel(parent, wxID_ANY)
{
CreateGui();
}
void wgtMsgPanel::CreateGui()
{
SetBackgroundColour(StateColor::darkModeColorFor(wxColour(*wxWHITE)));
wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
m_label_title = new Label(this, _L("Note:"));
m_label_title->SetFont(::Label::Body_14);
main_sizer->Add(m_label_title, 0, wxEXPAND | wxALL, FromDIP(4));
m_list_sizer = new wxBoxSizer(wxVERTICAL);
main_sizer->Add(m_list_sizer, 1, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(4));
SetSizer(main_sizer);
Layout();
}
void wgtMsgPanel::AddMessage(const wxString& text,
const wxColour& colour,
const wxString& wiki_url)
{
if (!m_list_sizer) return;
auto* label = new ::Label(this, wxString::Format("%d. ", GetMessageCount() + 1));
label->SetFont(::Label::Body_14);
label->SetBackgroundColour(StateColor::darkModeColorFor(wxColour(*wxWHITE)));
label->SetForegroundColour(colour);
auto* item = new wgtMsgPanelItem(this, colour, text, GetSize().GetWidth(), wiki_url);
auto h_sizer = new wxBoxSizer(wxHORIZONTAL);
h_sizer->Add(label, 0, wxEXPAND);
h_sizer->Add(item, 0, wxEXPAND);
m_list_sizer->Add(h_sizer, 0, wxEXPAND | wxTOP | wxBOTTOM, FromDIP(2));
Layout();
}
void wgtMsgPanel::Clear()
{
if (!m_list_sizer) return;
Freeze();
wxSizerItemList children = m_list_sizer->GetChildren();
for (auto it = children.begin(); it != children.end(); ++it) {
if (wxWindow* win = (*it)->GetWindow()) {
win->Destroy();
}
}
m_list_sizer->Clear(true);
Thaw();
Layout();
}
} // namespace Slic3r::GUI

View File

@@ -0,0 +1,69 @@
#pragma once
#include <wx/panel.h>
#include <wx/hyperlink.h>
#include <wx/sizer.h>
#include <string>
class Label;
namespace Slic3r
{
namespace GUI
{
// Single message item, including color, text and optional wiki link
class wgtMsgPanelItem : public wxPanel
{
public:
wgtMsgPanelItem(wxWindow* parent,
const wxColour& colour,
const wxString& text,
int max_width,
const wxString& wiki_url = wxEmptyString);
void SetColour(const wxColour& colour);
void SetText(const wxString& text);
void SetWiki(const wxString& wiki_url);
private:
void CreateGui();
void OnClickWiki(wxHyperlinkEvent& evt);
private:
int m_max_width;
wxColour m_colour;
wxString m_text;
wxString m_wiki_url;
Label* m_text_label{ nullptr };
wxHyperlinkCtrl* m_wiki_link{ nullptr };
};
// Message panel based on wxWidget, shows wgtMsgPanelItem in a list
class wgtMsgPanel : public wxPanel
{
public:
explicit wgtMsgPanel(wxWindow* parent);
~wgtMsgPanel() override = default;
// Add a message (preferred API)
void AddMessage(const wxString& text,
const wxColour& colour,
const wxString& wiki_url = wxEmptyString);
void Clear();
// Const API
int GetMessageCount() const { return m_list_sizer ? m_list_sizer->GetItemCount() : 0; }
private:
void CreateGui();
private:
Label* m_label_title{ nullptr };
wxBoxSizer* m_list_sizer{ nullptr };
};
}
} // namespace Slic3r::GUI