Nested BorderPane with Canvas only grows, never shrinks - javafx-8

I have a BorderPane containing a Canvas, with the Canvas' height and width properties bound to the properties of the BorderPane. The BorderPane in turn is the root of a Scene.
This all works fine. If I resize the window, the BorderPane's size changes to match the size of the Scene, and the Canvas changes to match the size of the BorderPane.
However, if I introduce another BorderPane, it stops working: the inner BorderPane, and the Canvas, will grow as the Scene and outer BorderPane grow, but when I shrink the window, they don't shrink.
Working: Not working:
┌──────────────┐ ┌────────────────┐
│ Scene │ │ Scene │
│┌────────────┐│ │┌──────────────┐│
││ BorderPane ││ ││ BorderPane ││
││┌──────────┐││ ││┌────────────┐││
│││ Canvas │││ │││ BorderPane │││
││└──────────┘││ │││┌──────────┐│││
│└────────────┘│ ││││ Canvas ││││
└──────────────┘ │││└──────────┘│││
││└────────────┘││
│└──────────────┘│
└────────────────┘
The code that works:
BorderPane parent = new BorderPane();
Canvas canvas = new Canvas();
canvas.widthProperty().bind(parent.widthProperty());
canvas.heightProperty().bind(parent.heightProperty());
parent.setCenter(canvas);
Scene scene = new Scene(parent);
stage.setScene(scene);
stage.show();
The code that doesn't work:
BorderPane inner = new BorderPane();
Canvas canvas = new Canvas();
canvas.widthProperty().bind(inner.widthProperty());
canvas.heightProperty().bind(inner.heightProperty());
inner.setCenter(canvas);
BorderPane outer = new BorderPane();
outer.setCenter(inner);
Scene scene = new Scene(outer);
stage.setScene(scene);
stage.show();
I added some logging to confirm the problem:
# scene, outer pane, inner pane, and canvas growing
outer width: 1364.0 -> 1374.0
scene width: 1364.0 -> 1374.0
outer height: 339.0 -> 342.0
scene height: 339.0 -> 342.0
canvas width: 1364.0 -> 1374.0
inner width: 1364.0 -> 1374.0
canvas height: 339.0 -> 342.0
inner height: 339.0 -> 342.0
# scene and outer pane shrinking, canvas and inner not
outer width: 1374.0 -> 1327.0
scene width: 1374.0 -> 1327.0
outer height: 342.0 -> 330.0
scene height: 342.0 -> 330.0
outer width: 1327.0 -> 1290.0
scene width: 1327.0 -> 1290.0
outer height: 330.0 -> 326.0
scene height: 330.0 -> 326.0
Obviously in this toy example I can just remove the intermediate BorderPane, but what if I want to make a reusable, resizable component wrapping that Canvas? How can I make sure it's always resized with the parent? And what's the deal with nested BorderPanes, anyway?

Canvas is a non-resizable node. This means it's size is a lower bound for the min size of it's parent.
If you use the BorderPane as scene root, resizing the window forces the scene to be resized and resizing the scene forces the scene root to be resized to fit the window. For this reason the parent of the Canvas shrinks below it's min size and the Canvas shrinks too.
If you shrink a BorderPane below it's min size, it does not force it's resizable children to resize below their min sizes but sets their sizes to the min size instead. This way a BorderPane wrapped in a BorderPane is never forced to shrink below the size of the Canvas and the Canvas never shrinks.
You can fix this by setting the managed property of the Canvas to false which results in the Canvas not being considered when calculating the min size. (Note that unmanaged children are not considered for layout at all. You should therefore wrap the Canvas in a Pane as the only child to prevent undesired effects on siblings.)
Canvas canvas = ...
canvas.setManaged(false);
Pane canvasParent = new Pane(canvas);
canvas.widthProperty().bind(canvasParent.widthProperty());
canvas.heightProperty().bind(canvasParent.heightProperty());
// TODO: put canvasParent in the scene

Related

Javafx scale a control according to it's parent

I am writing a new Imageview with mask in Javafx.
The mask can show according to the direction of mouse.
Using Timeline makes it work well. But when the mask leaves, the mask is still visible.
I want to hide the part of mask, which is out of the Image. How can I scale it?
When mouse enter:
When mouse leave:
And my animation code
private void leftOut() {
KeyFrame k1 = new KeyFrame(Duration.ZERO, new KeyValue(mask.translateXProperty(), 0));
KeyFrame k2 = new KeyFrame(time, new KeyValue(mask.translateXProperty(), control.getWidth()*-1.0));
timeline = new Timeline();
timeline.getKeyFrames().addAll(k1,k2);
timeline.play();
}
Other directions are just like this.
You don't need to resize the mask at all. Simply apply a clip to it that has the same size as the image:
Image image = new Image("https://upload.wikimedia.org/wikipedia/commons/thumb/b/b1/Mona_Lisa%2C_by_Leonardo_da_Vinci%2C_from_C2RMF.jpg/164px-Mona_Lisa%2C_by_Leonardo_da_Vinci%2C_from_C2RMF.jpg");
ImageView imageView = new ImageView(image);
// init mask
Label mask = new Label("SomeText");
mask.setMaxSize(Region.USE_PREF_SIZE, Region.USE_PREF_SIZE);
mask.setPrefSize(image.getWidth(), image.getHeight());
mask.setStyle("-fx-background-color: rgba(255, 255, 255, 0.6);");
mask.setAlignment(Pos.CENTER);
// create clip
Rectangle clip = new Rectangle(image.getWidth(), image.getHeight());
mask.setClip(clip);
// keep clip in position horizontally in root
clip.translateXProperty().bind(mask.translateXProperty().negate());
StackPane root = new StackPane(imageView, mask);
root.setPrefSize(500, 500);
root.setStyle("-fx-background-color: orange;");
// probably a bit simpler to use TranslateTransition instead of Timeline
TranslateTransition transition = new TranslateTransition(Duration.seconds(5), mask);
transition.setByX(image.getWidth());
transition.play();

