All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jfxtras.scene.layout.responsivepane.ResponsivePane Maven / Gradle / Ivy

The newest version!
/**
 * Copyright (c) 2011-2021, JFXtras
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *    Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *    Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *    Neither the name of the organization nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL JFXTRAS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package jfxtras.scene.layout.responsivepane;

import java.util.List;
import java.util.TreeMap;

import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.ObservableMap;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.stage.Screen;
import javafx.stage.Window;

/**
 * = ResponsivePane
 * 
 * This layout chooses the best fitting layout and stylesheet for a given stage size.
 * 
 * ResponsivePane is loosely based on responsive design as advocated by website designs, and implemented in for example the Twitter Bootstrap project.
 * But there is a twist in the logic; responsive design assumes a given width and unlimited vertical space with the use of a scrollbar.
 * For websites this is ok, but applications do not have unlimited vertical space. 
 * Take for example a webbrowser; it contains a webview with a scrollbar, but it does not run inside a scrollpane itself.
 * So for applications there is no such thing as unlimited vertical space!
 * It has to use scalable controls, like lists, tables ort webview, to dynamically fill its horizontal and vertical space, just like we are used to in Swing and JavaFX for ages.
 * But given large screen size differences, from 4 inch phones to 32 inch desktops, it is ridiculous to think that a single layout can adapt to all sizes: on a phone you may choose to use a TabbedPane with less controls visible, and on desktop you go all out with a MigPane and a number of gimmicks.   
 * ResponsiveLayout allows you to define reusable nodes and have specific screen size related layouts, using the reusable nodes.
 * It will then use what layout fits best.  
 * 
 * A typical use of ResponsivePane would look like this:
 * [source,java]
 * --
    @Override
    public void start(Stage primaryStage) throws Exception {
    
        // create pane with reusable nodes
        ResponsivePane lResponsivePane = new ResponsivePane();
        lResponsivePane.addReusableNode("CalendarPicker", new CalendarPicker());
        lResponsivePane.addReusableNode("TreeView", new TreeView());
        lResponsivePane.addReusableNode("TableView", new TableView());
        lResponsivePane.addReusableNode("save", new Button("save"));
        lResponsivePane.addReusableNode("saveAndTomorrow", new Button("saveAndTomorrow"));
        lResponsivePane.addReusableNode("-", new Button("-"));
        lResponsivePane.addReusableNode("+", new Button("+"));
        lResponsivePane.addReusableNode("Logo", new Button("Logo"));
        lResponsivePane.addReusableNode("version", new Label("v1.0"));

        // define custom device
        lResponsivePane.setDeviceSize("PHABLET", Diagonal.inch(9.0));

        // define layouts
        lResponsivePane.addLayout(Diagonal.inch(3.5), createPhoneLayout());
        lResponsivePane.addLayout(Diagonal.inch(12.0), createDesktopLayout());
                
        // define css
        lResponsivePane.addSceneStylesheet(Diagonal.inch(4.0), getClass().getResource("phone.css").toExternalForm());
        lResponsivePane.addSceneStylesheet(Diagonal.inch(6.0), getClass().getResource("tablet.css").toExternalForm());
        lResponsivePane.addSceneStylesheet(Diagonal.inch(12.0), getClass().getResource("desktop.css").toExternalForm());
    }
    
    private Node createDesktopLayout() {
        MigPane migPane = new MigPane();
        migPane.add(new Ref("Logo"), new CC());
        migPane.add(new Ref("version"), new CC().spanX(2).alignX("right").alignY("top").wrap());
        migPane.add(new Ref("CalendarPicker"), new CC().alignY("top"));
        migPane.add(new Ref("TreeView"), new CC().grow().pushX().spanY(3).wrap());
        ...
        
        return migPane;
    }
    
    private Node createPhoneLayout() {
        TabPane tabPane = new TabPane();
        tabPane.getTabs().add(createTab("Date", "Calendar", new Ref("CalendarPicker")));
        tabPane.getTabs().add(createTab("Tree", "Projectboom", new Ref("TreeView")));
        tabPane.getTabs().add(createTab("Calc", "Totalen", new Ref("TreeView")));
        ... 
           
        return tabPane;
    }

 * --
 * 
 * The exact implementation of createPhoneLayout() and createDesktopLayout() is not relevant, except that they use "Ref" nodes to include the reusable nodes.
 *  
 * == Reusable nodes:
 * Reusable nodes are identified by their id, this is done for ease of use in FXML. 
 * They can then be used in the different layouts via a "new Ref()". 
 * Be aware that you cannot reuse Refs, they are place holders and each layout needs a Ref instance on its own. 
 * 
 * 
 * == Stylesheets:
 * Like layouts it is also possible to activate stylesheets based on the size of the stage. 
 * A stylesheet can be assigned to the scene (applicable for everything on the scene) or to the layout (applicable for the underlying node).
 * Responsive design assigned classes to the nodes, based on the size. 
 * Test have shown that this approach works ok when running the application on a desktop computer, with enough CPU power (GUI Garage's implementation does this), but on mobile these changes have a sever performance impact.
 * So ResponsivePane does not assign classes, but instead it replaces whole CSS files; for the CSS engine this is a single change and it needs to rerender only once.
 * In those CSS files you can assign behavior to the CSS classes, or not.
 * 
 * [source,java]
 * --
		// scene stylesheet
		responsivePane.addSceneStylesheet(Diagonal.inch(1.0), getClass().getResource("phone.css").toExternalForm());
			
		// pane stylesheet
		responsivePane.addMyStylesheet(Diagonal.inch(3.0), getClass().getResource("tablet.css").toExternalForm());
 * --
 *
 * There can only be one scene or layout stylesheet active at a given time.
 * 
 * == Size:
 * Determining the size is an interesting topic. 
 * Responsive design only looks at the width in pixel, but it is already explained that ResponsivePane does not assume unlimited vertical space, so the height must also be taking into account.
 * Therefore size is expressed as a diagonal instead of the width.
 * However, responsive design specifies the width in pixels, but 1000 pixels on a 100 ppi screen is something completely different on a 350 ppi screen; the first is 10 inches in real life, the second not even 3 inch!
 * ResponsivePane therefore (per default) expresses the size in inches (or cm), as we are used to do when talking about devices; a 4 inch phone, a 27 inch monitor.
 * But as a compatibility behavior it also is possible to the width; it will be at runtime converted to diagonal using the actual height of the layout. 
 * 
 * There also is a third option to set the size; ResponsivePane has some default diagonals for typical devices in the Device class, but you can also defined custom devices.
 * 
 * So sizes for layout and stylesheets can be defined like:
 * [source,java]
 * --
        responsivePane.addLayout(Diagonal.inch(3.5), ...);
        responsivePane.addLayout(Width.inch(3.0), ...);
        responsivePane.addLayout(Device.PHONE, ...);
        
        responsivePane.setDeviceSize("PHABLET", Diagonal.inch(9.0));
        responsivePane.addLayout(DeviceSize.of("PHABLET"), ...);

 * --
 * 
 * == FXML
 * ResponsivePane can of course also be used from FXML. 
 * Interesting is the how the width-based size is specified.
 *  
 * [source,xml]
 * --
	
	
	
	
	
	
	
	
	
	
	
	
		
			




© 2015 - 2024 Weber Informatics LLC | Privacy Policy