mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-15 11:51:33 +00:00
* Add runtime display backend detection for Wayland support Add LinuxDisplayBackend utility to detect X11 vs Wayland at runtime using GDK_IS_X11_DISPLAY / GDK_IS_WAYLAND_DISPLAY macros. This is the foundation for removing the forced GDK_BACKEND=x11 and enabling native Wayland support. - New files: LinuxDisplayBackend.hpp/.cpp with get_linux_display_backend(), is_running_on_wayland(), and is_running_on_x11() - Propagate wxHAVE_GDK_X11 / wxHAVE_GDK_WAYLAND from FindGTK3.cmake as compile definitions to libslic3r_gui - No-op on non-Linux platforms (returns Unknown / false) * Fix Phase 1 code quality: pragma once, source ordering, static cache * Make X11 initialization conditional for Wayland support Remove the unconditional GDK_BACKEND=x11 force that blocked native Wayland. Replace with conditional logic: - EGL safety fallback: re-force X11 only when wxUSE_GLCANVAS_EGL is off and WAYLAND_DISPLAY is set, with a warning log - XInitThreads() only called when DISPLAY is set (X11 in use) - __GLX_VENDOR_LIBRARY_NAME only set when DISPLAY is present (GLX-specific) - WEBKIT_DISABLE_COMPOSITING_MODE only set under XWayland (both DISPLAY and WAYLAND_DISPLAY present) - Guard X11/Xlib.h include with __has_include for robustness - Restore display validation to accept either DISPLAY or WAYLAND_DISPLAY This is Phase 2 of the Wayland support plan. * Fix Phase 2: safer EGL macro check, add clarifying comments * Add GLAD2 library and replace GLEW linkage in build system Set up GLAD2 as a static library to replace GLEW for OpenGL loading. GLAD2 supports both GLX and EGL, which is required for Wayland support. - Create src/glad/ with pre-generated GLAD2 sources (GL 4.6 compat) - Add src/glad/CMakeLists.txt building glad as a static library - Wire glad into src/CMakeLists.txt before libvgcode - Modify libvgcode to use shared glad for GL path (keeps local copy only for GLES2/Emscripten) to avoid duplicate symbol conflicts - Replace GLEW::GLEW with glad in libslic3r_gui link libraries Note: GLEW is kept in deps for OpenCSG. Code migration from GL/glew.h to glad/gl.h headers will follow in Phase 3B+3C. * Fix Phase 3A+3D: libvgcode GLAD include, dead files, dlopen dep, OpenGL link var * Migrate from GLEW to GLAD: replace headers and API calls across codebase Replace all #include <GL/glew.h> with <glad/gl.h> across 49 source files. Migrate GLEW API calls to GLAD equivalents: - glewInit/glewExperimental -> gladLoaderLoadGL() - GLEW_EXT_* / GLEW_ARB_* extension checks -> GLAD_GL_EXT_* / GLAD_GL_ARB_* - Remove GLEW-specific EGL/GLX mismatch #error guards (not needed with GLAD) - Replace unavailable EXT symbols with core GL equivalents in GLCanvas3D.cpp (GL_MAX_SAMPLES, glRenderbufferStorageMultisample, glBlitFramebuffer, GL_READ/DRAW_FRAMEBUFFER) - Update log messages from glewInit to gladLoadGL * Fix Phase 3B+3C: remove GLEW find, clean EXT symbols, update attribution - Remove find_package(GLEW) block from root CMakeLists.txt since GLEW is no longer linked by any main application code - Remove "glew" from SLIC3R_STATIC option description - Replace all remaining EXT framebuffer symbols with core equivalents in render_thumbnail_framebuffer_ext and _rectangular_selection_picking_pass - Update AboutDialog credits from GLEW to GLAD * Enable EGL in wxWidgets and add runtime GLX/EGL selection for Wayland - Set wxUSE_GLCANVAS_EGL=ON in wxWidgets build and Flatpak manifest - Add PreferGLX() call on X11 sessions for driver compatibility - Remove Phase 2 safety fallback (EGL is now always compiled in) - Guard SwapBuffers against hidden canvases to prevent Wayland stalls * Fix Phase 4: move PreferGLX to app startup, fix FPS counter guard Move wxGLCanvas::PreferGLX() from OpenGLManager::create_wxglcanvas() (static initializer) to GUI_App::on_init_inner() before any wxGLCanvas is constructed. This prevents a race where SkipPartCanvas could trigger wxGLBackend::Init() before the GLX preference is set. The new location also adds explicit is_running_on_wayland() detection with a warning for unknown backends. Move increment_fps_counter() inside the IsShownOnScreen() guard so FPS is only counted when a frame is actually swapped. * Update GLFW from 3.3.7 to 3.4 for runtime Wayland/X11 backend selection Replace the compile-time GLFW_USE_WAYLAND flag (which locked to a single backend) with GLFW 3.4's GLFW_BUILD_WAYLAND + GLFW_BUILD_X11 flags that build both backends and auto-select at runtime based on the available display server. This enables the CLI thumbnail renderer to work on both Wayland and X11 sessions without separate builds. * wayland: Fix UI call sites that rely on global screen coordinates On Wayland, wxGetMousePosition() returns (0,0) and SetPosition() is a no-op for top-level windows. Fix the highest-impact call sites: - GLCanvas3D: Use cached m_mouse.position from event handlers instead of wxGetMousePosition() + ScreenToClient() in get_local_mouse_position() - Plater: Use event-relative coords via ClientToScreen(e.GetPosition()) instead of wxGetMousePosition() in 3 leave-window handlers - BBLTopbar: Use event.GetPosition() and FindToolByPosition() directly in mouse handlers instead of wxGetMousePosition()/FindToolByCurrentPosition() - Search: Use focus-based dismiss logic on Wayland instead of wxGetMousePosition()-based rect checks in SearchDialog and SearchObjectDialog - GUI_App: Skip SetPosition() in window_pos_restore() on Wayland where it is a no-op; still restore size and maximize state - Button: Position tooltip relative to button widget via ClientToScreen instead of wxGetMousePosition() * Fix SearchDialog Wayland dismiss: guard against search_line focus * flatpak: Add Wayland socket permission for native Wayland support * spec * Fix crash on Wayland when wxWidgets lacks EGL support Restore the safety fallback that forces GDK_BACKEND=x11 when wxWidgets was not built with wxUSE_GLCANVAS_EGL=ON. Without this, the GLX backend tries to access a non-existent X11 display on native Wayland, crashing in wxGLCanvas::IsDisplaySupported() with SIGSEGV at offset 0xe4. Also add a defense-in-depth guard in detect_multisample() that skips the IsDisplaySupported call entirely on Wayland without EGL. Root cause: deps/wxWidgets must be rebuilt after enabling EGL. The compile-time check in OrcaSlicer.cpp detects the mismatch and falls back safely. * Fix EGL detection: use wxHAS_EGL instead of wxUSE_GLCANVAS_EGL wxUSE_GLCANVAS_EGL is a CMake build option, NOT a C++ preprocessor macro. The actual macro defined in wxWidgets setup.h is wxHAS_EGL. All compile-time EGL checks were using the wrong macro, causing the safety fallback to always trigger even with a properly built EGL-enabled wxWidgets. * Fix GL function pointers invalidated on Wayland/EGL gladLoaderLoadGL() dlopen's libGL.so.1 to resolve GL function pointers via dlsym, then immediately dlclose's the handle. On X11/GLX this is fine because the GLX context keeps libGL.so mapped. On Wayland/EGL, nothing else holds libGL.so open, so dlclose unmaps it and all function pointers become dangling — causing SIGSEGV on the first GL call. Fix: on Wayland, use gladLoadGL(eglGetProcAddress) which resolves function pointers through the EGL loader without opening/closing libGL.so. * fix crash on start and various rendering issues * fix crash on close * small refactor * move GPU selection to desktop file * clean up a bit * clean up more * fix appimage error
556 lines
16 KiB
C++
556 lines
16 KiB
C++
#include "Button.hpp"
|
|
#include "Label.hpp"
|
|
|
|
#include <wx/dcgraph.h>
|
|
#include <wx/tipwin.h>
|
|
#ifdef __APPLE__
|
|
#include "libslic3r/MacUtils.hpp"
|
|
#endif
|
|
BEGIN_EVENT_TABLE(Button, StaticBox)
|
|
|
|
EVT_LEFT_DOWN(Button::mouseDown)
|
|
EVT_LEFT_UP(Button::mouseReleased)
|
|
EVT_MOUSE_CAPTURE_LOST(Button::mouseCaptureLost)
|
|
EVT_KEY_DOWN(Button::keyDownUp)
|
|
EVT_KEY_UP(Button::keyDownUp)
|
|
|
|
// catch paint events
|
|
EVT_PAINT(Button::paintEvent)
|
|
|
|
END_EVENT_TABLE()
|
|
|
|
/*
|
|
* Called by the system of by wxWidgets when the panel needs
|
|
* to be redrawn. You can also trigger this call by
|
|
* calling Refresh()/Update().
|
|
*/
|
|
|
|
Button::Button()
|
|
: paddingSize(10, 8)
|
|
{
|
|
background_color = StateColor(
|
|
std::make_pair(0xF0F0F1, (int) StateColor::Disabled),
|
|
std::make_pair(0x52c7b8, (int) StateColor::Hovered | StateColor::Checked),
|
|
std::make_pair(0x009688, (int) StateColor::Checked),
|
|
std::make_pair(*wxLIGHT_GREY, (int) StateColor::Hovered),
|
|
std::make_pair(*wxWHITE, (int) StateColor::Normal));
|
|
text_color = StateColor(
|
|
std::make_pair(*wxLIGHT_GREY, (int) StateColor::Disabled),
|
|
std::make_pair(*wxBLACK, (int) StateColor::Normal));
|
|
}
|
|
|
|
Button::Button(wxWindow* parent, wxString text, wxString icon, long style, int iconSize, wxWindowID btn_id)
|
|
: Button()
|
|
{
|
|
Create(parent, text, icon, style, iconSize, btn_id);
|
|
}
|
|
|
|
bool Button::Create(wxWindow* parent, wxString text, wxString icon, long style, int iconSize, wxWindowID btn_id)
|
|
{
|
|
StaticBox::Create(parent, btn_id, wxDefaultPosition, wxDefaultSize, style);
|
|
state_handler.attach(std::vector<StateColor const*>{&text_color});
|
|
state_handler.update_binds();
|
|
//BBS set default font
|
|
SetFont(Label::Body_14);
|
|
wxWindow::SetLabel(text);
|
|
if (!icon.IsEmpty()) {
|
|
//BBS set button icon default size to 20
|
|
this->active_icon = ScalableBitmap(this, icon.ToStdString(), iconSize > 0 ? iconSize : 20);
|
|
}
|
|
messureSize();
|
|
return true;
|
|
}
|
|
|
|
void Button::SetLabel(const wxString& label)
|
|
{
|
|
if (label != wxWindow::GetLabel()) {
|
|
wxWindow::SetLabel(label);
|
|
messureSize();
|
|
Refresh();
|
|
}
|
|
}
|
|
|
|
bool Button::SetFont(const wxFont& font)
|
|
{
|
|
wxWindow::SetFont(font);
|
|
messureSize();
|
|
Refresh();
|
|
return true;
|
|
}
|
|
|
|
void Button::SetIcon(const wxString& icon)
|
|
{
|
|
auto tmpBitmap = ScalableBitmap(this, icon.ToStdString(), this->active_icon.px_cnt());
|
|
if (!icon.IsEmpty()) {
|
|
//BBS set button icon default size to 20
|
|
if (!tmpBitmap.bmp().IsSameAs(this->active_icon.bmp())) {
|
|
this->active_icon = tmpBitmap;
|
|
Refresh();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
this->active_icon = ScalableBitmap();
|
|
Refresh();
|
|
}
|
|
}
|
|
|
|
void Button::SetInactiveIcon(const wxString &icon)
|
|
{
|
|
if (!icon.IsEmpty()) {
|
|
// BBS set button icon default size to 20
|
|
this->inactive_icon = ScalableBitmap(this, icon.ToStdString(), this->active_icon.px_cnt());
|
|
} else {
|
|
this->inactive_icon = ScalableBitmap();
|
|
}
|
|
Refresh();
|
|
}
|
|
|
|
void Button::SetMinSize(const wxSize& size)
|
|
{
|
|
minSize = size;
|
|
messureSize();
|
|
}
|
|
|
|
void Button::SetMaxSize(const wxSize& size)
|
|
{
|
|
wxWindow::SetMaxSize(size);
|
|
messureSize();
|
|
}
|
|
|
|
void Button::SetPaddingSize(const wxSize& size)
|
|
{
|
|
paddingSize = size;
|
|
messureSize();
|
|
}
|
|
|
|
void Button::SetTextColor(StateColor const& color)
|
|
{
|
|
text_color = color;
|
|
state_handler.update_binds();
|
|
Refresh();
|
|
}
|
|
|
|
void Button::SetTextColorNormal(wxColor const &color)
|
|
{
|
|
text_color.setColorForStates(color, 0);
|
|
Refresh();
|
|
}
|
|
|
|
bool Button::Enable(bool enable)
|
|
{
|
|
bool result = wxWindow::Enable(enable);
|
|
if (result) {
|
|
wxCommandEvent e(EVT_ENABLE_CHANGED);
|
|
e.SetEventObject(this);
|
|
GetEventHandler()->ProcessEvent(e);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void Button::SetCanFocus(bool canFocus) {
|
|
StaticBox::SetCanFocus(canFocus);
|
|
this->canFocus = canFocus;
|
|
}
|
|
|
|
void Button::SetValue(bool state)
|
|
{
|
|
if (GetValue() == state) return;
|
|
state_handler.set_state(state ? StateHandler::Checked : 0, StateHandler::Checked);
|
|
}
|
|
|
|
bool Button::GetValue() const { return state_handler.states() & StateHandler::Checked; }
|
|
|
|
void Button::SetCenter(bool isCenter)
|
|
{
|
|
this->isCenter = isCenter; }
|
|
|
|
void Button::SetVertical(bool vertical)
|
|
{
|
|
this->vertical = vertical;
|
|
messureSize();
|
|
}
|
|
|
|
// Background Foreground Border on focus
|
|
// Button Colors 0-Disabled 1-Pressed 2-Hover 3-Normal 4-Enabled 5-Disabled 6-Normal 7-Hover 8-Dark 9-Light
|
|
wxString btn_regular[10] = {"#DFDFDF", "#DFDFDF", "#D4D4D4", "#DFDFDF", "#DFDFDF", "#6B6A6A", "#262E30", "#262E30", "#009688", "#009688"};
|
|
wxString btn_confirm[10] = {"#DFDFDF", "#009688", "#26A69A", "#009688", "#009688", "#6B6A6A", "#FEFEFE", "#FEFEFE", "#22bfb0", "#00FFD4"};
|
|
wxString btn_alert[10] = {"#DFDFDF", "#DFDFDF", "#E14747", "#DFDFDF", "#DFDFDF", "#6B6A6A", "#262E30", "#FFFFFD", "#009688", "#009688"};
|
|
wxString btn_disabled[10] = {"#DFDFDF", "#DFDFDF", "#DFDFDF", "#DFDFDF", "#DFDFDF", "#6B6A6A", "#6B6A6A", "#262E30", "#DFDFDF", "#DFDFDF"};
|
|
|
|
void Button::SetStyle(const ButtonStyle style, const ButtonType type)
|
|
{
|
|
if (type == ButtonType::Compact) {
|
|
this->SetPaddingSize(FromDIP(wxSize(8,3)));
|
|
this->SetCornerRadius(this->FromDIP(8));
|
|
this->SetFont(Label::Body_10);
|
|
}
|
|
else if (type == ButtonType::Window) {
|
|
this->SetSize(FromDIP(wxSize(58,24)));
|
|
this->SetMinSize(FromDIP(wxSize(58,24)));
|
|
this->SetCornerRadius(this->FromDIP(12));
|
|
this->SetFont(Label::Body_12);
|
|
}
|
|
else if (type == ButtonType::Choice) {
|
|
this->SetMinSize(FromDIP(wxSize(100,32)));
|
|
this->SetPaddingSize(FromDIP(wxSize(12,8)));
|
|
this->SetCornerRadius(this->FromDIP(4));
|
|
this->SetFont(Label::Body_14);
|
|
}
|
|
else if (type == ButtonType::Parameter) {
|
|
this->SetMinSize(FromDIP(wxSize(120,26)));
|
|
this->SetSize(FromDIP(wxSize(120,26)));
|
|
this->SetCornerRadius(this->FromDIP(4));
|
|
this->SetFont(Label::Body_14);
|
|
}
|
|
else if (type == ButtonType::Expanded) {
|
|
this->SetMinSize(FromDIP(wxSize(-1,32)));
|
|
this->SetPaddingSize(FromDIP(wxSize(12,8)));
|
|
this->SetCornerRadius(this->FromDIP(4));
|
|
this->SetFont(Label::Body_14);
|
|
}
|
|
|
|
this->SetBorderWidth(this->FromDIP(1));
|
|
|
|
bool is_dark = StateColor::darkModeColorFor("#FFFFFF") != wxColour("#FFFFFF");
|
|
|
|
auto clr_arr = style == ButtonStyle::Regular ? btn_regular :
|
|
style == ButtonStyle::Confirm ? btn_confirm :
|
|
style == ButtonStyle::Alert ? btn_alert :
|
|
style == ButtonStyle::Disabled ? btn_disabled :
|
|
btn_regular ;
|
|
|
|
auto bg_color = StateColor(
|
|
std::pair(wxColour(clr_arr[0]), (int)StateColor::Disabled),
|
|
std::pair(wxColour(clr_arr[1]), (int)StateColor::Pressed),
|
|
std::pair(wxColour(clr_arr[2]), (int)StateColor::Hovered),
|
|
std::pair(wxColour(clr_arr[3]), (int)StateColor::Normal),
|
|
std::pair(wxColour(clr_arr[4]), (int)StateColor::Enabled)
|
|
);
|
|
bg_color.setTakeFocusedAsHovered(false);
|
|
this->SetBackgroundColor(bg_color);
|
|
wxColour focus_clr = clr_arr[is_dark ? 8 : 9];
|
|
auto border_color = StateColor(
|
|
std::pair(wxColour(clr_arr[0]), (int)StateColor::Disabled),
|
|
std::pair(wxColour(clr_arr[2]), (int)(StateColor::Hovered | ~StateColor::Focused)),
|
|
std::pair(wxColour(focus_clr ), (int)StateColor::Focused),
|
|
std::pair(wxColour(clr_arr[3]), (int)StateColor::Normal)
|
|
);
|
|
border_color.setTakeFocusedAsHovered(false);
|
|
this->SetBorderColor(border_color);
|
|
this->SetTextColor(StateColor(
|
|
std::pair(wxColour(clr_arr[5]), (int)StateColor::Disabled),
|
|
std::pair(wxColour(clr_arr[7]), (int)StateColor::Hovered),
|
|
std::pair(wxColour(clr_arr[6]), (int)StateColor::Normal)
|
|
));
|
|
|
|
m_has_style = true;
|
|
m_style = style;
|
|
m_type = type;
|
|
}
|
|
|
|
void Button::Rescale()
|
|
{
|
|
if (this->active_icon.bmp().IsOk())
|
|
this->active_icon.msw_rescale();
|
|
|
|
if (this->inactive_icon.bmp().IsOk())
|
|
this->inactive_icon.msw_rescale();
|
|
|
|
messureSize();
|
|
|
|
if(m_has_style)
|
|
SetStyle(m_style, m_type);
|
|
|
|
Refresh();
|
|
}
|
|
|
|
void Button::paintEvent(wxPaintEvent& evt)
|
|
{
|
|
// depending on your system you may need to look at double-buffered dcs
|
|
wxPaintDC dc(this);
|
|
render(dc);
|
|
}
|
|
|
|
/*
|
|
* Here we do the actual rendering. I put it in a separate
|
|
* method so that it can work no matter what type of DC
|
|
* (e.g. wxPaintDC or wxClientDC) is used.
|
|
*/
|
|
void Button::render(wxDC& dc)
|
|
{
|
|
StaticBox::render(dc);
|
|
int states = state_handler.states();
|
|
wxSize size = GetSize();
|
|
dc.SetBrush(*wxTRANSPARENT_BRUSH);
|
|
// calc content size
|
|
wxSize szIcon;
|
|
wxSize textSize = this->textSize.GetSize();
|
|
|
|
ScalableBitmap icon;
|
|
if (m_selected || ((states & (int)StateColor::State::Hovered) != 0))
|
|
icon = active_icon;
|
|
else
|
|
icon = inactive_icon;
|
|
wxSize padding = this->paddingSize;
|
|
int spacing = 5;
|
|
// Wrap text
|
|
auto text = GetLabel();
|
|
if (vertical && textSize.x + padding.x * 2 > size.x) {
|
|
Label::split_lines(dc, size.x - padding.x * 2, text, text, 2);
|
|
textSize = dc.GetMultiLineTextExtent(text);
|
|
if (padding.x * 2 + textSize.x > size.x) {
|
|
text = wxControl::Ellipsize(text, dc, wxELLIPSIZE_END, size.x - padding.x * 2);
|
|
textSize = dc.GetMultiLineTextExtent(text);
|
|
}
|
|
}
|
|
auto szContent = textSize;
|
|
if (icon.bmp().IsOk()) {
|
|
if (szContent.y > 0) {
|
|
//BBS norrow size between text and icon
|
|
if (vertical)
|
|
szContent.y += spacing;
|
|
else
|
|
szContent.x += spacing;
|
|
}
|
|
szIcon = icon.GetBmpSize();
|
|
if (vertical) {
|
|
szContent.y += szIcon.y;
|
|
if (szIcon.x > szContent.x) szContent.x = szIcon.x;
|
|
} else {
|
|
szContent.x += szIcon.x;
|
|
if (szIcon.y > szContent.y) szContent.y = szIcon.y;
|
|
}
|
|
if (szContent.x > size.x) {
|
|
int d = std::min(padding.x, (szContent.x - size.x) / 2);
|
|
padding.x -= d;
|
|
szContent.x -= d;
|
|
}
|
|
}
|
|
// move to center
|
|
wxRect rcContent = { {0, 0}, size };
|
|
if (isCenter) {
|
|
wxSize offset = (size - szContent) / 2;
|
|
if (offset.x < 0) offset.x = 0;
|
|
rcContent.Deflate(offset.x, offset.y);
|
|
}
|
|
// start draw
|
|
wxPoint pt = rcContent.GetLeftTop();
|
|
if (icon.bmp().IsOk()) {
|
|
if (vertical)
|
|
pt.x += (rcContent.width - szIcon.x) / 2;
|
|
else
|
|
pt.y += (rcContent.height - szIcon.y) / 2;
|
|
dc.DrawBitmap(icon.bmp(), pt);
|
|
//BBS norrow size between text and icon
|
|
if (vertical) {
|
|
pt.y += szIcon.y + spacing;
|
|
pt.x = rcContent.x;
|
|
} else {
|
|
pt.x += szIcon.x + spacing;
|
|
pt.y = rcContent.y;
|
|
}
|
|
}
|
|
if (!text.IsEmpty()) {
|
|
if (vertical) {
|
|
pt.x += (rcContent.width - textSize.x) / 2;
|
|
} else {
|
|
if (pt.x + textSize.x > size.x)
|
|
text = wxControl::Ellipsize(text, dc, wxELLIPSIZE_END, size.x - pt.x);
|
|
pt.y += (rcContent.height - textSize.y) / 2;
|
|
}
|
|
dc.SetTextForeground(text_color.colorForStates(states));
|
|
#if 0
|
|
dc.SetBrush(*wxLIGHT_GREY);
|
|
dc.SetPen(wxPen(*wxLIGHT_GREY));
|
|
dc.DrawRectangle(pt, textSize.GetSize());
|
|
#endif
|
|
dc.DrawText(text, pt);
|
|
}
|
|
}
|
|
|
|
void Button::messureSize()
|
|
{
|
|
wxClientDC dc(this);
|
|
dc.GetTextExtent(GetLabel(), &textSize.width, &textSize.height, &textSize.x, &textSize.y);
|
|
wxSize szContent = textSize.GetSize();
|
|
if (this->active_icon.bmp().IsOk()) {
|
|
if (szContent.y > 0) {
|
|
//BBS norrow size between text and icon
|
|
if (vertical)
|
|
szContent.y += 5;
|
|
else
|
|
szContent.x += 5;
|
|
}
|
|
wxSize szIcon = this->active_icon.GetBmpSize();
|
|
if (vertical) {
|
|
szContent.y += szIcon.y;
|
|
if (szIcon.x > szContent.x) szContent.x = szIcon.x;
|
|
} else {
|
|
szContent.x += szIcon.x;
|
|
if (szIcon.y > szContent.y) szContent.y = szIcon.y;
|
|
}
|
|
}
|
|
wxSize size = szContent + paddingSize * 2;
|
|
if (minSize.GetHeight() > 0)
|
|
size.SetHeight(minSize.GetHeight());
|
|
|
|
if (auto w = GetMaxWidth(); w > 0 && size.GetWidth() > w) {
|
|
size.SetWidth(GetMaxWidth());
|
|
|
|
const wxString& tip_str = GetToolTipText();
|
|
if (tip_str.IsEmpty()) {
|
|
SetToolTip(GetLabel());
|
|
}
|
|
}
|
|
|
|
if (minSize.GetWidth() > size.GetWidth())
|
|
wxWindow::SetMinSize(minSize);
|
|
else
|
|
wxWindow::SetMinSize(size);
|
|
}
|
|
|
|
void Button::mouseDown(wxMouseEvent& event)
|
|
{
|
|
event.Skip();
|
|
pressedDown = true;
|
|
if (canFocus)
|
|
SetFocus();
|
|
if (!HasCapture())
|
|
CaptureMouse();
|
|
}
|
|
|
|
void Button::mouseReleased(wxMouseEvent& event)
|
|
{
|
|
event.Skip();
|
|
if (pressedDown) {
|
|
pressedDown = false;
|
|
if (HasCapture())
|
|
ReleaseMouse();
|
|
if (wxRect({0, 0}, GetSize()).Contains(event.GetPosition()))
|
|
sendButtonEvent();
|
|
}
|
|
}
|
|
|
|
void Button::mouseCaptureLost(wxMouseCaptureLostEvent &event)
|
|
{
|
|
wxMouseEvent evt;
|
|
mouseReleased(evt);
|
|
}
|
|
|
|
void Button::keyDownUp(wxKeyEvent &event)
|
|
{
|
|
if (event.GetKeyCode() == WXK_SPACE || event.GetKeyCode() == WXK_RETURN) {
|
|
wxMouseEvent evt(event.GetEventType() == wxEVT_KEY_UP ? wxEVT_LEFT_UP : wxEVT_LEFT_DOWN);
|
|
event.SetEventObject(this);
|
|
GetEventHandler()->ProcessEvent(evt);
|
|
return;
|
|
}
|
|
if (event.GetEventType() == wxEVT_KEY_DOWN &&
|
|
(event.GetKeyCode() == WXK_TAB || event.GetKeyCode() == WXK_LEFT || event.GetKeyCode() == WXK_RIGHT
|
|
|| event.GetKeyCode() == WXK_UP || event.GetKeyCode() == WXK_DOWN))
|
|
HandleAsNavigationKey(event);
|
|
else
|
|
event.Skip();
|
|
}
|
|
|
|
void Button::sendButtonEvent()
|
|
{
|
|
wxCommandEvent event(wxEVT_COMMAND_BUTTON_CLICKED, GetId());
|
|
event.SetEventObject(this);
|
|
GetEventHandler()->ProcessEvent(event);
|
|
}
|
|
|
|
#ifdef __WIN32__
|
|
|
|
WXLRESULT Button::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam)
|
|
{
|
|
if (nMsg == WM_GETDLGCODE) { return DLGC_WANTMESSAGE; }
|
|
if (nMsg == WM_KEYDOWN) {
|
|
wxKeyEvent event(CreateKeyEvent(wxEVT_KEY_DOWN, wParam, lParam));
|
|
switch (wParam) {
|
|
case WXK_RETURN: { // WXK_RETURN key is handled by default button
|
|
GetEventHandler()->ProcessEvent(event);
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
return wxWindow::MSWWindowProc(nMsg, wParam, lParam);
|
|
}
|
|
|
|
#endif
|
|
|
|
bool Button::AcceptsFocus() const { return canFocus; }
|
|
|
|
void Button::EnableTooltipEvenDisabled()
|
|
{
|
|
#if defined(_MSC_VER) || defined(_WIN32)
|
|
auto parent = this->GetParent();
|
|
if (parent)
|
|
{
|
|
parent->Bind(wxEVT_MOTION, &Button::OnParentMotion, this);
|
|
parent->Bind(wxEVT_LEAVE_WINDOW, &Button::OnParentLeave, this);
|
|
};
|
|
#endif
|
|
};
|
|
|
|
void Button::OnParentMotion(wxMouseEvent& event)
|
|
{
|
|
auto parent = this->GetParent();
|
|
if (!parent) return event.Skip();
|
|
|
|
wxPoint pos = parent->ClientToScreen(event.GetPosition());
|
|
wxRect screen_rect = this->GetScreenRect();
|
|
wxString tip = this->GetToolTipText();
|
|
if (!tip.IsEmpty() && !this->IsEnabled() && screen_rect.Contains(pos))
|
|
{
|
|
if (!tipWindow)
|
|
{
|
|
tipWindow = new wxTipWindow(this, tip);
|
|
tipWindow->Bind(wxEVT_DESTROY, [this](wxEvent& event) { this->tipWindow = nullptr;});
|
|
tipWindow->Enable(false);
|
|
}
|
|
|
|
if (tipWindow->GetLabel() != tip)
|
|
{
|
|
tipWindow->SetLabel(tip);
|
|
}
|
|
|
|
// Position tooltip relative to the button widget itself rather than
|
|
// using wxGetMousePosition() which returns (0,0) on Wayland.
|
|
tipWindow->Position(this->ClientToScreen(wxPoint(0, 0)), this->GetSize());
|
|
tipWindow->Popup();
|
|
}
|
|
else
|
|
{
|
|
if (tipWindow)
|
|
{
|
|
delete tipWindow;
|
|
tipWindow = nullptr;
|
|
}
|
|
}
|
|
|
|
event.Skip();
|
|
}
|
|
|
|
void Button::OnParentLeave(wxMouseEvent& event)
|
|
{
|
|
auto parent = this->GetParent();
|
|
if (!parent) return event.Skip();
|
|
|
|
if (tipWindow)
|
|
{
|
|
wxPoint pos = parent->ClientToScreen(event.GetPosition());
|
|
wxRect screen_rect = this->GetScreenRect();
|
|
wxString tip = this->GetToolTipText();
|
|
if (!screen_rect.Contains(pos))
|
|
{
|
|
tipWindow->Dismiss();
|
|
delete tipWindow;
|
|
tipWindow = nullptr;
|
|
}
|
|
}
|
|
|
|
event.Skip();
|
|
}
|