Skip to content

Instantly share code, notes, and snippets.

@stweyel
Last active June 10, 2022 12:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save stweyel/9c5ebb6aa1b4533f904ecec291d354f5 to your computer and use it in GitHub Desktop.
Save stweyel/9c5ebb6aa1b4533f904ecec291d354f5 to your computer and use it in GitHub Desktop.
IPlug2 Control for swapping IControls on a vertical or horizontal axis
// 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);
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;
};
@stweyel
Copy link
Author

stweyel commented Jun 10, 2022

This is an IPlug2 IControl used for reordering an array of nested IControls on a horizontal or vertical axis.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment