(Go: >> BACK << -|- >> HOME <<)

Skip to content

Commit

Permalink
Merge pull request #187 from cneben/f/#185-grid-keyboard
Browse files Browse the repository at this point in the history
F/#185 grid keyboard
  • Loading branch information
cneben committed Dec 27, 2022
2 parents b447aae + 6568d62 commit 16f7aa5
Show file tree
Hide file tree
Showing 14 changed files with 270 additions and 110 deletions.
12 changes: 11 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# CHANGELOG

## 20221226 prev2.3.0:
- #185: Add horizontal or vertical constrain on node/group dragging, see new property `qan::NodeItem::dragOrientation`.

## 20221225 prev2.3.0:
- #185: Add support for "snap to grid" when moving node or groups.
- Add |qan::Graph`, `snapToGrid` and `snapToGridSize` properties.

## 20221225 v2.2.0:
- Release v2.2.0

## 20221215 v2.2.0:
- #183: Add a `multipleSelectionEnabled` property to `qan::Graph` to enable or disable multiple selection.

Expand All @@ -16,7 +26,7 @@
- #167: Add partial edge selection support.
- #164: Change signature of qan::Graph::connectorRequestEdgeCreation().

## 20220824 v2.1.1:
## 20220824 v2*$.1.1:
- Change signature of two public API methods (public but presumably mainly used internally):
- Rename qan::Graph::collectInerEdges() to qan::Graph::collectInnerEdges().
- Rename qan::Graph::collectAncestorsDfs() to qan::Graph::collectAncestors().
Expand Down
12 changes: 6 additions & 6 deletions quickqanava.pro
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ test-cpp.subdir = samples/cpp
test-tools.subdir = samples/tools

# Uncomment to activate samples projects:
SUBDIRS += test-nodes
SUBDIRS += test-edges
SUBDIRS += test-connector
#SUBDIRS += test-nodes
#SUBDIRS += test-edges
#SUBDIRS += test-connector
SUBDIRS += test-groups
SUBDIRS += test-selection
#SUBDIRS += test-selection
#SUBDIRS += test-style
SUBDIRS += test-topology
SUBDIRS += test-dataflow
#SUBDIRS += test-topology
#SUBDIRS += test-dataflow
#SUBDIRS += test-cpp
#SUBDIRS += test-tools

Expand Down
18 changes: 16 additions & 2 deletions samples/groups/groups.qml
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ ApplicationWindow {
RowLayout {
anchors.top: parent.top; anchors.topMargin: 15
anchors.horizontalCenter: parent.horizontalCenter
width: 200
width: 550
ToolButton {
text: "Add Group"
onClicked: {
Expand Down Expand Up @@ -221,7 +221,6 @@ ApplicationWindow {
property var node: undefined
enabled: node !== undefined
onClicked: {
console.info("node.group=" + node.group)
if (node && node.group )
topology.ungroupNode(node)
}
Expand All @@ -236,6 +235,21 @@ ApplicationWindow {
;
}
}

Switch {
text: "Snap to Grid"
checked: topology.snapToGrid
onClicked: topology.snapToGrid = checked
}
Label { text: "Grid size:" }
SpinBox {
enabled: topology.snapToGrid
from: 1
to: 100
stepSize: 5
value: topology.snapToGridSize.width
onValueModified: { topology.snapToGridSize = Qt.size(value, value) }
}
}
Pane {
id: groupEditor
Expand Down
3 changes: 2 additions & 1 deletion src/qanAbstractDraggableCtrl.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ class AbstractDraggableCtrl
//! \c dragInitialMousePos in window coordinate system.
virtual void beginDragMove(const QPointF& dragInitialMousePos, bool dragSelection = true) = 0;
//! \c delta in scene coordinate system.
virtual void dragMove(const QPointF& delta, bool dragSelection = true) = 0;
virtual void dragMove(const QPointF& delta, bool dragSelection = true,
bool disableSnapToGrid = false, bool disableOrientation = false) = 0;
virtual void endDragMove(bool dragSelection = true) = 0;
};

Expand Down
115 changes: 80 additions & 35 deletions src/qanDraggableCtrl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,6 @@
// \date 2017 03 15
//-----------------------------------------------------------------------------

// Qt headers
// Nil

// QuickQanava headers
#include "./qanDraggableCtrl.h"
#include "./qanNodeItem.h"
Expand Down Expand Up @@ -83,16 +80,16 @@ void DraggableCtrl::handleDragLeaveEvent(QDragLeaveEvent* event)

void DraggableCtrl::handleDropEvent(QDropEvent* event)
{
if ( _targetItem &&
_targetItem->getAcceptDrops() &&
event->source() != nullptr ) { // Get the source item from the quick drag attached object received
if (_targetItem &&
_targetItem->getAcceptDrops() &&
event->source() != nullptr) { // Get the source item from the quick drag attached object received
QQuickItem* sourceItem = qobject_cast<QQuickItem*>(event->source());
if ( sourceItem != nullptr ) {
QVariant draggedStyle = sourceItem->property( "draggedStyle" ); // The source item (usually a style node or edge delegate must expose a draggedStyle property).
if ( draggedStyle.isValid() ) {
if (sourceItem != nullptr) {
QVariant draggedStyle = sourceItem->property("draggedStyle"); // The source item (usually a style node or edge delegate must expose a draggedStyle property).
if (draggedStyle.isValid()) {
auto style = draggedStyle.value<qan::Style*>();
if ( style != nullptr )
_targetItem->setItemStyle( style );
if (style != nullptr)
_targetItem->setItemStyle(style);
}
}
}
Expand Down Expand Up @@ -130,14 +127,13 @@ bool DraggableCtrl::handleMouseMoveEvent(QMouseEvent* event)
const auto rootItem = getGraph()->getContainerItem();
if (rootItem != nullptr && // Root item exist, left button is pressed and the target item
event->buttons().testFlag(Qt::LeftButton)) { // is draggable and not collapsed
const auto globalPos = rootItem->mapFromGlobal(event->globalPos());
const auto sceneDragPos = rootItem->mapFromGlobal(event->globalPos());
if (!_targetItem->getDragged()) {
beginDragMove(globalPos, _targetItem->getSelected());
// Project in scene rect (for example is a node is part of a group)
beginDragMove(sceneDragPos, _targetItem->getSelected());
return true;
} else {
const auto delta = globalPos - _dragLastPos;
_dragLastPos = globalPos;
dragMove(delta, _targetItem->getSelected());
dragMove(sceneDragPos, _targetItem->getSelected());
return true;
}
}
Expand All @@ -157,7 +153,7 @@ void DraggableCtrl::handleMouseReleaseEvent(QMouseEvent* event)
endDragMove();
}

void DraggableCtrl::beginDragMove(const QPointF& dragInitialMousePos, bool dragSelection)
void DraggableCtrl::beginDragMove(const QPointF& sceneDragPos, bool dragSelection)
{
if (_targetItem == nullptr)
return;
Expand All @@ -166,20 +162,23 @@ void DraggableCtrl::beginDragMove(const QPointF& dragInitialMousePos, bool dr
_target != nullptr)
emit graph->nodeAboutToBeMoved(_target);
_targetItem->setDragged(true);
_dragLastPos = dragInitialMousePos;
_initialDragPos = sceneDragPos;
const auto rootItem = getGraph()->getContainerItem();
if (rootItem != nullptr) // Project in scene rect (for example is a node is part of a group)
_initialTargetPos = rootItem->mapFromItem(_targetItem, QPointF{0,0});

// If there is a selection, keep start position for all selected nodes.
if (dragSelection) {
const auto graph = getGraph();
if (graph != nullptr &&
graph->hasMultipleSelection()) {

auto beginDragMoveSelected = [this, &dragInitialMousePos] (auto primitive) { // Call beginDragMove() on a given node or group
auto beginDragMoveSelected = [this, &sceneDragPos] (auto primitive) { // Call beginDragMove() on a given node or group
if (primitive != nullptr &&
primitive->getItem() != nullptr &&
static_cast<QQuickItem*>(primitive->getItem()) != static_cast<QQuickItem*>(this->_targetItem.data()) &&
primitive->get_group() == nullptr) // Do not drag nodes that are inside a group
primitive->getItem()->draggableCtrl().beginDragMove( dragInitialMousePos, false );
primitive->getItem()->draggableCtrl().beginDragMove(sceneDragPos, false);
};

// Call beginDragMove on all selected nodes and groups.
Expand All @@ -189,7 +188,8 @@ void DraggableCtrl::beginDragMove(const QPointF& dragInitialMousePos, bool dr
}
}

void DraggableCtrl::dragMove(const QPointF& delta, bool dragSelection)
void DraggableCtrl::dragMove(const QPointF& sceneDragPos, bool dragSelection,
bool disableSnapToGrid, bool disableOrientation)
{
// PRECONDITIONS:
// _graph must be configured (non nullptr)
Expand All @@ -213,32 +213,76 @@ void DraggableCtrl::dragMove(const QPointF& delta, bool dragSelection)
// 3. If the node is ungroupped and the drag is not an inside group dragging, propose
// the node for grouping (ie just hilight the potential target group item).

const auto delta = (sceneDragPos - _initialDragPos);
const auto targetUnsnapPos = _initialTargetPos + delta;

const auto targetGroup = _target->get_group();
auto movedInsideGroup = false;
if (targetGroup &&
targetGroup->getItem() != nullptr) {
const QRectF targetRect{_targetItem->position() + delta,
QSizeF{ _targetItem->width(), _targetItem->height() }};
const QRectF groupRect{QPointF{0., 0.},
QSizeF{ targetGroup->getItem()->width(), targetGroup->getItem()->height() }};
const QRectF targetRect{
targetUnsnapPos,
QSizeF{_targetItem->width(),
_targetItem->height()}
};
const QRectF groupRect{
QPointF{0., 0.},
QSizeF{targetGroup->getItem()->width(),
targetGroup->getItem()->height()}
};

movedInsideGroup = groupRect.contains(targetRect);
if (!movedInsideGroup) {
if (!movedInsideGroup)
graph->ungroupNode(_target, _target->get_group());
}
}

const auto localPos = _targetItem->position();
_targetItem->setPosition(localPos + delta);
// Drag algorithm:
// 2.1. Convert the mouse drag position to "target item" space unspaned pos
// 2.2. If target position is "centered" on grid
// or mouse delta > grid
// 2.2.1 Compute snapped position, apply it
const auto targetDragOrientation = _targetItem->getDragOrientation();
const auto dragHorizontally = disableOrientation ||
((targetDragOrientation == qan::NodeItem::DragOrientation::DragAll) ||
(targetDragOrientation == qan::NodeItem::DragOrientation::DragHorizontal));
const auto dragVertically = disableOrientation ||
((targetDragOrientation == qan::NodeItem::DragOrientation::DragAll) ||
(targetDragOrientation == qan::NodeItem::DragOrientation::DragVertical));
if (!disableSnapToGrid && getGraph()->getSnapToGrid()) {
const auto& gridSize = getGraph()->getSnapToGridSize();
bool applyX = dragHorizontally &&
std::fabs(delta.x()) > (gridSize.width() / 2.001);
bool applyY = dragVertically &&
std::fabs(delta.y()) > (gridSize.height() / 2.001);
if (!applyX && dragHorizontally) {
const auto posModGridX = fmod(targetUnsnapPos.x(), gridSize.width());
applyX = qFuzzyIsNull(posModGridX);
}
if (!applyY && dragVertically) {
const auto posModGridY = fmod(targetUnsnapPos.y(), gridSize.height());
applyY = qFuzzyIsNull(posModGridY);
}
if (applyX || applyY) {
const auto targetSnapPosX = dragHorizontally ? gridSize.width() * std::round(targetUnsnapPos.x() / gridSize.width()) :
_initialTargetPos.x();
const auto targetSnapPosY = dragVertically ? gridSize.height() * std::round(targetUnsnapPos.y() / gridSize.height()) :
_initialTargetPos.y();
_targetItem->setPosition(QPointF{targetSnapPosX,
targetSnapPosY});
}
} else { // Do not snap to grid
_targetItem->setPosition(QPointF{dragHorizontally ? targetUnsnapPos.x() : _initialTargetPos.x(),
dragVertically ? targetUnsnapPos.y() : _initialTargetPos.y()});
}

if (dragSelection) {
auto dragMoveSelected = [this, &delta] (auto primitive) { // Call dragMove() on a given node or group
auto dragMoveSelected = [this, &sceneDragPos] (auto primitive) { // Call dragMove() on a given node or group
const auto primitiveIsNotSelf = static_cast<QQuickItem*>(primitive->getItem()) !=
static_cast<QQuickItem*>(this->_targetItem.data());
if ( primitive != nullptr &&
primitive->getItem() != nullptr &&
primitiveIsNotSelf) // Note: Contrary to beginDragMove(), drag nodes that are inside a group
primitive->getItem()->draggableCtrl().dragMove(delta, false);
if (primitive != nullptr &&
primitive->getItem() != nullptr &&
primitiveIsNotSelf) // Note: Contrary to beginDragMove(), drag nodes that are inside a group
primitive->getItem()->draggableCtrl().dragMove(sceneDragPos, false);
};

std::for_each(graph->getSelectedNodes().begin(), graph->getSelectedNodes().end(), dragMoveSelected);
Expand Down Expand Up @@ -273,7 +317,8 @@ void DraggableCtrl::dragMove(const QPointF& delta, bool dragSelection)

void DraggableCtrl::endDragMove(bool dragSelection)
{
_dragLastPos = QPointF{0., 0.}; // Invalid all cached coordinates when drag ends
_initialDragPos = QPointF{0., 0.}; // Invalid all cached coordinates when drag ends
_initialTargetPos = QPointF{0., 0.};
_lastProposedGroup = nullptr;

// PRECONDITIONS:
Expand Down
17 changes: 10 additions & 7 deletions src/qanDraggableCtrl.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@

// QuickQanava headers
#include "./qanAbstractDraggableCtrl.h"
#include "./qanStyle.h" // Used in handleDropEvent()
#include "./qanGroup.h"

namespace qan { // ::qan
Expand Down Expand Up @@ -102,15 +101,19 @@ class DraggableCtrl : public qan::AbstractDraggableCtrl
void handleMouseReleaseEvent(QMouseEvent* event);

public:
//! \c dragInitialMousePos in window coordinate system.
virtual void beginDragMove(const QPointF& dragInitialMousePos, bool dragSelection = true) override;
//! \c delta in scene coordinate system.
virtual void dragMove(const QPointF& delta, bool dragSelection = true) override;
//! \c sceneDragPos is current mouse drag position in scene coordinate system.
virtual void beginDragMove(const QPointF& sceneDragPos, bool dragSelection = true) override;
//! \c sceneDragPos is current mouse drag position in scene coordinate system.
virtual void dragMove(const QPointF& sceneDragPos, bool dragSelection = true,
bool disableSnapToGrid = false, bool disableOrientation = false) override;
virtual void endDragMove(bool dragSelection = true) override;

private:
//! Internal position cache.
QPointF _dragLastPos{0., 0.};
//! Internal (mouse) initial dragging position.
QPointF _initialDragPos{0., 0.};
//! Internal (target) initial dragging position.
QPointF _initialTargetPos{0., 0.};

//! Last group hovered during a node drag (cached to generate a dragLeave signal on qan::Group).
QPointer<qan::Group> _lastProposedGroup{nullptr};
//@}
Expand Down
Loading

0 comments on commit 16f7aa5

Please sign in to comment.