diff --git a/src/slic3r/GUI/DeviceTab/CMakeLists.txt b/src/slic3r/GUI/DeviceTab/CMakeLists.txt index 1438bf139b..cf2a058e7e 100644 --- a/src/slic3r/GUI/DeviceTab/CMakeLists.txt +++ b/src/slic3r/GUI/DeviceTab/CMakeLists.txt @@ -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) \ No newline at end of file diff --git a/src/slic3r/GUI/DeviceTab/uiAMSBestPositionPopup.cpp b/src/slic3r/GUI/DeviceTab/uiAMSBestPositionPopup.cpp new file mode 100644 index 0000000000..b749a42c84 --- /dev/null +++ b/src/slic3r/GUI/DeviceTab/uiAMSBestPositionPopup.cpp @@ -0,0 +1,1046 @@ +//**********************************************************/ +/* File: uiAMSBestPositionPopup.hpp +* Description: The popup with suggest best ams position +* +//**********************************************************/ + +#include "uiAMSBestPositionPopup.hpp" + +#include "slic3r/Utils/WxFontUtils.hpp" + +#include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/I18N.hpp" +#include "slic3r/GUI/Widgets/StateColor.hpp" + + +#include +#include + +namespace Slic3r { namespace GUI { + +wxDEFINE_EVENT(wxEVT_REFRESH_DATA, wxCommandEvent); + +UiStyledAMSPanel::UiStyledAMSPanel(wxWindow* parent, + wxWindowID id, + const wxPoint& pos, + const wxSize& size, + const wxColour& borderColor, + const wxColour& bgColor, + bool borderDashed, + wxString name, + bool isTop) + : wxPanel(parent, id, pos, size), + m_borderDashed(borderDashed), + m_borderColor(borderColor), + m_bgColor(bgColor), + m_name(name), + m_isTop(isTop) +{ + SetDoubleBuffered(true); + SetBackgroundStyle(wxBG_STYLE_PAINT); + + Bind(wxEVT_PAINT, &UiStyledAMSPanel::OnPaint, this); +} + + +void UiStyledAMSPanel::OnPaint(wxPaintEvent& event) +{ + + wxPaintDC dc(this); + dc.Clear(); + m_borderWidth = FromDIP(2); + m_radius = FromDIP(5); + wxSize clientSize = GetClientSize(); + int width = clientSize.GetWidth(); + int height = clientSize.GetHeight(); + + + dc.SetBrush(wxBrush(m_bgColor)); + dc.SetPen(wxPen(*wxTRANSPARENT_PEN)); + dc.DrawRectangle(wxRect(0, 0, width, height)); + // dc.DrawRoundedRectangle(wxRect(0, 0, width, height), m_radius); + + + wxPen borderPen; + if (m_borderDashed) + { + + borderPen = wxPen(m_borderColor, m_borderWidth, wxPENSTYLE_SHORT_DASH); + } + else + { + + borderPen = wxPen(m_borderColor, m_borderWidth, wxPENSTYLE_SOLID); + } + dc.SetPen(borderPen); + + int offset = m_borderWidth; + dc.DrawRoundedRectangle(wxRect(offset, offset, width - 2 * offset, height - 2 * offset), m_radius); + + + int labelX = offset; + int labelY = m_isTop ? offset : (height - offset - FromDIP(27)); // true=顶部, false=底部 + + + wxRect labelRect(labelX, labelY, width - 2 * offset, FromDIP(10)); + wxRect labelRoundRect(labelX, labelY, width - 2 * offset, FromDIP(27)); + dc.SetBrush(wxBrush(m_borderColor)); + dc.SetPen(wxPen(*wxTRANSPARENT_PEN)); + dc.DrawRoundedRectangle(labelRoundRect, m_radius); + dc.DrawRectangle(labelRect); + + + wxFont font12(12, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL); + dc.SetTextForeground(wxColour("#858585")); + // dc.SetFont(font12); + dc.SetFont(::Label::Body_12); + wxCoord textWidth, textHeight; + dc.GetTextExtent(m_name, &textWidth, &textHeight); + + int textPosX = labelX + 4 * offset; + int textPosY = labelY + (FromDIP(27) -textHeight ) / 2; + dc.DrawText(m_name, textPosX, textPosY); + + + // event.Skip(); +} + +UiStyledSwitchPanel::UiStyledSwitchPanel(wxWindow* parent, + wxWindowID id = wxID_ANY, + const wxPoint& pos = wxDefaultPosition, + const wxSize& size = wxDefaultSize, + const wxColour& borderColor = wxColour("#EEEEEE"), + const wxColour& bgColor = wxColour("#FFFFFF"), + bool borderDashed = true, + int borderWidth = 2, + int radius = 1, + bool isTop = true) + : wxPanel(parent, id, pos, size), + m_borderDashed(borderDashed), + m_borderWidth(borderWidth), + m_radius(radius), + m_borderColor(borderColor), + m_bgColor(bgColor), + m_isTop(isTop) +{ + + SetDoubleBuffered(true); + SetBackgroundStyle(wxBG_STYLE_PAINT); + + Bind(wxEVT_PAINT, &UiStyledSwitchPanel::OnPaint, this); + + + + m_mainSizer = new wxBoxSizer(wxVERTICAL); + + + m_mainSizer->AddSpacer(labelHeight); + + + m_contentSizer = new wxBoxSizer(wxVERTICAL); + + + m_splitSizer = new wxBoxSizer(wxHORIZONTAL); + + + m_leftSizer = new wxBoxSizer(wxVERTICAL); + m_splitSizer->Add(m_leftSizer, 1, wxEXPAND | wxALL, 0); + + + m_rightSizer = new wxBoxSizer(wxVERTICAL); + m_splitSizer->Add(m_rightSizer, 1, wxEXPAND | wxALL, 0); + + + m_contentSizer->Add(m_splitSizer, 1, wxEXPAND | wxALL, 0); + + + m_mainSizer->Add(m_contentSizer, 1, wxEXPAND | wxALL, FromDIP(1)); + + SetSizer(m_mainSizer); +} + +void UiStyledSwitchPanel::Clear(bool deleteWindows) +{ + if (m_leftSizer) + { + m_leftSizer->Clear(deleteWindows); + m_leftSizer->Layout(); + } + + if (m_rightSizer) + { + m_rightSizer->Clear(deleteWindows); + m_rightSizer->Layout(); + } + + LayoutAndFit(); + + Refresh(); + Update(); +} + +void UiStyledSwitchPanel::AddToLeft(wxWindow* window, int proportion, int flag, int border) +{ + m_leftSizer->Add(window, proportion, flag, border); + Layout(); +} + +void UiStyledSwitchPanel::AddToRight(wxWindow* window, int proportion, int flag, int border) +{ + m_rightSizer->Add(window, proportion, flag, border); + Layout(); +} + + +void UiStyledSwitchPanel::OnPaint(wxPaintEvent& event) +{ + wxPaintDC dc(this); + dc.Clear(); + wxSize clientSize = GetClientSize(); + int width = clientSize.GetWidth(); + int height = clientSize.GetHeight() - FromDIP(90); // for selector space + + dc.SetBrush(wxBrush(m_bgColor)); + dc.SetPen(wxPen(*wxTRANSPARENT_PEN)); + dc.DrawRectangle(wxRect(0, 0, width, height + FromDIP(90))); + + dc.SetBrush(wxBrush(m_bgColor)); + dc.SetPen(wxPen(*wxTRANSPARENT_PEN)); + dc.DrawRoundedRectangle(wxRect(0, 0, width, height), m_radius); + + wxPen borderPen; + if (m_borderDashed) + { + borderPen = wxPen(wxColour("#ACACAC"), m_borderWidth, wxPENSTYLE_SHORT_DASH); + } + else + { + borderPen = wxPen(wxColour("#ACACAC"), m_borderWidth, wxPENSTYLE_SOLID); + } + dc.SetBrush(wxBrush(*wxTRANSPARENT_BRUSH)); + dc.SetPen(borderPen); + // dc.DrawRoundedRectangle(wxRect(offset, offset, width - 2 * offset, height - 2 * offset), m_radius); + dc.DrawRoundedRectangle(wxRect(FromDIP(1), FromDIP(1), width - FromDIP(1), height), m_radius); + + int offset = m_borderWidth; + int labelX = offset; + int labelY = m_isTop ? offset : (height - offset - labelHeight); // true=top, false=bottom + labelX += FromDIP(1); + labelY += FromDIP(1); + + wxRect labelRoundRect(labelX, labelY, width - 2 * offset - FromDIP(2), FromDIP(labelHeight)); + wxRect labelRect(labelX, labelY + m_radius, width - 2 * offset - FromDIP(2), FromDIP(labelHeight) - m_radius); + dc.SetBrush(wxBrush(m_borderColor)); + dc.SetPen(wxPen(*wxTRANSPARENT_PEN)); + dc.DrawRoundedRectangle(labelRoundRect, m_radius); + dc.DrawRectangle(labelRect); + + // dc.SetPen(wxPen(m_borderColor, m_borderWidth, wxPENSTYLE_SHORT_DASH)); + dc.SetPen(wxPen(wxColour("#ACACAC"), m_borderWidth, wxPENSTYLE_SHORT_DASH)); + dc.SetBrush(*wxTRANSPARENT_BRUSH); + dc.DrawLine(width / 2, offset, width / 2, height - offset); + + wxFont font12(12, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL); + dc.SetTextForeground(wxColour("#000000")); + // dc.SetFont(font12); + dc.SetFont(::Label::Body_14); + wxCoord textWidth, textHeight; + dc.GetTextExtent(_L("Filament Inlet A"), &textWidth, &textHeight); + + int aTextPosX = labelX + 4 * offset; + int aTextPosY = labelY + (labelHeight - textHeight ) / 2; + dc.DrawText(_L("Filament Inlet A"), aTextPosX, aTextPosY); + + int bTextPosX = labelX + 4 * offset + width / 2; + int bTextPosY = aTextPosY; + dc.DrawText(_L("Filament Inlet B"), bTextPosX, bTextPosY); + + height += FromDIP(90); //total height + + dc.SetBrush(wxBrush(m_bgColor)); + dc.SetPen(wxPen(m_borderColor, m_borderWidth, wxPENSTYLE_SOLID)); + wxCoord selTextWidth, selTextHeight; + dc.GetTextExtent(_L("Filament Track Switch"), &selTextWidth, &selTextHeight); + + int selWidth = selTextWidth + FromDIP(20); + int selHeight = FromDIP(44); + int selBaseX = (width - selWidth) / 2; + int selBaseY = height - FromDIP(56); + dc.DrawRoundedRectangle(wxRect(selBaseX, selBaseY, selWidth, selHeight), FromDIP(4)); + // wxFont font(12, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL); + dc.SetTextForeground(wxColour("#262E30")); + // dc.SetFont(font); + int selTextPosX = selBaseX + (selWidth - selTextWidth) / 2; + int selTextPosY = selBaseY + (selHeight - selTextHeight) / 2; + dc.DrawText(_L("Filament Track Switch"), selTextPosX, selTextPosY); + + //draw Horizontal + int hLBaseX = (width - FromDIP(184)) / 2; + int hLBaseY = selBaseY - FromDIP(17); + int hWidth = FromDIP(76); + int hHeight = FromDIP(5); + int hGap = FromDIP(32); + wxRect HL(hLBaseX, hLBaseY + FromDIP(1), hWidth, hHeight); + wxRect HR(hLBaseX + hWidth + hGap, hLBaseY + FromDIP(1), hWidth, hHeight); + dc.SetBrush(wxBrush(wxColour("#D9D9D9"))); + dc.SetPen(wxPen(*wxTRANSPARENT_PEN)); + dc.DrawRectangle(HL); + dc.DrawRectangle(HR); + //draw vertical + int vLTopBaseX = hLBaseX; + int vLTopBaseY = height - FromDIP(90); + int vWidht = FromDIP(5); + int vHeight = FromDIP(23); + // vtopL vtopR + dc.DrawRectangle(wxRect(vLTopBaseX, vLTopBaseY, vWidht, vHeight)); + dc.DrawRectangle(wxRect(vLTopBaseX + FromDIP(184) - vWidht, vLTopBaseY, vWidht, vHeight)); + + // vmidL vmidR + dc.DrawRectangle(wxRect(vLTopBaseX + hWidth - vWidht, vLTopBaseY + vHeight - vWidht, vWidht, FromDIP(17))); + dc.DrawRectangle(wxRect(vLTopBaseX + hWidth + hGap, vLTopBaseY + vHeight - vWidht, vWidht, FromDIP(17))); + + //vbotL vbotR + dc.DrawRectangle(wxRect(vLTopBaseX + hWidth - vWidht, height - FromDIP(12), vWidht, FromDIP(12))); + dc.DrawRectangle(wxRect(vLTopBaseX + hWidth + hGap, height - FromDIP(12), vWidht, FromDIP(12))); + // event.Skip(); +} + +UiAMSSlot::UiAMSSlot(wxWindow* parent, + const std::vector& bgColours, + const wxString& text, + DataStatusType status, + wxWindowID id, + const wxPoint& pos, + const wxSize& size, + double colourFactor, + double scaleFactor) + : wxPanel(parent, id, pos, wxDefaultSize, wxBORDER_NONE), + m_bgColours(bgColours), m_text(text), m_status(status), m_size(size), m_colourFactor(colourFactor), m_scaleFactor(scaleFactor) +{ + SetDoubleBuffered(true); + SetBackgroundStyle(wxBG_STYLE_PAINT); + m_ams_slot_readonly = new ScalableBitmap(this, "ams_readonly", 10 * m_scaleFactor); // Orca: reuse the existing readonly glyph + + m_size = wxSize( + static_cast(size.GetWidth() * scaleFactor), + static_cast(size.GetHeight() * scaleFactor) + ); + SetMinSize(m_size); + // SetMaxSize(m_size); + Bind(wxEVT_PAINT, &UiAMSSlot::OnPaint, this); +} + +void UiAMSSlot::DrawRectangle(wxPaintDC& dc, const wxSize& cli) +{ + + if (!m_bgColours.empty()) + { + auto baseX = (cli.x - rectangleW) / 2; + auto step = rectangleW / m_bgColours.size(); + + for (int i = 0; i < m_bgColours.size(); i++) + { + baseX += step * i; + wxRect rr(baseX, (cli.y - rectangleH) / 2, step, rectangleH); + + dc.SetBrush(wxBrush(LightenColour(m_bgColours[i]))); + dc.SetPen(wxPen(LightenColour(m_bgColours[i]))); + dc.DrawRectangle(rr); + } + } + + wxString line1 = m_text.BeforeFirst('\n'); + wxString line2 = m_text.AfterFirst ('\n'); + wxFont font12 = ::Label::Body_12; + wxFont font14 = ::Label::Body_14; + int newSizeFont12 = static_cast(font12.GetPointSize() * m_scaleFactor); + int newSizeFont14 = static_cast(font14.GetPointSize() * m_scaleFactor); + newSizeFont12 = std::max(1, newSizeFont12); + newSizeFont14 = std::max(1, newSizeFont14); + font12.SetPointSize(newSizeFont12); + font14.SetPointSize(newSizeFont14); + wxColour textColour = wxColour("#000000"); + if (!m_bgColours.empty()) + { + textColour = IsDark(m_bgColours.front()) ? wxColour("#FFFFFF") + : wxColour("#000000"); + } + + dc.SetTextForeground(textColour); + + dc.SetFont(font12); + // dc.SetFont(::Label::Body_12); + wxCoord w1, h1; + dc.GetTextExtent(_L(line1), &w1, &h1); + + dc.SetFont(font14); + // dc.SetFont(::Label::Body_14); + wxCoord w2, h2; + dc.GetTextExtent(_L(line2), &w2, &h2); + + int maxLine2Width = rectangleW - FromDIP(2); + if (w2 > maxLine2Width && newSizeFont14 > 1) + { + int currentFontSize = newSizeFont14; + while (currentFontSize > 1) + { + currentFontSize--; + font14.SetPointSize(currentFontSize); + dc.SetFont(font14); + dc.GetTextExtent(_L(line2), &w2, &h2); + if (w2 <= maxLine2Width) + { + break; + } + } + } + + int topGap = static_cast(FromDIP(5) * m_scaleFactor); + int textGap = static_cast(FromDIP(5) * m_scaleFactor); + int bmpH = m_ams_slot_readonly->GetBmpHeight(); + int bmpW = m_ams_slot_readonly->GetBmpWidth(); + int baseY = (cli.y - h1 - h2 - topGap - textGap - bmpH - textGap) / 2; + + dc.SetFont(font12); + // dc.SetFont(::Label::Body_12); + dc.DrawText(_L(line1), (cli.x - w1) / 2, baseY + topGap); + dc.SetFont(font14); + // dc.SetFont(::Label::Body_14); + dc.DrawText(_L(line2), (cli.x - w2) / 2, baseY + topGap + h1 + textGap); + + //draw svg + if (line2 != "Empty") + { + dc.SetPen(*wxTRANSPARENT_PEN); + dc.SetBrush(*wxWHITE); + dc.DrawBitmap(m_ams_slot_readonly->bmp(), wxPoint((cli.x - bmpW) / 2, baseY + topGap + h1 + textGap + h2 + textGap)); + } +} + +void UiAMSSlot::DrawLine(wxPaintDC& dc, const wxSize& cli) +{ + // no scaled + int bgRemainPointX = (cli.x - FromDIP(30)) / 2; + int bgRemainPointY = 0; + int bgRemainH = FromDIP(4); + int bgRemainW = FromDIP(30); + int forRemainW = FromDIP(30 - 8); + //remain status bar + dc.SetBrush(wxBrush(wxColour("#c2c2c2"))); + dc.SetPen(wxPen(wxColour("#c2c2c2"))); + dc.DrawRoundedRectangle(wxRect(bgRemainPointX, 0, bgRemainW, bgRemainH), FromDIP(3)); + + if (!m_bgColours.empty()) { + if (m_bgColours.size() == 1) { + dc.SetBrush(wxBrush(LightenColour(m_bgColours[0]))); + dc.SetPen(wxPen(LightenColour(m_bgColours[0]))); + dc.DrawRoundedRectangle(wxRect(bgRemainPointX, bgRemainPointY, forRemainW, bgRemainH), FromDIP(3)); + } else { + auto step = bgRemainH / m_bgColours.size(); + for (int i = 0; i < m_bgColours.size(); i++) { + dc.SetBrush(wxBrush(LightenColour(m_bgColours[i]))); + dc.SetPen(wxPen(LightenColour(m_bgColours[i]))); + dc.DrawRoundedRectangle(wxRect(bgRemainPointX, bgRemainPointY + step * i, forRemainW, bgRemainH / m_bgColours.size()), FromDIP(3)); + } + } + } + + if (m_status == DataStatusType::ADJUST) + { + dc.SetBrush(wxColour("#ff6f00")); + dc.SetPen(wxPen(wxColour("#ff6f00"))); + //top + int topGap = static_cast(FromDIP(8) * m_scaleFactor); + int bottomGap = static_cast(FromDIP(4) * m_scaleFactor); + int tbHeight = static_cast(FromDIP(3) * m_scaleFactor); + dc.DrawRectangle(wxRect((cli.x - rectangleW) / 2, (cli.y - rectangleH - topGap) / 2, rectangleW, tbHeight)); + //bottom + dc.DrawRectangle(wxRect((cli.x - rectangleW) / 2, (cli.y - rectangleH - topGap) / 2 + rectangleH + bottomGap, rectangleW, tbHeight)); + + // left + int radius = static_cast(FromDIP(4) * m_scaleFactor); + int width = static_cast(FromDIP(10) * m_scaleFactor); + int height = static_cast(cli.y - FromDIP(3)); + dc.DrawRoundedRectangle(wxRect(0, FromDIP(static_cast(3 * m_scaleFactor)), width, height), radius); + //right + // dc.DrawRectangle(wxRect(55, 7, 3, 78)); + dc.DrawRoundedRectangle(wxRect(static_cast(cli.x - FromDIP(10) * m_scaleFactor), + static_cast(FromDIP(3) * m_scaleFactor), + static_cast(FromDIP(10) * m_scaleFactor), + static_cast(cli.y - FromDIP(3))), radius); + } + + if (m_status != DataStatusType::UNMATCHED) + { + dc.SetBrush(wxColour(144, 144, 144)); + dc.SetPen(wxPen(wxColour(144, 144, 144))); + dc.DrawRoundedRectangle(wxRect(FromDIP(static_cast(3 * m_scaleFactor)), + FromDIP(static_cast(6 * m_scaleFactor)), + FromDIP(static_cast(4 * m_scaleFactor)), + FromDIP(static_cast(81 * m_scaleFactor))), FromDIP(static_cast(2 * m_scaleFactor))); + dc.DrawRoundedRectangle(wxRect(FromDIP(static_cast(51 * m_scaleFactor)), + FromDIP(static_cast(6 * m_scaleFactor)), + FromDIP(static_cast(4 * m_scaleFactor)), + FromDIP(static_cast(81 * m_scaleFactor))), FromDIP(static_cast(2 * m_scaleFactor))); + } + +} + +wxColour UiAMSSlot::LightenColour(const wxColour& original) +{ + if (std::abs(m_colourFactor - 1.0) < 1e-3) + { + return original; + } + m_colourFactor = std::clamp(m_colourFactor, 0.0, 1.0); + + int r = original.Red(); + int g = original.Green(); + int b = original.Blue(); + int a = original.Alpha(); + + r = static_cast(r + (255 - r) * (1 - m_colourFactor)); + g = static_cast(g + (255 - g) * (1 - m_colourFactor)); + b = static_cast(b + (255 - b) * (1 - m_colourFactor)); + + r = std::min(r, 255); + g = std::min(g, 255); + b = std::min(b, 255); + + return wxColour(r, g, b, a); +} + +void UiAMSSlot::OnPaint(wxPaintEvent&) +{ + wxPaintDC dc(this); + dc.Clear(); + //draw all background + rectangleW = FromDIP(m_rectangleW); + rectangleH = FromDIP(m_rectangleH); + rectangleW = static_cast(rectangleW * m_scaleFactor); + rectangleH = static_cast(rectangleH * m_scaleFactor); + wxSize cli = GetClientSize(); + dc.SetBrush(wxBrush(wxColour("#ffffff"))); + dc.SetPen(wxPen(wxColour("#ffffff"))); + if (m_scaleFactor < 1.0) + { + dc.SetBrush(wxBrush(wxColour("#F8F8F8"))); + dc.SetPen(wxPen(wxColour("#F8F8F8"))); + } + dc.DrawRectangle(wxRect(0, 0, cli.x, cli.y)); + DrawLine(dc, cli); + DrawRectangle(dc, cli); +} + + +UiAMS::UiAMS( wxWindow* parent, + const std::vector& amsInfo, + wxWindowID id = wxID_ANY, + const wxPoint& pos = wxDefaultPosition, + const wxSize& minSize = wxDefaultSize) + : UiStyledAMSPanel(parent, id, pos, wxDefaultSize, wxColour("#dbdbdb"), wxColour("#ffffff"), false, amsInfo.front().amsName), + + m_amsInfo(amsInfo), m_minSize(minSize) +{ + if (m_amsInfo.size() == 1) + { + wxSize size(FromDIP(92), FromDIP(134)); + SetMinSize(size); + m_minSize = size; + } + else // 4 slot + { + wxSize size(FromDIP(262), FromDIP(134)); + SetMinSize(size); + m_minSize = size; + } + init(); +} + +void UiAMS::init() +{ + // int minW = m_minSize.GetWidth() * m_amsInfo.size(); + int minW = m_minSize.GetWidth(); + wxBoxSizer* mainSizer = new wxBoxSizer(wxVERTICAL); + + //ams model + // wxPanel* amsPanel = new wxPanel(this, wxID_ANY, wxDefaultPosition); + + wxBoxSizer* amsPanelSizer = new wxBoxSizer(wxHORIZONTAL); + wxSize wxSlotSize = wxSize(FromDIP(58), FromDIP(90)); + int allEmptySpacer = minW - m_amsInfo.size() * wxSlotSize.GetWidth(); + int gap = (m_amsInfo.size() == 1 ? allEmptySpacer / 2 : allEmptySpacer / ( 2 * m_amsInfo.size() - 1)); + for (const auto& slot : m_amsInfo) + { + amsPanelSizer->AddStretchSpacer(gap); + amsPanelSizer->Add(new UiAMSSlot(this, slot.colours, slot.name, slot.status, wxID_ANY, wxDefaultPosition, wxSlotSize, slot.colourFactor, slot.scaleFactor)); + amsPanelSizer->AddStretchSpacer(gap); + } + // amsPanel->SetSizer(amsPanelSizer); + // mainSizer->AddStretchSpacer(); + mainSizer->Add(amsPanelSizer, 0, wxALIGN_CENTER | wxEXPAND | wxALL, FromDIP(5)); + // mainSizer->AddStretchSpacer(); + SetSizer(mainSizer); +} + +ReselectMachineDialog::ReselectMachineDialog(wxWindow* parent) + : wxDialog(parent, wxID_ANY, _L("Suggested rearrangement"), + wxDefaultPosition, wxDefaultSize, wxCAPTION | wxCLOSE_BOX) +{ + SetBackgroundColour(wxColour("#FFFFFF")); + // SetSize(wxSize(FromDIP(630), FromDIP(719))); + SetMinSize(wxSize(FromDIP(630), -1)); + SetMaxSize(wxSize(FromDIP(630), -1)); + + mainSizer = new wxBoxSizer(wxVERTICAL); + + + textPanel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize); + textSizer = new wxBoxSizer(wxVERTICAL); + suggestText = new Label(textPanel, wxEmptyString); + + linkwiki = new Label(textPanel, _L("How to save time?→")); + linkwiki->SetForegroundColour(wxColour("#009688")); + linkwiki->SetBackgroundColour(wxColour("#FFFFFF")); + linkwiki->SetFont(Label::Body_14); + // Orca: no vendor wiki page for this feature; hide the placeholder link and drop the external URL. + linkwiki->Hide(); + textSizer->Add(suggestText, 0, wxALIGN_LEFT | wxTOP, FromDIP(5)); + textSizer->Add(linkwiki, 0, wxALIGN_LEFT | wxTOP, FromDIP(2)); + textPanel->SetSizer(textSizer); + + summaryText = new Label(this, wxEmptyString); + summaryText->SetFont(Label::Body_14); + // summaryText->SetFont(wxGetApp().normal_font()); + + filamentSwitch = new UiStyledSwitchPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, //wxSize(FromDIP(571), FromDIP(439)), + wxColour("#EEEEEE"), wxColour("#ffffff"), true, FromDIP(1), FromDIP(5), true); + + + filamentTips = new wxStaticText(this, wxID_ANY, _L("Filament Status:")); + filamentTips->SetFont(wxGetApp().normal_font()); + + // statusBar = new UiStatusContainer(this); + statusBar = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize); + statusBar->SetBackgroundColour(wxColour("#F8F8F8")); + statusBar->SetMinSize(wxSize(FromDIP(570), -1)); + wxBoxSizer* statusBarSizer = new wxBoxSizer(wxHORIZONTAL); + statusBar->SetSizer(statusBarSizer); + + wxBoxSizer* adjustGroupSizer = new wxBoxSizer(wxHORIZONTAL); + UiAMSSlot* amsSlotAdjust= new UiAMSSlot(statusBar, colourAdjust, wxString("Ax\nPLA"), DataStatusType::ADJUST, wxID_ANY, wxDefaultPosition, wxSize(FromDIP(58), FromDIP(90)), 1.0, 1.0); + wxStaticText* adjustLabel = new wxStaticText(statusBar, wxID_ANY, _L("Position adjustment required")); + adjustLabel->SetFont(wxGetApp().normal_font()); + adjustLabel->SetForegroundColour(wxColour("#6B6B6B")); + + adjustGroupSizer->Add(amsSlotAdjust, 0, wxALIGN_CENTER); + adjustGroupSizer->Add(adjustLabel, 0, wxALIGN_CENTER | wxLEFT, FromDIP(8)); + + wxBoxSizer* okGroupSizer = new wxBoxSizer(wxHORIZONTAL); + UiAMSSlot* amsSlotOK= new UiAMSSlot(statusBar, colourOK, wxString("Ax\nPLA"), DataStatusType::OK, wxID_ANY, wxDefaultPosition, wxSize(FromDIP(58), FromDIP(90)), 1.0, 1.0); + wxStaticText* okLabel = new wxStaticText(statusBar, wxID_ANY, _CTX(L_CONTEXT("OK", "FilamentTrack"), "FilamentTrack")); + okLabel->SetFont(wxGetApp().normal_font()); + okLabel->SetForegroundColour(wxColour("#6B6B6B")); + + okGroupSizer->Add(amsSlotOK, 0, wxALIGN_CENTER); + okGroupSizer->Add(okLabel, 0, wxALIGN_CENTER | wxLEFT, FromDIP(8)); + + wxBoxSizer* unusedGroupSizer = new wxBoxSizer(wxHORIZONTAL); + UiAMSSlot* amsSlotUnused= new UiAMSSlot(statusBar, colourUnused, wxString("Ax\nPLA"), DataStatusType::UNMATCHED, wxID_ANY, wxDefaultPosition, wxSize(FromDIP(58), FromDIP(90)), 0.2, 1.0); + wxStaticText* unusedLabel = new wxStaticText(statusBar, wxID_ANY, _L("Unused")); + unusedLabel->SetFont(wxGetApp().normal_font()); + unusedLabel->SetForegroundColour(wxColour("#6B6B6B")); + + unusedGroupSizer->Add(amsSlotUnused, 0, wxALIGN_CENTER); + unusedGroupSizer->Add(unusedLabel, 0, wxALIGN_CENTER | wxLEFT, FromDIP(8)); + + statusBarSizer->Add(adjustGroupSizer, 1, wxALIGN_CENTER | wxLEFT, FromDIP(10)); + statusBarSizer->Add(okGroupSizer, 1, wxALIGN_CENTER | wxLEFT, FromDIP(30)); + statusBarSizer->Add(unusedGroupSizer, 1, wxALIGN_CENTER | wxLEFT, FromDIP(40)); + + statusBarSizer->AddStretchSpacer(1); + + btnSizer = new wxBoxSizer(wxHORIZONTAL); + m_buttonRefresh = new Button(this, _L("Refresh")); + m_buttonRefresh->SetMinSize(wxSize(FromDIP(80), FromDIP(32))); + m_buttonRefresh->SetMaxSize(wxSize(FromDIP(80), FromDIP(32))); + m_buttonRefresh->Bind(wxEVT_BUTTON, &ReselectMachineDialog::OnRefreshButton, this); + m_buttonRefresh->Hide(); + m_buttonClose = new Button(this, _L("Close")); + m_buttonClose->SetMinSize(wxSize(FromDIP(80), FromDIP(32))); + m_buttonClose->SetMaxSize(wxSize(FromDIP(80), FromDIP(32))); + m_buttonClose->SetBackgroundColor(wxColour("#009688")); // Orca: accent green + m_buttonClose->Bind(wxEVT_BUTTON, [this](wxCommandEvent &e){ + this->Close(); + }); + btnSizer->Add(m_buttonRefresh, 0, wxALIGN_LEFT); + btnSizer->Add(m_buttonClose, 0, wxALIGN_LEFT | wxLEFT, FromDIP(10)); + //btn + + mainSizer->Add(0, FromDIP(20)); + mainSizer->Add(textPanel, 0, wxALIGN_LEFT | wxLEFT, FromDIP(26)); + mainSizer->Add(0, FromDIP(12)); + mainSizer->Add(summaryText, 0, wxALIGN_LEFT | wxLEFT | wxEXPAND, FromDIP(30)); + mainSizer->Add(0, FromDIP(15)); + mainSizer->Add(filamentSwitch, 0, wxALIGN_LEFT | wxLEFT, FromDIP(26)); + // mainSizer->Add(m_bitmapSelectMachine, 0, wxALIGN_LEFT | wxLEFT, FromDIP(214)); + mainSizer->Add(0, FromDIP(8)); + mainSizer->Add(filamentTips, 0, wxALIGN_LEFT | wxLEFT, FromDIP(23)); + mainSizer->Add(0, FromDIP(7)); + mainSizer->Add(statusBar, 0, wxALIGN_LEFT | wxLEFT, FromDIP(23)); + mainSizer->Add(btnSizer, 0, wxALIGN_RIGHT | wxALL, FromDIP(23)); + SetSizer(mainSizer); + + this->Bind(wxEVT_DPI_CHANGED, [this](wxDPIChangedEvent& evt) { + m_buttonRefresh->SetMinSize(wxSize(FromDIP(80), FromDIP(32))); + m_buttonRefresh->SetMaxSize(wxSize(FromDIP(80), FromDIP(32))); + m_buttonClose->SetMinSize(wxSize(FromDIP(80), FromDIP(32))); + m_buttonClose->SetMaxSize(wxSize(FromDIP(80), FromDIP(32))); + Layout(); + Refresh(); + evt.Skip(); + }); + + Fit(); + Layout(); + Centre(); +} + +void ReselectMachineDialog::Update(MachineObject* obj, const std::map& best_pos_map, const std::vector& ams_mapping, wxString save_time) +{ + + if (suggestText) + { + suggestText->SetLabel(wxString::Format(_L("Based on the diagram below, rearrange the filaments on the printer for optimal results to save approximately %s."), (save_time.empty() ? "0s" : save_time))); + suggestText->Wrap(FromDIP(600)); + suggestText->SetBackgroundColour(wxColour("#FFFFFF")); + suggestText->SetFont(Label::Body_14); + suggestText->Show(); + } + + if (summaryText) + { + wxString summaryInfo; + if (!save_time.empty()) { + summaryInfo = wxString::Format(_L("Summary:")); + + for (const auto& pair : best_pos_map) { + for (auto fila : ams_mapping) { + if (fila.id == pair.first) { + const auto& ams_opt = obj->GetFilaSystem()->GetAmsById(fila.ams_id); + if (ams_opt && ams_opt->GetSwitcherPos() != pair.second) { + summaryInfo += "\n"; + const auto& recommned_pos = (pair.second == DevFilaSwitch::SwitchPos::POS_IN_A) ? _L("AMS on Filament Inlet A") : _L("AMS on Filament Inlet B"); + summaryInfo += wxString::Format(_L("The No.%d slicing filament is matched to '%s'. Assigning it to '%s' can save printing time."), + pair.first + 1, getTrayID(obj, fila.ams_id, fila.slot_id), recommned_pos); + } + + break; + } + } + } + + summaryInfo += "\n\n"; + summaryInfo += _L("*Tips: If you have moved the spool position on the machine, please close this page and re‑match the filament."); + + } else { + summaryInfo = wxString::Format(_L("Summary: This is currently the most suitable position for placement.")); + } + + summaryText->SetLabel(summaryInfo); + summaryText->Wrap(FromDIP(571)); + summaryText->Layout(); + summaryText->Fit(); + } + + auto switchHight = CaculateSwitcherDistribution(obj, best_pos_map, ams_mapping); + wxSize switchSize; + if (122 == switchHight) //32 + 90 fixed size + { + switchSize = wxSize(FromDIP(571), FromDIP(439)); + } + else + { + switchSize = wxSize(FromDIP(571), FromDIP(switchHight)); + } + if (filamentSwitch) + { + filamentSwitch->Clear(true); + filamentSwitch->SetMinSize(switchSize); + filamentSwitch->SetMaxSize(switchSize); + filamentSwitch->SetSize(switchSize); + std::sort(inAAMS.begin(), inAAMS.end(), [](const auto& a, const auto& b) { + return a.size() > b.size(); + }); + std::sort(inBAMS.begin(), inBAMS.end(), [](const auto& a, const auto& b) { + return a.size() > b.size(); + }); + + auto addToSwitchFun = [this] (DevFilaSwitch::SwitchPos dir) { + const auto& INAMS = (dir == DevFilaSwitch::SwitchPos::POS_IN_A ? inAAMS : inBAMS); + for (size_t i = 0; i < INAMS.size(); ++i) + { + const auto& slots = INAMS[i]; + if (slots.size() == 4) + { + UiAMS* ams = new UiAMS(this->filamentSwitch, slots, wxID_ANY, wxDefaultPosition, wxSize(FromDIP(92), FromDIP(134))); + if (dir == DevFilaSwitch::SwitchPos::POS_IN_A) + { + this->filamentSwitch->AddToLeft(ams, 0, wxALL, FromDIP(10)); + this->filamentSwitch->GetLeftSizer()->Add(0, FromDIP(20)); + + } + if (dir == DevFilaSwitch::SwitchPos::POS_IN_B) + { + this->filamentSwitch->AddToRight(ams, 0, wxALL, FromDIP(10)); + this->filamentSwitch->GetRightSizer()->Add(0, FromDIP(20)); + } + } + if (slots.size() == 1) + { + if (i + 1 < INAMS.size()) + { + const auto& slots2 = INAMS[i + 1]; + + wxPanel* pairPanel = new wxPanel(this->filamentSwitch); + wxBoxSizer* pairSizer = new wxBoxSizer(wxHORIZONTAL); + + UiAMS* ams1 = new UiAMS(pairPanel, slots, wxID_ANY, + wxDefaultPosition, wxSize(FromDIP(92), FromDIP(134))); + pairSizer->Add(ams1, 0, wxALL, FromDIP(5)); + + UiAMS* ams2 = new UiAMS(pairPanel, slots2, wxID_ANY, + wxDefaultPosition, wxSize(FromDIP(92), FromDIP(134))); + pairSizer->Add(ams2, 0, wxALL, FromDIP(5)); + + pairPanel->SetSizer(pairSizer); + if (dir == DevFilaSwitch::SwitchPos::POS_IN_A) + { + this->filamentSwitch->AddToLeft(pairPanel, 0, wxALL, FromDIP(10)); + } + if (dir == DevFilaSwitch::SwitchPos::POS_IN_B) + { + this->filamentSwitch->AddToRight(pairPanel, 0, wxALL, FromDIP(10)); + } + + ++i; + } + else + { + UiAMS* ams = new UiAMS(this->filamentSwitch, slots, wxID_ANY, + wxDefaultPosition, wxSize(FromDIP(92), FromDIP(134))); + if (dir == DevFilaSwitch::SwitchPos::POS_IN_A) + { + this->filamentSwitch->AddToLeft(ams, 0, wxALL, FromDIP(10)); + } + if (dir == DevFilaSwitch::SwitchPos::POS_IN_B) + { + this->filamentSwitch->AddToRight(ams, 0, wxALL, FromDIP(10)); + } + } + + if (dir == DevFilaSwitch::SwitchPos::POS_IN_A) + { + this->filamentSwitch->GetLeftSizer()->Add(0, FromDIP(20)); + } + if (dir == DevFilaSwitch::SwitchPos::POS_IN_B) + { + this->filamentSwitch->GetRightSizer()->Add(0, FromDIP(20)); + } + } + } + }; + addToSwitchFun(DevFilaSwitch::SwitchPos::POS_IN_A); + addToSwitchFun(DevFilaSwitch::SwitchPos::POS_IN_B); + filamentSwitch->LayoutAndFit(); + } + + //FilamentInfo + mainSizer->Layout(); + + Layout(); + Fit(); + Refresh(true); + // wxDialog::Update(); +} + +int ReselectMachineDialog::CaculateSwitcherDistribution(MachineObject* obj, const std::map& best_pos_map, const std::vector& ams_mapping) +{ + if (!obj) + { + return 0; + } + + const auto& ams_list = obj->GetFilaSystem()->GetAmsList(); + std::map> ams_info; //all ams set + std::map> ams_DisplayName; + int inAFourSlotNum = 0, inAOneSlotNum = 0; + int inBFourSlotNum = 0, inBOneSlotNum = 0; + for (auto ams = ams_list.begin(); ams != ams_list.end(); ams++) //not include extend ams + { + AMSinfo info; + info.ams_id = ams->first; + if (ams->second->IsExist() && info.parse_ams_info(obj, ams->second, obj->GetFilaSystem()->IsDetectRemainEnabled(), obj->is_support_ams_humidity)) + { + auto pos = ams->second->GetSwitcherPos(); + auto amsName = ams->second->GetDisplayName(); + if (pos.has_value()) + { + ams_info[pos.value()].push_back(info); + ams_DisplayName[pos.value()].push_back(amsName); + if (pos.value() == DevFilaSwitch::SwitchPos::POS_IN_A) + { + ams->second->GetSlotCount() == 4 ? ++inAFourSlotNum : ++inAOneSlotNum; + } + if (pos.value() == DevFilaSwitch::SwitchPos::POS_IN_B) + { + ams->second->GetSlotCount() == 4 ? ++inBFourSlotNum : ++inBOneSlotNum; + } + } + } + } + //ams id //slot id + using trayHelper = std::tuple; + std::vector posOK, posAdjust; + for (const auto& fila : ams_mapping) + { + auto it = std::find_if(best_pos_map.begin(), best_pos_map.end(), [&](const std::pair& p){ + return fila.id == p.first; + }); + if (it != best_pos_map.end()) + { + posAdjust.push_back({fila.ams_id, fila.slot_id}); + } + else + { + posOK.push_back({fila.ams_id, fila.slot_id}); + } + } + + + int inAHeight = (inAFourSlotNum + (inAOneSlotNum / 2 + inAOneSlotNum % 2)) * 150; //150 one AMS height and spacer + int inBHeight = (inBFourSlotNum + (inBOneSlotNum / 2 + inBOneSlotNum % 2)) * 150; + + auto caculateAMSDistribution = [this, obj] (std::map>& amsInfo, + std::map>& amsDisplayName, + const std::vector& posOK, const std::vector& posAdjust, + DevFilaSwitch::SwitchPos dir) { + if (dir == DevFilaSwitch::SwitchPos::POS_IN_A) + { + this->inAAMS.clear(); + } + if (dir == DevFilaSwitch::SwitchPos::POS_IN_B) + { + this->inBAMS.clear(); + } + auto& AMSVector = amsInfo[dir]; + auto& AMSNameVector = amsDisplayName[dir]; + for (auto i = 0; i < AMSVector.size(); i++) + { + const auto& ams = AMSVector[i]; + std::vector allSlot; + for (auto j = 0; j < ams.cans.size(); j++) + { + const auto& can = ams.cans[j]; + auto id = getTrayID(obj, ams.ams_id, can.can_id); + auto material = can.material_name; + if (can.material_state == AMSCanType::AMS_CAN_TYPE_THIRDBRAND || + can.material_state == AMSCanType::AMS_CAN_TYPE_BRAND || + can.material_state == AMSCanType::AMS_CAN_TYPE_VIRTUAL) + { + if (can.material_name.empty()) + { + material = L("?"); + } + } + if (can.material_state == AMSCanType::AMS_CAN_TYPE_EMPTY) + { + material = "Empty"; + } + + auto itOK = std::find_if(posOK.begin(), posOK.end(), [&](const trayHelper& tray){ + auto amsID = std::get<0>(tray); + auto slotID = std::get<1>(tray); + return amsID == ams.ams_id && slotID == can.can_id; + }); + auto itAdjust = std::find_if(posAdjust.begin(), posAdjust.end(), [&](const trayHelper& tray){ + auto amsID = std::get<0>(tray); + auto slotID = std::get<1>(tray); + return amsID == ams.ams_id && slotID == can.can_id; + }); + double colourFactor = itOK != posOK.end() ? 1.0 : 0.2; + DataStatusType status = DataStatusType::UNMATCHED; + if (itOK != posOK.end()) + { + status = DataStatusType::OK; + } + if (itAdjust != posAdjust.end()) + { + status = DataStatusType::ADJUST; + } + if (can.material_cols.empty()) { + allSlot.push_back(DataAmsSlotInfo{ AMSNameVector[i], wxString(id + "\n" + material), {can.material_colour}, colourFactor, 1.0, status }); + } else { + allSlot.push_back(DataAmsSlotInfo{ AMSNameVector[i], wxString(id + "\n" + material), can.material_cols, colourFactor, 1.0, status }); + } + } + if (dir == DevFilaSwitch::SwitchPos::POS_IN_A) + { + this->inAAMS.push_back(allSlot); + } + if (dir == DevFilaSwitch::SwitchPos::POS_IN_B) + { + this->inBAMS.push_back(allSlot); + } + } + }; + + caculateAMSDistribution(ams_info, ams_DisplayName, posOK, posAdjust, DevFilaSwitch::SwitchPos::POS_IN_A); + caculateAMSDistribution(ams_info, ams_DisplayName, posOK, posAdjust, DevFilaSwitch::SwitchPos::POS_IN_B); + return std::max(inAHeight, inBHeight) + 32 + 90; //32 label height //184 * 90 selector width height +} + +wxString ReselectMachineDialog::getTrayID(MachineObject* obj, const std::string& amsID, const std::string& slotID) +{ + if (!amsID.empty() && !slotID.empty()) + { + if (obj) + { + auto filaSys = obj->GetFilaSystem(); + if (filaSys) + { + DevAms* ams = filaSys->GetAmsById(amsID); + int ams_id_int = std::stoi(amsID); + int slot_id_int = std::stoi(slotID); + int tray_id = 0; + if (ams->GetAmsType() == DevAmsType::AMS || ams->GetAmsType() == DevAmsType::AMS_LITE || ams->GetAmsType() == DevAmsType::N3F) { + tray_id = ams_id_int * 4 + slot_id_int; + } else if (ams->GetAmsType() == DevAmsType::N3S) { + tray_id = ams_id_int + slot_id_int; + } else if (ams->GetAmsType() == DevAmsType::EXT_SPOOL) { + tray_id = slot_id_int; + } + return wxGetApp().transition_tridid(tray_id); + } + } + } + return ""; +} + + +void ReselectMachineDialog::OnRefreshButton(wxCommandEvent& event) +{ + // EndModal(wxID_CANCEL); + wxCommandEvent evt(wxEVT_REFRESH_DATA, GetId()); + evt.SetEventObject(this); + GetParent()->ProcessWindowEvent(evt); +} + +ReselectMachineDialog::~ReselectMachineDialog() +{ + +} + +} // namespace GUI + +} // namespace Slic3r \ No newline at end of file diff --git a/src/slic3r/GUI/DeviceTab/uiAMSBestPositionPopup.hpp b/src/slic3r/GUI/DeviceTab/uiAMSBestPositionPopup.hpp new file mode 100644 index 0000000000..455576d3cc --- /dev/null +++ b/src/slic3r/GUI/DeviceTab/uiAMSBestPositionPopup.hpp @@ -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 + +#include + +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& 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 range,128 mid + } +private: + const std::vector& 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 colours; + double colourFactor; + double scaleFactor; + DataStatusType status; +}; + +class UiAMS : public UiStyledAMSPanel +{ +public: + UiAMS(wxWindow* parent, + const std::vector& amsInfo, + wxWindowID id, + const wxPoint& pos, + const wxSize& minSize); +private: + void init(); + std::vector m_amsInfo; + wxString m_amsTitle; + wxSize m_minSize; +}; + + +struct DataStatusParam { + // int count = 0; + int width = 0; + int height = 0; + std::vector slots; +}; + +class ReselectMachineDialog : public wxDialog +{ +public: + ReselectMachineDialog(wxWindow* parent); + ~ReselectMachineDialog(); + void Update(MachineObject* obj, + const std::map& best_pos_map, + const std::vector& ams_mapping, + wxString save_time); + +private: + int CaculateSwitcherDistribution(MachineObject* obj, const std::map& best_pos_map, const std::vector& 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> inAAMS{}; + std::vector> inBAMS{}; + wxPanel* statusBar{nullptr}; + wxBoxSizer* btnSizer{nullptr}; + Button* m_buttonClose{nullptr }; + Button* m_buttonRefresh{ nullptr }; + std::vectorcolourAdjust{wxColour("#675AFF")}; + std::vectorcolourOK{wxColour("#FF8181")}; + std::vectorcolourUnused{wxColour("#FF818140")}; + // wxStaticBitmap* m_bitmapSelectMachine{nullptr}; +}; + +wxDECLARE_EVENT(wxEVT_REFRESH_DATA, wxCommandEvent); + +}} // namespace Slic3r::GUI \ No newline at end of file diff --git a/src/slic3r/GUI/DeviceTab/uiAmsHumidityPopup.cpp b/src/slic3r/GUI/DeviceTab/uiAmsHumidityPopup.cpp index 57b2b43e6e..33e9cdf518 100644 --- a/src/slic3r/GUI/DeviceTab/uiAmsHumidityPopup.cpp +++ b/src/slic3r/GUI/DeviceTab/uiAmsHumidityPopup.cpp @@ -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) diff --git a/src/slic3r/GUI/DeviceTab/uiAmsHumidityPopup.h b/src/slic3r/GUI/DeviceTab/uiAmsHumidityPopup.h index 1c4295dc53..0f22c6b662 100644 --- a/src/slic3r/GUI/DeviceTab/uiAmsHumidityPopup.h +++ b/src/slic3r/GUI/DeviceTab/uiAmsHumidityPopup.h @@ -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; diff --git a/src/slic3r/GUI/DeviceTab/uiDeviceUpdateVersion.cpp b/src/slic3r/GUI/DeviceTab/uiDeviceUpdateVersion.cpp index fb5f71efd1..8a0bf21c9e 100644 --- a/src/slic3r/GUI/DeviceTab/uiDeviceUpdateVersion.cpp +++ b/src/slic3r/GUI/DeviceTab/uiDeviceUpdateVersion.cpp @@ -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 @@ -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); diff --git a/src/slic3r/GUI/DeviceTab/wgtDeviceNozzleRack.cpp b/src/slic3r/GUI/DeviceTab/wgtDeviceNozzleRack.cpp index da8eda24e0..44686090a7 100644 --- a/src/slic3r/GUI/DeviceTab/wgtDeviceNozzleRack.cpp +++ b/src/slic3r/GUI/DeviceTab/wgtDeviceNozzleRack.cpp @@ -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 + +#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 a_nozzle_seq = { 0, 2, 4, 1, 3, 5 }; +static std::vector 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 nozzl return nozzle_box; } +static void s_update_title(const std::shared_ptr 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 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& nozzle_items, std::shared_ptr nozzle_rack) { @@ -409,6 +448,7 @@ void wgtDeviceNozzleRackArea::UpdateRackInfo(std::weak_ptr 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 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(); +}; + +}; diff --git a/src/slic3r/GUI/DeviceTab/wgtDeviceNozzleRack.h b/src/slic3r/GUI/DeviceTab/wgtDeviceNozzleRack.h index c4fa464e05..fe12b8bc50 100644 --- a/src/slic3r/GUI/DeviceTab/wgtDeviceNozzleRack.h +++ b/src/slic3r/GUI/DeviceTab/wgtDeviceNozzleRack.h @@ -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 #include #include -#include -#include // 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 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 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 \ No newline at end of file diff --git a/src/slic3r/GUI/DeviceTab/wgtDeviceNozzleRackNozzleItem.cpp b/src/slic3r/GUI/DeviceTab/wgtDeviceNozzleRackNozzleItem.cpp deleted file mode 100644 index 7dde545f99..0000000000 --- a/src/slic3r/GUI/DeviceTab/wgtDeviceNozzleRackNozzleItem.cpp +++ /dev/null @@ -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 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 diff --git a/src/slic3r/GUI/DeviceTab/wgtDeviceNozzleRackNozzleItem.h b/src/slic3r/GUI/DeviceTab/wgtDeviceNozzleRackNozzleItem.h index 2ed2b48d66..df4e2ef707 100644 --- a/src/slic3r/GUI/DeviceTab/wgtDeviceNozzleRackNozzleItem.h +++ b/src/slic3r/GUI/DeviceTab/wgtDeviceNozzleRackNozzleItem.h @@ -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 -#include - -// 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 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 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" diff --git a/src/slic3r/GUI/DeviceTab/wgtDeviceNozzleRackUpdate.cpp b/src/slic3r/GUI/DeviceTab/wgtDeviceNozzleRackUpdate.cpp index c88bc7c516..ce10c972fe 100644 --- a/src/slic3r/GUI/DeviceTab/wgtDeviceNozzleRackUpdate.cpp +++ b/src/slic3r/GUI/DeviceTab/wgtDeviceNozzleRackUpdate.cpp @@ -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 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(); diff --git a/src/slic3r/GUI/DeviceTab/wgtDeviceNozzleRackUpdate.h b/src/slic3r/GUI/DeviceTab/wgtDeviceNozzleRackUpdate.h index 49df93c2c7..0fa07fd63a 100644 --- a/src/slic3r/GUI/DeviceTab/wgtDeviceNozzleRackUpdate.h +++ b/src/slic3r/GUI/DeviceTab/wgtDeviceNozzleRackUpdate.h @@ -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 #include -#include -#include // Previous definitions class Button; @@ -49,7 +39,7 @@ public: wgtDeviceNozzleRackUpgradeDlg(wxWindow* parent, const std::shared_ptr rack); public: - void UpdateRackInfo(const std::shared_ptr rack); + void UpdateRackInfo(const std::shared_ptr 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 \ No newline at end of file diff --git a/src/slic3r/GUI/DeviceTab/wgtDeviceNozzleSelect.cpp b/src/slic3r/GUI/DeviceTab/wgtDeviceNozzleSelect.cpp index dda471ac23..b211510de0 100644 --- a/src/slic3r/GUI/DeviceTab/wgtDeviceNozzleSelect.cpp +++ b/src/slic3r/GUI/DeviceTab/wgtDeviceNozzleSelect.cpp @@ -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 a_nozzle_seq = {16, 18, 20, 17, 19, 21}; @@ -132,7 +132,7 @@ void wgtDeviceNozzleRackSelect::UpdateNozzleInfos(std::shared_ptr } } -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_ptrSetSelected(false); diff --git a/src/slic3r/GUI/DeviceTab/wgtDeviceNozzleSelect.h b/src/slic3r/GUI/DeviceTab/wgtDeviceNozzleSelect.h index 43f3378ff2..3ff866f3a1 100644 --- a/src/slic3r/GUI/DeviceTab/wgtDeviceNozzleSelect.h +++ b/src/slic3r/GUI/DeviceTab/wgtDeviceNozzleSelect.h @@ -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 #include -#include -#include -#include // Previous definitions class Label; @@ -71,4 +68,4 @@ private: std::unordered_map m_nozzle_items; // from 16 to 21 }; -};// end of namespace Slic3r::GUI +};// end of namespace Slic3r::GUI \ No newline at end of file diff --git a/src/slic3r/GUI/DeviceTab/wgtMsgBox.h b/src/slic3r/GUI/DeviceTab/wgtMsgBox.h index 0668893d09..9f05abef96 100644 --- a/src/slic3r/GUI/DeviceTab/wgtMsgBox.h +++ b/src/slic3r/GUI/DeviceTab/wgtMsgBox.h @@ -28,4 +28,4 @@ private: }; } -#endif // WGTMSGBOX_H +#endif // WGTMSGBOX_H \ No newline at end of file diff --git a/src/slic3r/GUI/DeviceTab/wgtMsgPanel.cpp b/src/slic3r/GUI/DeviceTab/wgtMsgPanel.cpp new file mode 100644 index 0000000000..64195d97a8 --- /dev/null +++ b/src/slic3r/GUI/DeviceTab/wgtMsgPanel.cpp @@ -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 + +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 diff --git a/src/slic3r/GUI/DeviceTab/wgtMsgPanel.h b/src/slic3r/GUI/DeviceTab/wgtMsgPanel.h new file mode 100644 index 0000000000..1f983ce2e4 --- /dev/null +++ b/src/slic3r/GUI/DeviceTab/wgtMsgPanel.h @@ -0,0 +1,69 @@ +#pragma once + +#include +#include +#include + +#include + +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