// ***************************************************************************** // * This file is part of the FreeFileSync project. It is distributed under * // * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0 * // * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved * // ***************************************************************************** #include "triple_splitter.h" #include #include using namespace zen; using namespace fff; namespace { //------------ Grid Constants ------------------------------- const int SASH_HIT_TOLERANCE_DIP = 5; //currently only a placebo! const int SASH_SIZE_DIP = 10; const double SASH_GRAVITY = 0.5; //value within [0, 1]; 1 := resize left only, 0 := resize right only const int CHILD_WINDOW_MIN_SIZE_DIP = 50; //min. size of managed windows } class TripleSplitter::SashMove { public: SashMove(wxWindow& wnd, int mousePosX, int centerOffset) : wnd_(wnd), mousePosX_(mousePosX), centerOffset_(centerOffset) { wnd_.SetCursor(wxCURSOR_SIZEWE); wnd_.CaptureMouse(); } ~SashMove() { wnd_.SetCursor(*wxSTANDARD_CURSOR); if (wnd_.HasCapture()) wnd_.ReleaseMouse(); } int getMousePosXStart () const { return mousePosX_; } int getCenterOffsetStart() const { return centerOffset_; } private: wxWindow& wnd_; const int mousePosX_; const int centerOffset_; }; TripleSplitter::TripleSplitter(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style) : wxWindow(parent, id, pos, size, style | wxTAB_TRAVERSAL), //tab between windows sashSize_ (dipToWxsize(SASH_SIZE_DIP)), childWindowMinSize_(dipToWxsize(CHILD_WINDOW_MIN_SIZE_DIP)) { //https://wiki.wxwidgets.org/Flicker-Free_Drawing SetBackgroundStyle(wxBG_STYLE_PAINT); //get's rid of needless wxEVT_ERASE_BACKGROUND Bind(wxEVT_PAINT, [this](wxPaintEvent& event) { onPaintEvent(event); }); Bind(wxEVT_SIZE, [this](wxSizeEvent& event) { updateWindowSizes(); event.Skip(); }); Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent& event) { onMouseLeftDown (event); }); Bind(wxEVT_LEFT_UP, [this](wxMouseEvent& event) { onMouseLeftUp (event); }); Bind(wxEVT_MOTION, [this](wxMouseEvent& event) { onMouseMovement (event); }); Bind(wxEVT_LEAVE_WINDOW, [this](wxMouseEvent& event) { onLeaveWindow (event); }); Bind(wxEVT_LEFT_DCLICK, [this](wxMouseEvent& event) { onMouseLeftDouble(event); }); Bind(wxEVT_MOUSE_CAPTURE_LOST, [this](wxMouseCaptureLostEvent& event) { onMouseCaptureLost(event); }); } TripleSplitter::~TripleSplitter() {} //make sure correct destructor gets created for std::unique_ptr void TripleSplitter::updateWindowSizes() { if (windowL_ && windowC_ && windowR_) { const int centerPosX = getCenterPosX(); const int centerWidth = getCenterWidth(); const wxRect clientRect = GetClientRect(); const int widthL = centerPosX; const int windowRposX = widthL + centerWidth; const int widthR = clientRect.width - windowRposX; windowL_->SetSize(0, 0, widthL, clientRect.height); windowC_->SetSize(widthL + sashSize_, 0, windowC_->GetSize().GetWidth(), clientRect.height); windowR_->SetSize(windowRposX, 0, widthR, clientRect.height); Refresh(); //repaint sash } } inline int TripleSplitter::getCenterWidth() const { return 2 * sashSize_ + (windowC_ ? windowC_->GetSize().GetWidth() : 0); } int TripleSplitter::getCenterPosXOptimal() const { const wxRect clientRect = GetClientRect(); const int centerWidth = getCenterWidth(); return (clientRect.width - centerWidth) * SASH_GRAVITY; //allowed to be negative for extreme client widths! } int TripleSplitter::getCenterPosX() const { const wxRect clientRect = GetClientRect(); const int centerWidth = getCenterWidth(); const int centerPosXOptimal = getCenterPosXOptimal(); //normalize "centerPosXOptimal + centerOffset" if (clientRect.width < 2 * childWindowMinSize_ + centerWidth) //use fixed "centeroffset" when "clientRect.width == 2 * childWindowMinSize_ + centerWidth" return centerPosXOptimal + childWindowMinSize_ - static_cast(2 * childWindowMinSize_ * SASH_GRAVITY); //avoid rounding error //make sure transition between conditional branches is continuous! return std::max(childWindowMinSize_, //make sure centerPosXOptimal + offset is within bounds std::min(centerPosXOptimal + centerOffset_, clientRect.width - childWindowMinSize_ - centerWidth)); } void TripleSplitter::onPaintEvent(wxPaintEvent& event) { DynBufPaintDC dc(*this, doubleBuffer_); //GetUpdateRegion()? nah, just redraw everything => we mostly land here due to wxEVT_SIZE anyway (see Refresh() in updateWindowSizes()) assert(GetSize() == GetClientSize()); const int centerPosX = getCenterPosX(); const int centerWidth = getCenterWidth(); auto draw = [&](wxRect rect) { //clear everything in border color: clearArea(dc, rect, wxSystemSettings::GetColour(wxSYS_COLOUR_BTNSHADOW)); //clear inner area except for left/right borders clearArea(dc, wxRect(rect.x + dipToWxsize(1), rect.y, rect.width - 2 * dipToWxsize(1), rect.height), wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE)); }; const wxRect rectSashL(centerPosX, 0, sashSize_, GetClientRect().height); const wxRect rectSashR(centerPosX + centerWidth - sashSize_, 0, sashSize_, GetClientRect().height); draw(rectSashL); draw(rectSashR); } bool TripleSplitter::hitOnSashLine(int posX) const { const int centerPosX = getCenterPosX(); const int centerWidth = getCenterWidth(); //we don't get events outside of sash, so SASH_HIT_TOLERANCE_DIP is currently *useless* auto hitSash = [&](int sashX) { return sashX - dipToWxsize(SASH_HIT_TOLERANCE_DIP) <= posX && posX < sashX + sashSize_ + dipToWxsize(SASH_HIT_TOLERANCE_DIP); }; return hitSash(centerPosX) || hitSash(centerPosX + centerWidth - sashSize_); //hit one of the two sash lines } void TripleSplitter::onMouseLeftDown(wxMouseEvent& event) { activeMove_.reset(); const int posX = event.GetPosition().x; if (hitOnSashLine(posX)) activeMove_ = std::make_unique(*this, posX, centerOffset_); event.Skip(); } void TripleSplitter::onMouseLeftUp(wxMouseEvent& event) { activeMove_.reset(); //nothing else to do, actual work done by onMouseMovement() event.Skip(); } void TripleSplitter::onMouseMovement(wxMouseEvent& event) { if (activeMove_) { centerOffset_ = activeMove_->getCenterOffsetStart() + event.GetPosition().x - activeMove_->getMousePosXStart(); //CAVEAT: function getCenterPosX() normalizes centerPosX *not* centerOffset! //This can lead to the strange effect of window not immediately resizing when centerOffset is extremely off limits //=> normalize centerOffset right here centerOffset_ = getCenterPosX() - getCenterPosXOptimal(); updateWindowSizes(); Update(); //no time to wait until idle event! } else { //we receive those only while above the sash, not the managed windows (except when the managed windows are disabled!) const int posX = event.GetPosition().x; if (hitOnSashLine(posX)) SetCursor(wxCURSOR_SIZEWE); //set window-local only! else SetCursor(*wxSTANDARD_CURSOR); } event.Skip(); } void TripleSplitter::onLeaveWindow(wxMouseEvent& event) { //even called when moving from sash over to managed windows! if (!activeMove_) SetCursor(*wxSTANDARD_CURSOR); event.Skip(); } void TripleSplitter::onMouseCaptureLost(wxMouseCaptureLostEvent& event) { activeMove_.reset(); updateWindowSizes(); //event.Skip(); -> we DID handle it! } void TripleSplitter::onMouseLeftDouble(wxMouseEvent& event) { const int posX = event.GetPosition().x; if (hitOnSashLine(posX)) { centerOffset_ = 0; //reset sash according to gravity updateWindowSizes(); } event.Skip(); }