From aff3864200e1fb72090427eedbe1f331769e6e58 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 18 Oct 2023 14:46:08 +0200 Subject: [PATCH] Added some widgets from BambuStudio(http://github.com/bambulab) from the state of https://github.com/bambulab/BambuStudio/tree/96707fc4b4b40c30b7e5610d2489ef283fe952a4 Thanks https://github.com/bambu123, https://github.com/lanewei120 and https://github.com/walterwongbbl for implementation. --- src/slic3r/GUI/Widgets/Button.cpp | 303 +++++++++++++++ src/slic3r/GUI/Widgets/Button.hpp | 78 ++++ src/slic3r/GUI/Widgets/CheckBox.cpp | 125 +++++++ src/slic3r/GUI/Widgets/CheckBox.hpp | 55 +++ src/slic3r/GUI/Widgets/ComboBox.cpp | 301 +++++++++++++++ src/slic3r/GUI/Widgets/ComboBox.hpp | 92 +++++ src/slic3r/GUI/Widgets/DropDown.cpp | 472 ++++++++++++++++++++++++ src/slic3r/GUI/Widgets/DropDown.hpp | 111 ++++++ src/slic3r/GUI/Widgets/Label.cpp | 124 +++++++ src/slic3r/GUI/Widgets/Label.hpp | 49 +++ src/slic3r/GUI/Widgets/SpinInput.cpp | 323 ++++++++++++++++ src/slic3r/GUI/Widgets/SpinInput.hpp | 96 +++++ src/slic3r/GUI/Widgets/StateColor.cpp | 93 +++++ src/slic3r/GUI/Widgets/StateColor.hpp | 84 +++++ src/slic3r/GUI/Widgets/StateHandler.cpp | 122 ++++++ src/slic3r/GUI/Widgets/StateHandler.hpp | 61 +++ src/slic3r/GUI/Widgets/StaticBox.cpp | 214 +++++++++++ src/slic3r/GUI/Widgets/StaticBox.hpp | 62 ++++ src/slic3r/GUI/Widgets/SwitchButton.cpp | 134 +++++++ src/slic3r/GUI/Widgets/SwitchButton.hpp | 40 ++ src/slic3r/GUI/Widgets/TextInput.cpp | 230 ++++++++++++ src/slic3r/GUI/Widgets/TextInput.hpp | 77 ++++ 22 files changed, 3246 insertions(+) create mode 100644 src/slic3r/GUI/Widgets/Button.cpp create mode 100644 src/slic3r/GUI/Widgets/Button.hpp create mode 100644 src/slic3r/GUI/Widgets/CheckBox.cpp create mode 100644 src/slic3r/GUI/Widgets/CheckBox.hpp create mode 100644 src/slic3r/GUI/Widgets/ComboBox.cpp create mode 100644 src/slic3r/GUI/Widgets/ComboBox.hpp create mode 100644 src/slic3r/GUI/Widgets/DropDown.cpp create mode 100644 src/slic3r/GUI/Widgets/DropDown.hpp create mode 100644 src/slic3r/GUI/Widgets/Label.cpp create mode 100644 src/slic3r/GUI/Widgets/Label.hpp create mode 100644 src/slic3r/GUI/Widgets/SpinInput.cpp create mode 100644 src/slic3r/GUI/Widgets/SpinInput.hpp create mode 100644 src/slic3r/GUI/Widgets/StateColor.cpp create mode 100644 src/slic3r/GUI/Widgets/StateColor.hpp create mode 100644 src/slic3r/GUI/Widgets/StateHandler.cpp create mode 100644 src/slic3r/GUI/Widgets/StateHandler.hpp create mode 100644 src/slic3r/GUI/Widgets/StaticBox.cpp create mode 100644 src/slic3r/GUI/Widgets/StaticBox.hpp create mode 100644 src/slic3r/GUI/Widgets/SwitchButton.cpp create mode 100644 src/slic3r/GUI/Widgets/SwitchButton.hpp create mode 100644 src/slic3r/GUI/Widgets/TextInput.cpp create mode 100644 src/slic3r/GUI/Widgets/TextInput.hpp diff --git a/src/slic3r/GUI/Widgets/Button.cpp b/src/slic3r/GUI/Widgets/Button.cpp new file mode 100644 index 0000000000..fa1dc15ee2 --- /dev/null +++ b/src/slic3r/GUI/Widgets/Button.cpp @@ -0,0 +1,303 @@ +#include "Button.hpp" +#include "Label.hpp" + +#include + +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(0xF0F0F0, (int) StateColor::Disabled), + std::make_pair(0x37EE7C, (int) StateColor::Hovered | StateColor::Checked), + std::make_pair(0x00AE42, (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) + : Button() +{ + Create(parent, text, icon, style, iconSize); +} + +bool Button::Create(wxWindow* parent, wxString text, wxString icon, long style, int iconSize) +{ + StaticBox::Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, style); + state_handler.attach({&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) +{ + wxWindow::SetLabel(label); + messureSize(); + Refresh(); +} + +void Button::SetIcon(const wxString& icon) +{ + if (!icon.IsEmpty()) { + //BBS set button icon default size to 20 + this->active_icon = ScalableBitmap(this, icon.ToStdString(), this->active_icon.px_cnt()); + } + 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::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) { this->canFocus = canFocus; } + +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(); +} + +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 szContent = textSize; + + ScalableBitmap icon; + if (m_selected || ((states & (int)StateColor::State::Hovered) != 0)) + icon = active_icon; + else + icon = inactive_icon; + int padding = 5; + if (icon.bmp().IsOk()) { + if (szContent.y > 0) { + //BBS norrow size between text and icon + szContent.x += padding; + } + szIcon = icon.GetBmpSize(); + szContent.x += szIcon.x; + if (szIcon.y > szContent.y) + szContent.y = szIcon.y; + if (szContent.x > size.x) { + int d = std::min(padding, szContent.x - size.x); + padding -= d; + szContent.x -= d; + } + } + // move to center + wxRect rcContent = { {0, 0}, size }; + 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()) { + pt.y += (rcContent.height - szIcon.y) / 2; + dc.DrawBitmap(icon.bmp(), pt); + //BBS norrow size between text and icon + pt.x += szIcon.x + padding; + pt.y = rcContent.y; + } + auto text = GetLabel(); + if (!text.IsEmpty()) { + 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.SetFont(GetFont()); + dc.SetTextForeground(text_color.colorForStates(states)); + dc.DrawText(text, pt); + } +} + +void Button::messureSize() +{ + wxClientDC dc(this); + textSize = dc.GetTextExtent(GetLabel()); + if (minSize.GetWidth() > 0) { + wxWindow::SetMinSize(minSize); + return; + } + wxSize szContent = textSize; + if (this->active_icon.bmp().IsOk()) { + if (szContent.y > 0) { + //BBS norrow size between text and icon + szContent.x += 5; + } + wxSize szIcon = this->active_icon.GetBmpSize(); + 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()); + wxWindow::SetMinSize(size); +} + +void Button::mouseDown(wxMouseEvent& event) +{ + event.Skip(); + pressedDown = true; + if (canFocus) + SetFocus(); + 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; } diff --git a/src/slic3r/GUI/Widgets/Button.hpp b/src/slic3r/GUI/Widgets/Button.hpp new file mode 100644 index 0000000000..43aac6b9fb --- /dev/null +++ b/src/slic3r/GUI/Widgets/Button.hpp @@ -0,0 +1,78 @@ +#ifndef slic3r_GUI_Button_hpp_ +#define slic3r_GUI_Button_hpp_ + +#include "../wxExtensions.hpp" +#include "StaticBox.hpp" + +class Button : public StaticBox +{ + wxSize textSize; + wxSize minSize; // set by outer + wxSize paddingSize; + ScalableBitmap active_icon; + ScalableBitmap inactive_icon; + + StateColor text_color; + + bool pressedDown = false; + bool m_selected = true; + bool canFocus = true; + + static const int buttonWidth = 200; + static const int buttonHeight = 50; + +public: + Button(); + + Button(wxWindow* parent, wxString text, wxString icon = "", long style = 0, int iconSize = 0); + + bool Create(wxWindow* parent, wxString text, wxString icon = "", long style = 0, int iconSize = 0); + + void SetLabel(const wxString& label) override; + + void SetIcon(const wxString& icon); + + void SetInactiveIcon(const wxString& icon); + + void SetMinSize(const wxSize& size) override; + + void SetPaddingSize(const wxSize& size); + + void SetTextColor(StateColor const &color); + + void SetTextColorNormal(wxColor const &color); + + void SetSelected(bool selected = true) { m_selected = selected; } + + bool Enable(bool enable = true) override; + + void SetCanFocus(bool canFocus) override; + + void Rescale(); + +protected: +#ifdef __WIN32__ + WXLRESULT MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam) override; +#endif + + bool AcceptsFocus() const override; + +private: + void paintEvent(wxPaintEvent& evt); + + void render(wxDC& dc); + + void messureSize(); + + // some useful events + void mouseDown(wxMouseEvent& event); + void mouseReleased(wxMouseEvent& event); + void mouseCaptureLost(wxMouseCaptureLostEvent &event); + void keyDownUp(wxKeyEvent &event); + + void sendButtonEvent(); + + DECLARE_EVENT_TABLE() +}; + +#endif // !slic3r_GUI_Button_hpp_ diff --git a/src/slic3r/GUI/Widgets/CheckBox.cpp b/src/slic3r/GUI/Widgets/CheckBox.cpp new file mode 100644 index 0000000000..4ca1ca5ba0 --- /dev/null +++ b/src/slic3r/GUI/Widgets/CheckBox.cpp @@ -0,0 +1,125 @@ +#include "CheckBox.hpp" + +#include "../wxExtensions.hpp" + +CheckBox::CheckBox(wxWindow* parent) + : wxBitmapToggleButton(parent, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxDefaultSize, wxBORDER_NONE) + , m_on(this, "check_on", 18) + , m_half(this, "check_half", 18) + , m_off(this, "check_off", 18) + , m_on_disabled(this, "check_on_disabled", 18) + , m_half_disabled(this, "check_half_disabled", 18) + , m_off_disabled(this, "check_off_disabled", 18) + , m_on_focused(this, "check_on_focused", 18) + , m_half_focused(this, "check_half_focused", 18) + , m_off_focused(this, "check_off_focused", 18) +{ + //SetBackgroundStyle(wxBG_STYLE_TRANSPARENT); + if (parent) + SetBackgroundColour(parent->GetBackgroundColour()); + Bind(wxEVT_TOGGLEBUTTON, [this](auto& e) { m_half_checked = false; update(); e.Skip(); }); +#ifdef __WXOSX__ // State not fully implement on MacOS + Bind(wxEVT_SET_FOCUS, &CheckBox::updateBitmap, this); + Bind(wxEVT_KILL_FOCUS, &CheckBox::updateBitmap, this); + Bind(wxEVT_ENTER_WINDOW, &CheckBox::updateBitmap, this); + Bind(wxEVT_LEAVE_WINDOW, &CheckBox::updateBitmap, this); +#endif + SetSize(m_on.GetBmpSize()); + SetMinSize(m_on.GetBmpSize()); + update(); +} + +void CheckBox::SetValue(bool value) +{ + wxBitmapToggleButton::SetValue(value); + update(); +} + +void CheckBox::SetHalfChecked(bool value) +{ + m_half_checked = value; + update(); +} + +void CheckBox::Rescale() +{ + m_on.msw_rescale(); + m_half.msw_rescale(); + m_off.msw_rescale(); + m_on_disabled.msw_rescale(); + m_half_disabled.msw_rescale(); + m_off_disabled.msw_rescale(); + m_on_focused.msw_rescale(); + m_half_focused.msw_rescale(); + m_off_focused.msw_rescale(); + SetSize(m_on.GetBmpSize()); + update(); +} + +void CheckBox::update() +{ + SetBitmapLabel((m_half_checked ? m_half : GetValue() ? m_on : m_off).bmp()); + SetBitmapDisabled((m_half_checked ? m_half_disabled : GetValue() ? m_on_disabled : m_off_disabled).bmp()); +#ifdef __WXMSW__ + SetBitmapFocus((m_half_checked ? m_half_focused : GetValue() ? m_on_focused : m_off_focused).bmp()); +#endif + SetBitmapCurrent((m_half_checked ? m_half_focused : GetValue() ? m_on_focused : m_off_focused).bmp()); +#ifdef __WXOSX__ + wxCommandEvent e(wxEVT_UPDATE_UI); + updateBitmap(e); +#endif +} + +#ifdef __WXMSW__ + +CheckBox::State CheckBox::GetNormalState() const { return State_Normal; } + +#endif + + +#ifdef __WXOSX__ + +bool CheckBox::Enable(bool enable) +{ + bool result = wxBitmapToggleButton::Enable(enable); + if (result) { + m_disable = !enable; + wxCommandEvent e(wxEVT_ACTIVATE); + updateBitmap(e); + } + return result; +} + +wxBitmap CheckBox::DoGetBitmap(State which) const +{ + if (m_disable) { + return wxBitmapToggleButton::DoGetBitmap(State_Disabled); + } + if (m_focus) { + return wxBitmapToggleButton::DoGetBitmap(State_Current); + } + return wxBitmapToggleButton::DoGetBitmap(which); +} + +void CheckBox::updateBitmap(wxEvent & evt) +{ + evt.Skip(); + if (evt.GetEventType() == wxEVT_ENTER_WINDOW) { + m_hover = true; + } else if (evt.GetEventType() == wxEVT_LEAVE_WINDOW) { + m_hover = false; + } else { + if (evt.GetEventType() == wxEVT_SET_FOCUS) { + m_focus = true; + } else if (evt.GetEventType() == wxEVT_KILL_FOCUS) { + m_focus = false; + } + wxMouseEvent e; + if (m_hover) + OnEnterWindow(e); + else + OnLeaveWindow(e); + } +} + +#endif diff --git a/src/slic3r/GUI/Widgets/CheckBox.hpp b/src/slic3r/GUI/Widgets/CheckBox.hpp new file mode 100644 index 0000000000..53c41b6470 --- /dev/null +++ b/src/slic3r/GUI/Widgets/CheckBox.hpp @@ -0,0 +1,55 @@ +#ifndef slic3r_GUI_CheckBox_hpp_ +#define slic3r_GUI_CheckBox_hpp_ + +#include "../wxExtensions.hpp" + +#include + +class CheckBox : public wxBitmapToggleButton +{ +public: + CheckBox(wxWindow * parent = NULL); + +public: + void SetValue(bool value) override; + + void SetHalfChecked(bool value = true); + + void Rescale(); + +#ifdef __WXOSX__ + virtual bool Enable(bool enable = true) wxOVERRIDE; +#endif + +protected: +#ifdef __WXMSW__ + virtual State GetNormalState() const wxOVERRIDE; +#endif + +#ifdef __WXOSX__ + virtual wxBitmap DoGetBitmap(State which) const wxOVERRIDE; + + void updateBitmap(wxEvent & evt); + + bool m_disable = false; + bool m_hover = false; + bool m_focus = false; +#endif + +private: + void update(); + +private: + ScalableBitmap m_on; + ScalableBitmap m_half; + ScalableBitmap m_off; + ScalableBitmap m_on_disabled; + ScalableBitmap m_half_disabled; + ScalableBitmap m_off_disabled; + ScalableBitmap m_on_focused; + ScalableBitmap m_half_focused; + ScalableBitmap m_off_focused; + bool m_half_checked = false; +}; + +#endif // !slic3r_GUI_CheckBox_hpp_ diff --git a/src/slic3r/GUI/Widgets/ComboBox.cpp b/src/slic3r/GUI/Widgets/ComboBox.cpp new file mode 100644 index 0000000000..01d7d9614c --- /dev/null +++ b/src/slic3r/GUI/Widgets/ComboBox.cpp @@ -0,0 +1,301 @@ +#include "ComboBox.hpp" +#include "Label.hpp" + +#include + +BEGIN_EVENT_TABLE(ComboBox, TextInput) + +EVT_LEFT_DOWN(ComboBox::mouseDown) +//EVT_MOUSEWHEEL(ComboBox::mouseWheelMoved) +EVT_KEY_DOWN(ComboBox::keyDown) + +// catch paint events +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(). + */ + +ComboBox::ComboBox(wxWindow * parent, + wxWindowID id, + const wxString &value, + const wxPoint & pos, + const wxSize & size, + int n, + const wxString choices[], + long style) + : drop(texts, icons) +{ + if (style & wxCB_READONLY) + style |= wxRIGHT; + text_off = style & CB_NO_TEXT; + TextInput::Create(parent, "", value, (style & CB_NO_DROP_ICON) ? "" : "drop_down", pos, size, + style | wxTE_PROCESS_ENTER); + drop.Create(this, style & DD_STYLE_MASK); + + if (style & wxCB_READONLY) { + GetTextCtrl()->Hide(); + TextInput::SetFont(Label::Body_14); + TextInput::SetBorderColor(StateColor(std::make_pair(0xDBDBDB, (int) StateColor::Disabled), + std::make_pair(0x00AE42, (int) StateColor::Hovered), + std::make_pair(0xDBDBDB, (int) StateColor::Normal))); + TextInput::SetBackgroundColor(StateColor(std::make_pair(0xF0F0F0, (int) StateColor::Disabled), + std::make_pair(0xEDFAF2, (int) StateColor::Focused), + std::make_pair(*wxWHITE, (int) StateColor::Normal))); + TextInput::SetLabelColor(StateColor(std::make_pair(0x909090, (int) StateColor::Disabled), + std::make_pair(0x262E30, (int) StateColor::Normal))); + } else { + GetTextCtrl()->Bind(wxEVT_KEY_DOWN, &ComboBox::keyDown, this); + } + drop.Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &e) { + SetSelection(e.GetInt()); + e.SetEventObject(this); + e.SetId(GetId()); + GetEventHandler()->ProcessEvent(e); + }); + drop.Bind(EVT_DISMISS, [this](auto &) { + drop_down = false; + wxCommandEvent e(wxEVT_COMBOBOX_CLOSEUP); + GetEventHandler()->ProcessEvent(e); + }); + for (int i = 0; i < n; ++i) Append(choices[i]); +} + +int ComboBox::GetSelection() const { return drop.GetSelection(); } + +void ComboBox::SetSelection(int n) +{ + drop.SetSelection(n); + SetLabel(drop.GetValue()); + if (drop.selection >= 0) + SetIcon(icons[drop.selection]); +} + +void ComboBox::Rescale() +{ + TextInput::Rescale(); + drop.Rescale(); +} + +wxString ComboBox::GetValue() const +{ + return drop.GetSelection() >= 0 ? drop.GetValue() : GetLabel(); +} + +void ComboBox::SetValue(const wxString &value) +{ + drop.SetValue(value); + SetLabel(value); + if (drop.selection >= 0) + SetIcon(icons[drop.selection]); +} + +void ComboBox::SetLabel(const wxString &value) +{ + if (GetTextCtrl()->IsShown() || text_off) + GetTextCtrl()->SetValue(value); + else + TextInput::SetLabel(value); +} + +wxString ComboBox::GetLabel() const +{ + if (GetTextCtrl()->IsShown() || text_off) + return GetTextCtrl()->GetValue(); + else + return TextInput::GetLabel(); +} + +void ComboBox::SetTextLabel(const wxString& label) +{ + TextInput::SetLabel(label); +} + +wxString ComboBox::GetTextLabel() const +{ + return TextInput::GetLabel(); +} + +bool ComboBox::SetFont(wxFont const& font) +{ + if (GetTextCtrl() && GetTextCtrl()->IsShown()) + return GetTextCtrl()->SetFont(font); + else + return TextInput::SetFont(font); +} + +int ComboBox::Append(const wxString &item, const wxBitmap &bitmap) +{ + return Append(item, bitmap, nullptr); +} + +int ComboBox::Append(const wxString &item, + const wxBitmap &bitmap, + void * clientData) +{ + texts.push_back(item); + icons.push_back(bitmap); + datas.push_back(clientData); + types.push_back(wxClientData_None); + drop.Invalidate(); + return texts.size() - 1; +} + +void ComboBox::DoClear() +{ + texts.clear(); + icons.clear(); + datas.clear(); + types.clear(); + drop.Invalidate(true); +} + +void ComboBox::DoDeleteOneItem(unsigned int pos) +{ + if (pos >= texts.size()) return; + texts.erase(texts.begin() + pos); + icons.erase(icons.begin() + pos); + datas.erase(datas.begin() + pos); + types.erase(types.begin() + pos); + drop.Invalidate(true); +} + +unsigned int ComboBox::GetCount() const { return texts.size(); } + +wxString ComboBox::GetString(unsigned int n) const +{ + return n < texts.size() ? texts[n] : wxString{}; +} + +void ComboBox::SetString(unsigned int n, wxString const &value) +{ + if (n >= texts.size()) return; + texts[n] = value; + drop.Invalidate(); + if (n == drop.GetSelection()) SetLabel(value); +} + +wxBitmap ComboBox::GetItemBitmap(unsigned int n) { return icons[n]; } + +int ComboBox::DoInsertItems(const wxArrayStringsAdapter &items, + unsigned int pos, + void ** clientData, + wxClientDataType type) +{ + if (pos > texts.size()) return -1; + for (int i = 0; i < items.GetCount(); ++i) { + texts.insert(texts.begin() + pos, items[i]); + icons.insert(icons.begin() + pos, wxNullBitmap); + datas.insert(datas.begin() + pos, clientData ? clientData[i] : NULL); + types.insert(types.begin() + pos, type); + ++pos; + } + drop.Invalidate(true); + return pos - 1; +} + +void *ComboBox::DoGetItemClientData(unsigned int n) const { return n < texts.size() ? datas[n] : NULL; } + +void ComboBox::DoSetItemClientData(unsigned int n, void *data) +{ + if (n < texts.size()) + datas[n] = data; +} + +void ComboBox::mouseDown(wxMouseEvent &event) +{ + SetFocus(); + if (drop_down) { + drop.Hide(); + } else if (drop.HasDismissLongTime()) { + drop.autoPosition(); + drop_down = true; + drop.Popup(); + wxCommandEvent e(wxEVT_COMBOBOX_DROPDOWN); + GetEventHandler()->ProcessEvent(e); + } +} + +void ComboBox::mouseWheelMoved(wxMouseEvent &event) +{ + event.Skip(); + if (drop_down) return; + auto delta = (event.GetWheelRotation() < 0 == event.IsWheelInverted()) ? -1 : 1; + unsigned int n = GetSelection() + delta; + if (n < GetCount()) { + SetSelection((int) n); + sendComboBoxEvent(); + } +} + +void ComboBox::keyDown(wxKeyEvent& event) +{ + switch (event.GetKeyCode()) { + case WXK_RETURN: + case WXK_SPACE: + if (drop_down) { + drop.DismissAndNotify(); + } else if (drop.HasDismissLongTime()) { + drop.autoPosition(); + drop_down = true; + drop.Popup(); + wxCommandEvent e(wxEVT_COMBOBOX_DROPDOWN); + GetEventHandler()->ProcessEvent(e); + } + break; + case WXK_UP: + case WXK_DOWN: + case WXK_LEFT: + case WXK_RIGHT: + if ((event.GetKeyCode() == WXK_UP || event.GetKeyCode() == WXK_LEFT) && GetSelection() > 0) { + SetSelection(GetSelection() - 1); + } else if ((event.GetKeyCode() == WXK_DOWN || event.GetKeyCode() == WXK_RIGHT) && GetSelection() + 1 < texts.size()) { + SetSelection(GetSelection() + 1); + } else { + break; + } + { + wxCommandEvent e(wxEVT_COMBOBOX); + e.SetEventObject(this); + e.SetId(GetId()); + e.SetInt(GetSelection()); + GetEventHandler()->ProcessEvent(e); + } + break; + case WXK_TAB: + HandleAsNavigationKey(event); + break; + default: + event.Skip(); + break; + } +} + +void ComboBox::OnEdit() +{ + auto value = GetTextCtrl()->GetValue(); + SetValue(value); +} + +#ifdef __WIN32__ + +WXLRESULT ComboBox::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam) +{ + if (nMsg == WM_GETDLGCODE) { + return DLGC_WANTALLKEYS; + } + return TextInput::MSWWindowProc(nMsg, wParam, lParam); +} + +#endif + +void ComboBox::sendComboBoxEvent() +{ + wxCommandEvent event(wxEVT_COMBOBOX, GetId()); + event.SetEventObject(this); + event.SetInt(drop.GetSelection()); + event.SetString(drop.GetValue()); + GetEventHandler()->ProcessEvent(event); +} diff --git a/src/slic3r/GUI/Widgets/ComboBox.hpp b/src/slic3r/GUI/Widgets/ComboBox.hpp new file mode 100644 index 0000000000..0a8e018dce --- /dev/null +++ b/src/slic3r/GUI/Widgets/ComboBox.hpp @@ -0,0 +1,92 @@ +#ifndef slic3r_GUI_ComboBox_hpp_ +#define slic3r_GUI_ComboBox_hpp_ + +#include "TextInput.hpp" +#include "DropDown.hpp" + +#define CB_NO_DROP_ICON DD_NO_CHECK_ICON +#define CB_NO_TEXT DD_NO_TEXT + +class ComboBox : public wxWindowWithItems +{ + std::vector texts; + std::vector icons; + std::vector datas; + std::vector types; + + DropDown drop; + bool drop_down = false; + bool text_off = false; + +public: + ComboBox(wxWindow * parent, + wxWindowID id, + const wxString &value = wxEmptyString, + const wxPoint & pos = wxDefaultPosition, + const wxSize & size = wxDefaultSize, + int n = 0, + const wxString choices[] = NULL, + long style = 0); + + DropDown & GetDropDown() { return drop; } + + virtual bool SetFont(wxFont const & font) override; + +public: + int Append(const wxString &item, const wxBitmap &bitmap = wxNullBitmap); + + int Append(const wxString &item, const wxBitmap &bitmap, void *clientData); + + unsigned int GetCount() const override; + + int GetSelection() const override; + + void SetSelection(int n) override; + + virtual void Rescale() override; + + wxString GetValue() const; + void SetValue(const wxString &value); + + void SetLabel(const wxString &label) override; + wxString GetLabel() const override; + + void SetTextLabel(const wxString &label); + wxString GetTextLabel() const; + + wxString GetString(unsigned int n) const override; + void SetString(unsigned int n, wxString const &value) override; + + wxBitmap GetItemBitmap(unsigned int n); + +protected: + virtual int DoInsertItems(const wxArrayStringsAdapter &items, + unsigned int pos, + void ** clientData, + wxClientDataType type) override; + virtual void DoClear() override; + + void DoDeleteOneItem(unsigned int pos) override; + + void *DoGetItemClientData(unsigned int n) const override; + void DoSetItemClientData(unsigned int n, void *data) override; + + void OnEdit() override; + +#ifdef __WIN32__ + WXLRESULT MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam) override; +#endif + +private: + + // some useful events + void mouseDown(wxMouseEvent &event); + void mouseWheelMoved(wxMouseEvent &event); + void keyDown(wxKeyEvent &event); + + void sendComboBoxEvent(); + + DECLARE_EVENT_TABLE() +}; + +#endif // !slic3r_GUI_ComboBox_hpp_ diff --git a/src/slic3r/GUI/Widgets/DropDown.cpp b/src/slic3r/GUI/Widgets/DropDown.cpp new file mode 100644 index 0000000000..c0074b531d --- /dev/null +++ b/src/slic3r/GUI/Widgets/DropDown.cpp @@ -0,0 +1,472 @@ +#include "DropDown.hpp" +#include "Label.hpp" + +#include + +wxDEFINE_EVENT(EVT_DISMISS, wxCommandEvent); + +BEGIN_EVENT_TABLE(DropDown, wxPopupTransientWindow) + +EVT_LEFT_DOWN(DropDown::mouseDown) +EVT_LEFT_UP(DropDown::mouseReleased) +EVT_MOUSE_CAPTURE_LOST(DropDown::mouseCaptureLost) +EVT_MOTION(DropDown::mouseMove) +EVT_MOUSEWHEEL(DropDown::mouseWheelMoved) + +// catch paint events +EVT_PAINT(DropDown::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(). + */ + +DropDown::DropDown(std::vector &texts, + std::vector &icons) + : texts(texts) + , icons(icons) + , state_handler(this) + , border_color(0xDBDBDB) + , text_color(0x363636) + , selector_border_color(std::make_pair(0x00AE42, (int) StateColor::Hovered), + std::make_pair(*wxWHITE, (int) StateColor::Normal)) + , selector_background_color(std::make_pair(0xEDFAF2, (int) StateColor::Checked), + std::make_pair(*wxWHITE, (int) StateColor::Normal)) +{ +} + +DropDown::DropDown(wxWindow * parent, + std::vector &texts, + std::vector &icons, + long style) + : DropDown(texts, icons) +{ + Create(parent, style); +} + +void DropDown::Create(wxWindow * parent, + long style) +{ + wxPopupTransientWindow::Create(parent); + SetBackgroundStyle(wxBG_STYLE_PAINT); + SetBackgroundColour(*wxWHITE); + state_handler.attach({&border_color, &text_color, &selector_border_color, &selector_background_color}); + state_handler.update_binds(); + if ((style & DD_NO_CHECK_ICON) == 0) + check_bitmap = ScalableBitmap(this, "checked", 16); + text_off = style & DD_NO_TEXT; + + // BBS set default font + SetFont(Label::Body_14); +#ifdef __WXOSX__ + // wxPopupTransientWindow releases mouse on idle, which may cause various problems, + // such as losting mouse move, and dismissing soon on first LEFT_DOWN event. + Bind(wxEVT_IDLE, [] (wxIdleEvent & evt) {}); +#endif +} + +void DropDown::Invalidate(bool clear) +{ + if (clear) { + selection = hover_item = -1; + offset = wxPoint(); + } + assert(selection < (int) texts.size()); + need_sync = true; +} + +void DropDown::SetSelection(int n) +{ + assert(n < (int) texts.size()); + if (n >= (int) texts.size()) + n = -1; + if (selection == n) return; + selection = n; + paintNow(); +} + +wxString DropDown::GetValue() const +{ + return selection >= 0 ? texts[selection] : wxString(); +} + +void DropDown::SetValue(const wxString &value) +{ + auto i = std::find(texts.begin(), texts.end(), value); + selection = i == texts.end() ? -1 : std::distance(texts.begin(), i); +} + +void DropDown::SetCornerRadius(double radius) +{ + this->radius = radius; + paintNow(); +} + +void DropDown::SetBorderColor(StateColor const &color) +{ + border_color = color; + state_handler.update_binds(); + paintNow(); +} + +void DropDown::SetSelectorBorderColor(StateColor const &color) +{ + selector_border_color = color; + state_handler.update_binds(); + paintNow(); +} + +void DropDown::SetTextColor(StateColor const &color) +{ + text_color = color; + state_handler.update_binds(); + paintNow(); +} + +void DropDown::SetSelectorBackgroundColor(StateColor const &color) +{ + selector_background_color = color; + state_handler.update_binds(); + paintNow(); +} + +void DropDown::SetUseContentWidth(bool use) +{ + if (use_content_width == use) + return; + use_content_width = use; + need_sync = true; + messureSize(); +} + +void DropDown::SetAlignIcon(bool align) { align_icon = align; } + +void DropDown::Rescale() +{ + need_sync = true; +} + +bool DropDown::HasDismissLongTime() +{ + auto now = boost::posix_time::microsec_clock::universal_time(); + return !IsShown() && + (now - dismissTime).total_milliseconds() >= 200; +} + +void DropDown::paintEvent(wxPaintEvent& evt) +{ + // depending on your system you may need to look at double-buffered dcs + wxBufferedPaintDC dc(this); + render(dc); +} + +/* + * Alternatively, you can use a clientDC to paint on the panel + * at any time. Using this generally does not free you from + * catching paint events, since it is possible that e.g. the window + * manager throws away your drawing when the window comes to the + * background, and expects you will redraw it when the window comes + * back (by sending a paint event). + */ +void DropDown::paintNow() +{ + // depending on your system you may need to look at double-buffered dcs + //wxClientDC dc(this); + //render(dc); + Refresh(); +} + +/* + * 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 DropDown::render(wxDC &dc) +{ + if (texts.size() == 0) return; + int states = state_handler.states(); + dc.SetPen(wxPen(border_color.colorForStates(states))); + dc.SetBrush(wxBrush(GetBackgroundColour())); + // if (GetWindowStyle() & wxBORDER_NONE) + // dc.SetPen(wxNullPen); + + // draw background + wxSize size = GetSize(); + if (radius == 0) + dc.DrawRectangle(0, 0, size.x, size.y); + else + dc.DrawRoundedRectangle(0, 0, size.x, size.y, radius); + + // draw hover rectangle + wxRect rcContent = {{0, offset.y}, rowSize}; + if (hover_item >= 0 && (states & StateColor::Hovered)) { + rcContent.y += rowSize.y * hover_item; + if (rcContent.GetBottom() > 0 && rcContent.y < size.y) { + if (selection == hover_item) + dc.SetBrush(wxBrush(selector_background_color.colorForStates(states | StateColor::Checked))); + dc.SetPen(wxPen(selector_border_color.colorForStates(states))); + rcContent.Deflate(4, 1); + dc.DrawRectangle(rcContent); + rcContent.Inflate(4, 1); + } + rcContent.y = offset.y; + } + // draw checked rectangle + if (selection >= 0 && (selection != hover_item || (states & StateColor::Hovered) == 0)) { + rcContent.y += rowSize.y * selection; + if (rcContent.GetBottom() > 0 && rcContent.y < size.y) { + dc.SetBrush(wxBrush(selector_background_color.colorForStates(states | StateColor::Checked))); + dc.SetPen(wxPen(selector_background_color.colorForStates(states))); + rcContent.Deflate(4, 1); + dc.DrawRectangle(rcContent); + rcContent.Inflate(4, 1); + } + rcContent.y = offset.y; + } + dc.SetBrush(*wxTRANSPARENT_BRUSH); + { + wxSize offset = (rowSize - textSize) / 2; + rcContent.Deflate(0, offset.y); + } + + // draw position bar + if (rowSize.y * texts.size() > size.y) { + int height = rowSize.y * texts.size(); + wxRect rect = {size.x - 6, -offset.y * size.y / height, 4, + size.y * size.y / height}; + dc.SetPen(wxPen(border_color.defaultColor())); + dc.SetBrush(wxBrush(*wxLIGHT_GREY)); + dc.DrawRoundedRectangle(rect, 2); + rcContent.width -= 6; + } + + // draw check icon + rcContent.x += 5; + rcContent.width -= 5; + if (check_bitmap.bmp().IsOk()) { + auto szBmp = check_bitmap.GetBmpSize(); + if (selection >= 0) { + wxPoint pt = rcContent.GetLeftTop(); + pt.y += (rcContent.height - szBmp.y) / 2; + pt.y += rowSize.y * selection; + if (pt.y + szBmp.y > 0 && pt.y < size.y) + dc.DrawBitmap(check_bitmap.bmp(), pt); + } + rcContent.x += szBmp.x + 5; + rcContent.width -= szBmp.x + 5; + } + // draw texts & icons + dc.SetTextForeground(text_color.colorForStates(states)); + for (int i = 0; i < texts.size(); ++i) { + if (rcContent.GetBottom() < 0) { + rcContent.y += rowSize.y; + continue; + } + if (rcContent.y > size.y) break; + wxPoint pt = rcContent.GetLeftTop(); + auto & icon = icons[i]; + if (iconSize.x > 0) { + if (icon.IsOk()) { + pt.y += (rcContent.height - icon.GetSize().y) / 2; + dc.DrawBitmap(icon, pt); + } + pt.x += iconSize.x + 5; + pt.y = rcContent.y; + } else if (icon.IsOk()) { + pt.y += (rcContent.height - icon.GetSize().y) / 2; + dc.DrawBitmap(icon, pt); + pt.x += icon.GetWidth() + 5; + pt.y = rcContent.y; + } + auto text = texts[i]; + if (!text_off && !text.IsEmpty()) { + wxSize tSize = dc.GetMultiLineTextExtent(text); + if (pt.x + tSize.x > rcContent.GetRight()) { + text = wxControl::Ellipsize(text, dc, wxELLIPSIZE_END, + rcContent.GetRight() - pt.x); + } + pt.y += (rcContent.height - textSize.y) / 2; + dc.SetFont(GetFont()); + dc.DrawText(text, pt); + } + rcContent.y += rowSize.y; + } +} + +void DropDown::messureSize() +{ + if (!need_sync) return; + textSize = wxSize(); + iconSize = wxSize(); + wxClientDC dc(GetParent() ? GetParent() : this); + for (size_t i = 0; i < texts.size(); ++i) { + wxSize size1 = text_off ? wxSize() : dc.GetMultiLineTextExtent(texts[i]); + if (icons[i].IsOk()) { + wxSize size2 = icons[i].GetSize(); + if (size2.x > iconSize.x) iconSize = size2; + if (!align_icon) { + size1.x += size2.x + (text_off ? 0 : 5); + } + } + if (size1.x > textSize.x) textSize = size1; + } + if (!align_icon) iconSize.x = 0; + wxSize szContent = textSize; + szContent.x += 10; + if (check_bitmap.bmp().IsOk()) { + auto szBmp = check_bitmap.bmp().GetSize(); + szContent.x += szBmp.x + 5; + } + if (iconSize.x > 0) szContent.x += iconSize.x + (text_off ? 0 : 5); + if (iconSize.y > szContent.y) szContent.y = iconSize.y; + szContent.y += 10; + if (texts.size() > 15) szContent.x += 6; + if (GetParent()) { + auto x = GetParent()->GetSize().x; + if (!use_content_width || x > szContent.x) + szContent.x = x; + } + rowSize = szContent; + szContent.y *= std::min((size_t)15, texts.size()); + szContent.y += texts.size() > 15 ? rowSize.y / 2 : 0; + wxWindow::SetSize(szContent); + need_sync = false; +} + +void DropDown::autoPosition() +{ + messureSize(); + wxPoint pos = GetParent()->ClientToScreen(wxPoint(0, -6)); + wxPoint old = GetPosition(); + wxSize size = GetSize(); + Position(pos, {0, GetParent()->GetSize().y + 12}); + if (old != GetPosition()) { + size = rowSize; + size.y *= std::min((size_t)15, texts.size()); + size.y += texts.size() > 15 ? rowSize.y / 2 : 0; + if (size != GetSize()) { + wxWindow::SetSize(size); + offset = wxPoint(); + Position(pos, {0, GetParent()->GetSize().y + 12}); + } + } + if (GetPosition().y > pos.y) { + // may exceed + auto drect = wxDisplay(GetParent()).GetGeometry(); + if (GetPosition().y + size.y + 10 > drect.GetBottom()) { + if (use_content_width && texts.size() <= 15) size.x += 6; + size.y = drect.GetBottom() - GetPosition().y - 10; + wxWindow::SetSize(size); + if (selection >= 0) { + if (offset.y + rowSize.y * (selection + 1) > size.y) + offset.y = size.y - rowSize.y * (selection + 1); + else if (offset.y + rowSize.y * selection < 0) + offset.y = -rowSize.y * selection; + } + } + } +} + +void DropDown::mouseDown(wxMouseEvent& event) +{ + // Receivce unexcepted LEFT_DOWN on Mac after OnDismiss + if (!IsShown()) + return; + // force calc hover item again + mouseMove(event); + pressedDown = true; + CaptureMouse(); + dragStart = event.GetPosition(); +} + +void DropDown::mouseReleased(wxMouseEvent& event) +{ + if (pressedDown) { + dragStart = wxPoint(); + pressedDown = false; + if (HasCapture()) + ReleaseMouse(); + if (hover_item >= 0) { // not moved + sendDropDownEvent(); + DismissAndNotify(); + } + } +} + +void DropDown::mouseCaptureLost(wxMouseCaptureLostEvent &event) +{ + wxMouseEvent evt; + mouseReleased(evt); +} + +void DropDown::mouseMove(wxMouseEvent &event) +{ + wxPoint pt = event.GetPosition(); + if (pressedDown) { + wxPoint pt2 = offset + pt - dragStart; + dragStart = pt; + if (pt2.y > 0) + pt2.y = 0; + else if (pt2.y + rowSize.y * texts.size() < GetSize().y) + pt2.y = GetSize().y - rowSize.y * texts.size(); + if (pt2.y != offset.y) { + offset = pt2; + hover_item = -1; // moved + } else { + return; + } + } + if (!pressedDown || hover_item >= 0) { + int hover = (pt.y - offset.y) / rowSize.y; + if (hover >= (int) texts.size()) hover = -1; + if (hover == hover_item) return; + hover_item = hover; + if (hover >= 0) + SetToolTip(texts[hover]); + } + paintNow(); +} + +void DropDown::mouseWheelMoved(wxMouseEvent &event) +{ + auto delta = event.GetWheelRotation() > 0 ? rowSize.y : -rowSize.y; + wxPoint pt2 = offset + wxPoint{0, delta}; + if (pt2.y > 0) + pt2.y = 0; + else if (pt2.y + rowSize.y * texts.size() < GetSize().y) + pt2.y = GetSize().y - rowSize.y * texts.size(); + if (pt2.y != offset.y) { + offset = pt2; + } else { + return; + } + int hover = (event.GetPosition().y - offset.y) / rowSize.y; + if (hover >= (int) texts.size()) hover = -1; + if (hover != hover_item) { + hover_item = hover; + if (hover >= 0) SetToolTip(texts[hover]); + } + paintNow(); +} + +// currently unused events +void DropDown::sendDropDownEvent() +{ + selection = hover_item; + wxCommandEvent event(wxEVT_COMBOBOX, GetId()); + event.SetEventObject(this); + event.SetInt(selection); + event.SetString(GetValue()); + GetEventHandler()->ProcessEvent(event); +} + +void DropDown::OnDismiss() +{ + dismissTime = boost::posix_time::microsec_clock::universal_time(); + hover_item = -1; + wxCommandEvent e(EVT_DISMISS); + GetEventHandler()->ProcessEvent(e); +} diff --git a/src/slic3r/GUI/Widgets/DropDown.hpp b/src/slic3r/GUI/Widgets/DropDown.hpp new file mode 100644 index 0000000000..b2b7eaa73e --- /dev/null +++ b/src/slic3r/GUI/Widgets/DropDown.hpp @@ -0,0 +1,111 @@ +#ifndef slic3r_GUI_DropDown_hpp_ +#define slic3r_GUI_DropDown_hpp_ + +#include +#include "../wxExtensions.hpp" +#include "StateHandler.hpp" + +#define DD_NO_CHECK_ICON 0x0001 +#define DD_NO_TEXT 0x0002 +#define DD_STYLE_MASK 0x0003 + +wxDECLARE_EVENT(EVT_DISMISS, wxCommandEvent); + +class DropDown : public wxPopupTransientWindow +{ + std::vector & texts; + std::vector & icons; + bool need_sync = false; + int selection = -1; + int hover_item = -1; + + double radius = 0; + bool use_content_width = false; + bool align_icon = false; + bool text_off = false; + + wxSize textSize; + wxSize iconSize; + wxSize rowSize; + + StateHandler state_handler; + StateColor text_color; + StateColor border_color; + StateColor selector_border_color; + StateColor selector_background_color; + ScalableBitmap check_bitmap; + + bool pressedDown = false; + boost::posix_time::ptime dismissTime; + wxPoint offset; // x not used + wxPoint dragStart; + +public: + DropDown(std::vector &texts, + std::vector &icons); + + DropDown(wxWindow * parent, + std::vector &texts, + std::vector &icons, + long style = 0); + + void Create(wxWindow * parent, + long style = 0); + +public: + void Invalidate(bool clear = false); + + int GetSelection() const { return selection; } + + void SetSelection(int n); + + wxString GetValue() const; + void SetValue(const wxString &value); + +public: + void SetCornerRadius(double radius); + + void SetBorderColor(StateColor const & color); + + void SetSelectorBorderColor(StateColor const & color); + + void SetTextColor(StateColor const &color); + + void SetSelectorBackgroundColor(StateColor const &color); + + void SetUseContentWidth(bool use); + + void SetAlignIcon(bool align); + +public: + void Rescale(); + + bool HasDismissLongTime(); + +protected: + void OnDismiss() override; + +private: + void paintEvent(wxPaintEvent& evt); + void paintNow(); + + void render(wxDC& dc); + + friend class ComboBox; + void messureSize(); + void autoPosition(); + + // some useful events + void mouseDown(wxMouseEvent& event); + void mouseReleased(wxMouseEvent &event); + void mouseCaptureLost(wxMouseCaptureLostEvent &event); + void mouseMove(wxMouseEvent &event); + void mouseWheelMoved(wxMouseEvent &event); + + void sendDropDownEvent(); + + + DECLARE_EVENT_TABLE() +}; + +#endif // !slic3r_GUI_DropDown_hpp_ diff --git a/src/slic3r/GUI/Widgets/Label.cpp b/src/slic3r/GUI/Widgets/Label.cpp new file mode 100644 index 0000000000..4a541f3478 --- /dev/null +++ b/src/slic3r/GUI/Widgets/Label.cpp @@ -0,0 +1,124 @@ +#include "Label.hpp" +#include "StaticBox.hpp" + +wxFont Label::sysFont(int size, bool bold) +{ +//#ifdef __linux__ +// return wxFont{}; +//#endif +#ifndef __APPLE__ + size = size * 4 / 5; +#endif + + auto face = wxString::FromUTF8("HarmonyOS Sans SC"); + wxFont font{size, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, bold ? wxFONTWEIGHT_BOLD : wxFONTWEIGHT_NORMAL, false, face}; + font.SetFaceName(face); + if (!font.IsOk()) { + font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT); + if (bold) font.MakeBold(); + font.SetPointSize(size); + } + return font; +} +wxFont Label::Head_24; +wxFont Label::Head_20; +wxFont Label::Head_18; +wxFont Label::Head_16; +wxFont Label::Head_15; +wxFont Label::Head_14; +wxFont Label::Head_13; +wxFont Label::Head_12; +wxFont Label::Head_10; + +wxFont Label::Body_16; +wxFont Label::Body_15; +wxFont Label::Body_14; +wxFont Label::Body_13; +wxFont Label::Body_12; +wxFont Label::Body_11; +wxFont Label::Body_10; +wxFont Label::Body_9; + +void Label::initSysFont() +{ + Head_24 = Label::sysFont(24, true); + Head_20 = Label::sysFont(20, true); + Head_18 = Label::sysFont(18, true); + Head_16 = Label::sysFont(16, true); + Head_15 = Label::sysFont(15, true); + Head_14 = Label::sysFont(14, true); + Head_13 = Label::sysFont(13, true); + Head_12 = Label::sysFont(12, true); + Head_10 = Label::sysFont(10, true); + + Body_16 = Label::sysFont(16, false); + Body_15 = Label::sysFont(15, false); + Body_14 = Label::sysFont(14, false); + Body_13 = Label::sysFont(13, false); + Body_12 = Label::sysFont(12, false); + Body_11 = Label::sysFont(11, false); + Body_10 = Label::sysFont(10, false); + Body_9 = Label::sysFont(9, false); +} + +wxSize Label::split_lines(wxDC &dc, int width, const wxString &text, wxString &multiline_text) +{ + multiline_text = text; + if (width > 0 && dc.GetTextExtent(text).x > width) { + size_t start = 0; + while (true) { + size_t idx = size_t(-1); + for (size_t i = start; i < multiline_text.Len(); i++) { + if (multiline_text[i] == ' ') { + if (dc.GetTextExtent(multiline_text.SubString(start, i)).x < width) + idx = i; + else { + if (idx == size_t(-1)) idx = i; + break; + } + } + } + if (idx == size_t(-1)) break; + multiline_text[idx] = '\n'; + start = idx + 1; + if (dc.GetTextExtent(multiline_text.Mid(start)).x < width) break; + } + } + return dc.GetMultiLineTextExtent(multiline_text); +} + +Label::Label(wxWindow *parent, wxString const &text, long style) : Label(parent, Body_14, text, style) {} + +Label::Label(wxWindow *parent, wxFont const &font, wxString const &text, long style) + : wxStaticText(parent, wxID_ANY, text, wxDefaultPosition, wxDefaultSize, style) +{ + this->font = font; + SetFont(font); + SetBackgroundColour(StaticBox::GetParentBackgroundColor(parent)); + Bind(wxEVT_ENTER_WINDOW, [this](auto &e) { + if (GetWindowStyle() & LB_HYPERLINK) { + SetFont(this->font.Underlined()); + Refresh(); + } + }); + Bind(wxEVT_LEAVE_WINDOW, [this](auto &e) { + SetFont(this->font); + Refresh(); + }); +} + +void Label::SetWindowStyleFlag(long style) +{ + if (style == GetWindowStyle()) + return; + wxStaticText::SetWindowStyleFlag(style); + if (style & LB_HYPERLINK) { + this->color = GetForegroundColour(); + static wxColor clr_url("#00AE42"); + SetForegroundColour(clr_url); + } else { + SetForegroundColour(this->color); + SetFont(this->font); + } + Refresh(); +} diff --git a/src/slic3r/GUI/Widgets/Label.hpp b/src/slic3r/GUI/Widgets/Label.hpp new file mode 100644 index 0000000000..1d3d387fb7 --- /dev/null +++ b/src/slic3r/GUI/Widgets/Label.hpp @@ -0,0 +1,49 @@ +#ifndef slic3r_GUI_Label_hpp_ +#define slic3r_GUI_Label_hpp_ + +#include + +#define LB_HYPERLINK 0x0001 + + +class Label : public wxStaticText +{ +public: + Label(wxWindow *parent, wxString const &text = {}, long style = 0); + + Label(wxWindow *parent, wxFont const &font, wxString const &text = {}, long style = 0); + + void SetWindowStyleFlag(long style) override; + +private: + wxFont font; + wxColour color; + +public: + static wxFont Head_24; + static wxFont Head_20; + static wxFont Head_18; + static wxFont Head_16; + static wxFont Head_15; + static wxFont Head_14; + static wxFont Head_13; + static wxFont Head_12; + static wxFont Head_10; + + static wxFont Body_16; + static wxFont Body_15; + static wxFont Body_14; + static wxFont Body_13; + static wxFont Body_12; + static wxFont Body_10; + static wxFont Body_11; + static wxFont Body_9; + + static void initSysFont(); + + static wxFont sysFont(int size, bool bold = false); + + static wxSize split_lines(wxDC &dc, int width, const wxString &text, wxString &multiline_text); +}; + +#endif // !slic3r_GUI_Label_hpp_ diff --git a/src/slic3r/GUI/Widgets/SpinInput.cpp b/src/slic3r/GUI/Widgets/SpinInput.cpp new file mode 100644 index 0000000000..9bea97dcdc --- /dev/null +++ b/src/slic3r/GUI/Widgets/SpinInput.cpp @@ -0,0 +1,323 @@ +#include "SpinInput.hpp" +#include "Label.hpp" +#include "Button.hpp" + +#include + +BEGIN_EVENT_TABLE(SpinInput, wxPanel) + +EVT_KEY_DOWN(SpinInput::keyPressed) +EVT_MOUSEWHEEL(SpinInput::mouseWheelMoved) + +EVT_PAINT(SpinInput::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(). + */ + +SpinInput::SpinInput() + : label_color(std::make_pair(0x909090, (int) StateColor::Disabled), std::make_pair(0x6B6B6B, (int) StateColor::Normal)) + , text_color(std::make_pair(0x909090, (int) StateColor::Disabled), std::make_pair(0x262E30, (int) StateColor::Normal)) +{ + radius = 0; + border_width = 1; + border_color = StateColor(std::make_pair(0xDBDBDB, (int) StateColor::Disabled), std::make_pair(0x00AE42, (int) StateColor::Hovered), + std::make_pair(0xDBDBDB, (int) StateColor::Normal)); + background_color = StateColor(std::make_pair(0xF0F0F0, (int) StateColor::Disabled), std::make_pair(*wxWHITE, (int) StateColor::Normal)); +} + + +SpinInput::SpinInput(wxWindow *parent, + wxString text, + wxString label, + const wxPoint &pos, + const wxSize & size, + long style, + int min, int max, int initial) + : SpinInput() +{ + Create(parent, text, label, pos, size, style, min, max, initial); +} + +void SpinInput::Create(wxWindow *parent, + wxString text, + wxString label, + const wxPoint &pos, + const wxSize & size, + long style, + int min, int max, int initial) +{ + StaticBox::Create(parent, wxID_ANY, pos, size); + SetFont(Label::Body_12); + wxWindow::SetLabel(label); + state_handler.attach({&label_color, &text_color}); + state_handler.update_binds(); + text_ctrl = new wxTextCtrl(this, wxID_ANY, text, {20, 4}, wxDefaultSize, style | wxBORDER_NONE | wxTE_PROCESS_ENTER, wxTextValidator(wxFILTER_DIGITS)); + text_ctrl->SetFont(Label::Body_14); + text_ctrl->SetBackgroundColour(background_color.colorForStates(state_handler.states())); + text_ctrl->SetForegroundColour(text_color.colorForStates(state_handler.states())); + text_ctrl->SetInitialSize(text_ctrl->GetBestSize()); + state_handler.attach_child(text_ctrl); + text_ctrl->Bind(wxEVT_KILL_FOCUS, &SpinInput::onTextLostFocus, this); + text_ctrl->Bind(wxEVT_TEXT_ENTER, &SpinInput::onTextEnter, this); + text_ctrl->Bind(wxEVT_KEY_DOWN, &SpinInput::keyPressed, this); + text_ctrl->Bind(wxEVT_RIGHT_DOWN, [this](auto &e) {}); // disable context menu + button_inc = createButton(true); + button_dec = createButton(false); + delta = 0; + timer.Bind(wxEVT_TIMER, &SpinInput::onTimer, this); + + long initialFromText; + if (text.ToLong(&initialFromText)) initial = initialFromText; + SetRange(min, max); + SetValue(initial); + messureSize(); +} + +void SpinInput::SetCornerRadius(double radius) +{ + this->radius = radius; + Refresh(); +} + +void SpinInput::SetLabel(const wxString &label) +{ + wxWindow::SetLabel(label); + messureSize(); + Refresh(); +} + +void SpinInput::SetLabelColor(StateColor const &color) +{ + label_color = color; + state_handler.update_binds(); +} + +void SpinInput::SetTextColor(StateColor const &color) +{ + text_color = color; + state_handler.update_binds(); +} + +void SpinInput::SetSize(wxSize const &size) +{ + wxWindow::SetSize(size); + Rescale(); +} + +void SpinInput::SetValue(const wxString &text) +{ + long value; + if ( text.ToLong(&value) ) + SetValue(value); +} + +void SpinInput::SetValue(int value) +{ + if (value < min) value = min; + else if (value > max) value = max; + this->val = value; + text_ctrl->SetValue(wxString::FromDouble(value)); +} + +int SpinInput::GetValue()const +{ + return val; +} + +void SpinInput::SetRange(int min, int max) +{ + this->min = min; + this->max = max; +} + +void SpinInput::DoSetToolTipText(wxString const &tip) +{ + wxWindow::DoSetToolTipText(tip); + text_ctrl->SetToolTip(tip); +} + +void SpinInput::Rescale() +{ + button_inc->Rescale(); + button_dec->Rescale(); + messureSize(); +} + +bool SpinInput::Enable(bool enable) +{ + bool result = text_ctrl->Enable(enable) && wxWindow::Enable(enable); + if (result) { + wxCommandEvent e(EVT_ENABLE_CHANGED); + e.SetEventObject(this); + GetEventHandler()->ProcessEvent(e); + text_ctrl->SetBackgroundColour(background_color.colorForStates(state_handler.states())); + text_ctrl->SetForegroundColour(text_color.colorForStates(state_handler.states())); + button_inc->Enable(enable); + button_dec->Enable(enable); + } + return result; +} + +void SpinInput::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 SpinInput::render(wxDC& dc) +{ + StaticBox::render(dc); + int states = state_handler.states(); + wxSize size = GetSize(); + // draw seperator of buttons + wxPoint pt = button_inc->GetPosition(); + pt.y = size.y / 2; + dc.SetPen(wxPen(border_color.defaultColor())); + dc.DrawLine(pt, pt + wxSize{button_inc->GetSize().x - 2, 0}); + // draw label + auto label = GetLabel(); + if (!label.IsEmpty()) { + pt.x = size.x - labelSize.x - 5; + pt.y = (size.y - labelSize.y) / 2; + dc.SetFont(GetFont()); + dc.SetTextForeground(label_color.colorForStates(states)); + dc.DrawText(label, pt); + } +} + +void SpinInput::messureSize() +{ + wxSize size = GetSize(); + wxSize textSize = text_ctrl->GetSize(); + int h = textSize.y + 8; + if (size.y < h) { + size.y = h; + SetSize(size); + SetMinSize(size); + } else { + textSize.y = size.y * 14 / 24; + } + wxSize btnSize = {14, (size.y - 4) / 2}; + btnSize.x = btnSize.x * btnSize.y / 10; + wxClientDC dc(this); + labelSize = dc.GetMultiLineTextExtent(GetLabel()); + textSize.x = size.x - labelSize.x - btnSize.x - 16; + text_ctrl->SetSize(textSize); + text_ctrl->SetPosition({6 + btnSize.x, (size.y - textSize.y) / 2}); + button_inc->SetSize(btnSize); + button_dec->SetSize(btnSize); + button_inc->SetPosition({3, size.y / 2 - btnSize.y - 1}); + button_dec->SetPosition({3, size.y / 2 + 1}); +} + +Button *SpinInput::createButton(bool inc) +{ + auto btn = new Button(this, "", inc ? "spin_inc" : "spin_dec", wxBORDER_NONE, 6); + btn->SetCornerRadius(0); + btn->DisableFocusFromKeyboard(); + btn->Bind(wxEVT_LEFT_DOWN, [=](auto &e) { + delta = inc ? 1 : -1; + SetValue(val + delta); + text_ctrl->SetFocus(); + btn->CaptureMouse(); + delta *= 8; + timer.Start(100); + sendSpinEvent(); + }); + btn->Bind(wxEVT_LEFT_DCLICK, [=](auto &e) { + delta = inc ? 1 : -1; + btn->CaptureMouse(); + SetValue(val + delta); + sendSpinEvent(); + }); + btn->Bind(wxEVT_LEFT_UP, [=](auto &e) { + btn->ReleaseMouse(); + timer.Stop(); + text_ctrl->SelectAll(); + delta = 0; + }); + return btn; +} + +void SpinInput::onTimer(wxTimerEvent &evnet) { + if (delta < -1 || delta > 1) { + delta /= 2; + return; + } + SetValue(val + delta); + sendSpinEvent(); +} + +void SpinInput::onTextLostFocus(wxEvent &event) +{ + timer.Stop(); + for (auto * child : GetChildren()) + if (auto btn = dynamic_cast(child)) + if (btn->HasCapture()) + btn->ReleaseMouse(); + wxCommandEvent e; + onTextEnter(e); + // pass to outer + event.SetId(GetId()); + ProcessEventLocally(event); + e.Skip(); +} + +void SpinInput::onTextEnter(wxCommandEvent &event) +{ + long value; + if (!text_ctrl->GetValue().ToLong(&value)) { value = val; } + if (value != val) { + SetValue(value); + sendSpinEvent(); + } + event.SetId(GetId()); + ProcessEventLocally(event); +} + +void SpinInput::mouseWheelMoved(wxMouseEvent &event) +{ + auto delta = (event.GetWheelRotation() < 0 == event.IsWheelInverted()) ? 1 : -1; + SetValue(val + delta); + sendSpinEvent(); + text_ctrl->SetFocus(); +} + +void SpinInput::keyPressed(wxKeyEvent &event) +{ + switch (event.GetKeyCode()) { + case WXK_UP: + case WXK_DOWN: + long value; + if (!text_ctrl->GetValue().ToLong(&value)) { value = val; } + if (event.GetKeyCode() == WXK_DOWN && value > min) { + --value; + } else if (event.GetKeyCode() == WXK_UP && value + 1 < max) { + ++value; + } + if (value != val) { + SetValue(value); + sendSpinEvent(); + } + break; + default: event.Skip(); break; + } +} + +void SpinInput::sendSpinEvent() +{ + wxCommandEvent event(wxEVT_SPINCTRL, GetId()); + event.SetEventObject(this); + GetEventHandler()->ProcessEvent(event); +} diff --git a/src/slic3r/GUI/Widgets/SpinInput.hpp b/src/slic3r/GUI/Widgets/SpinInput.hpp new file mode 100644 index 0000000000..5b0868880c --- /dev/null +++ b/src/slic3r/GUI/Widgets/SpinInput.hpp @@ -0,0 +1,96 @@ +#ifndef slic3r_GUI_SpinInput_hpp_ +#define slic3r_GUI_SpinInput_hpp_ + +#include +#include "StaticBox.hpp" + +class Button; + +class SpinInput : public wxNavigationEnabled +{ + wxSize labelSize; + StateColor label_color; + StateColor text_color; + wxTextCtrl * text_ctrl; + Button * button_inc; + Button * button_dec; + wxTimer timer; + + int val; + int min; + int max; + int delta; + + static const int SpinInputWidth = 200; + static const int SpinInputHeight = 50; + +public: + SpinInput(); + + SpinInput(wxWindow * parent, + wxString text, + wxString label = "", + const wxPoint &pos = wxDefaultPosition, + const wxSize & size = wxDefaultSize, + long style = 0, + int min = 0, int max = 100, int initial = 0); + + void Create(wxWindow * parent, + wxString text, + wxString label = "", + const wxPoint &pos = wxDefaultPosition, + const wxSize & size = wxDefaultSize, + long style = 0, + int min = 0, + int max = 100, + int initial = 0); + + void SetCornerRadius(double radius); + + void SetLabel(const wxString &label) wxOVERRIDE; + + void SetLabelColor(StateColor const &color); + + void SetTextColor(StateColor const &color); + + void SetSize(wxSize const &size); + + void Rescale(); + + virtual bool Enable(bool enable = true) wxOVERRIDE; + + wxTextCtrl * GetTextCtrl() { return text_ctrl; } + + void SetValue(const wxString &text); + + void SetValue (int value); + + int GetValue () const; + + void SetRange(int min, int max); + +protected: + void DoSetToolTipText(wxString const &tip) override; + +private: + void paintEvent(wxPaintEvent& evt); + + void render(wxDC& dc); + + void messureSize(); + + Button *createButton(bool inc); + + // some useful events + void mouseWheelMoved(wxMouseEvent& event); + void keyPressed(wxKeyEvent& event); + void onTimer(wxTimerEvent &evnet); + void onTextLostFocus(wxEvent &event); + void onTextEnter(wxCommandEvent &event); + + void sendSpinEvent(); + + DECLARE_EVENT_TABLE() +}; + +#endif // !slic3r_GUI_SpinInput_hpp_ diff --git a/src/slic3r/GUI/Widgets/StateColor.cpp b/src/slic3r/GUI/Widgets/StateColor.cpp new file mode 100644 index 0000000000..a3419b08d3 --- /dev/null +++ b/src/slic3r/GUI/Widgets/StateColor.cpp @@ -0,0 +1,93 @@ +#include "StateColor.hpp" + +StateColor::StateColor(wxColour const &color) { append(color, 0); } + +StateColor::StateColor(wxString const &color) { append(color, 0); } + +StateColor::StateColor(unsigned long color) { append(color, 0); } + +void StateColor::append(wxColour const & color, int states) +{ + statesList_.push_back(states); + colors_.push_back(color); +} + +void StateColor::append(wxString const & color, int states) +{ + wxColour c1(color); + append(c1, states); +} + +void StateColor::append(unsigned long color, int states) +{ + if ((color & 0xff000000) == 0) + color |= 0xff000000; + wxColour cl; cl.SetRGBA(color & 0xff00ff00 | ((color & 0xff) << 16) | ((color >> 16) & 0xff)); + append(cl, states); +} + +void StateColor::clear() +{ + statesList_.clear(); + colors_.clear(); +} + +int StateColor::states() const +{ + int states = 0; + for (auto s : statesList_) states |= s; + states = (states & 0xffff) | (states >> 16); + if (takeFocusedAsHovered_ && (states & Hovered)) + states |= Focused; + return states; +} + +wxColour StateColor::defaultColor() { + return colorForStates(0); +} + +wxColour StateColor::colorForStates(int states) +{ + bool focused = takeFocusedAsHovered_ && (states & Focused); + for (int i = 0; i < statesList_.size(); ++i) { + int s = statesList_[i]; + int on = s & 0xffff; + int off = s >> 16; + if ((on & states) == on && (off & ~states) == off) { + return colors_[i]; + } + if (focused && (on & Hovered)) { + on |= Focused; + on &= ~Hovered; + if ((on & states) == on && (off & ~states) == off) { + return colors_[i]; + } + } + } + return wxColour(0, 0, 0, 0); +} + +int StateColor::colorIndexForStates(int states) +{ + for (int i = 0; i < statesList_.size(); ++i) { + int s = statesList_[i]; + int on = s & 0xffff; + int off = s >> 16; + if ((on & states) == on && (off & ~states) == off) { return i; } + } + return -1; +} + +bool StateColor::setColorForStates(wxColour const &color, int states) +{ + for (int i = 0; i < statesList_.size(); ++i) { + if (statesList_[i] == states) { + colors_[i] = color; + return true; + } + } + return false; +} + +void StateColor::setTakeFocusedAsHovered(bool set) { takeFocusedAsHovered_ = set; } + diff --git a/src/slic3r/GUI/Widgets/StateColor.hpp b/src/slic3r/GUI/Widgets/StateColor.hpp new file mode 100644 index 0000000000..0855e64db4 --- /dev/null +++ b/src/slic3r/GUI/Widgets/StateColor.hpp @@ -0,0 +1,84 @@ +#ifndef slic3r_GUI_StateColor_hpp_ +#define slic3r_GUI_StateColor_hpp_ + +#include + +class StateColor +{ +public: + enum State { + Normal = 0, + Enabled = 1, + Checked = 2, + Focused = 4, + Hovered = 8, + Pressed = 16, + Disabled = 1 << 16, + NotChecked = 2 << 16, + NotFocused = 4 << 16, + NotHovered = 8 << 16, + NotPressed = 16 << 16, + }; + +public: + template + StateColor(std::pair... colors) { + fill(colors...); + } + + // single color + StateColor(wxColour const & color); + + // single color + StateColor(wxString const &color); + + // single color + StateColor(unsigned long color); + +public: + void append(wxColour const & color, int states); + + void append(wxString const &color, int states); + + void append(unsigned long color, int states); + + void clear(); + +public: + int count() const { return statesList_.size(); } + + int states() const; + +public: + wxColour defaultColor(); + + wxColour colorForStates(int states); + + int colorIndexForStates(int states); + + bool setColorForStates(wxColour const & color, int states); + + void setTakeFocusedAsHovered(bool set); + +private: + template + void fill(std::pair color, std::pair... colors) { + fillOne(color); + fill(colors...); + } + + template + void fillOne(std::pair color) { + append(color.first, color.second); + } + + void fill() { + } + +private: + std::vector statesList_; + std::vector colors_; + bool takeFocusedAsHovered_ = true; +}; + +#endif // !slic3r_GUI_StateColor_hpp_ diff --git a/src/slic3r/GUI/Widgets/StateHandler.cpp b/src/slic3r/GUI/Widgets/StateHandler.cpp new file mode 100644 index 0000000000..72616ed8fd --- /dev/null +++ b/src/slic3r/GUI/Widgets/StateHandler.cpp @@ -0,0 +1,122 @@ +#include "StateHandler.hpp" + +wxDEFINE_EVENT(EVT_ENABLE_CHANGED, wxCommandEvent); + +StateHandler::StateHandler(wxWindow * owner) + : owner_(owner) +{ + owner_->PushEventHandler(this); + if (owner->IsEnabled()) + states_ |= Enabled; + if (owner->HasFocus()) + states_ |= Focused; +} + +StateHandler::~StateHandler() { owner_->RemoveEventHandler(this); } + +void StateHandler::attach(StateColor const &color) +{ + colors_.push_back(&color); +} + +void StateHandler::attach(std::vector const & colors) +{ + colors_.insert(colors_.end(), colors.begin(), colors.end()); +} + +void StateHandler::attach_child(wxWindow *child) +{ + auto ch = new StateHandler(this, child); + children_.emplace_back(ch); + ch->update_binds(); + states2_ |= ch->states(); +} + +void StateHandler::remove_child(wxWindow *child) +{ + children_.erase(std::remove_if(children_.begin(), children_.end(), + [child](auto &c) { return c->owner_ == child; }), children_.end()); + states2_ = 0; + for (auto & c : children_) states2_ |= c->states(); +} + +void StateHandler::update_binds() +{ + int bind_states = parent_ ? (parent_->bind_states_ & ~Enabled) : 0; + for (auto c : colors_) { + bind_states |= c->states(); + } + bind_states = bind_states | (bind_states >> 16); + int diff = bind_states ^ bind_states_; + State states[] = {Enabled, Checked, Focused, Hovered, Pressed}; + wxEventType events[] = {EVT_ENABLE_CHANGED, wxEVT_CHECKBOX, wxEVT_SET_FOCUS, wxEVT_ENTER_WINDOW, wxEVT_LEFT_DOWN}; + wxEventType events2[] = {{0}, {0}, wxEVT_KILL_FOCUS, wxEVT_LEAVE_WINDOW, wxEVT_LEFT_UP}; + for (int i = 0; i < 5; ++i) { + int s = states[i]; + if (diff & s) { + if (bind_states & s) { + Bind(events[i], &StateHandler::changed, this); + if (events2[i]) + Bind(events2[i], &StateHandler::changed, this); + } else { + Unbind(events[i], &StateHandler::changed, this); + if (events2[i]) + owner_->Unbind(events2[i], &StateHandler::changed, this); + } + } + } + bind_states_ = bind_states; + for (auto &c : children_) c->update_binds(); +} + +StateHandler::StateHandler(StateHandler *parent, wxWindow *owner) + : StateHandler(owner) +{ + states_ &= ~Enabled; + parent_ = parent; +} + +void StateHandler::changed(wxEvent &event) +{ + event.Skip(); + wxEventType events[] = {EVT_ENABLE_CHANGED, wxEVT_CHECKBOX, wxEVT_SET_FOCUS, wxEVT_ENTER_WINDOW, wxEVT_LEFT_DOWN}; + wxEventType events2[] = {{0}, {0}, wxEVT_KILL_FOCUS, wxEVT_LEAVE_WINDOW, wxEVT_LEFT_UP}; + int old = states_; + // some events are from another window (ex: text_ctrl of TextInput), save state in states2_ to avoid conflicts + for (int i = 0; i < 5; ++i) { + if (events2[i]) { + if (event.GetEventType() == events[i]) { + states_ |= 1 << i; + break; + } else if (event.GetEventType() == events2[i]) { + states_ &= ~(1 << i); + break; + } + } + else { + if (event.GetEventType() == events[i]) { + states_ ^= (1 << i); + break; + } + } + } + if (old != states_ && (old | states2_) != (states_ | states2_)) { + if (parent_) + parent_->changed(states_ | states2_); + else + owner_->Refresh(); + } +} + +void StateHandler::changed(int) +{ + int old = states2_; + states2_ = 0; + for (auto &c : children_) states2_ |= c->states(); + if (old != states2_ && (old | states_) != (states_ | states2_)) { + if (parent_) + parent_->changed(states_ | states2_); + else + owner_->Refresh(); + } +} diff --git a/src/slic3r/GUI/Widgets/StateHandler.hpp b/src/slic3r/GUI/Widgets/StateHandler.hpp new file mode 100644 index 0000000000..9ef155c7db --- /dev/null +++ b/src/slic3r/GUI/Widgets/StateHandler.hpp @@ -0,0 +1,61 @@ +#ifndef slic3r_GUI_StateHandler_hpp_ +#define slic3r_GUI_StateHandler_hpp_ + +#include + +#include "StateColor.hpp" + +wxDECLARE_EVENT(EVT_ENABLE_CHANGED, wxCommandEvent); + +class StateHandler : public wxEvtHandler +{ +public: + enum State { + Enabled = 1, + Checked = 2, + Focused = 4, + Hovered = 8, + Pressed = 16, + Disabled = 1 << 16, + NotChecked = 2 << 16, + NotFocused = 4 << 16, + NotHovered = 8 << 16, + NotPressed = 16 << 16, + }; + +public: + StateHandler(wxWindow * owner); + + ~StateHandler(); + +public: + void attach(StateColor const & color); + + void attach(std::vector const & colors); + + void attach_child(wxWindow *child); + + void remove_child(wxWindow *child); + + void update_binds(); + + int states() const { return states_ | states2_; } + +private: + StateHandler(StateHandler * parent, wxWindow *owner); + + void changed(wxEvent &event); + + void changed(int state2); + +private: + wxWindow * owner_; + std::vector colors_; + int bind_states_ = 0; + int states_ = 0; + int states2_ = 0; // from children + std::vector> children_; + StateHandler * parent_ = nullptr; +}; + +#endif // !slic3r_GUI_StateHandler_hpp_ diff --git a/src/slic3r/GUI/Widgets/StaticBox.cpp b/src/slic3r/GUI/Widgets/StaticBox.cpp new file mode 100644 index 0000000000..1a806e0007 --- /dev/null +++ b/src/slic3r/GUI/Widgets/StaticBox.cpp @@ -0,0 +1,214 @@ +#include "StaticBox.hpp" +#include "../GUI.hpp" +#include + +BEGIN_EVENT_TABLE(StaticBox, wxWindow) + +// catch paint events +//EVT_ERASE_BACKGROUND(StaticBox::eraseEvent) +EVT_PAINT(StaticBox::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(). + */ + +StaticBox::StaticBox() + : state_handler(this) + , radius(8) +{ + border_color = StateColor( + std::make_pair(*wxLIGHT_GREY, (int) StateColor::Disabled), + std::make_pair(0x303A3C, (int) StateColor::Normal)); +} + +StaticBox::StaticBox(wxWindow* parent, + wxWindowID id, + const wxPoint & pos, + const wxSize & size, long style) + : StaticBox() +{ + Create(parent, id, pos, size, style); +} + +bool StaticBox::Create(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style) +{ + if (style & wxBORDER_NONE) + border_width = 0; + wxWindow::Create(parent, id, pos, size, style); + state_handler.attach({&border_color, &background_color, &background_color2}); + state_handler.update_binds(); + SetBackgroundColour(GetParentBackgroundColor(parent)); + return true; +} + +void StaticBox::SetCornerRadius(double radius) +{ + this->radius = radius; + Refresh(); +} + +void StaticBox::SetBorderWidth(int width) +{ + border_width = width; + Refresh(); +} + +void StaticBox::SetBorderColor(StateColor const &color) +{ + border_color = color; + state_handler.update_binds(); + Refresh(); +} + +void StaticBox::SetBorderColorNormal(wxColor const &color) +{ + border_color.setColorForStates(color, 0); + Refresh(); +} + +void StaticBox::SetBackgroundColor(StateColor const &color) +{ + background_color = color; + state_handler.update_binds(); + Refresh(); +} + +void StaticBox::SetBackgroundColorNormal(wxColor const &color) +{ + background_color.setColorForStates(color, 0); + Refresh(); +} + +void StaticBox::SetBackgroundColor2(StateColor const &color) +{ + background_color2 = color; + state_handler.update_binds(); + Refresh(); +} + +wxColor StaticBox::GetParentBackgroundColor(wxWindow* parent) +{ + if (auto box = dynamic_cast(parent)) { + if (box->background_color.count() > 0) { + if (box->background_color2.count() == 0) + return box->background_color.defaultColor(); + auto s = box->background_color.defaultColor(); + auto e = box->background_color2.defaultColor(); + int r = (s.Red() + e.Red()) / 2; + int g = (s.Green() + e.Green()) / 2; + int b = (s.Blue() + e.Blue()) / 2; + return wxColor(r, g, b); + } + } + if (parent) + return parent->GetBackgroundColour(); + return *wxWHITE; +} + +void StaticBox::eraseEvent(wxEraseEvent& evt) +{ + // for transparent background, but not work +#ifdef __WXMSW__ + wxDC *dc = evt.GetDC(); + wxSize size = GetSize(); + wxClientDC dc2(GetParent()); + dc->Blit({0, 0}, size, &dc2, GetPosition()); +#endif +} + +void StaticBox::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 StaticBox::render(wxDC& dc) +{ +#ifdef __WXMSW__ + if (radius == 0) { + doRender(dc); + return; + } + + wxSize size = GetSize(); + wxMemoryDC memdc; + wxBitmap bmp(size.x, size.y); + memdc.SelectObject(bmp); + //memdc.Blit({0, 0}, size, &dc, {0, 0}); + memdc.SetBackground(wxBrush(GetBackgroundColour())); + memdc.Clear(); + { + wxGCDC dc2(memdc); + doRender(dc2); + } + + memdc.SelectObject(wxNullBitmap); + dc.DrawBitmap(bmp, 0, 0); +#else + doRender(dc); +#endif +} + +void StaticBox::doRender(wxDC& dc) +{ + wxSize size = GetSize(); + int states = state_handler.states(); + if (background_color2.count() == 0) { + if ((border_width && border_color.count() > 0) || background_color.count() > 0) { + wxRect rc(0, 0, size.x, size.y); + if (border_width && border_color.count() > 0) { + if (dc.GetContentScaleFactor() == 1.0) { + int d = floor(border_width / 2.0); + int d2 = floor(border_width - 1); + rc.x += d; + rc.width -= d2; + rc.y += d; + rc.height -= d2; + } else { + int d = 1; + rc.x += d; + rc.width -= d; + rc.y += d; + rc.height -= d; + } + dc.SetPen(wxPen(border_color.colorForStates(states), border_width)); + } else { + dc.SetPen(wxPen(background_color.colorForStates(states))); + } + if (background_color.count() > 0) + dc.SetBrush(wxBrush(background_color.colorForStates(states))); + else + dc.SetBrush(wxBrush(GetBackgroundColour())); + if (radius == 0) { + dc.DrawRectangle(rc); + } + else { + dc.DrawRoundedRectangle(rc, radius - border_width); + } + } + } + else { + wxColor start = background_color.colorForStates(states); + wxColor stop = background_color2.colorForStates(states); + int r = start.Red(), g = start.Green(), b = start.Blue(); + int dr = (int) stop.Red() - r, dg = (int) stop.Green() - g, db = (int) stop.Blue() - b; + int lr = 0, lg = 0, lb = 0; + for (int y = 0; y < size.y; ++y) { + dc.SetPen(wxPen(wxColor(r, g, b))); + dc.DrawLine(0, y, size.x, y); + lr += dr; while (lr >= size.y) { ++r, lr -= size.y; } while (lr <= -size.y) { --r, lr += size.y; } + lg += dg; while (lg >= size.y) { ++g, lg -= size.y; } while (lg <= -size.y) { --g, lg += size.y; } + lb += db; while (lb >= size.y) { ++b, lb -= size.y; } while (lb <= -size.y) { --b, lb += size.y; } + } + } +} diff --git a/src/slic3r/GUI/Widgets/StaticBox.hpp b/src/slic3r/GUI/Widgets/StaticBox.hpp new file mode 100644 index 0000000000..871c5651d9 --- /dev/null +++ b/src/slic3r/GUI/Widgets/StaticBox.hpp @@ -0,0 +1,62 @@ +#ifndef slic3r_GUI_StaticBox_hpp_ +#define slic3r_GUI_StaticBox_hpp_ + +#include "../wxExtensions.hpp" +#include "StateHandler.hpp" + +#include + +class StaticBox : public wxWindow +{ +public: + StaticBox(); + + StaticBox(wxWindow* parent, + wxWindowID id = wxID_ANY, + const wxPoint & pos = wxDefaultPosition, + const wxSize & size = wxDefaultSize, + long style = 0); + + bool Create(wxWindow* parent, + wxWindowID id = wxID_ANY, + const wxPoint & pos = wxDefaultPosition, + const wxSize & size = wxDefaultSize, + long style = 0); + + void SetCornerRadius(double radius); + + void SetBorderWidth(int width); + + void SetBorderColor(StateColor const & color); + + void SetBorderColorNormal(wxColor const &color); + + void SetBackgroundColor(StateColor const &color); + + void SetBackgroundColorNormal(wxColor const &color); + + void SetBackgroundColor2(StateColor const &color); + + static wxColor GetParentBackgroundColor(wxWindow * parent); + +protected: + void eraseEvent(wxEraseEvent& evt); + + void paintEvent(wxPaintEvent& evt); + + void render(wxDC& dc); + + virtual void doRender(wxDC& dc); + +protected: + double radius; + int border_width = 1; + StateHandler state_handler; + StateColor border_color; + StateColor background_color; + StateColor background_color2; + + DECLARE_EVENT_TABLE() +}; + +#endif // !slic3r_GUI_StaticBox_hpp_ diff --git a/src/slic3r/GUI/Widgets/SwitchButton.cpp b/src/slic3r/GUI/Widgets/SwitchButton.cpp new file mode 100644 index 0000000000..8ae1165dcb --- /dev/null +++ b/src/slic3r/GUI/Widgets/SwitchButton.cpp @@ -0,0 +1,134 @@ +#include "SwitchButton.hpp" +#include "Label.hpp" +#include "StaticBox.hpp" + +#include "../wxExtensions.hpp" +#include "../Utils/MacDarkMode.hpp" + +#include + +SwitchButton::SwitchButton(wxWindow* parent, wxWindowID id) + : wxBitmapToggleButton(parent, id, wxNullBitmap, wxDefaultPosition, wxDefaultSize, wxBORDER_NONE | wxBU_EXACTFIT) + , m_on(this, "toggle_on", 16) + , m_off(this, "toggle_off", 16) + , text_color(std::pair{*wxWHITE, (int) StateColor::Checked}, std::pair{0x6B6B6B, (int) StateColor::Normal}) + , track_color(0xD9D9D9) + , thumb_color(std::pair{0x00AE42, (int) StateColor::Checked}, std::pair{0xD9D9D9, (int) StateColor::Normal}) +{ + SetBackgroundColour(StaticBox::GetParentBackgroundColor(parent)); + Bind(wxEVT_TOGGLEBUTTON, [this](auto& e) { update(); e.Skip(); }); + SetFont(Label::Body_12); + Rescale(); +} + +void SwitchButton::SetLabels(wxString const& lbl_on, wxString const& lbl_off) +{ + labels[0] = lbl_on; + labels[1] = lbl_off; + Rescale(); +} + +void SwitchButton::SetTextColor(StateColor const& color) +{ + text_color = color; +} + +void SwitchButton::SetTrackColor(StateColor const& color) +{ + track_color = color; +} + +void SwitchButton::SetThumbColor(StateColor const& color) +{ + thumb_color = color; +} + +void SwitchButton::SetValue(bool value) +{ + if (value != GetValue()) + wxBitmapToggleButton::SetValue(value); + update(); +} + +void SwitchButton::Rescale() +{ + if (labels[0].IsEmpty()) { + m_on.msw_rescale(); + m_off.msw_rescale(); + } + else { +#ifdef __WXOSX__ + auto scale = Slic3r::GUI::mac_max_scaling_factor(); + int BS = (int) scale; +#else + constexpr int BS = 1; +#endif + wxSize thumbSize; + wxSize trackSize; + wxClientDC dc(this); +#ifdef __WXOSX__ + dc.SetFont(dc.GetFont().Scaled(scale)); +#endif + wxSize textSize[2]; + { + textSize[0] = dc.GetTextExtent(labels[0]); + textSize[1] = dc.GetTextExtent(labels[1]); + } + { + thumbSize = textSize[0]; + auto size = textSize[1]; + if (size.x > thumbSize.x) thumbSize.x = size.x; + else size.x = thumbSize.x; + thumbSize.x += BS * 12; + thumbSize.y += BS * 6; + trackSize.x = thumbSize.x + size.x + BS * 10; + trackSize.y = thumbSize.y + BS * 2; + auto maxWidth = GetMaxWidth(); +#ifdef __WXOSX__ + maxWidth *= scale; +#endif + if (trackSize.x > maxWidth) { + thumbSize.x -= (trackSize.x - maxWidth) / 2; + trackSize.x = maxWidth; + } + } + for (int i = 0; i < 2; ++i) { + wxMemoryDC memdc(&dc); + wxBitmap bmp(trackSize.x, trackSize.y); + memdc.SelectObject(bmp); + memdc.SetBackground(wxBrush(GetBackgroundColour())); + memdc.Clear(); + memdc.SetFont(dc.GetFont()); + auto state = i == 0 ? StateColor::Enabled : (StateColor::Checked | StateColor::Enabled); + { +#ifdef __WXMSW__ + wxGCDC dc2(memdc); +#else + wxDC &dc2(memdc); +#endif + dc2.SetBrush(wxBrush(track_color.colorForStates(state))); + dc2.SetPen(wxPen(track_color.colorForStates(state))); + dc2.DrawRoundedRectangle(wxRect({0, 0}, trackSize), trackSize.y / 2); + dc2.SetBrush(wxBrush(thumb_color.colorForStates(StateColor::Checked | StateColor::Enabled))); + dc2.SetPen(wxPen(thumb_color.colorForStates(StateColor::Checked | StateColor::Enabled))); + dc2.DrawRoundedRectangle(wxRect({ i == 0 ? BS : (trackSize.x - thumbSize.x - BS), BS}, thumbSize), thumbSize.y / 2); + } + memdc.SetTextForeground(text_color.colorForStates(state ^ StateColor::Checked)); + memdc.DrawText(labels[0], {BS + (thumbSize.x - textSize[0].x) / 2, BS + (thumbSize.y - textSize[0].y) / 2}); + memdc.SetTextForeground(text_color.colorForStates(state)); + memdc.DrawText(labels[1], {trackSize.x - thumbSize.x - BS + (thumbSize.x - textSize[1].x) / 2, BS + (thumbSize.y - textSize[1].y) / 2}); + memdc.SelectObject(wxNullBitmap); +#ifdef __WXOSX__ + bmp = wxBitmap(bmp.ConvertToImage(), -1, scale); +#endif + (i == 0 ? m_off : m_on).bmp() = bmp; + } + } + SetSize(m_on.GetBmpSize()); + update(); +} + +void SwitchButton::update() +{ + SetBitmap((GetValue() ? m_on : m_off).bmp()); +} diff --git a/src/slic3r/GUI/Widgets/SwitchButton.hpp b/src/slic3r/GUI/Widgets/SwitchButton.hpp new file mode 100644 index 0000000000..25581f3762 --- /dev/null +++ b/src/slic3r/GUI/Widgets/SwitchButton.hpp @@ -0,0 +1,40 @@ +#ifndef slic3r_GUI_SwitchButton_hpp_ +#define slic3r_GUI_SwitchButton_hpp_ + +#include "../wxExtensions.hpp" +#include "StateColor.hpp" + +#include + +class SwitchButton : public wxBitmapToggleButton +{ +public: + SwitchButton(wxWindow * parent = NULL, wxWindowID id = wxID_ANY); + +public: + void SetLabels(wxString const & lbl_on, wxString const & lbl_off); + + void SetTextColor(StateColor const &color); + + void SetTrackColor(StateColor const &color); + + void SetThumbColor(StateColor const &color); + + void SetValue(bool value) override; + + void Rescale(); + +private: + void update(); + +private: + ScalableBitmap m_on; + ScalableBitmap m_off; + + wxString labels[2]; + StateColor text_color; + StateColor track_color; + StateColor thumb_color; +}; + +#endif // !slic3r_GUI_SwitchButton_hpp_ diff --git a/src/slic3r/GUI/Widgets/TextInput.cpp b/src/slic3r/GUI/Widgets/TextInput.cpp new file mode 100644 index 0000000000..823dd5512d --- /dev/null +++ b/src/slic3r/GUI/Widgets/TextInput.cpp @@ -0,0 +1,230 @@ +#include "TextInput.hpp" +#include "Label.hpp" + +#include + +BEGIN_EVENT_TABLE(TextInput, wxPanel) + +EVT_PAINT(TextInput::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(). + */ + +TextInput::TextInput() + : label_color(std::make_pair(0x909090, (int) StateColor::Disabled), + std::make_pair(0x6B6B6B, (int) StateColor::Normal)) + , text_color(std::make_pair(0x909090, (int) StateColor::Disabled), + std::make_pair(0x262E30, (int) StateColor::Normal)) +{ + radius = 0; + border_width = 1; + border_color = StateColor(std::make_pair(0xDBDBDB, (int) StateColor::Disabled), std::make_pair(0x00AE42, (int) StateColor::Hovered), + std::make_pair(0xDBDBDB, (int) StateColor::Normal)); + background_color = StateColor(std::make_pair(0xF0F0F0, (int) StateColor::Disabled), std::make_pair(*wxWHITE, (int) StateColor::Normal)); + SetFont(Label::Body_12); +} + +TextInput::TextInput(wxWindow * parent, + wxString text, + wxString label, + wxString icon, + const wxPoint &pos, + const wxSize & size, + long style) + : TextInput() +{ + Create(parent, text, label, icon, pos, size, style); +} + +void TextInput::Create(wxWindow * parent, + wxString text, + wxString label, + wxString icon, + const wxPoint &pos, + const wxSize & size, + long style) +{ + text_ctrl = nullptr; + StaticBox::Create(parent, wxID_ANY, pos, size, style); + wxWindow::SetLabel(label); + style &= ~wxRIGHT; + state_handler.attach({&label_color, & text_color}); + state_handler.update_binds(); + text_ctrl = new wxTextCtrl(this, wxID_ANY, text, {4, 4}, wxDefaultSize, style | wxBORDER_NONE | wxTE_PROCESS_ENTER); + text_ctrl->SetFont(Label::Body_14); + text_ctrl->SetInitialSize(text_ctrl->GetBestSize()); + text_ctrl->SetBackgroundColour(background_color.colorForStates(state_handler.states())); + text_ctrl->SetForegroundColour(text_color.colorForStates(state_handler.states())); + state_handler.attach_child(text_ctrl); + text_ctrl->Bind(wxEVT_KILL_FOCUS, [this](auto &e) { + OnEdit(); + e.SetId(GetId()); + ProcessEventLocally(e); + e.Skip(); + }); + text_ctrl->Bind(wxEVT_TEXT_ENTER, [this](auto &e) { + OnEdit(); + e.SetId(GetId()); + ProcessEventLocally(e); + }); + text_ctrl->Bind(wxEVT_RIGHT_DOWN, [this](auto &e) {}); // disable context menu + if (!icon.IsEmpty()) { + this->icon = ScalableBitmap(this, icon.ToStdString(), 16); + } + messureSize(); +} + +void TextInput::SetCornerRadius(double radius) +{ + this->radius = radius; + Refresh(); +} + +void TextInput::SetLabel(const wxString& label) +{ + wxWindow::SetLabel(label); + messureSize(); + Refresh(); +} + +void TextInput::SetIcon(const wxBitmap &icon) +{ + this->icon.bmp() = icon; + Rescale(); +} + +void TextInput::SetLabelColor(StateColor const &color) +{ + label_color = color; + state_handler.update_binds(); +} + +void TextInput::SetTextColor(StateColor const& color) +{ + text_color= color; + state_handler.update_binds(); +} + +void TextInput::Rescale() +{ + if (!this->icon.name().empty()) + this->icon.msw_rescale(); + messureSize(); + Refresh(); +} + +bool TextInput::Enable(bool enable) +{ + bool result = text_ctrl->Enable(enable) && wxWindow::Enable(enable); + if (result) { + wxCommandEvent e(EVT_ENABLE_CHANGED); + e.SetEventObject(this); + GetEventHandler()->ProcessEvent(e); + text_ctrl->SetBackgroundColour(background_color.colorForStates(state_handler.states())); + text_ctrl->SetForegroundColour(text_color.colorForStates(state_handler.states())); + } + return result; +} + +void TextInput::SetMinSize(const wxSize& size) +{ + wxSize size2 = size; + if (size2.y < 0) { +#ifdef __WXMAC__ + if (GetPeer()) // peer is not ready in Create on mac +#endif + size2.y = GetSize().y; + } + wxWindow::SetMinSize(size2); +} + +void TextInput::DoSetSize(int x, int y, int width, int height, int sizeFlags) +{ + wxWindow::DoSetSize(x, y, width, height, sizeFlags); + if (sizeFlags & wxSIZE_USE_EXISTING) return; + wxSize size = GetSize(); + wxPoint textPos = {5, 0}; + if (this->icon.bmp().IsOk()) { + wxSize szIcon = this->icon.GetBmpSize(); + textPos.x += szIcon.x; + } + bool align_right = GetWindowStyle() & wxRIGHT; + if (align_right) + textPos.x += labelSize.x; + if (text_ctrl) { + wxSize textSize = text_ctrl->GetSize(); + textSize.x = size.x - textPos.x - labelSize.x - 10; + text_ctrl->SetSize(textSize); + text_ctrl->SetPosition({textPos.x, (size.y - textSize.y) / 2}); + } +} + +void TextInput::DoSetToolTipText(wxString const &tip) +{ + wxWindow::DoSetToolTipText(tip); + text_ctrl->SetToolTip(tip); +} + +void TextInput::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 TextInput::render(wxDC& dc) +{ + StaticBox::render(dc); + int states = state_handler.states(); + wxSize size = GetSize(); + bool align_right = GetWindowStyle() & wxRIGHT; + // start draw + wxPoint pt = {5, 0}; + if (icon.bmp().IsOk()) { + wxSize szIcon = icon.GetBmpSize(); + pt.y = (size.y - szIcon.y) / 2; + dc.DrawBitmap(icon.bmp(), pt); + pt.x += szIcon.x + 0; + } + auto text = wxWindow::GetLabel(); + if (!text.IsEmpty()) { + wxSize textSize = text_ctrl->GetSize(); + if (align_right) { + if (pt.x + labelSize.x > size.x) + text = wxControl::Ellipsize(text, dc, wxELLIPSIZE_END, size.x - pt.x); + pt.y = (size.y - labelSize.y) / 2; + } else { + pt.x += textSize.x; + pt.y = (size.y + textSize.y) / 2 - labelSize.y; + } + dc.SetTextForeground(label_color.colorForStates(states)); + dc.SetFont(GetFont()); + dc.DrawText(text, pt); + } +} + +void TextInput::messureSize() +{ + wxSize size = GetSize(); + wxClientDC dc(this); + labelSize = dc.GetTextExtent(wxWindow::GetLabel()); + wxSize textSize = text_ctrl->GetSize(); + int h = textSize.y + 8; + if (size.y < h) { + size.y = h; + } + wxSize minSize = size; + minSize.x = GetMinWidth(); + SetMinSize(minSize); + SetSize(size); +} diff --git a/src/slic3r/GUI/Widgets/TextInput.hpp b/src/slic3r/GUI/Widgets/TextInput.hpp new file mode 100644 index 0000000000..152fb88f3d --- /dev/null +++ b/src/slic3r/GUI/Widgets/TextInput.hpp @@ -0,0 +1,77 @@ +#ifndef slic3r_GUI_TextInput_hpp_ +#define slic3r_GUI_TextInput_hpp_ + +#include +#include "StaticBox.hpp" + +class TextInput : public wxNavigationEnabled +{ + + wxSize labelSize; + ScalableBitmap icon; + StateColor label_color; + StateColor text_color; + wxTextCtrl * text_ctrl; + + static const int TextInputWidth = 200; + static const int TextInputHeight = 50; + +public: + TextInput(); + + TextInput(wxWindow * parent, + wxString text, + wxString label = "", + wxString icon = "", + const wxPoint &pos = wxDefaultPosition, + const wxSize & size = wxDefaultSize, + long style = 0); + +public: + void Create(wxWindow * parent, + wxString text, + wxString label = "", + wxString icon = "", + const wxPoint &pos = wxDefaultPosition, + const wxSize & size = wxDefaultSize, + long style = 0); + + void SetCornerRadius(double radius); + + void SetLabel(const wxString& label); + + void SetIcon(const wxBitmap & icon); + + void SetLabelColor(StateColor const &color); + + void SetTextColor(StateColor const &color); + + virtual void Rescale(); + + virtual bool Enable(bool enable = true) override; + + virtual void SetMinSize(const wxSize& size) override; + + wxTextCtrl *GetTextCtrl() { return text_ctrl; } + + wxTextCtrl const *GetTextCtrl() const { return text_ctrl; } + +protected: + virtual void OnEdit() {} + + virtual void DoSetSize( + int x, int y, int width, int height, int sizeFlags = wxSIZE_AUTO); + + void DoSetToolTipText(wxString const &tip) override; + +private: + void paintEvent(wxPaintEvent& evt); + + void render(wxDC& dc); + + void messureSize(); + + DECLARE_EVENT_TABLE() +}; + +#endif // !slic3r_GUI_TextInput_hpp_