Skip to main content


Eclipse Community Forums
Forum Search:

Search      Help    Register    Login    Home
Home » Eclipse Projects » GEF » Constrain size of InfiniteCanvas
Constrain size of InfiniteCanvas [message #1729191] Tue, 12 April 2016 01:46 Go to next message
Colin Sharples is currently offline Colin SharplesFriend
Messages: 90
Registered: July 2009
Location: Wellington, New Zealand
Member

This might seem a little contradictory, but I want to be able to constrain the FXViewer's InfiniteCanvas to be a certain size. My model object defines a background of a certain size, and I would like the viewport of the canvas constrained to fit within those bounds. I tried the following:

viewer.getCanvas().setMaxHeight(map.getHeight());
viewer.getCanvas().setMaxWidth(map.getWidth());

This sets the scrollbars of the viewport to the maximum size, but you can quite easily go past those limits using the scroll wheel.

Or should I be handling this in the parts I create? At the moment the root of my model adds a Group - maybe that should be a ScrollPane? If so, how do I size the ScrollPane's viewport to fit the available screen width of my canvas?


Colin Sharples
CTG Games Ltd
Wellington, New Zealand
Re: Constrain size of InfiniteCanvas [message #1729457 is a reply to message #1729191] Thu, 14 April 2016 09:47 Go to previous messageGo to next message
Matthias Wienand is currently offline Matthias WienandFriend
Messages: 163
Registered: March 2015
Senior Member
Hi Colin,

I do not quite understand the problem, I'm afraid. The following snippet creates an InfiniteCanvas and constrains its max-width and max-height according to two slider settings. The content that is rendered within the canvas is correctly clipped at the canvas bounds:

import java.util.Arrays;

import org.eclipse.gef4.fx.nodes.InfiniteCanvas;

import javafx.application.Application;
import javafx.geometry.Orientation;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.Separator;
import javafx.scene.control.Slider;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.paint.CycleMethod;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.Stop;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;

public class InfiniteCanvasFixedSizeSnippet extends Application {

	public static void main(String[] args) {
		launch();
	}

	@Override
	public void start(Stage primaryStage) throws Exception {
		// create scene
		AnchorPane root = new AnchorPane();
		Scene scene = new Scene(root, 500, 500);
		root.setStyle("-fx-font-size: 12pt;");

		// create width slider
		Label widthLabel = new Label("MaxWidth:");
		widthLabel.setStyle("-fx-text-fill: white;");
		Slider widthSlider = new Slider(100, 500, 300);
		widthSlider.setOrientation(Orientation.HORIZONTAL);
		widthSlider.setMaxWidth(Double.MAX_VALUE);
		HBox widthBox = new HBox();
		HBox.setHgrow(widthSlider, Priority.ALWAYS);
		widthBox.setMaxWidth(Double.MAX_VALUE);
		widthBox.getChildren().addAll(widthLabel, widthSlider);

		// create height sldier
		Label heightLabel = new Label("MaxHeight:");
		heightLabel.setStyle("-fx-text-fill: white;");
		Slider heightSlider = new Slider(100, 500, 300);
		heightSlider.setOrientation(Orientation.HORIZONTAL);
		heightSlider.setMaxWidth(Double.MAX_VALUE);
		HBox heightBox = new HBox();
		HBox.setHgrow(heightSlider, Priority.ALWAYS);
		heightBox.setMaxWidth(Double.MAX_VALUE);
		heightBox.getChildren().addAll(heightLabel, heightSlider);

		// assemble in controls box
		VBox controlsBox = new VBox();
		controlsBox.setMaxWidth(Double.MAX_VALUE);
		Label canvasLabel = new Label("InfiniteCanvas:");
		canvasLabel.setStyle("-fx-text-fill: white;");
		controlsBox.getChildren().addAll(widthBox, heightBox,
				new Separator(Orientation.HORIZONTAL), canvasLabel);

		// create canvas
		InfiniteCanvas canvas = new InfiniteCanvas();
		canvas.setShowGrid(false);
		canvas.setClipContent(true);
		canvas.maxWidthProperty().bind(widthSlider.valueProperty());
		canvas.maxHeightProperty().bind(heightSlider.valueProperty());
		for (int i = 0; i < 10; i++) {
			Rectangle rectangle = new Rectangle(50, 50, Color.RED);
			rectangle.setTranslateX(i * 50);
			rectangle.setTranslateY(i * 50);
			canvas.getContentGroup().getChildren().add(rectangle);
		}

		// create background
		Rectangle background = new Rectangle();
		background.setWidth(500);
		background.setHeight(500);
		background.setFill(new LinearGradient(0, 0, 1, 1, true,
				CycleMethod.REFLECT, Arrays.asList(new Stop(0d, Color.GREEN),
						new Stop(1d, Color.CYAN))));

		// arrange in root pane
		root.getChildren().addAll(background, controlsBox, canvas);

		// stretch controls
		AnchorPane.setLeftAnchor(controlsBox, 0d);
		AnchorPane.setRightAnchor(controlsBox, 0d);

		// position canvas below controls
		canvas.translateYProperty().bind(controlsBox.heightProperty());

		primaryStage.setScene(scene);
		primaryStage.sizeToScene();
		primaryStage.setTitle("InfinteCanvas Fixed Size");
		primaryStage.show();
	}

}

Could you explain the problem to me again with reference to this snippet, please?

Best regards,
Matthias
Re: Constrain size of InfiniteCanvas [message #1729466 is a reply to message #1729457] Thu, 14 April 2016 10:31 Go to previous messageGo to next message
Colin Sharples is currently offline Colin SharplesFriend
Messages: 90
Registered: July 2009
Location: Wellington, New Zealand
Member

I am running this in an e4 application, so primary stage and scene are created by the RCP framework, and are out of my control - perhaps that's a factor?
Change your example so that the size of the Rectangle is 1000x1000, then size the window so that you can set the max width out to the edge of the screen, but not the max height. In your example, the canvas is constrained by the max width and height - if you move the scrollbar to the top or bottom, that's as far as you can go.

In my application, I get the scrollbars appearing, and they size correctly based on the contents that are created, but you can still go beyond the contents. If you pull the scrollbar thumb down to the bottom, the canvas stops scrolling, but if you use the mouse scroll wheel, it carries on scrolling, and map part starts to disappear up off the top of the screen. The same with the horizontal scroll bar - the thumb is restricted to the correct size, but using the left/right arrow keys you can go beyond the bounds. Note that if I set the InfiniteCanvas to never show scrollbars, I can still move the canvas up/down using the scroll wheel, but not left/right.

Here is the postConstruct method of my e4 view:
  @PostConstruct
  public void postConstruct(BorderPane container) {
    Injector injector = Guice.createInjector(createGuiceModule());
    FXDomain domain = injector.getInstance(FXDomain.class);
    FXViewer viewer = domain.getAdapter(FXViewer.class);
    VBox palette = new VBox();
    palette.setMaxWidth(180);
    InfiniteCanvas canvas = viewer.getCanvas();
    SplitPane sp = new SplitPane(palette, canvas);
    container.setCenter(sp);
    canvas.setClipContent(true);
    canvas.setHorizontalScrollBarPolicy(ScrollBarPolicy.ALWAYS);
    canvas.setVerticalScrollBarPolicy(ScrollBarPolicy.ALWAYS);
    canvas.sceneProperty().addListener((observable, oldValue, newValue) -> {
      if (canvas.getScene() != null) {
        domain.activate();
        try {
          viewer.getAdapter(ContentModel.class).getContents().setAll(createContents());
          canvas.setMaxHeight(map.getHeight());
          canvas.setMaxWidth(map.getWidth());
        } catch (Exception e) {
          e.printStackTrace();
        }
      }
    });
  }

I am putting the canvas in a SplitPane because the left part of that will be my palette component. The width and height come from my root model object, so is only available once I have created the contents.


Colin Sharples
CTG Games Ltd
Wellington, New Zealand
Re: Constrain size of InfiniteCanvas [message #1729477 is a reply to message #1729466] Thu, 14 April 2016 13:07 Go to previous messageGo to next message
Matthias Wienand is currently offline Matthias WienandFriend
Messages: 163
Registered: March 2015
Senior Member
Alright, I think the problems are only related to the viewport-changing interaction policies FXPanOrZoomOnScrollPolicy, FXZoomOnPinchSpreadPolicy, and FXPanOnTypePolicy, which are registered for FXRootPart. These policies allow scrolling and zooming out of the content area. Internally, they use FXChangeViewportPolicy to perform their changes. Therefore, you could exchange the FXChangeViewportPolicy that is registered for FXRootPart by overriding bindFXChangeViewportPolicyAsFXRootPartAdapter() within your Module. The new ContentRestrictedChangeViewportPolicy could then disallow scrolling outside of the content area. Alternatively, if you do not want the user to alter the scroll position with mouse/keyboard, you could remove the bindings for the viewport-changing interaction policies altogether. I can provide you with a ContentRestrictedChangeViewportPolicy if desired.

Best regards,
Matthias
Re: Constrain size of InfiniteCanvas [message #1729504 is a reply to message #1729477] Thu, 14 April 2016 19:07 Go to previous messageGo to next message
Colin Sharples is currently offline Colin SharplesFriend
Messages: 90
Registered: July 2009
Location: Wellington, New Zealand
Member

An example would be great, thanks. I am happy for the user to use the scroll wheel and keyboard for panning, but I'd prefer to disable scroll wheel zooming. I'd rather use the ctrl and alt keys in combination with the scroll wheel to change the pan direction, e.g. ctrl+wheel = left/right, alt+wheel = diagonal right-up/left-down, ctrl+alt+wheel = diagonal left-up/right-down. I would have zooming function on buttons to constrain it to a set number of levels, rather than being free form.

Thanks again for your help.


Colin Sharples
CTG Games Ltd
Wellington, New Zealand
Re: Constrain size of InfiniteCanvas [message #1730184 is a reply to message #1729504] Fri, 22 April 2016 07:08 Go to previous messageGo to next message
Matthias Wienand is currently offline Matthias WienandFriend
Messages: 163
Registered: March 2015
Senior Member
Sorry for the delay, I will post an example policy this weekend.
Re: Constrain size of InfiniteCanvas [message #1732413 is a reply to message #1730184] Tue, 17 May 2016 06:00 Go to previous messageGo to next message
Matthias Wienand is currently offline Matthias WienandFriend
Messages: 163
Registered: March 2015
Senior Member
Once again, I apologise for the delay and thank you for waiting so patiently. Anyway, as I stated earlier, in order to restrict the viewport manipulations to the content area, you could use a ContentRestrictedFXChangeViewportPolicy:
import org.eclipse.gef4.fx.nodes.InfiniteCanvas;
import org.eclipse.gef4.mvc.fx.policies.FXChangeViewportPolicy;
import org.eclipse.gef4.mvc.fx.viewer.FXViewer;

import javafx.geometry.Bounds;

public class ContentRestrictedFXChangeViewportPolicy extends FXChangeViewportPolicy {

	@Override
	protected void locallyExecuteOperation() {
		// determine current translation
		double tx = getChangeViewportOperation().getNewHorizontalScrollOffset();
		double ty = getChangeViewportOperation().getNewVerticalScrollOffset();

		// determine direction of change
		double dx = tx - getChangeViewportOperation().getInitialHorizontalScrollOffset();
		double dy = ty - getChangeViewportOperation().getInitialVerticalScrollOffset();

		// determine scrollable bounds
		FXViewer viewer = (FXViewer) getHost().getRoot().getViewer();
		InfiniteCanvas canvas = viewer.getCanvas();
		Bounds scrollableBounds = canvas.getScrollableBounds();

		// compute content restricted translation range
		// XXX: Scrolling is implemented by translating the scene graph node
		// that holds all children. Therefore, scrolling to the right is
		// actually moving this node to the left, and vice versa.
		double contentRestrictedMinTranslateX = -scrollableBounds.getMinX();
		double contentRestrictedMinTranslateY = -scrollableBounds.getMinY();
		// XXX: The size of the canvas is the visible area which has to be
		// subtracted from the scrollable bounds to yield the translation range.
		double contentRestrictedMaxTranslateX = -(scrollableBounds.getMaxX() - canvas.getWidth());
		double contentRestrictedMaxTranslateY = -(scrollableBounds.getMaxY() - canvas.getHeight());

		// sort content restricted bounds numerically
		if (contentRestrictedMinTranslateX > contentRestrictedMaxTranslateX) {
			double tmp = contentRestrictedMinTranslateX;
			contentRestrictedMinTranslateX = contentRestrictedMaxTranslateX;
			contentRestrictedMaxTranslateX = tmp;
		}
		if (contentRestrictedMinTranslateY > contentRestrictedMaxTranslateY) {
			double tmp = contentRestrictedMinTranslateY;
			contentRestrictedMinTranslateY = contentRestrictedMaxTranslateY;
			contentRestrictedMaxTranslateY = tmp;
		}

		// do not allow translation when the corresponding scrollbar is
		// invisible, and otherwise restrict the translation values to the
		// scrollable bounds
		if (!canvas.getHorizontalScrollBar().isVisible()) {
			tx = getChangeViewportOperation().getInitialHorizontalScrollOffset();
		} else if (tx < contentRestrictedMinTranslateX || tx > contentRestrictedMaxTranslateX) {
			// restrict to minimum/maximum depending on direction
			if (dx < 0) {
				tx = contentRestrictedMinTranslateX;
			} else {
				tx = contentRestrictedMaxTranslateX;
			}
		}
		if (!canvas.getVerticalScrollBar().isVisible()) {
			ty = getChangeViewportOperation().getInitialVerticalScrollOffset();
		} else if (ty < contentRestrictedMinTranslateY || ty > contentRestrictedMaxTranslateY) {
			// restrict to minimum/maximum depending on direction
			if (dy < 0) {
				ty = contentRestrictedMinTranslateY;
			} else {
				ty = contentRestrictedMaxTranslateY;
			}
		}

		// change operation values
		getChangeViewportOperation().setNewHorizontalScrollOffset(tx);
		getChangeViewportOperation().setNewVerticalScrollOffset(ty);

		// execute
		super.locallyExecuteOperation();
	}

}

The policy computes the valid horizontal and vertical translation ranges and the directions in which the translation is changed. Subsequently, the translation values are restricted to the translation ranges depending on their direction of change, i.e. if the user scrolls down and reaches a vertical translation value outside of the vertical translation range, then the vertical translation value is set to the maximum of the range.

In order to customize the interactions, you would need to subclass the individual interaction policies and/or remove/add interaction policies to satisfy your needs. For example, the FXPanOrZoomOnScrollPolicy can be customized as follows to disallow zooming, change the pan direction with Ctrl rather than Shift, and allow diagonal movement with Alt:
import org.eclipse.gef4.geometry.planar.Dimension;

import javafx.scene.input.ScrollEvent;

public class FXPanOnScrollPolicy extends FXPanOrZoomOnScrollPolicy {

	@Override
	protected Dimension computeDelta(ScrollEvent event) {
		// let base class compute delta (includes swapping of direction)
		Dimension delta = super.computeDelta(event);
		// adjust delta for diagonal movement
		if (isDiagonal(event)) {
			boolean isSwapped = isSwapDirection(event);
			if (delta.width == 0) {
				delta.width = isSwapped ? -delta.height : delta.height;
			} else if (delta.height == 0) {
				delta.width = isSwapped ? -delta.width : delta.width;
			}
		}
		return delta;
	}

	/**
	 * Returns <code>true</code> if the pan direction should be diagonal for the
	 * given {@link ScrollEvent}. Otherwise returns <code>false</code>.
	 *
	 * @param event
	 *            The {@link ScrollEvent} in question.
	 * @return <code>true</code> if panning should be diagonal for the given
	 *         event, otherwise <code>false</code>.
	 */
	protected boolean isDiagonal(ScrollEvent event) {
		// enable diagonal movement when Alt is pressed
		return event.isAltDown();
	}

	@Override
	protected boolean isPan(ScrollEvent event) {
		// only disable panning when Shift is pressed
		return !event.isShiftDown();
	}

	@Override
	protected boolean isSwapDirection(ScrollEvent event) {
		// swap direction when Ctrl is pressed
		return event.isControlDown();
	}

	@Override
	protected boolean isZoom(ScrollEvent event) {
		// disable zooming
		return false;
	}

}

Best regards,
Matthias
Re: Constrain size of InfiniteCanvas [message #1732585 is a reply to message #1732413] Wed, 18 May 2016 11:20 Go to previous messageGo to next message
Colin Sharples is currently offline Colin SharplesFriend
Messages: 90
Registered: July 2009
Location: Wellington, New Zealand
Member

I just updated to the 20160513 integration build to try this out, and now my code is completely broken (I was on the 20160507 integration build previously). It looks like the change to allow multiple FXViewers has done something weird. My code now fails when trying to set the content model, although it's not even consistent - sometimes it fails earlier, when trying to get the canvas from the FXViewer. I can't work out what is going on, and now I can't run the application at all. Is there a different way the content part factory has to be registered? I am using:
    binder().bind(new TypeLiteral<IContentPartFactory<Node>>() {
    }).toInstance(new MapPartFactory());


Colin Sharples
CTG Games Ltd
Wellington, New Zealand
Re: Constrain size of InfiniteCanvas [message #1732648 is a reply to message #1732585] Wed, 18 May 2016 19:31 Go to previous messageGo to next message
Colin Sharples is currently offline Colin SharplesFriend
Messages: 90
Registered: July 2009
Location: Wellington, New Zealand
Member

Phew, got it working again. It was actually some policies that had previously been bound in the method bindFXRootPartAdapters(), which disappeared. I moved them into bindFXRootPartAsContentViewerAdapter(), which turned out to be the wrong place. The odd thing is that the error did not happen consistently in one place, so when debugging it was really hard to find the actual error. Eventually I found a ClassCastException, and by putting a breakpoint on that was able to track down the exact binding error. I moved the policy bindings into bindContentViewerRootPartAdapters() and everything started working again.

Now that's all working, the examples you gave do the trick very nicely. Thanks for your help. I will just add a separate policy to manage the zooming, which will calculate the min/max zoom allowed, and also will adjust the viewport as necessary so that a zoom out will not scroll outside the allowed bounds. Awesome.


Colin Sharples
CTG Games Ltd
Wellington, New Zealand
Re: Constrain size of InfiniteCanvas [message #1732699 is a reply to message #1732648] Thu, 19 May 2016 09:57 Go to previous messageGo to next message
Matthias Wienand is currently offline Matthias WienandFriend
Messages: 163
Registered: March 2015
Senior Member
Glad to hear that you were able to fix it. We changed quite a lot when allowing multiple viewers and were afraid to break adopters again. Most notably, the content viewer and its root part are now associated with a "contentViewer" role. The bindFXRootPartAsContentViewerAdapter() method registers the binding for FXRootPart as an adapter at the content viewer, using the "contentViewer" role for its adapter key.
Re: Constrain size of InfiniteCanvas [message #1735428 is a reply to message #1732699] Sun, 19 June 2016 10:43 Go to previous messageGo to next message
Colin Sharples is currently offline Colin SharplesFriend
Messages: 90
Registered: July 2009
Location: Wellington, New Zealand
Member

I just got round to implementing the zoom part of this. I have the zoom level managed from some buttons, which go through a custom zoom manager class. This class works out the minimum and maximum zoom levels, based on the size of the canvas so that at maximum zoom out the canvas will fit the viewport exactly for either width or height, depending on the shape of the canvas and the size of the monitor (usually the width). Each click of the zoom in/out buttons will change the zoom by a set scale factor, generally somewhere about 0.8 or 0.9 depending on the size of the canvas - this means that you can always get back to exactly 1.0 scale, e.g. 5 clicks on zoom out followed by 5 clicks on zoom in. To perform the zoom in/out, the zoom manager calls the zoomRelative() method on the ContentRestrictedFXChangeViewportPolicy. That part all works fine.

I was expecting that I would then be able to update the locallyExecuteOperation() method to take account of the zoom level, and adjust the horiz/vertical offsets of the change viewport operation so that the new zoom would only display the content within the constrained bounds. However, it seems it's not as easy as that. When I get to the locallyExecuteOperation(), the scrollable bounds of the canvas has already been updated to take account of the new position of the canvas. For example, if you were at the top left of the scene, i.e. the top left of the root part is at the top left of the viewport, then after a zoom out the top left of the root part is displaced down and to the right. When you get to locallyExecuteOperation(), the scroll bar offsets have not changed, but the scrollable bounds of the canvas have been adjusted to account for the area of blank space to the left and top of the root part, so you can't work out where the offsets should be. If you then scroll to the right and down, the scrollable bounds gets adjusted back down, and the scrollbars are once again constrained within the correct bounds.

Is there a way that I can force the canvas to use the correct scrollable bounds, based on the current zoom level?


Colin Sharples
CTG Games Ltd
Wellington, New Zealand
Re: Constrain size of InfiniteCanvas [message #1738754 is a reply to message #1735428] Sat, 23 July 2016 02:20 Go to previous messageGo to next message
Colin Sharples is currently offline Colin SharplesFriend
Messages: 90
Registered: July 2009
Location: Wellington, New Zealand
Member

Still scratching my head over this one. The calculation of the zoom seems a bit weird - the actual bounds of the zoomed view do not match what I calculated they should be, so if I try to set the bounds explicitly, the scrollbars end up in the wrong place.

I also need to be able to programmatically scroll to make a certain node visible, but there doesn't seem to be any way to do this other than directly manipulating the scrollbars, is that right?


Colin Sharples
CTG Games Ltd
Wellington, New Zealand
Re: Constrain size of InfiniteCanvas [message #1742648 is a reply to message #1738754] Tue, 06 September 2016 10:53 Go to previous message
Matthias Wienand is currently offline Matthias WienandFriend
Messages: 163
Registered: March 2015
Senior Member
Hi Colins,

for programmatically scrolling to make a certain node visible, you can use FXViewer#reveal(Node) / InfiniteCanvas#reveal(Node).

The InfiniteCanvas defines three bounds:
- viewport = width and height of the InfiniteCanvas
- content-bounds = layout-bounds of the content group
- scrollable-bounds = content-bounds plus viewport, i.e. including empty space (without content) that is currently visible in the viewport.

When zooming, the content-bounds should match what you calculated it should be, however, scrollable-bounds are dependent on content-bounds and viewport, so that actual bounds of the zoomed view can differ from that.

Best regards,
Matthias
Previous Topic:GEF4 Geometry performance
Next Topic:Selection of elements in GEF
Goto Forum:
  


Current Time: Mon Nov 19 18:09:26 GMT 2018

Powered by FUDForum. Page generated in 0.02805 seconds
.:: Contact :: Home ::.

Powered by: FUDforum 3.0.2.
Copyright ©2001-2010 FUDforum Bulletin Board Software

Back to the top