osci-render/Source/components/DraggableListBox.h

134 wiersze
5.0 KiB
C++

#pragma once
#include "JuceHeader.h"
#include "VListBox.h"
// Your item-data container must inherit from this, and override at least the first
// four member functions.
struct DraggableListBoxItemData
{
virtual ~DraggableListBoxItemData() = 0;
virtual int getNumItems() = 0;
virtual void moveBefore(int indexOfItemToMove, int indexOfItemToPlaceBefore) = 0;
virtual void moveAfter(int indexOfItemToMove, int indexOfItemToPlaceAfter) = 0;
// If you need a dynamic list, override these functions as well.
virtual void deleteItem(int /*indexOfItemToDelete*/) {};
virtual void addItemAtEnd() {};
};
// DraggableListBox extends VListBox to both initiate drags and act as a target, so
// it can paint a clean, consistent drop indicator between row components.
class DraggableListBox : public VListBox,
public juce::DragAndDropContainer,
public juce::DragAndDropTarget
{
public:
using VListBox::VListBox;
// DragAndDropTarget
bool isInterestedInDragSource(const SourceDetails&) override { return true; }
void itemDragEnter(const SourceDetails& details) override { updateDropIndicator(details); }
void itemDragMove(const SourceDetails& details) override { updateDropIndicator(details); }
void itemDragExit(const SourceDetails&) override { clearDropIndicator(); }
void itemDropped(const SourceDetails& details) override;
bool shouldDrawDragImageWhenOver() override { return true; }
// Paint a global drop indicator between rows
void paintOverChildren(juce::Graphics& g) override;
// Allow children to drive indicator positioning
void updateDropIndicatorAt(const juce::Point<int>& listLocalPos);
void clearDropIndicator();
private:
void updateDropIndicator(const SourceDetails& details);
bool showDropIndicator = false;
int dropInsertIndex = -1; // index to insert before; may be getNumRows() for end
};
// Everything below this point should be generic.
class DraggableListBoxItem : public juce::Component, public juce::DragAndDropTarget, public juce::Timer
{
public:
DraggableListBoxItem(DraggableListBox& lb, DraggableListBoxItemData& data, int rn)
: rowNum(rn), modelData(data), listBox(lb) {}
// Component
void paint(juce::Graphics& g) override;
void mouseEnter(const juce::MouseEvent&) override;
void mouseExit(const juce::MouseEvent&) override;
void mouseDrag(const juce::MouseEvent&) override;
// DragAndDropTarget
bool isInterestedInDragSource(const SourceDetails&) override { return true; }
void itemDragEnter(const SourceDetails&) override;
void itemDragMove(const SourceDetails&) override;
void itemDragExit(const SourceDetails&) override;
void itemDropped(const SourceDetails&) override;
bool shouldDrawDragImageWhenOver() override { return true; }
// DraggableListBoxItem
protected:
void updateInsertLines(const SourceDetails &dragSourceDetails);
void hideInsertLines();
void updateAutoScroll(const SourceDetails& dragSourceDetails);
void stopAutoScroll();
void timerCallback() override;
int rowNum;
DraggableListBoxItemData& modelData;
DraggableListBox& listBox;
juce::MouseCursor savedCursor;
bool insertAfter = false;
bool insertBefore = false;
// Auto-scroll state while dragging near viewport edges
double scrollPixelsPerTick = 0.0; // positive = scroll down, negative = up
bool autoScrollActive = false;
};
class DraggableListBoxModel : public VListBoxModel
{
public:
DraggableListBoxModel(DraggableListBox& lb, DraggableListBoxItemData& md)
: listBox(lb), modelData(md) {}
int getNumRows() override { return modelData.getNumItems(); }
void paintListBoxItem(int, juce::Graphics &, int, int, bool) override {}
juce::Component* refreshComponentForRow(int, bool, juce::Component*) override;
// Convenience: move an item using an insertion index (before position). Handles index shifting.
void moveByInsertIndex(int fromIndex, int insertIndex)
{
const int count = modelData.getNumItems();
if (count <= 0) return;
int toIndex = juce::jlimit(0, count, insertIndex);
// No-op if user drops item back in place (before itself) or immediately after itself.
if (toIndex == fromIndex || toIndex == fromIndex + 1)
return;
if (toIndex <= 0)
modelData.moveBefore(fromIndex, 0);
else if (toIndex >= count)
modelData.moveAfter(fromIndex, count - 1); // treat anything past last valid index as end
else
modelData.moveBefore(fromIndex, toIndex);
listBox.updateContent();
}
protected:
// Draggable model has a reference to its owner ListBox, so it can tell it to update after DnD.
DraggableListBox &listBox;
// It also has a reference to the model data, which it uses to get the current items count,
// and which it passes to the DraggableListBoxItem objects it creates/updates.
DraggableListBoxItemData& modelData;
};