mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-16 12:21:11 +00:00
* Fix TabCtrl clipping titles and indicator dots Button measures a label plus icon without the icon gap that render draws, so icon tabs were ellipsized and the indicator dot was cut off. Widen the horizontal padding so the measured width covers the gap. * Re-layout publish dialog when extruder switch labels change Setting the indicator bullet on a switch option changed its width without laying out the page, leaving the switch distorted until the next layout.
382 lines
10 KiB
C++
382 lines
10 KiB
C++
#include "TabCtrl.hpp"
|
|
|
|
#include "slic3r/GUI/Widgets/StaticBox.hpp"
|
|
#include <wx/anybutton.h>
|
|
#include <wx/checklst.h>
|
|
#include <cstddef>
|
|
#include <utility>
|
|
#include <wx/colour.h>
|
|
#include <algorithm>
|
|
#include <wx/dc.h>
|
|
#include <wx/event.h>
|
|
#include <wx/gdicmn.h>
|
|
#include <wx/sizer.h>
|
|
#include <wx/string.h>
|
|
#include "slic3r/GUI/Widgets/Button.hpp"
|
|
#include "slic3r/GUI/Widgets/StateColor.hpp"
|
|
#include "slic3r/GUI/Widgets/StateHandler.hpp"
|
|
|
|
wxDEFINE_EVENT(wxEVT_TAB_SEL_CHANGING, wxCommandEvent);
|
|
wxDEFINE_EVENT(wxEVT_TAB_SEL_CHANGED, wxCommandEvent);
|
|
|
|
BEGIN_EVENT_TABLE(TabCtrl, StaticBox)
|
|
|
|
EVT_KEY_DOWN(TabCtrl::keyDown)
|
|
|
|
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().
|
|
*/
|
|
|
|
#define TAB_BUTTON_SPACE 2
|
|
#define TAB_BUTTON_PADDING_X 3
|
|
#define TAB_BUTTON_PADDING_Y 2
|
|
#define TAB_BUTTON_PADDING TAB_BUTTON_PADDING_X, TAB_BUTTON_PADDING_Y
|
|
|
|
TabCtrl::TabCtrl(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style)
|
|
: StaticBox(parent, id, pos, size, style)
|
|
{
|
|
#if 0
|
|
radius = 5;
|
|
#else
|
|
radius = 1;
|
|
#endif
|
|
border_width = 1;
|
|
SetBorderColor(0xcecece);
|
|
sizer = new wxBoxSizer(wxHORIZONTAL);
|
|
sizer->AddSpacer(10);
|
|
auto hsizer = new wxBoxSizer(wxVERTICAL);
|
|
hsizer->Add(sizer, 0, wxEXPAND | wxBOTTOM, border_width * 4);
|
|
SetSizer(hsizer);
|
|
Bind(wxEVT_COMMAND_BUTTON_CLICKED, &TabCtrl::buttonClicked, this);
|
|
// wxString reason;
|
|
// IsTransparentBackgroundSupported(&reason);
|
|
}
|
|
|
|
TabCtrl::~TabCtrl() { delete images; }
|
|
|
|
int TabCtrl::GetSelection() const { return sel; }
|
|
|
|
void TabCtrl::SelectItem(int item)
|
|
{
|
|
if (item == sel || !sendTabCtrlEvent(true))
|
|
return;
|
|
if (sel >= 0) {
|
|
wxCommandEvent e(wxEVT_CHECKBOX);
|
|
auto b = btns[sel];
|
|
e.SetEventObject(b);
|
|
b->GetEventHandler()->ProcessEvent(e);
|
|
}
|
|
sel = item;
|
|
if (sel >= 0) {
|
|
wxCommandEvent e(wxEVT_CHECKBOX);
|
|
auto b = btns[sel];
|
|
e.SetEventObject(b);
|
|
b->GetEventHandler()->ProcessEvent(e);
|
|
}
|
|
sendTabCtrlEvent();
|
|
relayout();
|
|
Refresh();
|
|
}
|
|
|
|
void TabCtrl::Unselect() { SelectItem(-1); }
|
|
|
|
void TabCtrl::Rescale()
|
|
{
|
|
for (auto& b : btns)
|
|
b->Rescale();
|
|
relayout();
|
|
}
|
|
|
|
bool TabCtrl::SetFont(wxFont const& font)
|
|
{
|
|
StaticBox::SetFont(font);
|
|
bold = font.Bold();
|
|
for (size_t i = 0; i < btns.size(); ++i)
|
|
btns[i]->SetFont(i == sel ? bold : font);
|
|
return true;
|
|
}
|
|
|
|
int TabCtrl::AppendItem(const wxString& item, int image, int selImage, void* clientData)
|
|
{
|
|
Button* btn = new Button();
|
|
btn->Create(this, item, "", wxBORDER_NONE);
|
|
btn->SetFont(GetFont());
|
|
btn->SetTextColor(
|
|
StateColor(std::make_pair(0x6B6B6C, (int) StateColor::NotChecked), std::make_pair(wxColour("#262E30"), (int) StateColor::Normal)));
|
|
btn->SetBackgroundColor(StateColor());
|
|
btn->SetCornerRadius(0);
|
|
btn->SetPaddingSize({TAB_BUTTON_PADDING});
|
|
btns.push_back(btn);
|
|
if (btns.size() > 1)
|
|
sizer->GetItem(sizer->GetItemCount() - 1)->SetMinSize({0, 0});
|
|
sizer->Add(btn, 0, wxALIGN_CENTER_VERTICAL);
|
|
sizer->AddStretchSpacer(1);
|
|
relayout();
|
|
return btns.size() - 1;
|
|
}
|
|
|
|
int TabCtrl::AppendItem(const wxString& item, const wxBitmap& bitmap, void* clientData)
|
|
{
|
|
const int index = AppendItem(item, -1, -1, clientData);
|
|
SetItemBitmap(index, bitmap);
|
|
return index;
|
|
}
|
|
|
|
bool TabCtrl::DeleteItem(int item)
|
|
{
|
|
if (item < 0 || item >= btns.size()) {
|
|
return false;
|
|
}
|
|
const bool selection_changed = sel >= item;
|
|
|
|
if (selection_changed) {
|
|
sendTabCtrlEvent(true);
|
|
}
|
|
|
|
Button* btn = btns[item];
|
|
btn->Destroy();
|
|
btns.erase(btns.begin() + item);
|
|
sizer->Remove(item * 2);
|
|
if (btns.size() > 1)
|
|
sizer->GetItem(sizer->GetItemCount() - 1)->SetMinSize({0, 0});
|
|
|
|
if (selection_changed) {
|
|
sel--; // `relayout()` uses `sel` so we need to update this before calling `relayout()`
|
|
}
|
|
relayout();
|
|
if (selection_changed) {
|
|
sendTabCtrlEvent();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void TabCtrl::DeleteAllItems()
|
|
{
|
|
sizer->Clear(true);
|
|
sizer->AddSpacer(10);
|
|
btns.clear();
|
|
if (sel >= 0) {
|
|
sel = -1;
|
|
sendTabCtrlEvent();
|
|
}
|
|
}
|
|
|
|
unsigned int TabCtrl::GetCount() const { return btns.size(); }
|
|
|
|
wxString TabCtrl::GetItemText(unsigned int item) const { return item < btns.size() ? btns[item]->GetLabel() : wxString{}; }
|
|
|
|
void TabCtrl::SetItemText(unsigned int item, wxString const& value)
|
|
{
|
|
if (item >= btns.size())
|
|
return;
|
|
btns[item]->SetLabel(value);
|
|
}
|
|
|
|
void TabCtrl::SetItemBitmap(unsigned int item, const wxBitmap& bitmap)
|
|
{
|
|
if (item >= btns.size())
|
|
return;
|
|
btns[item]->SetIcon(bitmap);
|
|
relayout();
|
|
}
|
|
|
|
void TabCtrl::SetItemIndicator(unsigned int item, bool on)
|
|
{
|
|
if (item >= btns.size())
|
|
return;
|
|
btns[item]->SetIndicator(on);
|
|
relayout();
|
|
}
|
|
|
|
bool TabCtrl::GetItemBold(unsigned int item) const
|
|
{
|
|
if (item >= btns.size())
|
|
return false;
|
|
return btns[item]->GetFont() == bold;
|
|
}
|
|
|
|
void TabCtrl::SetItemBold(unsigned int item, bool bold)
|
|
{
|
|
if (item >= btns.size())
|
|
return;
|
|
btns[item]->SetFont(bold ? this->bold : GetFont());
|
|
btns[item]->Rescale();
|
|
}
|
|
|
|
void* TabCtrl::GetItemData(unsigned int item) const
|
|
{
|
|
if (item >= btns.size())
|
|
return nullptr;
|
|
return btns[item]->GetClientData();
|
|
}
|
|
|
|
void TabCtrl::SetItemData(unsigned int item, void* clientData)
|
|
{
|
|
if (item >= btns.size())
|
|
return;
|
|
btns[item]->SetClientData(clientData);
|
|
}
|
|
|
|
void TabCtrl::AssignImageList(wxImageList* imageList)
|
|
{
|
|
if (images == imageList)
|
|
return;
|
|
delete images;
|
|
images = imageList;
|
|
}
|
|
|
|
void TabCtrl::SetItemTextColour(unsigned int item, const StateColor& col)
|
|
{
|
|
if (item >= btns.size())
|
|
return;
|
|
btns[item]->SetTextColor(col);
|
|
}
|
|
|
|
int TabCtrl::GetFirstVisibleItem() const { return btns.size() == 0 ? -1 : 0; }
|
|
|
|
int TabCtrl::GetNextVisible(int item) const { return ++item < btns.size() ? item : -1; }
|
|
|
|
bool TabCtrl::IsVisible(unsigned int item) const { return true; }
|
|
|
|
void TabCtrl::DoSetSize(int x, int y, int width, int height, int sizeFlags)
|
|
{
|
|
auto size = GetSize();
|
|
wxWindow::DoSetSize(x, y, width, height, sizeFlags);
|
|
if (size == GetSize())
|
|
return;
|
|
relayout();
|
|
}
|
|
|
|
#ifdef __WIN32__
|
|
|
|
WXLRESULT TabCtrl::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam)
|
|
{
|
|
if (nMsg == WM_GETDLGCODE) {
|
|
return DLGC_WANTARROWS;
|
|
}
|
|
return wxWindow::MSWWindowProc(nMsg, wParam, lParam);
|
|
}
|
|
|
|
#endif
|
|
|
|
void TabCtrl::relayout()
|
|
{
|
|
int offset = 10;
|
|
int item = sel + 1;
|
|
int first = 0;
|
|
for (int i = 0; i < item; ++i)
|
|
offset += btns[i]->GetMinSize().x;
|
|
if (item < btns.size())
|
|
offset += btns[item]->GetMinSize().x;
|
|
int width = GetSize().x;
|
|
for (int i = 0; i < btns.size(); ++i) {
|
|
auto size = btns[i]->GetMinSize().x;
|
|
if (i < sel && offset > width) {
|
|
sizer->Show(i * 2 + 1, false);
|
|
sizer->Show(i * 2 + 2, false);
|
|
offset -= size;
|
|
first = i + 1;
|
|
} else if (i <= item) {
|
|
sizer->Show(i * 2 + 1, true);
|
|
sizer->Show(i * 2 + 2, true);
|
|
} else if (offset <= width) {
|
|
sizer->Show(i * 2 + 1, true);
|
|
sizer->Show(i * 2 + 2, true);
|
|
offset += size;
|
|
item = i;
|
|
} else {
|
|
sizer->Show(i * 2 + 1, false);
|
|
sizer->Show(i * 2 + 2, false);
|
|
}
|
|
sizer->GetItem(i * 2 + 2)->SetMinSize({0, 0});
|
|
}
|
|
if (item >= btns.size())
|
|
--item;
|
|
// Keep spacing 2 ~ 10 TAB_BUTTON_SPACE
|
|
int b = GetSize().x - offset - 10 - (item + 1 - first) * 16;
|
|
sizer->GetItem(item * 2 + 2)->SetMinSize({b > 0 ? b : 0, 0});
|
|
Layout();
|
|
}
|
|
|
|
int TabCtrl::GetFullSize() const
|
|
{
|
|
// Mirrors relayout(): a 10px leading spacer plus every button's min width.
|
|
int width = 10;
|
|
for (const Button* btn : btns)
|
|
width += btn->GetMinSize().x;
|
|
return width;
|
|
}
|
|
|
|
void TabCtrl::buttonClicked(wxCommandEvent& event)
|
|
{
|
|
SetFocus();
|
|
auto btn = event.GetEventObject();
|
|
auto iter = std::find(btns.begin(), btns.end(), btn);
|
|
SelectItem(iter == btns.end() ? -1 : iter - btns.begin());
|
|
}
|
|
|
|
void TabCtrl::keyDown(wxKeyEvent& event)
|
|
{
|
|
switch (event.GetKeyCode()) {
|
|
case WXK_UP:
|
|
case WXK_DOWN:
|
|
case WXK_LEFT:
|
|
case WXK_RIGHT:
|
|
if ((event.GetKeyCode() == WXK_UP || event.GetKeyCode() == WXK_LEFT) && GetSelection() > 0) {
|
|
SelectItem(GetSelection() - 1);
|
|
} else if ((event.GetKeyCode() == WXK_DOWN || event.GetKeyCode() == WXK_RIGHT) && GetSelection() + 1 < btns.size()) {
|
|
SelectItem(GetSelection() + 1);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
void TabCtrl::doRender(wxDC& dc)
|
|
{
|
|
wxSize size = GetSize();
|
|
int states = state_handler.states();
|
|
if (sel < 0) {
|
|
return;
|
|
}
|
|
|
|
auto x1 = btns[sel]->GetPosition().x;
|
|
auto x2 = x1 + btns[sel]->GetSize().x;
|
|
const int BS2 = (1 + border_width) / 2;
|
|
#if 0
|
|
const int BS = border_width / 2;
|
|
x1 -= TAB_BUTTON_SPACE; x2 += TAB_BUTTON_SPACE;
|
|
dc.SetPen(wxPen(border_color.colorForStates(states), border_width));
|
|
dc.SetBrush(*wxTRANSPARENT_BRUSH);
|
|
dc.DrawLine(0, size.y - BS2, x1 - radius + BS2, size.y - BS2);
|
|
dc.DrawArc(x1 - radius, size.y, x1, size.y - radius, x1 - radius, size.y - radius);
|
|
dc.DrawLine(x1, size.y - radius, x1, radius);
|
|
dc.DrawArc(x1 + radius, 0, x1, radius, x1 + radius, radius);
|
|
dc.DrawLine(x1 + radius, BS, x2 - radius, BS);
|
|
dc.DrawArc(x2, radius, x2 - radius, 0, x2 - radius, radius);
|
|
dc.DrawLine(x2, radius, x2, size.y - radius);
|
|
dc.DrawArc(x2, size.y - radius, x2 + radius, size.y, x2 + radius, size.y - radius);
|
|
dc.DrawLine(x2 + radius - BS2, size.y - BS2, size.x, size.y - BS2);
|
|
#else
|
|
dc.SetPen(wxPen(border_color.colorForStates(states), border_width));
|
|
dc.DrawLine(0, size.y - BS2, size.x, size.y - BS2);
|
|
wxColour c("#009688"); // ORCA selected tab underline stroke color
|
|
dc.SetPen(wxPen(c, 1));
|
|
dc.SetBrush(c);
|
|
dc.DrawRoundedRectangle(x1 - radius, size.y - BS2 - border_width * 3, x2 + radius * 2 - x1, border_width * 3, radius);
|
|
#endif
|
|
}
|
|
|
|
bool TabCtrl::sendTabCtrlEvent(bool changing)
|
|
{
|
|
wxCommandEvent event(changing ? wxEVT_TAB_SEL_CHANGING : wxEVT_TAB_SEL_CHANGED, GetId());
|
|
event.SetEventObject(this);
|
|
event.SetInt(sel);
|
|
GetEventHandler()->ProcessEvent(event);
|
|
return true;
|
|
}
|