summaryrefslogtreecommitdiff
path: root/ui/triple_splitter.cpp
blob: f6ef006d38247f9d8e182a5f402d4387f4902694 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
// **************************************************************************
// * This file is part of the FreeFileSync project. It is distributed under *
// * GNU General Public License: http://www.gnu.org/licenses/gpl.html       *
// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved        *
// **************************************************************************

#include "triple_splitter.h"
#include <algorithm>

using namespace zen;


namespace
{
//------------ Grid Constants -------------------------------
const int SASH_HIT_TOLERANCE = 5; //currently onla a placebo!
const int SASH_SIZE = 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 = 50; //min. size of managed windows

const wxColor COLOR_SASH_GRADIENT_FROM = wxColour(192, 192, 192); //light grey
const wxColor COLOR_SASH_GRADIENT_TO = *wxWHITE;
}

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
    centerOffset(0),
    windowL(nullptr),
    windowC(nullptr),
    windowR(nullptr)
{
    Connect(wxEVT_PAINT,            wxPaintEventHandler(TripleSplitter::onPaintEvent     ), nullptr, this);
    Connect(wxEVT_SIZE,             wxSizeEventHandler (TripleSplitter::onSizeEvent      ), nullptr, this);
    //http://wiki.wxwidgets.org/Flicker-Free_Drawing
    Connect(wxEVT_ERASE_BACKGROUND, wxEraseEventHandler(TripleSplitter::onEraseBackGround), nullptr, this);
#if wxCHECK_VERSION(2, 9, 1)
    SetBackgroundStyle(wxBG_STYLE_PAINT);
#else
    SetBackgroundStyle(wxBG_STYLE_CUSTOM);
#endif
    Connect(wxEVT_LEFT_DOWN,    wxMouseEventHandler(TripleSplitter::onMouseLeftDown  ), nullptr, this);
    Connect(wxEVT_LEFT_UP,      wxMouseEventHandler(TripleSplitter::onMouseLeftUp    ), nullptr, this);
    Connect(wxEVT_MOTION,       wxMouseEventHandler(TripleSplitter::onMouseMovement  ), nullptr, this);
    Connect(wxEVT_LEAVE_WINDOW, wxMouseEventHandler(TripleSplitter::onLeaveWindow    ), nullptr, this);
    Connect(wxEVT_MOUSE_CAPTURE_LOST, wxMouseCaptureLostEventHandler(TripleSplitter::onMouseCaptureLost), nullptr, this);
    Connect(wxEVT_LEFT_DCLICK,  wxMouseEventHandler(TripleSplitter::onMouseLeftDouble), nullptr, this);
}


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 + SASH_SIZE, 0, windowC->GetSize().GetWidth(), clientRect.height);
        windowR->SetSize(windowRposX,        0, widthR,                        clientRect.height);

        wxClientDC dc(this);
        drawSash(dc);
    }
}


class TripleSplitter::SashMove
{
public:
    SashMove(wxWindow& wnd, int mousePosX, int centerPosX) : wnd_(wnd), mousePosX_(mousePosX), centerPosX_(centerPosX)
    {
        wnd_.SetCursor(wxCURSOR_SIZEWE);
        wnd_.CaptureMouse();
    }
    ~SashMove()
    {
        wnd_.SetCursor(*wxSTANDARD_CURSOR);
        if (wnd_.HasCapture())
            wnd_.ReleaseMouse();
    }
    int getMousePosXStart () { return mousePosX_; }
    int getCenterPosXStart() { return centerPosX_; }

private:
    wxWindow& wnd_;
    const int mousePosX_;
    const int centerPosX_;
};


inline
int TripleSplitter::getCenterWidth() const
{
    return 2 * SASH_SIZE + (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 * CHILD_WINDOW_MIN_SIZE + centerWidth) //continuous transition between conditional branches!
        //use fixed "centeroffset" when "clientRect.width == 2 * CHILD_WINDOW_MIN_SIZE + centerWidth"
        return centerPosXOptimal + CHILD_WINDOW_MIN_SIZE - static_cast<int>(2 * CHILD_WINDOW_MIN_SIZE * SASH_GRAVITY); //avoid rounding error
    else
        return std::max(CHILD_WINDOW_MIN_SIZE, //make sure centerPosXOptimal + offset is within bounds
                        std::min(centerPosXOptimal + centerOffset, clientRect.width - CHILD_WINDOW_MIN_SIZE - centerWidth));
}


void TripleSplitter::drawSash(wxDC& dc)
{
    const int centerPosX  = getCenterPosX();
    const int centerWidth = getCenterWidth();

    auto draw = [&](wxRect rect)
    {
        const int sash2ndHalf = 3;
        rect.width -= sash2ndHalf;
        dc.GradientFillLinear(rect, COLOR_SASH_GRADIENT_FROM, COLOR_SASH_GRADIENT_TO, wxEAST);

        rect.x += rect.width;
        rect.width = sash2ndHalf;
        dc.GradientFillLinear(rect, COLOR_SASH_GRADIENT_FROM, COLOR_SASH_GRADIENT_TO, wxWEST);

        static_assert(SASH_SIZE > sash2ndHalf, "");
    };

    const wxRect rectSashL(centerPosX,                           0, SASH_SIZE, GetClientRect().height);
    const wxRect rectSashR(centerPosX + centerWidth - SASH_SIZE, 0, SASH_SIZE, 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 is currently *useless*
    auto hitSash = [&](int sashX) { return sashX - SASH_HIT_TOLERANCE <= posX && posX < sashX + SASH_SIZE + SASH_HIT_TOLERANCE; };

    return hitSash(centerPosX) || hitSash(centerPosX + centerWidth - SASH_SIZE); //hit one of the two sash lines
}


void TripleSplitter::onMouseLeftDown(wxMouseEvent& event)
{
    activeMove.reset();

    const int posX = event.GetPosition().x;
    if (hitOnSashLine(posX))
        activeMove.reset(new SashMove(*this, posX, getCenterPosX()));
    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->getCenterPosXStart() - getCenterPosXOptimal() + event.GetPosition().x - activeMove->getMousePosXStart();

        //CAVEAT: centerOffset is evaluated *before* normalization in getCenterPosX()!
        //This can lead to the strange effect of window not immediately resizing when centerOffset is extremely off limits => normalize 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();
}
bgstack15