Last active
June 10, 2022 12:28
-
-
Save stweyel/9c5ebb6aa1b4533f904ecec291d354f5 to your computer and use it in GitHub Desktop.
IPlug2 Control for swapping IControls on a vertical or horizontal axis
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Attaching an ISwapControl with initializer list | |
IRECT Handles = IRECT(0, 20, 0, 0); | |
pGraphics->AttachControl(MySwapControl1 = new IControlSwap({ctrl1, ctrl2 ctrl3, ctrl4, ctrl5}, Handles, [=](IControl* pControl){ | |
std::cout << "New Order: "; | |
for(int i = 0; i < MySwapControl1->GetSize(); ++i){ | |
std::cout << MySwapControl1->GetIdxAt(i) << " "; | |
} | |
std::cout << "\n"; | |
})); | |
// or manual adding of IControls | |
pGraphics->AttachControl(MySwapControl2 = new IControlSwap(Handles, [=](IControl* pControl){ | |
// do what needs to be done | |
}, EDirection::Vertical)); | |
MySwapControl2->AddControl(ctrl1); | |
MySwapControl2->AddControl(ctrl2); | |
MySwapControl2->AddControl(ctrl3); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
static constexpr int SLIDE_MS = 200; | |
static constexpr float HANDLESIZE = 25.f; | |
class IControlSwap : public IControl | |
{ | |
public: | |
IControlSwap(const IRECT& handles = IRECT(), IActionFunction aF = nullptr, EDirection direction = EDirection::Horizontal) | |
: IControl(IRECT()), mHorizontal(direction == EDirection::Horizontal) | |
{ | |
SetActionFunction(aF); | |
SetHandles(handles); | |
} | |
IControlSwap(std::initializer_list<IControl*> list, const IRECT& handles = IRECT(), IActionFunction aF = nullptr, EDirection direction = EDirection::Horizontal) | |
: IControl(IRECT()), mHorizontal(direction == EDirection::Horizontal) | |
{ | |
SetActionFunction(aF); | |
for( auto ctrl : list) | |
AddControl(ctrl); | |
SetHandles(handles); | |
} | |
void SetHandles(const IRECT& handles) | |
{ | |
if(handles.Empty()){ | |
mHandles = mHorizontal? IRECT(HANDLESIZE, 0, HANDLESIZE, 0) : IRECT(0, HANDLESIZE, 0, HANDLESIZE); | |
} | |
else | |
mHandles = handles; | |
} | |
void AddControl(IControl *ctrl) | |
{ | |
vCtrls.push_back({static_cast<int>(vCtrls.size()), ctrl}); | |
mRectList.Add(ctrl->GetRECT()); | |
SetTargetAndDrawRECTs(mRECT.Union(ctrl->GetRECT())); | |
} | |
int GetIdxAt(const int pos) { return vCtrls.at(pos).idx;} | |
IControl* GetCtrlAt(const int pos) { return vCtrls.at(pos).ctrl;} | |
int GetSize() { return vCtrls.size();} | |
void OnMouseOver(float x, float y, const IMouseMod &mod) override | |
{ | |
for(int i = 0; i < vCtrls.size(); ++i){ | |
auto ctrl = vCtrls[i].ctrl; | |
ctrl->OnMouseOut(); | |
if(ctrl->IsHit(x, y)){ | |
mHandleHit = false; | |
mLastHitCell = i; | |
mSelectedCtrl = ctrl; | |
IRECT ir = ctrl->GetRECT(); | |
IRECT ctrlrect = ir.GetPadded(-mHandles.L, -mHandles.T, -mHandles.R, -mHandles.B); | |
if(!ctrlrect.Contains(x, y)){ | |
GetUI()->SetMouseCursor(mHorizontal? ECursor::SIZEWE : ECursor::SIZENS); | |
mHandleHit = true; | |
mXLdelta = x - ir.L; | |
mXRdelta = x - ir.R; | |
mYTdelta = y - ir.T; | |
mYBdelta = y - ir.B; | |
} | |
else{ | |
ctrl->OnMouseOver(x, y, mod); | |
GetUI()->SetMouseCursor(ECursor::ARROW); | |
} | |
break; | |
} | |
} | |
} | |
void OnMouseOut() override | |
{ | |
for(auto &ctrlidx : vCtrls){ | |
ctrlidx.ctrl->OnMouseOut(); | |
} | |
GetUI()->SetMouseCursor(ECursor::ARROW); | |
} | |
void OnMouseDblClick(float x, float y, const IMouseMod& mod) override | |
{ | |
if(mHandleHit) | |
ResetControls(); | |
else if(mSelectedCtrl) | |
mSelectedCtrl->OnMouseDblClick(x, y, mod); | |
} | |
void OnMouseDown(float x, float y, const IMouseMod &mod) override | |
{ | |
if(mHandleHit){ | |
mMoveCell = mLastHitCell; | |
mCellW = mSelectedCtrl->GetRECT().W(); | |
mCellH = mSelectedCtrl->GetRECT().H(); | |
} | |
else if(mSelectedCtrl) | |
mSelectedCtrl->OnMouseDown(x, y, mod); | |
} | |
void OnMouseUp(float x, float y, const IMouseMod &mod) override | |
{ | |
if(mDragged){ | |
auto ir = mRectList.Get(mLastHitCell); | |
mSelectedCtrl->SetPosition(ir.L, ir.T); | |
GetUI()->SetMouseCursor(ECursor::ARROW); | |
SetDirty(mSwapped); | |
mSwapped = mDragged = false; | |
} | |
} | |
void OnMouseDrag(float x, float y, float dX, float dY, const IMouseMod& mod) override | |
{ | |
if(mHandleHit){ | |
int prevNeighbour = std::max(0, mLastHitCell - 1); | |
int nextNeighbour = std::min(static_cast<int>(vCtrls.size() - 1), mLastHitCell + 1); | |
float prevPos, nextPos; | |
if(mHorizontal){ | |
mSelectedCtrl->SetPosition(Clip(x - mXLdelta, mRECT.L, mRECT.R - mCellW), mRECT.T); | |
prevPos = x - mXLdelta; | |
nextPos = x - mXRdelta; | |
} | |
else{ | |
mSelectedCtrl->SetPosition(mRECT.L, Clip(y - mYTdelta, mRECT.T, mRECT.B - mCellH)); | |
prevPos = y - mYTdelta; | |
nextPos = y - mYBdelta; | |
} | |
auto getpos = [=](int cell) { return mHorizontal? mRectList.Get(cell).MW() : mRectList.Get(cell).MH();}; | |
if(prevPos < getpos(prevNeighbour)) | |
SwapPositions(mLastHitCell, prevNeighbour); | |
else if(nextPos > getpos(nextNeighbour)) | |
SwapPositions(mLastHitCell, nextNeighbour); | |
SetDirty(false); | |
mDragged = true; | |
} | |
else{ | |
mSelectedCtrl->OnMouseDrag(x, y, dX, dY, mod); | |
} | |
} | |
void ResetControls() | |
{ | |
for(int i = 0; i < vCtrls.size(); ++i) | |
Animate(GetCtrlAt(i), i, GetIdxAt(i)); | |
std::sort(vCtrls.begin(), vCtrls.end()); | |
SetDirty(true); | |
} | |
void SwapPositions(const int source, const int target) | |
{ | |
if(source != target){ | |
IControl* hitctrl = vCtrls.at(target).ctrl; | |
Animate(hitctrl, target, source); | |
std::swap(vCtrls.at(source),vCtrls.at(target)); | |
mLastHitCell = target; | |
mSwapped = target != mMoveCell; // only true if a final swap occured | |
} | |
} | |
void Draw(IGraphics& g) override | |
{ | |
// could be used for additional visual indicator | |
} | |
private: | |
void Animate(IControl* pControl, const int source, const int target) | |
{ | |
auto ir = mRectList.Get(target); | |
float start, delta; | |
if(mHorizontal){ | |
start = mRectList.Get(source).L; | |
delta = ir.L - start; | |
} | |
else{ | |
start = mRectList.Get(source).T; | |
delta = ir.T - start; | |
} | |
pControl->SetAnimation([=](IControl* pControl){ | |
auto progress = pControl->GetAnimationProgress(); | |
if(progress <= 1.f){ | |
float pos = start + EaseQuadraticInOut(progress) * delta; | |
float x = mHorizontal? pos : ir.L; | |
float y = mHorizontal? ir.T : pos; | |
pControl->SetPosition(x, y); | |
} | |
else{ | |
pControl->OnEndAnimation(); | |
pControl->SetPosition(ir.L, ir.T); | |
} | |
SetDirty(false); | |
}, SLIDE_MS); | |
} | |
struct ctrlidx{ | |
int idx; | |
IControl* ctrl; | |
bool operator < (const ctrlidx& ctrl) const { return (idx < ctrl.idx);} | |
}; | |
std::vector<ctrlidx> vCtrls; | |
IControl* mSelectedCtrl = nullptr; | |
IRECTList mRectList; | |
IRECT mHandles; | |
bool mHorizontal; | |
bool mSwapped = false; | |
bool mDragged = false; | |
bool mHandleHit; | |
int mLastHitCell = 0; | |
int mMoveCell = 0; | |
float mXLdelta, mXRdelta, mYTdelta, mYBdelta; | |
float mCellW, mCellH; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is an IPlug2 IControl used for reordering an array of nested IControls on a horizontal or vertical axis.