Class for RadioGroup & LabeledStaticBox and improvements for Calibration Dialogs, Windows with wxStaticBoxSizer (#9797)

* update

* Update calib_dlg.cpp

* Update LabeledStaticBox.hpp

* Update calib_dlg.cpp

* update

* update

* RadioGroup fix

* update

* update

* update

* update

* RadioGroup

* Fix render issue when position is set to default (-1)

* Fix macOS render issue by removing default NSBox border

* Fix compile

---------

Co-authored-by: Noisyfox <timemanager.rick@gmail.com>
This commit is contained in:
yw4z
2025-06-12 15:15:02 +03:00
committed by GitHub
parent 5c2fe4da87
commit 8fff1caa39
14 changed files with 833 additions and 331 deletions

View File

@@ -0,0 +1,181 @@
#include "LabeledStaticBox.hpp"
#include "libslic3r/Utils.hpp"
#include "../GUI.hpp"
#include "../GUI_Utils.hpp"
#include "Label.hpp"
/*
Fix label overflowing to inner frame
Fix use elypsis if text too long
setmin size
*/
LabeledStaticBox::LabeledStaticBox()
: state_handler(this)
{
m_radius = 3;
m_border_width = 1;
m_font = Label::Head_14;
text_color = StateColor(
std::make_pair(0x363636, (int) StateColor::Normal),
std::make_pair(0x6B6B6B, (int) StateColor::Disabled)
);
background_color = StateColor(
std::make_pair(0xFFFFFF, (int) StateColor::Normal),
std::make_pair(0xF0F0F1, (int) StateColor::Disabled)
);
border_color = StateColor(
std::make_pair(0xDBDBDB, (int) StateColor::Normal),
std::make_pair(0xDBDBDB, (int) StateColor::Disabled)
);
}
LabeledStaticBox::LabeledStaticBox(
wxWindow* parent,
const wxString& label,
const wxPoint& pos,
const wxSize& size,
long style
)
: LabeledStaticBox()
{
Create(parent, label, pos, size, style);
}
bool LabeledStaticBox::Create(
wxWindow* parent,
const wxString& label,
const wxPoint& pos,
const wxSize& size,
long style
)
{
if (style & wxBORDER_NONE)
m_border_width = 0;
wxStaticBox::Create(parent, wxID_ANY, label, pos, size, style);
#ifdef __WXOSX__
Slic3r::GUI::staticbox_remove_margin(this);
#endif
m_label = label;
m_scale = FromDIP(100) / 100.f;
m_pos = this->GetPosition();
int tW,tH,descent,externalLeading;
GetTextExtent("Yy", &tW, &tH, &descent, &externalLeading, &m_font);
m_label_height = tH - externalLeading;
GetTextExtent(m_label, &tW, &tH, &descent, &externalLeading, &m_font);
m_label_width = tW;
Bind(wxEVT_PAINT,([this](wxPaintEvent e) {
wxPaintDC dc(this);
PickDC(dc);
}));
state_handler.attach({&text_color, &background_color, &border_color});
state_handler.update_binds();
#ifndef __WXOSX__
SetBackgroundStyle(wxBG_STYLE_PAINT);
#endif
SetBackgroundColour(background_color.colorForStates(state_handler.states()));
SetForegroundColour( text_color.colorForStates(state_handler.states()));
SetBorderColor( border_color.colorForStates(state_handler.states()));
SetCanFocus(false);
return true;
}
void LabeledStaticBox::SetCornerRadius(int radius)
{
this->m_radius = radius;
Refresh();
}
void LabeledStaticBox::SetBorderWidth(int width)
{
this->m_border_width = width;
Refresh();
}
void LabeledStaticBox::SetBorderColor(StateColor const &color)
{
border_color = color;
state_handler.update_binds();
Refresh();
}
void LabeledStaticBox::SetFont(wxFont set_font)
{
m_font = set_font;
Refresh();
}
bool LabeledStaticBox::Enable(bool enable)
{
bool result = this->wxStaticBox::Enable(enable);
if (result) {
wxCommandEvent e(EVT_ENABLE_CHANGED);
e.SetEventObject(this);
GetEventHandler()->ProcessEvent(e);
this->SetForegroundColour( text_color.colorForStates(state_handler.states()));
this->SetBorderColor( border_color.colorForStates(state_handler.states()));
}
return result;
}
void LabeledStaticBox::PickDC(wxDC& dc)
{
#ifdef __WXMSW__
wxSize size = GetSize();
if (size.x <= 0 || size.y <= 0)
return;
wxMemoryDC memdc(&dc);
if (!memdc.IsOk()) {
DrawBorderAndLabel(dc);
return;
}
wxBitmap bmp(size.x, size.y);
memdc.SelectObject(bmp);
memdc.SetBackground(wxBrush(GetBackgroundColour()));
memdc.Clear();
{
wxGCDC dc2(memdc);
DrawBorderAndLabel(dc2);
}
memdc.SelectObject(wxNullBitmap);
dc.DrawBitmap(bmp, 0, 0);
#else
DrawBorderAndLabel(dc);
#endif
}
void LabeledStaticBox::DrawBorderAndLabel(wxDC& dc)
{
// fill full background
dc.SetBackground(wxBrush(background_color.colorForStates(0)));
dc.Clear();
wxSize wSz = GetSize();
dc.SetBrush(*wxTRANSPARENT_BRUSH);
dc.SetPen(wxPen(border_color.colorForStates(state_handler.states()), m_border_width, wxSOLID));
dc.DrawRoundedRectangle( // Border
std::max(0, m_pos.x),
std::max(0, m_pos.y) + m_label_height * .5,
wSz.GetWidth(),
wSz.GetHeight() - m_label_height * .5,
m_radius * m_scale
);
if (!m_label.IsEmpty()) {
dc.SetFont(m_font);
dc.SetPen(*wxTRANSPARENT_PEN);
dc.SetBrush(wxBrush(background_color.colorForStates(0)));
dc.DrawRectangle(wxRect(7 * m_scale,0 , m_label_width + 7 * m_scale, m_label_height)); // text background
// NEEDFIX if text lenght > client size
dc.SetTextForeground(text_color.colorForStates(state_handler.states()));
dc.DrawText(m_label, wxPoint(10 * m_scale, 0));
}
}

View File

@@ -0,0 +1,68 @@
#ifndef slic3r_GUI_LabeledStaticBox_hpp_
#define slic3r_GUI_LabeledStaticBox_hpp_
#include <wx/window.h>
#include <wx/dc.h>
#include <wx/dcgraph.h>
#include <wx/dcclient.h>
#include <wx/dcbuffer.h>
#include <wx/settings.h>
#include <wx/statbox.h>
#include <wx/pen.h>
#include "libslic3r/Utils.hpp"
#include "slic3r/GUI/wxExtensions.hpp"
#include "slic3r/GUI/Widgets/StateHandler.hpp"
class LabeledStaticBox : public wxStaticBox
{
public:
LabeledStaticBox();
LabeledStaticBox(
wxWindow* parent,
const wxString& label = wxEmptyString,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
long style = 0
);
bool Create(
wxWindow* parent,
const wxString& label = wxEmptyString,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
long style = 0
);
void SetCornerRadius(int radius);
void SetBorderWidth(int width);
void SetBorderColor(StateColor const &color);
void SetFont(wxFont set_font);
bool Enable(bool enable) override;
private:
void PickDC(wxDC& dc);
virtual void DrawBorderAndLabel(wxDC& dc);
protected:
StateHandler state_handler;
StateColor text_color;
StateColor border_color;
StateColor background_color;
int m_border_width;
int m_radius;
wxFont m_font;
wxString m_label;
int m_label_height;
int m_label_width;
float m_scale;
wxPoint m_pos;
};
#endif // !slic3r_GUI_LabeledStaticBox_hpp_

View File

@@ -0,0 +1,149 @@
#include "RadioGroup.hpp"
#include "Label.hpp"
#include "StateColor.hpp"
RadioGroup::RadioGroup(
wxWindow* parent,
const std::vector<wxString>& labels,
long direction,
int row_col_limit
)
: wxPanel(parent, wxID_ANY)
, m_on( this, "radio_on" , 18)
, m_off( this, "radio_off" , 18)
, m_on_hover( this, "radio_on_hover" , 18)
, m_off_hover(this, "radio_off_hover", 18)
, m_disabled( this, "radio_off_hover", 18)
, m_selectedIndex(0)
, m_focused(false)
{
Create(parent, labels, direction, row_col_limit);
}
void RadioGroup::Create(
wxWindow* parent,
const std::vector<wxString>& labels,
long direction, /* wxHORIZONTAL / wxVERTICAL */
int row_col_limit /* sets column/row count depends on direction. creates new row if wxHORIZONTAL used after limit reached */
)
{
m_labels = labels;
auto bg = parent->GetBackgroundColour();
this->SetBackgroundColour(bg);
auto bmp_size = m_on.GetBmpSize();
int item_count = m_labels.size();
int item_limit = row_col_limit < 0 ? 1 : row_col_limit > item_count ? item_count : row_col_limit;
int count = (int(item_count / item_limit) + (item_count % item_limit));
int rows = (direction & wxHORIZONTAL) ? item_limit : count;
int cols = (direction & wxHORIZONTAL) ? count : item_limit;
wxFlexGridSizer* f_sizer = new wxFlexGridSizer(rows, cols, 0, 0);
SetDoubleBuffered(true);
AcceptsFocusFromKeyboard();
Bind(wxEVT_SET_FOCUS ,([this](wxFocusEvent e) {m_focused = true ;Refresh(); e.Skip();}));
Bind(wxEVT_KILL_FOCUS,([this](wxFocusEvent e) {m_focused = false;Refresh(); e.Skip();}));
Bind(wxEVT_PAINT,([this](wxPaintEvent e) {
wxPaintDC dc(this);
dc.Clear();
dc.SetPen(wxPen(StateColor::darkModeColorFor(wxColour("#009688")), 1, wxPENSTYLE_SOLID));
dc.SetBrush(*wxTRANSPARENT_BRUSH);
dc.DrawRectangle(
m_focused ? wxRect(
m_radioButtons[GetSelection()]->GetRect().GetTopLeft() - wxPoint(1, 3),
m_labelButtons[GetSelection()]->GetRect().GetBottomRight() + wxPoint(4, 1)
) : wxRect(0,0,0,0)
);
if (m_focused) // Required to take focus again since Refresh causing lossing focus
SetFocus();
}));
// DPIDialog's uses wxEVT_CHAR_HOOK
Bind(wxEVT_CHAR_HOOK, ([this](wxKeyEvent&e){
int k = e.GetKeyCode();
bool is_next = (k == WXK_DOWN || k == WXK_RIGHT);
bool is_prev = (k == WXK_LEFT || k == WXK_UP);
if(m_focused){
if (is_next) SelectNext();
else if (is_prev) SelectPrevious();
e.Skip(!(is_next || is_prev));
}else{
e.Skip();
}
}));
for (int i = 0; i < item_count; ++i){
auto rb = new wxStaticBitmap(this, wxID_ANY, m_off.bmp(), wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxNO_BORDER);
m_radioButtons.push_back(rb);
rb->Bind(wxEVT_LEFT_DOWN ,([this, i](wxMouseEvent e) {OnClick(i) ; e.Skip();}));
rb->Bind(wxEVT_ENTER_WINDOW,([this, i](wxMouseEvent e) {SetRadioIcon(i, true) ; e.Skip();}));
rb->Bind(wxEVT_LEAVE_WINDOW,([this, i](wxMouseEvent e) {
// prevent removing hover effect while switching between button and its text
if(wxFindWindowAtPoint(wxGetMousePosition())->GetId() != m_labelButtons[i]->GetId())
SetRadioIcon(i, false);
e.Skip();
}));
auto tx = new wxStaticText(this, wxID_ANY, " " + m_labels[i], wxDefaultPosition, wxDefaultSize);
tx->SetForegroundColour(wxColour("#363636"));
tx->SetFont(Label::Body_14);
m_labelButtons.push_back(tx);
tx->Bind(wxEVT_LEFT_DOWN ,([this, i](wxMouseEvent e) {OnClick(i) ; e.Skip();}));
tx->Bind(wxEVT_ENTER_WINDOW,([this, i](wxMouseEvent e) {SetRadioIcon(i, true) ; e.Skip();}));
tx->Bind(wxEVT_LEAVE_WINDOW,([this, i](wxMouseEvent e) {
// prevent removing hover effect while switching between button and its text
if(wxFindWindowAtPoint(wxGetMousePosition())->GetId() != m_radioButtons[i]->GetId())
SetRadioIcon(i, false);
e.Skip();
}));
wxBoxSizer* radio_sizer = new wxBoxSizer(wxHORIZONTAL);
radio_sizer->Add(rb, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, 1);
radio_sizer->Add(tx, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, this->FromDIP(15));
f_sizer->Add(radio_sizer, 0, wxTOP | wxBOTTOM, this->FromDIP(4));
}
SetSelection(m_selectedIndex);
SetSizer(f_sizer);
}
void RadioGroup::OnClick(int i)
{
m_focused = true; // prevents 2 time refresh
SetSelection(i);
}
void RadioGroup::SetSelection(int index)
{
if (index >= 0 && index < static_cast<int>(m_labels.size())){
m_selectedIndex = index;
for (size_t i = 0; i < m_labels.size(); ++i)
SetRadioIcon(i, HasFocus() && i == m_selectedIndex);
wxCommandEvent evt(wxEVT_COMMAND_RADIOBOX_SELECTED, GetId());
evt.SetInt(index);
evt.SetString(m_labels[index]);
GetEventHandler()->ProcessEvent(evt);
Refresh(); // refresh on every change
}
}
int RadioGroup::GetSelection()
{
return m_selectedIndex;
}
void RadioGroup::SelectNext(bool focus)
{
SetSelection(m_selectedIndex + 1 > (m_radioButtons.size() - 1) ? 0 : m_selectedIndex + 1);
}
void RadioGroup::SelectPrevious(bool focus)
{
SetSelection(m_selectedIndex - 1 < 0 ? (m_radioButtons.size() - 1) : m_selectedIndex - 1);
}
void RadioGroup::SetRadioIcon(int i, bool hover)
{
auto icon = m_selectedIndex == i ? (hover ? m_on_hover : m_on) : (hover ? m_off_hover : m_off);
m_radioButtons[i]->SetBitmap(icon.bmp());
}

View File

@@ -0,0 +1,63 @@
#ifndef slic3r_GUI_RADIOGROUP_hpp_
#define slic3r_GUI_RADIOGROUP_hpp_
#include "../wxExtensions.hpp"
#include <wx/wx.h>
#include <wx/dcclient.h>
#include <wx/dcgraph.h>
#include <vector>
#include <string>
class RadioGroup : public wxPanel
{
public:
RadioGroup();
RadioGroup(
wxWindow* parent,
const std::vector<wxString>& labels = {"1", "2", "3"},
long direction = wxHORIZONTAL,
int row_col_limit = -1
);
void Create(
wxWindow* parent,
const std::vector<wxString>& labels = {"1", "2", "3"},
long direction = wxHORIZONTAL,
int row_col_limit = -1
);
int GetSelection();
void SetSelection(int index);
void SelectNext(bool focus = true);
void SelectPrevious(bool focus = true);
private:
std::vector<wxString> m_labels;
std::vector<wxStaticBitmap*> m_radioButtons;
std::vector<wxStaticText*> m_labelButtons;
int m_selectedIndex;
bool m_focused;
ScalableBitmap m_on;
ScalableBitmap m_off;
ScalableBitmap m_on_hover;
ScalableBitmap m_off_hover;
ScalableBitmap m_disabled;
void OnClick(int i);
void UpdateFocus(bool focus);
void SetRadioIcon(int i, bool hover);
void OnKeyDown(wxKeyEvent& e);
};
#endif // !slic3r_GUI_RADIOGROUP_hpp_