#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 "wxExtensions.hpp" #include #include #include #include #ifdef _WIN32 #include #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(wxHORIZONTAL); 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 | wxBOTTOM, FromDIP(8)); main_sizer->AddSpacer(FromDIP(8)); // OK / Cancel buttons wxBoxSizer* btn_sizer = CreateButtonPanel(); main_sizer->Add(btn_sizer, 0, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(10)); container_sizer->Add(main_sizer, 1, wxEXPAND | wxALL, FromDIP(16)); SetSizer(container_sizer); Layout(); container_sizer->Fit(this); // 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 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 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 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 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 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->SetMinSize(wxSize(-1, FromDIP(36))); StateColor btn_bg( std::pair(wxColour(238, 238, 238), StateColor::Hovered), std::pair(wxColour(248, 248, 248), StateColor::Normal) ); m_more_btn->SetBackgroundColor(btn_bg); m_more_btn->SetBorderStyle(wxPENSTYLE_SHORT_DASH); m_more_btn->SetCornerRadius(FromDIP(0)); } wxBoxSizer* FilamentPickerDialog::CreateButtonPanel() { wxBoxSizer* btn_sizer = new wxBoxSizer(wxHORIZONTAL); // Add spacer to push buttons to the right btn_sizer->AddStretchSpacer(); // standard button color style StateColor btn_bg_green( std::pair(wxColour(0, 137, 123), StateColor::Pressed), std::pair(wxColour(38, 166, 154), StateColor::Hovered), std::pair(wxColour(0, 150, 136), StateColor::Normal) ); StateColor btn_bd_green( std::pair(wxColour(0, 150, 136), StateColor::Normal) ); StateColor btn_text_green( std::pair(wxColour(255, 255, 254), StateColor::Normal) ); StateColor btn_bg_white( std::pair(wxColour(206, 206, 206), StateColor::Pressed), std::pair(wxColour(238, 238, 238), StateColor::Hovered), std::pair(wxColour(255, 255, 255), StateColor::Normal) ); StateColor btn_bd_white( std::pair(wxColour(38, 46, 48), StateColor::Normal) ); StateColor btn_text_white( std::pair(wxColour(38, 46, 48), StateColor::Normal) ); // Create Cancel button using project's Button class m_cancel_btn = new Button(this, _L("Cancel"), "", 0, 0, wxID_CANCEL); m_cancel_btn->SetMinSize(wxSize(FromDIP(55), FromDIP(24))); m_cancel_btn->SetCornerRadius(FromDIP(12)); m_cancel_btn->SetBackgroundColor(btn_bg_white); m_cancel_btn->SetBorderColor(btn_bd_white); m_cancel_btn->SetTextColor(btn_text_white); btn_sizer->Add(m_cancel_btn, 0, wxEXPAND, 0); btn_sizer->AddSpacer(FromDIP(10)); // Create OK button using project's Button class m_ok_btn = new Button(this, _L("OK"), "", 0, 0, wxID_OK); m_ok_btn->SetMinSize(wxSize(FromDIP(55), FromDIP(24))); m_ok_btn->SetCornerRadius(FromDIP(12)); m_ok_btn->SetBackgroundColor(btn_bg_green); m_ok_btn->SetBorderColor(btn_bd_green); m_ok_btn->SetTextColor(btn_text_green); m_ok_btn->SetFocus(); btn_sizer->Add(m_ok_btn, 0, wxEXPAND, 0); return btn_sizer; } 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(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(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