Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

klighd.piccolo: important change in the drawing coordinate system application, solves #56 #104

Merged
merged 1 commit into from
Feb 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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