mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-08-09 19:17:36 +00:00
* For dialog without explicitly `SetMinSize`, we should use `SetSizerAndFit` instead, otherwise the dialog will not show correctly on GTK3. (OrcaSlicer/OrcaSlicer#14561)
- and if `SetSizer` is called before the full layout has been built, then an extra `SetSizeHints` should be called before layout/fit so the min size can be properly set automatically based on children's min sizes accordingly.
* Fix GTK3 dialog min size: SetSizer → SetSizerAndFit for dialogs without explicit SetMinSize
Replace SetSizer() with SetSizerAndFit() in 11 dialog constructors that
neither call SetMinSize() nor SetSizeHints(), ensuring proper minimum
size propagation from child widgets on GTK3.
SetSizerAndFit internally calls sizer->SetSizeHints(window), which
sets the window's minimum size based on children — the same fix
applied to ProjectDropDialog in 8a7662083e.
Also drop sizer->Fit(this) calls where present, since they only
resize but don't set the min size hint needed by GTK3.
Co-Authored-By: Claude <noreply@anthropic.com>
* Update code style
* Update TroubleshootDialog.hpp
* Fix unsaved preset dialog layout
* Fix MsgDialog layout
* Fix other 3 instances in MsgDialog.cpp
* Fix a few more instances
* Fix printer option dialog too big on Windows
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: yw4z <ywsyildiz@gmail.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, [btn](wxMouseEvent& evt) {
|
|
evt.Skip();
|
|
});
|
|
|
|
btn->Bind(wxEVT_LEAVE_WINDOW, [btn](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
|
|
|