mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-17 05:52:39 +00:00
* build: enable /Zc:lambda for MSVC MSVC keeps its legacy lambda processor under /std:c++17, which rejects reading a constexpr constant inside a lambda that does not capture it (C3493). No other compiler requires that capture, and clang reports it as an unused one, so the two cannot both be satisfied without the flag. /Zc:lambda selects the conforming lambda parser that clang and GCC already use. It is implied by /std:c++20 and /permissive-, so it is only needed while we are on C++17. clang-cl is conforming already and does not take the flag. It requires VS2019 16.8, so build_release_vs.bat now says 16.8+. * build: clear 237 unused lambda capture warnings 236 captures across 81 files, 142 of them `this`. Removing an unused capture changes no behavior; clang does not report a capture whose type has a non-trivial destructor, so nothing held only to extend an object's lifetime is in this set. Nine of them are the second half of the warning, "is not required to be captured for this use", where the capture is a const or constexpr value the body does read. Those depend on the /Zc:lambda change in the previous commit. One of them, in FillRectilinear.cpp, had been worked around with an #ifndef __APPLE__ guard around the capture list, which is now gone. GUI_ObjectTableSettings.cpp captured its reset button only to read it inside #ifdef __WXOSX_MAC__. That branch now takes the button from the event it is already handling. * build: fail configure on MSVC older than 19.28 instead of dropping /Zc:lambda cl.exe answers an unrecognized /Zc: sub-option with warning D9002 and keeps going, so on VS2019 before 16.8 the flag is silently ignored and the build instead dies with C3493 in FillRectilinear.cpp, nowhere near the cause. * fix: delete three locals that are now unused Their only remaining use was the lambda capture this branch removed. The Clang builds set -Wno-unused-variable, so the build never flagged them. --------- Co-authored-by: Rodrigo Faselli <162915171+RF47@users.noreply.github.com>
822 lines
27 KiB
C++
822 lines
27 KiB
C++
#include "FilamentPickerDialog.hpp"
|
|
#include "GUI.hpp"
|
|
#include "I18N.hpp"
|
|
#include "GUI_App.hpp"
|
|
#include "MainFrame.hpp"
|
|
#include "EncodedFilament.hpp"
|
|
#include "Widgets/Label.hpp"
|
|
#include "Widgets/Button.hpp"
|
|
#include "Widgets/StateColor.hpp"
|
|
#include "Widgets/DialogButtons.hpp"
|
|
#include "wxExtensions.hpp"
|
|
#include <wx/wx.h>
|
|
#include <wx/sizer.h>
|
|
#include <wx/stattext.h>
|
|
#include <wx/button.h>
|
|
|
|
#ifdef _WIN32
|
|
#include <windows.h>
|
|
#endif
|
|
|
|
#define COLOR_DEMO_SIZE wxSize(FromDIP(50), FromDIP(50))
|
|
#define COLOR_BTN_BITMAP_SIZE wxSize(FromDIP(24), FromDIP(24))
|
|
#define COLOR_BTN_SIZE wxSize(FromDIP(30), FromDIP(30))
|
|
#define GRID_GAP FromDIP(2)
|
|
#define COLS 9 // fixed column count
|
|
#define MAX_VISIBLE_ROWS 7 // max rows before scrollbar appears
|
|
|
|
namespace Slic3r { namespace GUI {
|
|
|
|
wxColour FilamentPickerDialog::GetSelectedColour() const
|
|
{
|
|
if (!m_color_demo) return wxNullColour;
|
|
return m_color_demo->GetBackgroundColour();
|
|
}
|
|
|
|
void FilamentPickerDialog::on_dpi_changed(const wxRect &suggested_rect)
|
|
{
|
|
// Handle DPI change
|
|
CreateShapedBitmap();
|
|
SetWindowShape();
|
|
Refresh();
|
|
Layout();
|
|
}
|
|
|
|
// Flash effect implementation
|
|
void FilamentPickerDialog::StartFlashing()
|
|
{
|
|
// Stop any existing flash timer
|
|
if (m_flash_timer) {
|
|
m_flash_timer->Stop();
|
|
delete m_flash_timer;
|
|
}
|
|
|
|
m_flash_timer = new wxTimer(this, wxID_ANY + 1);
|
|
Bind(wxEVT_TIMER, &FilamentPickerDialog::OnFlashTimer, this, m_flash_timer->GetId());
|
|
m_flash_step = 0;
|
|
m_flash_timer->Start(50);
|
|
}
|
|
|
|
FilamentPickerDialog::FilamentPickerDialog(wxWindow *parent, const wxString& fila_id, const FilamentColor& fila_color, const std::string& fila_type)
|
|
: DPIDialog(parent ? parent : wxGetApp().mainframe,
|
|
wxID_ANY,
|
|
_L("Select Filament"),
|
|
wxDefaultPosition,
|
|
wxDefaultSize,
|
|
wxBORDER_NONE | wxFRAME_NO_TASKBAR | wxFRAME_SHAPED)
|
|
{
|
|
SetBackgroundColour(wxColour(255, 255, 255));
|
|
|
|
m_color_query = new FilamentColorCodeQuery();
|
|
m_is_data_loaded = LoadFilamentData(fila_id);
|
|
m_cur_filament_color = fila_color;
|
|
wxString color_name = m_color_query->GetFilaColorName(fila_id, fila_color);
|
|
m_cur_color_name = new wxString(color_name);
|
|
|
|
wxBoxSizer *container_sizer = new wxBoxSizer(wxVERTICAL);
|
|
wxBoxSizer *main_sizer = new wxBoxSizer(wxVERTICAL);
|
|
|
|
// Preview panel (always present)
|
|
wxBoxSizer *preview_sizer = CreatePreviewPanel(fila_color, fila_type);
|
|
main_sizer->AddSpacer(FromDIP(4));
|
|
main_sizer->Add(preview_sizer, 0, wxEXPAND, 0);
|
|
main_sizer->AddSpacer(FromDIP(12));
|
|
|
|
wxBoxSizer *line_sizer = CreateSeparatorLine();
|
|
main_sizer->Add(line_sizer, 0, wxEXPAND, 0);
|
|
|
|
// If caller passed an initial colour, reflect it in preview box.
|
|
if (m_is_data_loaded) {
|
|
// Colour grid with all filaments
|
|
wxScrolledWindow* color_grid = CreateColorGrid();
|
|
main_sizer->Add(color_grid, 0, wxEXPAND | wxTOP | wxBOTTOM, FromDIP(8));
|
|
}
|
|
|
|
// "More colours" button (always present)
|
|
CreateMoreInfoButton();
|
|
main_sizer->Add(m_more_btn, 0, wxEXPAND | wxTOP, FromDIP(8));
|
|
//main_sizer->AddSpacer(FromDIP(8));
|
|
|
|
// OK / Cancel buttons
|
|
auto dlg_btns = new DialogButtons(this, {"OK", "Cancel"});
|
|
m_ok_btn = dlg_btns->GetOK();
|
|
m_cancel_btn = dlg_btns->GetCANCEL();
|
|
|
|
container_sizer->Add(main_sizer, 1, wxEXPAND | wxALL, FromDIP(10));
|
|
container_sizer->Add(dlg_btns, 0, wxEXPAND);
|
|
|
|
SetSizerAndFit(container_sizer);
|
|
Layout();
|
|
|
|
// Position the dialog relative to the parent window
|
|
if (GetParent()) {
|
|
// Align the dialog with the sidebar
|
|
auto& sidebar = wxGetApp().sidebar();
|
|
wxPoint sidebar_pos = sidebar.GetScreenPosition();
|
|
wxSize sidebar_size = sidebar.GetSize();
|
|
|
|
wxPoint new_pos(
|
|
sidebar_pos.x + sidebar_size.GetWidth() + FromDIP(10),
|
|
sidebar_pos.y + FromDIP(80)
|
|
);
|
|
SetPosition(new_pos);
|
|
} else {
|
|
Centre(wxBOTH); // If no parent window, center the dialog
|
|
}
|
|
|
|
// Create shaped window after sizing
|
|
CreateShapedBitmap();
|
|
#ifndef __WXGTK__
|
|
// Windows and macOS can set shape immediately
|
|
SetWindowShape();
|
|
#endif
|
|
#ifdef __WXGTK__
|
|
// GTK platform needs to wait for window creation
|
|
Bind(wxEVT_CREATE, &FilamentPickerDialog::OnWindowCreate, this);
|
|
#endif
|
|
|
|
wxGetApp().UpdateDlgDarkUI(this);
|
|
Layout();
|
|
// Set window transparency
|
|
SetTransparent(255);
|
|
BindEvents();
|
|
|
|
// Start click detection timer for outside click detection
|
|
StartClickDetection();
|
|
}
|
|
|
|
FilamentPickerDialog::~FilamentPickerDialog()
|
|
{
|
|
// Clean up all timers
|
|
CleanupTimers();
|
|
|
|
delete m_color_query;
|
|
m_color_query = nullptr;
|
|
|
|
delete m_cur_color_name;
|
|
m_cur_color_name = nullptr;
|
|
}
|
|
|
|
void FilamentPickerDialog::CreateShapedBitmap()
|
|
{
|
|
wxSize size = GetSize();
|
|
if (size.GetWidth() <= 0 || size.GetHeight() <= 0) {
|
|
return;
|
|
}
|
|
|
|
// Create a bitmap with alpha channel
|
|
m_shape_bmp.Create(size.GetWidth(), size.GetHeight(), 32);
|
|
|
|
wxMemoryDC dc;
|
|
dc.SelectObject(m_shape_bmp);
|
|
|
|
dc.SetBackground(wxBrush(wxColour(0, 0, 0)));
|
|
dc.Clear();
|
|
|
|
// Draw main white shape on top, positioned to let shadow show through
|
|
dc.SetBrush(wxBrush(wxColour(255, 255, 255, 255)));
|
|
dc.SetPen(*wxTRANSPARENT_PEN);
|
|
dc.DrawRoundedRectangle(0, 0,
|
|
size.GetWidth(),
|
|
size.GetHeight(),
|
|
FromDIP(m_corner_radius));
|
|
|
|
dc.SelectObject(wxNullBitmap);
|
|
}
|
|
|
|
void FilamentPickerDialog::SetWindowShape()
|
|
{
|
|
if (!m_shape_bmp.IsOk()) {
|
|
return;
|
|
}
|
|
|
|
// Create a region from the bitmap using magenta as transparent mask color
|
|
wxRegion region(m_shape_bmp, wxColour(0, 0, 0));
|
|
|
|
if (region.IsOk()) {
|
|
SetShape(region);
|
|
}
|
|
}
|
|
|
|
bool FilamentPickerDialog::LoadFilamentData(const wxString& fila_id)
|
|
{
|
|
m_cur_color_codes = m_color_query->GetFilaInfoMap(fila_id);
|
|
|
|
if (!m_cur_color_codes) {
|
|
BOOST_LOG_TRIVIAL(warning) << "No color codes found for filament ID: " << fila_id.ToStdString();
|
|
return false;
|
|
}
|
|
|
|
FilamentColor2CodeMap* color_map = m_cur_color_codes->GetFilamentColor2CodeMap();
|
|
if (!color_map) {
|
|
BOOST_LOG_TRIVIAL(warning) << "No color map found for filament ID: " << fila_id.ToStdString();
|
|
return false;
|
|
}
|
|
|
|
BOOST_LOG_TRIVIAL(info) << "Successfully loaded " << color_map->size() << " color variants for filament " << fila_id.ToStdString();
|
|
return !color_map->empty();
|
|
}
|
|
|
|
wxBoxSizer* FilamentPickerDialog::CreatePreviewPanel(const FilamentColor& fila_color, const std::string& fila_type)
|
|
{
|
|
wxBoxSizer *preview_sizer = new wxBoxSizer(wxHORIZONTAL);
|
|
|
|
// Create color preview bitmap
|
|
CreateColorBitmap(fila_color);
|
|
preview_sizer->Add(m_color_demo, 0, wxALIGN_CENTER_VERTICAL, 0);
|
|
preview_sizer->AddSpacer(FromDIP(12));
|
|
|
|
// Create info labels section
|
|
wxBoxSizer *label_sizer = CreateInfoSection();
|
|
SetupLabelsContent(fila_color, fila_type);
|
|
preview_sizer->Add(label_sizer, 1, wxALIGN_CENTER_VERTICAL, 0);
|
|
|
|
return preview_sizer;
|
|
}
|
|
|
|
void FilamentPickerDialog::CreateColorBitmap(const FilamentColor &fila_color)
|
|
{
|
|
m_color_demo = new wxStaticBitmap(this, wxID_ANY, wxNullBitmap, wxDefaultPosition, COLOR_DEMO_SIZE, 0);
|
|
|
|
// Generate bitmap content
|
|
if (fila_color.ColorCount() > 0) {
|
|
std::vector<wxColour> wx_colors(fila_color.m_colors.begin(), fila_color.m_colors.end());
|
|
wxBitmap init_bmp = create_filament_bitmap(wx_colors, COLOR_DEMO_SIZE,
|
|
fila_color.m_color_type == FilamentColor::ColorType::GRADIENT_CLR);
|
|
m_color_demo->SetBitmap(init_bmp);
|
|
}
|
|
else{
|
|
std::vector<wxColour> wx_colors;
|
|
wx_colors.push_back(wxNullColour);
|
|
wxBitmap init_bmp = create_filament_bitmap(wx_colors, COLOR_DEMO_SIZE, false);
|
|
m_color_demo->SetBitmap(init_bmp);
|
|
}
|
|
}
|
|
|
|
wxBoxSizer* FilamentPickerDialog::CreateInfoSection()
|
|
{
|
|
wxBoxSizer *main_sizer = new wxBoxSizer(wxVERTICAL);
|
|
|
|
// Create the container box
|
|
wxStaticBox *info_box = new wxStaticBox(this, wxID_ANY, wxEmptyString, wxDefaultPosition);
|
|
info_box->SetSize(wxSize(FromDIP(240), FromDIP(24)));
|
|
info_box->SetBackgroundColour(StateColor::darkModeColorFor(*wxWHITE));
|
|
wxStaticBoxSizer *box_sizer = new wxStaticBoxSizer(info_box, wxHORIZONTAL);
|
|
|
|
// Create labels with ellipsize style for text overflow
|
|
m_label_preview_color = new wxStaticText(this, wxID_ANY, _L("Custom Color"),
|
|
wxDefaultPosition, wxDefaultSize,
|
|
wxST_ELLIPSIZE_END);
|
|
m_label_preview_idx = new wxStaticText(this, wxID_ANY, "",
|
|
wxDefaultPosition, wxDefaultSize); // No size limit, no ellipsis
|
|
m_label_preview_type = new wxStaticText(this, wxID_ANY, "",
|
|
wxDefaultPosition, wxSize(FromDIP(220), FromDIP(16)),
|
|
wxST_ELLIPSIZE_END);
|
|
|
|
// Set maximum width for color label to enable proper ellipsis behavior
|
|
m_label_preview_color->SetMaxSize(wxSize(FromDIP(160), -1));
|
|
|
|
// Setup fonts
|
|
wxFont bold_font = m_label_preview_color->GetFont();
|
|
bold_font.SetWeight(wxFONTWEIGHT_BOLD);
|
|
#ifdef __WXMSW__
|
|
bold_font.SetPointSize(FromDIP(8));
|
|
#endif
|
|
m_label_preview_color->SetFont(bold_font);
|
|
m_label_preview_idx->SetFont(bold_font);
|
|
|
|
m_label_preview_type->SetForegroundColour(wxColour(128, 128, 128));
|
|
|
|
// Layout with platform-specific spacing
|
|
#ifdef __WXMSW__
|
|
int spacer = FromDIP(2), vPadding = FromDIP(0), gap1 = FromDIP(-6), gap2 = FromDIP(4);
|
|
#else
|
|
int spacer = FromDIP(0), vPadding = FromDIP(-1), gap1 = FromDIP(0), gap2 = FromDIP(2);
|
|
#endif
|
|
|
|
box_sizer->AddSpacer(spacer);
|
|
box_sizer->Add(m_label_preview_color, 0, wxALIGN_CENTER_VERTICAL | wxTOP | wxBOTTOM, vPadding);
|
|
box_sizer->AddSpacer(FromDIP(2));
|
|
box_sizer->Add(m_label_preview_idx, 0, wxALIGN_CENTER_VERTICAL | wxTOP | wxBOTTOM, vPadding);
|
|
box_sizer->AddSpacer(spacer);
|
|
|
|
main_sizer->Add(box_sizer, 0, wxALIGN_CENTER_VERTICAL | wxTOP, gap1);
|
|
main_sizer->AddSpacer(gap2);
|
|
main_sizer->Add(m_label_preview_type, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(6));
|
|
|
|
return main_sizer;
|
|
}
|
|
|
|
void FilamentPickerDialog::SetupLabelsContent(const FilamentColor &fila_color, const std::string &fila_type)
|
|
{
|
|
m_label_preview_type->SetLabel(from_u8(fila_type));
|
|
if (m_cur_color_name && !m_cur_color_name->IsEmpty()) {
|
|
m_label_preview_color->SetLabel(*m_cur_color_name);
|
|
|
|
// Try to get additional color code information
|
|
if (m_cur_color_codes) {
|
|
FilamentColorCode *color_code = m_cur_color_codes->GetColorCode(fila_color);
|
|
if (color_code) {
|
|
m_label_preview_idx->SetLabel(wxString::Format("(%s)", color_code->GetFilaColorCode()));
|
|
}
|
|
}
|
|
}
|
|
else{
|
|
if (fila_color.ColorCount() == 0){
|
|
m_label_preview_color->SetLabel(_L("Null Color"));
|
|
}
|
|
else if (fila_color.ColorCount() == 1) {
|
|
m_label_preview_color->SetLabel(fila_color.m_colors.begin()->GetAsString(wxC2S_HTML_SYNTAX));
|
|
}
|
|
else{
|
|
m_label_preview_color->SetLabel(_L("Multiple Color"));
|
|
}
|
|
}
|
|
}
|
|
|
|
wxBoxSizer* FilamentPickerDialog::CreateSeparatorLine()
|
|
{
|
|
wxBoxSizer *line_sizer = new wxBoxSizer(wxHORIZONTAL);
|
|
wxPanel* separator_line = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(-1, FromDIP(1)));
|
|
separator_line->SetBackgroundColour(wxColour(238,238,238));
|
|
wxStaticText* line_text = new wxStaticText(this, wxID_ANY, _L("Official Filament"), wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER_HORIZONTAL);
|
|
line_text->SetForegroundColour(wxColour(128, 128, 128));
|
|
line_sizer->Add(line_text, 0, wxEXPAND, 0);
|
|
line_sizer->AddSpacer(FromDIP(8));
|
|
line_sizer->Add(separator_line, 1, wxALIGN_CENTER_VERTICAL, 0);
|
|
return line_sizer;
|
|
}
|
|
|
|
wxScrolledWindow* FilamentPickerDialog::CreateColorGrid()
|
|
{
|
|
if (!m_cur_color_codes) return nullptr;
|
|
|
|
FilamentColor2CodeMap* color_map = m_cur_color_codes->GetFilamentColor2CodeMap();
|
|
if (!color_map) return nullptr;
|
|
|
|
// Calculate required row count
|
|
int total_colors = color_map->size();
|
|
int needed_rows = (total_colors + COLS - 1) / COLS; // round-up division
|
|
bool need_scroll = needed_rows > MAX_VISIBLE_ROWS;
|
|
|
|
// Create a vertical-only scrolled window
|
|
wxScrolledWindow* scroll_win = new wxScrolledWindow(
|
|
this,
|
|
wxID_ANY,
|
|
wxDefaultPosition,
|
|
wxDefaultSize,
|
|
wxVSCROLL | wxNO_BORDER
|
|
);
|
|
|
|
wxGridSizer* grid_sizer = new wxGridSizer(needed_rows, COLS, GRID_GAP, GRID_GAP);
|
|
|
|
if (!color_map->empty()) {
|
|
for (const auto& color_pair : *color_map) {
|
|
const FilamentColor& fila_color = color_pair.first; // color info
|
|
FilamentColorCode* color_code = color_pair.second; // color code
|
|
|
|
if (!color_code) continue;
|
|
std::vector<wxColour> wx_colors(fila_color.m_colors.begin(), fila_color.m_colors.end());
|
|
wxBitmap btn_bmp = create_filament_bitmap(
|
|
wx_colors,
|
|
COLOR_BTN_BITMAP_SIZE,
|
|
fila_color.m_color_type == FilamentColor::ColorType::GRADIENT_CLR
|
|
);
|
|
|
|
if (!btn_bmp.IsOk()) {
|
|
BOOST_LOG_TRIVIAL(error) << "Failed to create bitmap for filament " << color_code->GetFilaColorCode().ToStdString();
|
|
continue;
|
|
}
|
|
|
|
wxBitmapButton* btn = new wxBitmapButton(
|
|
scroll_win,
|
|
wxID_ANY,
|
|
btn_bmp,
|
|
wxDefaultPosition,
|
|
COLOR_BTN_SIZE,
|
|
wxBU_EXACTFIT | wxNO_BORDER
|
|
);
|
|
|
|
if (btn) {
|
|
// Remove any default background and borders
|
|
btn->SetBackgroundColour(StateColor::darkModeColorFor(*wxWHITE));
|
|
|
|
// Set tooltip with filament information
|
|
wxString tooltip = wxString::Format("%s", color_code->GetFilaColorName());
|
|
btn->SetToolTip(tooltip);
|
|
|
|
// Check if this color matches the current color name and set as selected
|
|
bool is_matching_color = (m_cur_color_name &&
|
|
!m_cur_color_name->IsEmpty() &&
|
|
*m_cur_color_name == color_code->GetFilaColorName());
|
|
|
|
if (is_matching_color) {
|
|
m_cur_filament_color = color_code->GetFilaColor();
|
|
m_cur_selected_btn = btn;
|
|
UpdatePreview(*color_code);
|
|
btn->Bind(wxEVT_PAINT, &FilamentPickerDialog::OnButtonPaint, this);
|
|
}
|
|
|
|
// Bind click
|
|
btn->Bind(wxEVT_LEFT_DOWN, [this, btn, color_code](wxMouseEvent& evt) {
|
|
m_cur_filament_color = color_code->GetFilaColor();
|
|
UpdatePreview(*color_code);
|
|
UpdateButtonStates(btn);
|
|
evt.Skip();
|
|
});
|
|
|
|
// Hover highlight
|
|
btn->Bind(wxEVT_ENTER_WINDOW, [](wxMouseEvent& evt) {
|
|
evt.Skip();
|
|
});
|
|
|
|
btn->Bind(wxEVT_LEAVE_WINDOW, [](wxMouseEvent& evt) {
|
|
evt.Skip();
|
|
});
|
|
|
|
grid_sizer->Add(btn, 0, wxALL | wxALIGN_CENTER, FromDIP(1));
|
|
}
|
|
}
|
|
}
|
|
|
|
scroll_win->SetSizer(grid_sizer);
|
|
|
|
if (need_scroll) {
|
|
int row_height = COLOR_BTN_SIZE.GetHeight() + FromDIP(2);
|
|
int col_width = COLOR_BTN_SIZE.GetWidth() + FromDIP(4);
|
|
|
|
// Reserve space for vertical scrollbar so it doesn't overlay content
|
|
int scrollbar_width = wxSystemSettings::GetMetric(wxSYS_VSCROLL_X);
|
|
|
|
// Set minimum visible area (including scrollbar width)
|
|
scroll_win->SetMinSize(wxSize(col_width * COLS + scrollbar_width, row_height * MAX_VISIBLE_ROWS));
|
|
|
|
// Let wxScrolledWindow calculate appropriate virtual size
|
|
scroll_win->FitInside();
|
|
scroll_win->SetScrollRate(0, row_height);
|
|
} else {
|
|
scroll_win->FitInside();
|
|
scroll_win->SetScrollRate(0, 0);
|
|
}
|
|
|
|
return scroll_win;
|
|
}
|
|
|
|
void FilamentPickerDialog::UpdatePreview(const FilamentColorCode& color_code)
|
|
{
|
|
FilamentColor fila_color = color_code.GetFilaColor();
|
|
|
|
std::vector<wxColour> wx_colors(fila_color.m_colors.begin(), fila_color.m_colors.end());
|
|
|
|
// Update preview bitmap
|
|
wxBitmap bmp = create_filament_bitmap(wx_colors, COLOR_DEMO_SIZE,
|
|
fila_color.m_color_type == FilamentColor::ColorType::GRADIENT_CLR);
|
|
|
|
if (bmp.IsOk()) {
|
|
BOOST_LOG_TRIVIAL(debug) << "Bitmap created successfully: " << bmp.GetWidth() << "x" << bmp.GetHeight();
|
|
m_color_demo->SetBitmap(bmp);
|
|
if (!wx_colors.empty()) {
|
|
m_color_demo->SetBackgroundColour(wx_colors[0]);
|
|
}
|
|
m_color_demo->Refresh();
|
|
} else {
|
|
BOOST_LOG_TRIVIAL(error) << "Failed to create bitmap";
|
|
}
|
|
|
|
// Update preview labels
|
|
m_label_preview_color->SetLabel(color_code.GetFilaColorName());
|
|
m_label_preview_idx->SetLabel(wxString::Format("(%s)", color_code.GetFilaColorCode()));
|
|
Layout();
|
|
}
|
|
|
|
void FilamentPickerDialog::UpdateCustomColorPreview(const wxColour& custom_color)
|
|
{
|
|
std::vector<wxColour> wx_colors = {custom_color};
|
|
|
|
// Update preview bitmap
|
|
wxBitmap bmp = create_filament_bitmap(wx_colors, COLOR_DEMO_SIZE, false);
|
|
|
|
if (bmp.IsOk()) {
|
|
BOOST_LOG_TRIVIAL(debug) << "Custom color bitmap created successfully: " << bmp.GetWidth() << "x" << bmp.GetHeight();
|
|
m_color_demo->SetBitmap(bmp);
|
|
m_color_demo->SetBackgroundColour(custom_color);
|
|
m_color_demo->Refresh();
|
|
} else {
|
|
BOOST_LOG_TRIVIAL(error) << "Failed to create custom color bitmap";
|
|
}
|
|
|
|
// Update preview labels for custom color
|
|
m_label_preview_color->SetLabel(custom_color.GetAsString(wxC2S_HTML_SYNTAX));
|
|
m_label_preview_idx->SetLabel("");
|
|
Layout();
|
|
}
|
|
|
|
void FilamentPickerDialog::UpdateButtonStates(wxBitmapButton* selected_btn)
|
|
{
|
|
// Reset selected button appearance
|
|
if (m_cur_selected_btn) {
|
|
m_cur_selected_btn->SetBackgroundColour(StateColor::darkModeColorFor(*wxWHITE));
|
|
m_cur_selected_btn->Unbind(wxEVT_PAINT, &FilamentPickerDialog::OnButtonPaint, this);
|
|
m_cur_selected_btn->Refresh();
|
|
}
|
|
|
|
if (selected_btn) {
|
|
// Bind paint event to draw custom green border
|
|
selected_btn->Bind(wxEVT_PAINT, &FilamentPickerDialog::OnButtonPaint, this);
|
|
selected_btn->Refresh();
|
|
}
|
|
|
|
m_cur_selected_btn = selected_btn;
|
|
}
|
|
|
|
void FilamentPickerDialog::CreateMoreInfoButton()
|
|
{
|
|
m_more_btn = new Button(this, "+ " + _L("More Colors"));
|
|
m_more_btn->SetStyle(ButtonStyle::Regular, ButtonType::Expanded);
|
|
}
|
|
|
|
wxColourData FilamentPickerDialog::GetSingleColorData()
|
|
{
|
|
wxColourData data;
|
|
data.SetChooseFull(true);
|
|
if (m_cur_filament_color.ColorCount() > 0) {
|
|
data.SetColour(*m_cur_filament_color.m_colors.begin());
|
|
}
|
|
return data;
|
|
}
|
|
|
|
void FilamentPickerDialog::BindEvents()
|
|
{
|
|
// Bind mouse events for window dragging
|
|
Bind(wxEVT_LEFT_DOWN, &FilamentPickerDialog::OnMouseLeftDown, this);
|
|
Bind(wxEVT_MOTION, &FilamentPickerDialog::OnMouseMove, this);
|
|
Bind(wxEVT_LEFT_UP, &FilamentPickerDialog::OnMouseLeftUp, this);
|
|
|
|
// Essential window events
|
|
Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent& event) {
|
|
if (HasCapture()) {
|
|
ReleaseMouse();
|
|
}
|
|
event.Skip();
|
|
});
|
|
|
|
// Bind more colors button event
|
|
if (m_more_btn) {
|
|
m_more_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) {
|
|
// Pause click detection while color picker is open
|
|
StopClickDetection();
|
|
|
|
wxColourData original_data = GetSingleColorData();
|
|
wxColourData result = show_sys_picker_dialog(this, original_data);
|
|
|
|
// Resume click detection after color picker closes
|
|
StartClickDetection();
|
|
|
|
// Check if user actually selected a different color
|
|
if (result.GetColour() != original_data.GetColour()) {
|
|
wxColour selected_color = result.GetColour();
|
|
|
|
// Update m_current_filament_color with the selected color
|
|
m_cur_filament_color.m_colors.clear();
|
|
m_cur_filament_color.m_colors.insert(selected_color);
|
|
m_cur_filament_color.m_color_type = FilamentColor::ColorType::SINGLE_CLR;
|
|
|
|
// Update preview
|
|
UpdateCustomColorPreview(selected_color);
|
|
|
|
// Clear currently selected button since custom color selected
|
|
UpdateButtonStates(nullptr);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Bind OK button event
|
|
if (m_ok_btn) {
|
|
m_ok_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) {
|
|
EndModal(wxID_OK);
|
|
});
|
|
}
|
|
|
|
// Bind Cancel button event
|
|
if (m_cancel_btn) {
|
|
m_cancel_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) {
|
|
EndModal(wxID_CANCEL);
|
|
});
|
|
}
|
|
}
|
|
|
|
#ifdef __WXGTK__
|
|
void FilamentPickerDialog::OnWindowCreate(wxWindowCreateEvent& event)
|
|
{
|
|
// GTK platform needs to wait for window creation
|
|
SetWindowShape();
|
|
}
|
|
#endif
|
|
|
|
void FilamentPickerDialog::OnMouseLeftDown(wxMouseEvent& event)
|
|
{
|
|
// Only allow dragging from empty areas (not from buttons or other controls)
|
|
wxWindow* hit_window = wxFindWindowAtPoint(ClientToScreen(event.GetPosition()));
|
|
if (hit_window && hit_window != this) {
|
|
// Click was on a child control, don't drag
|
|
event.Skip();
|
|
return;
|
|
}
|
|
|
|
// Release any existing capture first
|
|
if (HasCapture()) {
|
|
ReleaseMouse();
|
|
}
|
|
|
|
CaptureMouse();
|
|
wxPoint pt = ClientToScreen(event.GetPosition());
|
|
wxPoint origin = GetPosition();
|
|
int dx = pt.x - origin.x;
|
|
int dy = pt.y - origin.y;
|
|
m_drag_delta = wxPoint(dx, dy);
|
|
|
|
// Don't skip the event for dragging to work properly
|
|
}
|
|
|
|
void FilamentPickerDialog::OnMouseMove(wxMouseEvent& event)
|
|
{
|
|
wxPoint pt = event.GetPosition();
|
|
|
|
if (event.Dragging() && event.LeftIsDown() && HasCapture()) {
|
|
wxPoint pos = ClientToScreen(pt);
|
|
Move(wxPoint(pos.x - m_drag_delta.x, pos.y - m_drag_delta.y));
|
|
}
|
|
|
|
event.Skip();
|
|
}
|
|
|
|
void FilamentPickerDialog::OnMouseLeftUp(wxMouseEvent& event)
|
|
{
|
|
if (HasCapture()) {
|
|
ReleaseMouse();
|
|
}
|
|
|
|
event.Skip();
|
|
}
|
|
|
|
void FilamentPickerDialog::OnButtonPaint(wxPaintEvent& event)
|
|
{
|
|
wxWindow* button = dynamic_cast<wxWindow*>(event.GetEventObject());
|
|
if (!button) {
|
|
event.Skip();
|
|
return;
|
|
}
|
|
|
|
// Create paint DC and let default painting happen first
|
|
wxPaintDC dc(button);
|
|
|
|
//Clear the button with white background
|
|
dc.SetBrush(wxBrush(*wxTRANSPARENT_BRUSH));
|
|
dc.SetPen(*wxTRANSPARENT_PEN);
|
|
dc.DrawRectangle(0, 0, COLOR_BTN_SIZE.GetWidth(), COLOR_BTN_SIZE.GetHeight());
|
|
|
|
// Draw the bitmap in the center
|
|
wxBitmapButton* bmpBtn = dynamic_cast<wxBitmapButton*>(button);
|
|
if (bmpBtn && bmpBtn->GetBitmap().IsOk()) {
|
|
wxBitmap bmp = bmpBtn->GetBitmap();
|
|
int x = (COLOR_BTN_SIZE.GetWidth() - COLOR_BTN_BITMAP_SIZE.GetWidth()) / 2;
|
|
int y = (COLOR_BTN_SIZE.GetHeight() - COLOR_BTN_BITMAP_SIZE.GetHeight()) / 2;
|
|
dc.DrawBitmap(bmp, x, y, true);
|
|
}
|
|
|
|
// Draw the green border
|
|
dc.SetPen(wxPen(wxColour("#009688"), 2)); // Green pen, 2px thick
|
|
dc.SetBrush(*wxTRANSPARENT_BRUSH);
|
|
dc.DrawRectangle(1, 1, COLOR_BTN_SIZE.GetWidth() - 1, COLOR_BTN_SIZE.GetHeight() - 1);
|
|
}
|
|
|
|
bool FilamentPickerDialog::IsClickOnTopMostWindow(const wxPoint& mouse_pos)
|
|
{
|
|
wxWindow* main_window = wxGetApp().mainframe;
|
|
if (!main_window) {
|
|
return false;
|
|
}
|
|
|
|
wxRect main_rect = main_window->GetScreenRect();
|
|
bool in_main_app = main_rect.Contains(mouse_pos);
|
|
|
|
if (!in_main_app) {
|
|
return false;
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
// Windows: Use WindowFromPoint to check actual topmost window
|
|
POINT pt = {mouse_pos.x, mouse_pos.y};
|
|
HWND hwnd_at_point = WindowFromPoint(pt);
|
|
HWND main_hwnd = (HWND)main_window->GetHandle();
|
|
|
|
// Check if clicked window belongs to our main window hierarchy
|
|
HWND parent_hwnd = hwnd_at_point;
|
|
while (parent_hwnd != NULL) {
|
|
if (parent_hwnd == main_hwnd) {
|
|
return true;
|
|
}
|
|
parent_hwnd = ::GetParent(parent_hwnd);
|
|
}
|
|
return false;
|
|
|
|
#elif defined(__WXOSX__)
|
|
// macOS: Use focus and active window check
|
|
return (wxGetActiveWindow() == main_window) || main_window->HasFocus();
|
|
|
|
#else
|
|
// Linux: Use focus check (similar to macOS)
|
|
return (wxGetActiveWindow() == main_window) || main_window->HasFocus();
|
|
#endif
|
|
}
|
|
|
|
void FilamentPickerDialog::StartClickDetection()
|
|
{
|
|
if (m_click_timer) {
|
|
StopClickDetection();
|
|
}
|
|
|
|
m_click_timer = new wxTimer(this, wxID_ANY);
|
|
Bind(wxEVT_TIMER, &FilamentPickerDialog::OnTimerCheck, this, m_click_timer->GetId());
|
|
m_click_timer->Start(50);
|
|
}
|
|
|
|
void FilamentPickerDialog::StopClickDetection()
|
|
{
|
|
if (m_click_timer) {
|
|
m_click_timer->Stop();
|
|
delete m_click_timer;
|
|
m_click_timer = nullptr;
|
|
}
|
|
}
|
|
|
|
void FilamentPickerDialog::CleanupTimers()
|
|
{
|
|
StopClickDetection();
|
|
|
|
if (m_flash_timer) {
|
|
m_flash_timer->Stop();
|
|
delete m_flash_timer;
|
|
m_flash_timer = nullptr;
|
|
}
|
|
}
|
|
|
|
// Only perform complex detection when the mouse state actually changes
|
|
void FilamentPickerDialog::OnTimerCheck(wxTimerEvent& event)
|
|
{
|
|
static wxPoint last_mouse_pos(-1, -1);
|
|
wxPoint current_pos = wxGetMousePosition();
|
|
|
|
// If the mouse position and button state haven't changed, skip the detection
|
|
if (current_pos == last_mouse_pos &&
|
|
wxGetMouseState().LeftIsDown() == m_last_mouse_down) {
|
|
return;
|
|
}
|
|
|
|
last_mouse_pos = current_pos;
|
|
|
|
wxPoint mouse_pos = wxGetMousePosition();
|
|
wxRect window_rect = GetScreenRect();
|
|
|
|
// Check if mouse button state changed
|
|
bool mouse_down = wxGetMouseState().LeftIsDown();
|
|
|
|
if (mouse_down != m_last_mouse_down) {
|
|
if (mouse_down) {
|
|
bool in_dialog = window_rect.Contains(mouse_pos);
|
|
bool is_valid_click = IsClickOnTopMostWindow(mouse_pos);
|
|
|
|
if (is_valid_click && !in_dialog) {
|
|
StartFlashing();
|
|
}
|
|
}
|
|
|
|
m_last_mouse_down = mouse_down;
|
|
}
|
|
}
|
|
|
|
void FilamentPickerDialog::OnFlashTimer(wxTimerEvent& event)
|
|
{
|
|
// 5 flashes = 10 steps (semi-transparent -> opaque for each flash)
|
|
if (m_flash_step < 10) {
|
|
if (m_flash_step % 2 == 0) {
|
|
// Even steps: semi-transparent
|
|
SetTransparent(120);
|
|
} else {
|
|
// Odd steps: opaque
|
|
SetTransparent(255);
|
|
}
|
|
|
|
m_flash_step++;
|
|
} else {
|
|
// Complete flash sequence
|
|
SetTransparent(255);
|
|
m_flash_timer->Stop();
|
|
delete m_flash_timer;
|
|
m_flash_timer = nullptr;
|
|
}
|
|
}
|
|
|
|
}} // namespace Slic3r::GUI
|
|
|