Java tutorial
/** * SceneJS Vaadin Addon * Copyright (C) 2012 Federico Russo <chiccorusso@gmail.com> * Licensed under the MIT license. * * Includes SceneJS WebGL Scene Graph Library for JavaScript http://scenejs.org/ * Dual licensed under the MIT or GPL Version 2 licenses. http://scenejs.org/license * Copyright 2010, Lindsay Kay */ package org.vaadin.scenejs; import com.vaadin.terminal.PaintException; import com.vaadin.terminal.PaintTarget; import com.vaadin.ui.AbstractComponent; import com.vaadin.ui.ClientWidget; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.collections.MultiHashMap; import org.apache.commons.collections.MultiMap; import org.vaadin.scenejs.client.ui.VSceneJSCanvas; /** * The SceneJS canvas is a WebGL enabled component. It should work on every modern web browser * that supports WebGL. The programming paradigm of this Canvas is as similar as possible to * SceneJS', and most of its funcionalities are provided by its hierarchical node structure. * * In SceneJS, all the scene is composed by a hierarchical structure of nodes; each node has a type, * some properties and an array of children. All SceneJS node types will be wrapped in Java objects * that the addon will synchronize with their Javascript representations in the client. * * So, you can create a tree of nodes and put it in the Canvas. Scope of the Addon is to create such * trees server side in Java in the same way you'd create it in SceneJS on the client side in * Javascript. * * Server side component for the VSceneJSCanvas widget. * * @author Federico Russo */ @ClientWidget(VSceneJSCanvas.class) public class SceneJSCanvas extends AbstractComponent { private static final long serialVersionUID = 1L; private boolean stopping = false; //<editor-fold defaultstate="collapsed" desc="Constructors"> /** * Default constructor. The Canvas is always immediate. */ public SceneJSCanvas() { setImmediate(true); } //</editor-fold> //<editor-fold defaultstate="collapsed" desc="Nodes"> /** * Adds a node to the current scene. The scene can have many child nodes. * @param node Node to be added to the scene. * @return The newly added node. */ public SceneJSNode addNode(SceneJSNode node) { node.attach(this); addNewNode(node); requestRepaint(); return node; } /** * An alias for {@code addNode()}. */ public SceneJSNode a(SceneJSNode node) { return addNode(node); } /** * Adds a node to the current scene. The scene can have many child nodes. * @param node Node to be added to the scene. * @return The canvas itself. */ public SceneJSCanvas addNodeP(SceneJSNode node) { addNode(node); return this; } /** * An alias for {@code addNodeP()}. */ public SceneJSCanvas p(SceneJSNode node) { return addNodeP(node); } /** * This list contains the newly added nodes that have yet no representation in the client side. * This list is flushed by {@code paintContent()}. * When you add to the scene new nodes that already have children, the children are not added * to this list. * Strictly speaking, this is a new subtrees list. */ private final List<SceneJSNode> newNodes = new LinkedList<>(); /** * Adds a node to the list of the new nodes. * @param newNode New node that is being added. */ protected void addNewNode(SceneJSNode newNode) { newNodes.add(newNode); } //</editor-fold> //<editor-fold defaultstate="collapsed" desc="Heigth and Width"> @Override public void setHeight(String height) { super.setHeight(height); setDirty("height"); } @Override @Deprecated public void setHeight(float height) { super.setHeight(height); setDirty("height"); } @Override public void setHeight(float height, int unit) { super.setHeight(height, unit); setDirty("height"); } @Override public void setWidth(String width) { super.setWidth(width); setDirty("width"); } @Override @Deprecated public void setWidth(float width) { super.setWidth(width); setDirty("width"); } @Override public void setWidth(float width, int unit) { super.setWidth(width, unit); setDirty("width"); } //</editor-fold> //<editor-fold defaultstate="collapsed" desc="Dirty fields and partialPaint()"> private final DirtySupport dirty = new DirtySupport(null); private void setDirty(String fieldName) { dirty.set(fieldName); } private void setNotDirty(String fieldName) { dirty.remove(fieldName); } private boolean isDirty(String fieldName) { return dirty.is(fieldName); } //</editor-fold> //<editor-fold defaultstate="collapsed" desc="Movement type"> /** * Kind of possible movements. There is currently no possibility to move thew viewpoint. */ public static enum MovementType { /** Rotate the viewpoint around itself. */ AROUND, /** Rotate the model around itself. */ MODEL, /** Rotate the model * around itself, but constrain the rotation axis. */ CONSTRAINED } private MovementType movementType = null; /** * Sets the movement type. * @param movementType */ public void setMovementType(MovementType movementType) { this.movementType = movementType; setDirty("movement"); } //</editor-fold> //<editor-fold defaultstate="collapsed" desc="Vaadin methods"> /** * Paints the modified properties to the client. * @param target * @throws PaintException */ @Override public void paintContent(PaintTarget target) throws PaintException { super.paintContent(target); if (stopping) { // If we are stopping we send the stop variable and nothing else. stopping = false; target.addAttribute("stop", true); return; } // Height and width variables if (isDirty("height")) { target.addAttribute("height", "" + getHeight()); setNotDirty("height"); } if (isDirty("width")) { target.addAttribute("width", "" + getWidth()); setNotDirty("width"); } // Change in movement strategy. if (null != movementType && isDirty("movement")) { target.addAttribute("movement", movementType.name()); setNotDirty("movement"); } // New nodes to add. // The "newNodes" attribute is built as a concatenation of parent node id and node json. if (!newNodes.isEmpty()) { StringBuilder newNodesJson = new StringBuilder(); for (SceneJSNode newNode : newNodes) { // Each of these nodes was expressely added to the new node list. if (!currentNodes.containsKey(newNode.id)) { SceneJSNode parent = newNode.getParent(); String parentId = null == parent ? "" : parent.getId(); newNodesJson.append(parentId).append(";").append(newNode.getJson()).append(";"); addCurrentNode(newNode); newNode.dirty.clear(); } } target.addAttribute("newNodes", newNodesJson.toString()); newNodes.clear(); } // New PickListeners to attach. if (!newPickListeners.isEmpty()) { StringBuilder pickListenersJson = new StringBuilder(); for (Map.Entry<String, Set<PickListener>> entry : newPickListeners.entrySet()) { String nodeName = entry.getKey(); Set<PickListener> listeners = entry.getValue(); for (PickListener listener : listeners) { currentPickListeners.put(nodeName, listener); pickListenersJson.append(nodeName).append(";"); } } target.addAttribute("newPickListeners", pickListenersJson.toString()); newPickListeners.clear(); } // Nodes to be updated. if (!dirtyNodes.isEmpty()) { StringBuilder dirtyNodesJson = new StringBuilder(); for (SceneJSNode dirtyNode : dirtyNodes) { dirtyNodesJson.append(dirtyNode.getId()).append(";").append(dirtyNode.getShallowJson()).append(";"); dirtyNode.dirty.clear(); } target.addAttribute("dirtyNodes", dirtyNodesJson.toString()); dirtyNodes.clear(); } } /** * Receives variable changes from the client side. * @param source * @param variables */ @Override public void changeVariables(Object source, Map<String, Object> variables) { super.changeVariables(source, variables); for (Map.Entry<String, Object> entry : variables.entrySet()) { String variableName = entry.getKey(); Object variableValue = entry.getValue(); if (null != variableName) { switch (variableName) { case "pick": // We received a pick. The client is sending us the name af the Name node that was // picked. The name property of the Name node is equal to its ID. String nodeName = (String) variableValue; Collection<PickListener> listeners = (Collection<PickListener>) currentPickListeners .get(nodeName); if (null != listeners) { Name pickedNode = (Name) currentNodes.get(nodeName); for (PickListener listener : listeners) { listener.onPick(pickedNode); } } break; case "nopick": fireNothingPickedEvent(); break; } } } } //</editor-fold> private final Map<String, SceneJSNode> currentNodes = new HashMap<>(16); /** * Map of the pickListeners to add to the client mode. These PickListeners are extracted from * the new nodes when they are sent to the client and are added to the currentPickListeners map. * This map is keyed on the Name nodes name (that is also the node ID). */ private final Map<String, Set<PickListener>> newPickListeners = new HashMap<>(16); private final MultiMap currentPickListeners = new MultiHashMap(); /** * Recursive method that adds each node of a subtree to the currentNodes map. * @param node New node to add to the map. */ private void addCurrentNode(SceneJSNode node) { currentNodes.put(node.getId(), node); // If present, we add the pickListeners if (node instanceof Name) { Name name = (Name) node; Set<PickListener> pickListeners = name.getPickListeners(); if (!pickListeners.isEmpty()) { newPickListeners.put(name.getId(), pickListeners); } } for (SceneJSNode child : node.getChildren()) { addCurrentNode(child); } } public void stop() { stopping = true; requestRepaint(); } private final Collection<SceneJSNode> dirtyNodes = new HashSet<>(16); /** * Adds the specified node to the set of dirty nodes. This method should be invoked by each node * that knows it's becoming dirty. * @param node */ protected void addDirtyNode(SceneJSNode node) { dirtyNodes.add(node); } //<editor-fold defaultstate="collapsed" desc="Pick Listeners"> /** * Interface of the listener of the event of the pick on a Name node. */ public static interface PickListener { void onPick(Name sender); } /** * Interface of the listener of the event of the pick outside anything. */ public static interface NothingPickedListener { void nothingPicked(); } private final Collection<NothingPickedListener> nothingPickedListeners = new HashSet<>(16); public void addNothingPickedListener(NothingPickedListener listener) { nothingPickedListeners.add(listener); } public void removeNothingPickedListener(NothingPickedListener listener) { nothingPickedListeners.remove(listener); } protected void fireNothingPickedEvent() { for (NothingPickedListener listener : nothingPickedListeners) { listener.nothingPicked(); } } //</editor-fold> }