238 lines
8.3 KiB
C++
238 lines
8.3 KiB
C++
// *****************************************************************************
|
|
// * 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 <wx/settings.h>
|
|
#include <wx+/dc.h>
|
|
|
|
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<SashMove>
|
|
|
|
|
|
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<int>(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<SashMove>(*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();
|
|
}
|