gtkmm Gtk::Layout negative child position not setting its absolute position

The description of Gtk::Layout says
Infinite scrollable area containing child widgets and/or custom drawing
I put a Gtk::Layout inside a Gtk::ScrolledWindow, gave the Gtk::Layout a size of 400x400 and then placed a Gtk::Button at (450, 450), but the scrollbars only appeared until the scrolled window was resized to 400x400, not (450+button-width x 450+button-height). So I had to move the bottom-right corner of the layout by connecting a handler to button's signal_size_allocate() signal and manually call Gtk::Layout::set_size() function to resize it up to button's boundary. Now the scrolled window shows scrollbars to reach the button. Up to this everything was OK.
class LayoutWindowTest : public ApplicationWindow {
ScrolledWindow sw;
Layout l;
Button b;
public:
LayoutWindowTest()
: l(), b("test") {
set_title("layout test");
set_size_request(400, 400);
sw.set_hadjustment(l.get_hadjustment());
sw.set_vadjustment(l.get_vadjustment());
l.set_size(400, 400);
sw.add(l);
add(sw);
// b.get_allocation() won't work because button has not been allocated a
// size yet
b.signal_size_allocate().connect(
[this](Allocation& btn_sz) {
l.set_size(btn_sz.get_x() + btn_sz.get_width(),
btn_sz.get_y() + btn_sz.get_height());
});
l.put(b, 450, 450);
show_all_children();
}
virtual ~LayoutWindowTest() {}
};
Initial window showing scrollbars and then scrolled to bottom-right corner to show button
Now I need to do the same thing for the top-left corner, i.e. put the button out of visible boundary of scrolled window and then use the scrollbars to reach the button. I tried putting the button at (-10, -10), but unlike the previous case I can't set layout's top-left corner with code by resizing it, so can't move the layout's scrollbars. When I resize the scrolled window manually by its top-left corner the button moves towards top-left with it instead of staying there. How to remedy this situation?
Initial window with button at (-10, -10) and no scrollbars, tried resizing the window by top-left handle but button moves with it
In short, how can I scroll in both top-left and bottom-right directions infinitely?

Fake scrolling with JavaFX 8 ScrollPane

What I'm trying to do:
I'm trying to render a game board for a user using a canvas. The user
will be able to zoom.
Rendering the entire game board is impossible when the user is
zoomed in as the game board is very large. Instead I will render what
is onscreen every 1/30 sec.
I would like to create the illusion of the entire game board beings
inside a gui like the ScrollPane where the user can scroll with a
mouse.
In reality the ScrollPane will be linked to a single canvas exactly
the size of the viewport.
I'm not sure what the right way to go about this is. The scroll pane does not seem to have a property that allows you to pretend it has a large object in it. I could place a large canvas in the scroll pane but large canvases take up memory, as my crashes have suggested. Should I be using an AnchorPane or something? What's the proper way to do this?
It seems like putting an AnchorPane into the ScrollPane, setting that AnchorPane to the correct size (the size of the entire gameboard) works well to let the user scroll freely. From there all you have to do is put a canvas in the AnchorPane at the right place and keep moving it (and redrawing it) makes everything work out. To make it run smoothly I think you should render 100 to 500 pixels outside of the viewport of the ScrollPane. Here's some example code from my project:
public class GridViewPane {
/** ScrollPane that this grid exits in */
private ScrollPane gridScroll;
/** The anchor pane immediately inside of {#link #gridScroll}. The
* dimensions of this equals the dimensions of the {#link #can} if it was to
* render the entire grid at once */
private Pane gridView;
/** Exists in {#link #gridView} and is exactly the size of the viewport of
* {#link #gridScroll} */
private Canvas can;
/** A label the specifies what hex is currently moused-over */
private Label currentHexLabel;
public void update() {
// sets the canvas to the right size if needed
final Bounds viewportSize = gridScroll.getBoundsInLocal();
if (can == null || !can.getBoundsInLocal().equals(viewportSize)) {
can = new Canvas(viewportSize.getWidth(),
viewportSize.getHeight());
gc = can.getGraphicsContext2D();
}
// sets the anchorpane to the right size
final double[] dim = getPixleSizeOfGrid(w.columns(), w.rows(), r);
if (gridView == null) {
gridView = new AnchorPane();
gridScroll.setContent(gridView);
}
Bounds oldDim = gridView.getBoundsInLocal();
if (!(oldDim.getWidth() == dim[0]
&& oldDim.getHeight() == dim[1])) {
gridView.setPrefSize(dim[0], dim[1]);
}
// puts the canvas in the right position
if (!gridView.getChildren().contains(can))
gridView.getChildren().add(can);
double[] x = getXScreenRange();
double[] y = getYScreenRange();
can.relocate(x[0], y[0]);
// *********** Drawing Hexes ***********/
}
}

