Skip to content

Commit

Permalink
klighd.piccolo: important change in the drawing coordinate system app…
Browse files Browse the repository at this point in the history
…lication, solves #56

* now the local transform of the (main) diagram's clip node is *not* applied anymore if the diagram is clipped, while (for non-clipped diagrams) the transform of the diagram's root KNodeTopNode is supposed to be the identity under all circumstances;
* updated related code portions concerning the zooming, the clipping functionality, the outline, and the magnifier lens
  • Loading branch information
sailingKieler committed Dec 22, 2021
1 parent 14df889 commit bd14100
Show file tree
Hide file tree
Showing 8 changed files with 237 additions and 77 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,8 @@ public boolean isVisible(final KGraphElement diagramElement, final boolean check
}

final KNodeAbstractNode clip = getClipNode();
// use the camera's non-adjusted viewBounds here, not 'canvasCamera.getViewBoundsAdjustedByClipNodeScale()',
// since the clip node scaling is ignored by 'NodeUtil.clipRelativeGlobalBoundsOf(p, clip)' below as well!
final PBounds camBounds = canvasCamera.getViewBounds();
final PBounds elemFullBounds = NodeUtil.clipRelativeGlobalBoundsOf(p, clip);
return elemFullBounds != null && elemFullBounds.intersects(camBounds);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,10 +161,8 @@ public void zoom(final ZoomStyle zoomStyle, final KGraphElement desiredFocusElem
* time to animate in ms
*/
private void zoomToActualSize(final int duration) {
final KNode displayedKNode = this.canvasCamera.getDisplayedKNodeNode().getViewModelElement();

final PBounds newBounds = toPBoundsIncludingPortsAndLabels(displayedKNode);

final PBounds newBounds = toPBoundsIncludingPortsAndLabelsOfDisplayedKNode();

this.canvasCamera.animateViewToTransform(
PAffineTransform.getTranslateInstance(-newBounds.x, -newBounds.y), duration);
}
Expand All @@ -185,10 +183,8 @@ private void zoomToActualSize(final int duration) {
*/
private void zoomToFit(final int duration, boolean narrowDownToContents,
final Spacing defaultZoomToFitContentSpacing) {
final KNode displayedKNode = this.canvasCamera.getDisplayedKNodeNode().getViewModelElement();

final PBounds newBounds = toPBoundsIncludingPortsAndLabels(
displayedKNode, narrowDownToContents, defaultZoomToFitContentSpacing);
final PBounds newBounds = toPBoundsIncludingPortsAndLabelsOfDisplayedKNode(
narrowDownToContents, defaultZoomToFitContentSpacing);

if (this.canvasCamera.getBoundsReference().isEmpty()) {
// this case occurs while initializing the DiagramEditorPart
Expand Down Expand Up @@ -225,18 +221,23 @@ public void zoomToFocus(final KNode focus, final int duration) {
* diagram canvas area, see also {@link ZoomStyle#ZOOM_TO_FOCUS_OR_INCREASE_TO_FIT}
*/
private void zoomToFocus(final KNode focus, final int duration, final boolean increaseToFit) {
final PBounds diagramBounds = toPBoundsIncludingPortsAndLabelsOfDisplayedKNode();
final KNode displayedKNode = this.canvasCamera.getDisplayedKNodeNode().getViewModelElement();

// fetch bounds of the whole visible diagram
final PBounds focusBounds = toPBoundsIncludingPortsAndLabels(focus);
final PBounds focusBounds;

// we need the bounds in view coordinates (absolute), hence for
// a KNode add the translations of all parent nodes
if (focus == displayedKNode) {
focusBounds = diagramBounds;

if (focus != displayedKNode) {
} else {
focusBounds = toPBoundsIncludingPortsAndLabels(focus);
KNode parent = focus.getParent();

while (parent != null && parent != displayedKNode.getParent()) {
// we need the bounds in view coordinates (absolute), hence for
// a KNode add the translations of all parent nodes

while (parent != null && parent != displayedKNode) {
final double scale = parent.getProperty(CoreOptions.SCALE_FACTOR).doubleValue();

focusBounds.setSize(scale * focusBounds.width, scale * focusBounds.height);
Expand All @@ -253,7 +254,6 @@ private void zoomToFocus(final KNode focus, final int duration, final boolean in
final PBounds viewBounds = canvasCamera.getViewBounds();

if (increaseToFit) {
final PBounds diagramBounds = toPBoundsIncludingPortsAndLabels(displayedKNode);
final boolean fullyContains = viewBounds.getWidth() > diagramBounds.getWidth()
&& viewBounds.getHeight() > diagramBounds.getHeight();

Expand Down Expand Up @@ -285,9 +285,7 @@ private void zoomToFocus(final KNode focus, final int duration, final boolean in
* time to animate
*/
public void zoomToLevel(final float newZoomLevel, final int duration) {
final KNode displayedKNode = this.canvasCamera.getDisplayedKNodeNode().getViewModelElement();

final PBounds nodeBounds = toPBoundsIncludingPortsAndLabels(displayedKNode);
final PBounds nodeBounds = toPBoundsIncludingPortsAndLabelsOfDisplayedKNode();

// it would be possible to use PCamera#scaleViewAboutPoint(scale, x, y),
// however this method does not allow for animation
Expand All @@ -301,12 +299,9 @@ public void zoomToLevel(final float newZoomLevel, final int duration) {
origBounds.height * oldZoomLevel / newZoomLevel);

// add the necessary translation
final double normalizedWidth = origBounds.width * oldZoomLevel;
final double normalizedHeight = origBounds.height * oldZoomLevel;
final double transX = (origBounds.width - normalizedWidth / newZoomLevel) / 2f;
final double transY = (origBounds.height - normalizedHeight / newZoomLevel) / 2f;

newBounds.moveBy(transX, transY);
newBounds.moveBy(
(origBounds.width - newBounds.width) / 2f,
(origBounds.height - newBounds.height) / 2f);

// make sure at least some of the diagram is visible after zooming to scale 1
final PDimension dim = newBounds.deltaRequiredToContain(nodeBounds);
Expand Down Expand Up @@ -336,13 +331,18 @@ public void zoomToStay(final EObject focusElement, final KVector previousPositio
final KNode displayedKNode = this.canvasCamera.getDisplayedKNodeNode().getViewModelElement();

// Fetch bounds of the focused element.
final PBounds focusBounds = boundsComputer.toPBounds(focus);
final PBounds focusBounds;

// We need the bounds in view coordinates (absolute), hence for
// an element add the translations of all parent elements.
if (focus == displayedKNode) {
focusBounds = toPBoundsIncludingPortsAndLabelsOfDisplayedKNode();

if (focus != displayedKNode) {
} else {
focusBounds = boundsComputer.toPBounds(focus);
EObject parent = focus.eContainer();

// We need the bounds in view coordinates (absolute), hence for
// an element add the translations of all parent elements.

while (parent != null && parent != displayedKNode.getParent()) {
while (!(parent instanceof KShapeLayout) && !(parent instanceof EMapPropertyHolder) && parent != null) {
parent = parent.eContainer();
Expand Down Expand Up @@ -371,6 +371,41 @@ public void zoomToStay(final EObject focusElement, final KVector previousPositio
canvasCamera.animateViewToTransform(transform, duration);
}

/**
* Converts the current diagram clip KNode's layout data into {@link PBounds} s.t. its ports
* and labels are included, while its scale and offset are excluded.
*
* @return the requested bounding box in form of a {@link PBounds}
*/
private PBounds toPBoundsIncludingPortsAndLabelsOfDisplayedKNode() {
return toPBoundsIncludingPortsAndLabelsOfDisplayedKNode(false, null);
}

/**
* Converts the current diagram clip KNode's layout data into {@link PBounds} s.t. its ports
* and labels are included, while its scale and offset are excluded.
*
* @param doComputeSubDiagramSize
* set to <code>true</code> yields the bounding box of the nested diagram's content
* including <code>node</code>'s ports and labels if visible, with <code>false</code>
* the bounds of the given <code>node</code> including its port and labels if visible
* are returned
* @param defaultZoomToFitContentSpacing
* default spacing to be applied if <code>narrowDownToContents</code> is
* <code>true</code>, see also
* {@link de.cau.cs.kieler.klighd.util.KlighdProperties#ZOOM_TO_FIT_CONTENT_SPACING},
* may be <code>null</code>.
* @return the requested bounding box in form of a {@link PBounds}
*/
private PBounds toPBoundsIncludingPortsAndLabelsOfDisplayedKNode(
final boolean doComputeSubDiagramSize, final Spacing defaultZoomToFitContentSpacing) {
final KNode displayedKNode = this.canvasCamera.getDisplayedKNodeNode().getViewModelElement();

final PBounds bounds = toPBoundsIncludingPortsAndLabels(displayedKNode,
doComputeSubDiagramSize, defaultZoomToFitContentSpacing);
return eliminateOffsetAndScale(bounds, displayedKNode);
}

/**
* Converts <code>node</code>'s layout data into {@link PBounds} s.t. <code>node</code>'s ports
* and labels are included, respects an attached {@link LayoutOptions#SCALE_FACTOR}.
Expand Down Expand Up @@ -406,4 +441,35 @@ protected PBounds toPBoundsIncludingPortsAndLabels(final KNode node,
return boundsComputer.toPBoundsIncludingPortsAndLabels(
node, doComputeSubDiagramSize, defaultZoomToFitContentSpacing, true);
}

/**
* Reduces the given <code>bounds</code> by <code>node</code>'s translation and scale.
* It's required for computing the visible diagram's bounding box as clipped diagrams are drawn
* without applying the clip node's transform (containing the scale and offset), see
* KNodeNode#fullPaint(PPaintContext), and scaling and translating the KNodeTopNode (root node)
* is invalid.
*
* Note that <code>bounds</code>' (x,y) translation may differ from <code>node</code>'s (x,y)
* translation because of labels, ports, and port labels having negative x and y coordinates,
* i.e., are placed in left/above <code>node</code>. In that case <code>bounds</code> will than
* have negative x and/or y coordinates, which is on purpose.
*
* @param bounds
* full bounds of some {@link KNode} including its ports and labels, scaled by
* <code>node</code>'s scale and translated by <code>node</code>'s (x,y)
* @param node
* the {@link KNode} containing the reference scale and offset
* @return <code>bounds</code> being adjusted as described above (within the argument object)
*/
protected PBounds eliminateOffsetAndScale(final PBounds bounds, final KNode node) {

bounds.moveBy(-node.getXpos(), -node.getYpos());

final float scaleInverse = 1 / node.getProperty(CoreOptions.SCALE_FACTOR).floatValue();
bounds.setRect(
bounds.getX() * scaleInverse, bounds.getY() * scaleInverse,
bounds.getWidth() * scaleInverse, bounds.getHeight() * scaleInverse);

return bounds;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,16 @@ private Point2D determineLensOffset(final PInputEvent event) {

/**
* Composes a {@link PAffineTransform} incorporating the lens magnification according to the
* corresponding preference setting, the inverse of the clip node's scaling, and event's mouse
* pointer position in terms the diagram coordinates (rather than canvas coordinates).
* corresponding preference setting and event's mouse pointer position in terms the
* diagram coordinates (rather than canvas coordinates).
*
* Note that no attention need to be paid to the current diagram clip node's scale and offset.
* They're not applied while drawing them via the main camera, and since 'mainCamera' is
* the top element in the paintContext's cameraStack while drawing via 'lensCamera'
* (as its a transitive parent of 'lensCamera') KNodeNode#fullPaint(PPaintContext)
* will behave the same way as when called via 'mainCamera'.
*
* See also {@link PCamera#fullPaint(edu.umd.cs.piccolo.util.PPaintContext)}.
*
* @param event
* the {@link PInputEvent} to get the diagram position from
Expand All @@ -167,9 +175,6 @@ private PAffineTransform createViewTransform(final PInputEvent event) {
final float scale = STORE.getFloat(KlighdPreferences.MAGNIFICATION_LENS_SCALE) / 100f;
viewTransform.scale(scale, scale);

final double clipScale = mainCamera.getDisplayedKNodeNode().getScale();
viewTransform.scale(1 / clipScale, 1 / clipScale);

final Point2D pos = event.getPosition();
viewTransform.translate(-pos.getX(), -pos.getY());
return viewTransform;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -516,7 +516,7 @@ public boolean fullPick(final PPickPath pickPath) {
// instead we want this kNodeNode be the picked node anyway so, ...
if (!fullPick && isRootLayer) {
pickPath.pushNode(this);
pickPath.pushTransform(getTransform());
pickPath.pushTransform(new PAffineTransform());

return true;
}
Expand All @@ -538,10 +538,14 @@ public boolean fullPickOri(final PPickPath pickPath) {

// The filter is in charge of masking out the rendering while the diagram is
// clipped to this node and it's being drawn via the diagram's main camera!
final boolean isRootLayer = this.isRootLayer;
if (getVisible() && (getPickable() || getChildrenPickable())
&& fullIntersectsOri(pickPath.getPickBounds())) {
&& (isRootLayer || fullIntersectsOri(pickPath.getPickBounds()))) {
final PAffineTransform transform = isRootLayer ? new PAffineTransform() : getTransformReference(true);

pickPath.pushNode(this);
pickPath.pushTransform(getTransformReference(true));
pickPath.pushTransform(transform);


final boolean applyScale = nodeScale != null;

Expand All @@ -563,7 +567,7 @@ && fullIntersectsOri(pickPath.getPickBounds())) {
final int count = getChildrenCount();
for (int i = count - 1; i >= 0; i--) {
final PNode each = (PNode) getChildrenReference().get(i);
if (this.isRootLayer) {
if (isRootLayer) {
if (i == 0 && each != this.childArea) {
// do not try to pick the node's figure if the main diagram is clipped to
// this node
Expand Down Expand Up @@ -592,21 +596,58 @@ && fullIntersectsOri(pickPath.getPickBounds())) {
kpp.popNodeScale();
}

pickPath.popTransform(getTransformReference(false));
pickPath.popTransform(transform);
pickPath.popNode(this);
}

return false;
}

/**
* {@inheritDoc}
*/
@Override
public void repaintFrom(PBounds localBounds, PNode childOrThis) {
if (this.isRootLayer) {
// This customization reports a dirty area, e.g. reported by some child, to the attached
// cameras without adjusting its bounds by 'this.transform'. Attached cameras are expected
// only in case the (main) diagram is clipped to this node. In that case the main camera
// and optionally the magnifier lens camera.
// The customization is necessary as we now ignore this node's 'transform' once the (main))
// diagram is clipped to this node, because of https://github.com/kieler/KLighD/issues/56,
// see also the corresponding changes in #fullPickOri(...) and #fullPaint(...) above and below.
//
// The above check of 'this.isRootLayer' is actually redundant, as cameras should only be
// registered in case of 'this.isRootLayer' is equal to 'true', but is kept for documentation
// and easier understanding.
super.notifyCameras(localBounds);
}

// Apart from the above immediate camera notification, the usual propagation is kept in order to
// always keep the outline up to date, which happens via propagating up to the root KNodeTopNode
// and along that way to the outline camera.
super.repaintFrom(localBounds, childOrThis);
}

/**
* {@inheritDoc}
*/
@Override
protected void notifyCameras(PBounds parentBounds) {
// this customization is symmetric to the above one and just suppresses superfluous repaint
// requests of potentially incorrect bounds, as we ignore 'this.transform' if the (main)
// diagram is clipped to this node, see above.
// Otherwise, no attached cameras are expected at the time of writing this.
}

/**
* {@inheritDoc}
*/
@Override
public void fullPaint(final PPaintContext paintContext) {
final boolean isRootLayer = this.isRootLayer;
final KlighdPaintContext kpc = (KlighdPaintContext) paintContext;
if (!this.isRootLayer && this.visibilityHelper != null
if (!isRootLayer && this.visibilityHelper != null
&& this.visibilityHelper.isNotVisibleOn(kpc)) {
return;
}
Expand All @@ -621,8 +662,12 @@ public void fullPaint(final PPaintContext paintContext) {
// In contrast, the rendering figure is supposed to be drawn at all times
// while the diagram is drawn via the outline view's camera!

if (getVisible() && (kpc.isOutline() || fullIntersectsOri(paintContext.getLocalClip()))) {
final PAffineTransform transform = getTransformReference(false);
// the following flag is false if drawn on the outline view, for example
final boolean isRootAndDrawnViaMainCamera = isRootLayer && this.getCamerasReference().contains(paintContext.getCamera());

if (getVisible() && (kpc.isOutline() || isRootAndDrawnViaMainCamera || fullIntersectsOri(paintContext.getLocalClip()))) {
final PAffineTransform transform = isRootAndDrawnViaMainCamera ? new PAffineTransform() : getTransformReference(false);

paintContext.pushTransform(transform);
paintContext.pushTransparency(getTransparency());

Expand All @@ -641,10 +686,8 @@ public void fullPaint(final PPaintContext paintContext) {
for (int i = 0; i < count; i++) {
final PNode each = (PNode) getChildrenReference().get(i);

if (this.isRootLayer) {
// the following flag is false if drawn on the outline view, for example
final boolean drawnViaMainCamera = this.getCamerasReference().contains(paintContext.getCamera());
if (drawnViaMainCamera) {
if (isRootLayer) {
if (isRootAndDrawnViaMainCamera) {
if (i == 0 && each != this.childArea) {
// do not draw the node's figure on the main diagram if it is clipped to this node
continue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ public void validateFullPaint() {
isValidatingPaint = false;

if (!tempRect.isEmpty()) {
repaintFrom(tempRect, this);
super.repaintFrom(tempRect, this);
}
}

Expand Down
Loading

0 comments on commit bd14100

Please sign in to comment.