Adding a SubScene to the center of a BorderPane in ScalaFX

The main layout of my ScalaFX 8 application consists of a BorderPane. The top attribute contains a menu whereas bottom contains something similar to a status bar. My goal is to display a component for viewing 3D objects in the center of the BorderPane (acting as a SubScene).
stage = new PrimaryStage {
scene = new Scene(900, 900, true, SceneAntialiasing.Balanced) {
root = new BorderPane {
top = createMenu // creates a menu inside of a VBox
center = createViewer // should create a subscene inside of whatever is needed
bottom = createStatusBar // creates a status bar inside of a VBox
}
}
}
I'm trying to create a minimal working example with a SubScene that only consists of black background and a simple sphere, no more, no less. The SubScene should use the whole space that is available in the center of BorderPane and resize accordingly. Unfortunately, I can't make it work.
Since the size of a SubScene is fixed, I assume it is necessary to embed the SubScene in another container (which is able to resize automatically) and bind the dimensions of the SubScene to the dimensions of the container around it.
def createViewer = {
val bp = new BorderPane
val subScene: SubScene = new SubScene(bp, 200, 200, true, SceneAntialiasing.Balanced) {
fill = Color.Black
width <== bp.width
height <== bp.height
content = new Sphere(3) { material = new PhongMaterial(Color.Red) }
camera = new PerspectiveCamera(true) { ... }
}
bp.center = subScene
subScene
}
The result looks like this:
Two obvious problems:
The SubScene keeps the fixed size from its constructor. In neither "maximizes" in the center of the outer BorderPane, nor does it do anything when the window gets resized
There is the red dot, but the bottom right corner of the SubScene is not black (?)
My assumption is that I have some problems understanding what the root element of the SubScene really is and what it does. I found another thread for JavaFX with a similar problem, this solution distinguishes between the root element of the SubScene (I'm not sure where that element comes from) and the Pane but I couldn't apply it to my case. Any help is appreciated. Thanks.
the idea here is to get the read only properties for the top level scene there is probably a more elegant way to do this, but this works
scene = new Scene(900, 900, true, SceneAntialiasing.Balanced) {
// these are read only properties for the scene
var tmpw = this. width
var tmph = this. height
root = new BorderPane {
top = new HBox {
content = new Label {
text = "menu"
}
}
center = createView(tmpw, tmph)
}
}
width onChange show
height onChange show
}
the idea here is to bind the read only properties to the properties of the subscene, then
the subscene will re-size there is probably a way to avoid the 'this' key-word.
I have tested this and the subscene re-sizes with the parent scene. I have omitted the PerspectiveCamera code block as you did not include what you were using
def createView(boundWidth : ReadOnlyDoubleProperty, boundHeight : ReadOnlyDoubleProperty): BorderPane = {
new BorderPane {
center = new SubScene(boundWidth.get(), boundHeight.get(), true, SceneAntialiasing.Balanced) {
fill = Color.BLACK
content = new Sphere(3) { material = new PhongMaterial(Color.RED) }
camera = new PerspectiveCamera(true)
// bind the subscene properties to the parents
this.width.bind(boundWidth.add(-200))
this.height.bind(boundHeight.add(-200))
}
}
}

What the easiest way to add a image to my GWT application

I have .jpg file that I want to display. I have some Horizontal and Vertical panels and I would like to have it somewhere in there. It is a fairly large image but I would like to make a class or an object that will scale it down for me.
My first thought was to just put it in a Horizontal Panel like so but that does not seem to work as I intended
HorizontalPanel picturePanel = new HorizontalPanel();
picturePanel.setPixelSize(600, 300);
picturePanel.addStyleName("pic");
Css.css
.pic
{
background: url(images/mypic.jpg);
height: auto;
width: auto;
}
I'd like to set the pixel size of an object (panel) and add an image to that panel so that it fits within the bounds (while making sure the ratio is the same as in the picture) so I can programatically add it to a panel somewhere.
public interface MyResources extends ClientBundle {
MyResources INSTANCE = GWT.create(MyResources.class);
#Source("logo.png")
ImageResource logo();
}
in your view class
Image logo = new Image(MyResources.INSTANCE.logo());
add image to panel;
set resolution to your panel and also set the same to your image by using
setPixelSize(int,int);
This works:
Image image = new Image();
Image.setWidth("100px");
image.setUrl('http://127.0.0.1:8888/images/accounts.png');
Loads image form local server and will limit size of image.