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

com.codename1.ui.scene.Node Maven / Gradle / Ivy

There is a newer version: 7.0.167
Show newest version
/*
 * Copyright (c) 2012, Codename One and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Codename One designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Codename One in the LICENSE file that accompanied this code.
 *  
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 * 
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 * 
 * Please contact Codename One through http://www.codenameone.com/ if you 
 * need additional information or have any questions.
 */
package com.codename1.ui.scene;

import com.codename1.properties.Property;
import com.codename1.ui.Container;
import com.codename1.ui.Graphics;
import com.codename1.ui.Image;

import com.codename1.ui.Transform;
import com.codename1.ui.geom.GeneralPath;
import com.codename1.ui.geom.Rectangle;
import com.codename1.ui.geom.Rectangle2D;
import com.codename1.ui.layouts.BorderLayout;
import com.codename1.ui.plaf.Border;
import com.codename1.ui.plaf.Style;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * Encapsulates a Node in the scene-graph.  This wraps a component (the "renderer" of the Node) and an associated transform
 * that describes where, in the 3D space of the scene graph the component should be rendered.
 * @author shannah
 * @deprecated For internal use only.
 */
public class Node {
    /**
     * The scene that the node is currently attached to.
     */
    private Scene scene;
    
    /**
     * The parent node.
     */
    private Node parent;
    
    /**
     * Child nodes.
     */
    private List children;
    
    /**
     * Tags that are attached to this node. These are like CSS classes.
     */
    private HashSet tags;
    
    /**
     * The component that should be rendered inside this node.
     */
    //private Component renderer;
    NodePainter renderer;
    
    private Style style;
    
    /**
     * Private wrapper around the renderer component.  This gives us
     * access to some package-private methods in Component that we need to use
     * for rendering.
     */
    //private RenderableContainer rendererCnt;
    
    /**
     * Flag of whether to render the component onto an image buffer before rendering.
     * Components that use 9-piece image borders may show lines between the border 
     * segments if renderered directly onto the scene graph. Setting this flag will
     * resolve that (because it will first render to an image with the identity transform
     * and then transform that image on the Graphics context).
     */
    private boolean renderAsImage=false;
    
    public final Property 
            /**
             * The scale to apply to the node, along the x-axis.
             */
            scaleX, 
            
            /**
             * The scale to apply to the node along the y-axis.
             */
            scaleY, 
            /**
             * The scale to apply to the node along the z-axis.
             */
            scaleZ, 
            
            /**
             * X-coordinate in the Scene-graph where node should be rendered.
             */
            layoutX, 
            
            /**
             * Y-coordinate in the scene graph where node should be rendered.
             */
            layoutY, 
            
            /**
             * Z-coordinate in the scene graph where node should be rendered.
             */
            layoutZ, 
            
            /**
             * X-translation to apply to the node.
             */
            translateX, 
            
            
            /**
             * Y-translation to apply to the node.
             */
            translateY, 
            
            /**
             * 
             * Z-translation to apply to the node.
             */
            translateZ, 
            
            /**
             * Rotation to apply to the node.  In degrees.  Node is always rotated around its center.
             */
            rotate, 
            
            /**
             * The depth of the coordinate at which the renderer should paint itself.
             * By default nodes will have no depth, and renderer will be painted onto 
             * a rectangle on the Z=0 plane.  If you need to perform 3D rotations, you may want to
             * give the node depth by setting the depth of {@link #boundsInLocal} and changing the
             * {@link #localCanvasZ} to something other than 0 since the node will be rotated at its 
             * center point.
             */
            localCanvasZ,
            
            opacity
            ;
    
    /**
     * The rotation axis around which rotations should be performed. (0, 0, 1) results in a rotation
     * around the z-axis, (1, 0, 0) results in a rotation around the x-axis, and (0, 1, 0) a rotation
     * around the y-axis.
     */
    public final Property rotationAxis;
    
    /**
     * The local bounds of the node (without any of the transforms applied to it).  
     */
    public final Property boundsInLocal;
    
    /**
     * Flag to indicate whether the node should be visible or not.
     */
    public final Property visible;
    
    /**
     * The painting rectangle, into which the renderer should be painted inside the 
     * node's local bounds.
     */
    public final Property paintingRect;
    
    
    /**
     * Flag to specify whether the node should have its children re-laid out 
     */
    private boolean needsLayout=true;
    
    
    public Node() {
        scaleX = new Property("scaleX", 1.0);
        scaleY = new Property("scaleY", 1.0);
        scaleZ = new Property("scaleZ", 1.0);
        layoutX = new Property("layoutX", 0.0);
        layoutY = new Property("layoutY", 0.0);
        layoutZ = new Property("layoutZ", 0.0);
        translateX = new Property("translateX", 0.0);
        translateY = new Property("translateY", 0.0);
        translateZ = new Property("translateZ", 0.0);
        rotate = new Property("rotate", 0.0);
        rotationAxis = new Property("rotationAxis", (Point3D)null);
        boundsInLocal = new Property("boundsInLocal", new Bounds(0, 0, 0, 0, 0, 0));
        localCanvasZ = new Property("localCanvasZ", 0.0);
        visible = new Property("visible", true);
        paintingRect = new Property("paintingRect", (Rectangle)null);
        opacity = new Property("opacity", 1.0);
        
    }
    
    /**
     * Sets the render as image flag.  True to render this Node as an image.
     * @param t 
     */
    public void setRenderAsImage(boolean t) {
        renderAsImage = t;
    }
    
    /**
     * Adds tags to this node.
     * @param tags 
     */
    public void addTags(String... tags) {
        if (this.tags == null) {
            this.tags = new HashSet();
        }
        for (String tag : tags) {
            this.tags.add(tag);
        }
    }
    
    public Rectangle2D getBoundsInScene(Rectangle2D out) {
        Transform t = getLocalToSceneTransform();
        Bounds localBounds = boundsInLocal.get();
        float[] pt = new float[]{(float)localBounds.getMinX(), (float)localBounds.getMinY(), 0};
        t.transformPoint(pt, pt);
        out.setX(pt[0]);
        out.setY(pt[1]);
        pt[0] = (float)(localBounds.getMinX() + localBounds.getWidth());
        pt[1] = (float)(localBounds.getMinY() + localBounds.getHeight());
        
        t.transformPoint(pt, pt);
        out.setWidth(pt[0] - out.getX());
        out.setHeight(pt[1] - out.getY());
        return out;
    }
    
    /**
     * Removes tags from this node.
     * @param tags 
     */
    public void removeTags(String... tags) {
        if (this.tags != null) {
            for (String tag : tags) {
                this.tags.remove(tag);
            }
        }
    }
    
    /**
     * Check if this node has a tag.
     * @param tag
     * @return 
     */
    public boolean hasTag(String tag) {
        return (tags != null && tags.contains(tag));
    }
    
    /**
     * Sets the scene that this node is attached to.
     * @param scene 
     */
    void setScene(Scene scene) {
        this.scene = scene;
        if (children != null) {
            for (Node child : children) {
                child.setScene(scene);
            }
        }
    }
    
    //public void setLocalToParentTransform(Transform t) {
    //    this.localToParentTransform = t.copy();
    //}
    
    private static void translate(Transform t, double tx, double ty, double tz) {
        t.translate((float)tx, (float)ty, (float)tz);
    }
    
    private static void rotate(Transform t, double angle, double px, double py, double pz) {
        t.rotate((float)angle, (float)px, (float)py, (float)pz);
    }
    
    private static void scale(Transform t, double x, double y, double z) {
        t.scale((float)x, (float)y, (float)z);
    }
    
    /**
     * Gets the transform to use to transform the Node into its parent node's space.
     * @return 
     */
    public Transform getLocalToParentTransform() {
        Transform t = Transform.makeIdentity();
        Point3D rotationAxis = this.rotationAxis.get();
        if (rotationAxis == null) {
            rotationAxis = new Point3D(0, 0, 1);
        }
        Bounds localBounds = boundsInLocal.get();
        
        
        
        // Do translation
        translate(t, layoutX.get() + translateX.get(), layoutY.get() + translateY.get(), layoutZ.get() + translateZ.get());
        
        // Do scale
        
        
        // Do rotation
        translate(t, localBounds.getWidth()/2, localBounds.getHeight()/2, localBounds.getDepth()/2);
        scale(t, scaleX.get(), scaleY.get(), scaleZ.get());
        rotate(t, rotate.get() * Math.PI/180.0, rotationAxis.getX(), rotationAxis.getY(), rotationAxis.getZ());
        translate(t, -localBounds.getWidth()/2, -localBounds.getHeight()/2, -localBounds.getDepth()/2);
        translate(t, 0, 0, localCanvasZ.get());
        return t;
        
    }
    
    /**
     * Gets the transform to use to go from the local coordinates to scene coordinates.
     * @return 
     */
    public Transform getLocalToSceneTransform() {
        if (parent == null) {
            return getLocalToParentTransform();
        } else {
            Transform t = parent.getLocalToSceneTransform();
            t.concatenate(getLocalToParentTransform());
            return t;
        }
    }

    /**
     * Gets the scene that this node is attached to.
     * @return the scene
     */
    public Scene getScene() {
        return scene;
    }

    /**
     * Returns true if this node needs to have its children re-laid out before rendering
     * @return the needsLayout
     */
    public boolean isNeedsLayout() {
        return needsLayout;
    }

    /**
     * @param needsLayout the needsLayout to set
     */
    public void setNeedsLayout(boolean needsLayout) {
        this.needsLayout = needsLayout;
    }

    /**
     * @return the style
     */
    public Style getStyle() {
        return style;
    }

    /**
     * @param style the style to set
     */
    public void setStyle(Style style) {
        this.style = style;
    }
    
    
    
    private class RenderableContainer extends Container {

        public RenderableContainer() {
            super(new BorderLayout(), "NodeWrapper");
            Style s = getAllStyles();
            s.setPadding(0, 0, 0, 0);
            s.setMargin(0, 0, 0, 0);
            s.setBgTransparency(0);
            s.setBorder(Border.createEmpty());
           
        }
        
        
        
        void render(Graphics g) {
            try {
                paintComponentBackground(g);
                paint(g);
                paintBorder(g);
                g.setColor(0xff0000);
                int alpha = g.concatenateAlpha(getStyle().getFgAlpha());
                g.drawRect(getX(), getY(), getWidth()-1, getHeight()-1);
                g.setAlpha(alpha);
            } catch (Throwable t) {
                
            }
        }
    }
       
    
    /**
     * Sets the component that should be used to render the node's contents.
     * @param comp 
     */
    public void setRenderer(NodePainter comp) {
        renderer = comp;
    }
    
    private Rectangle getPaintingRect() {
        if (paintingRect != null && paintingRect.get() != null) {
            return paintingRect.get();
        } else {
            return new Rectangle(0, 0, (int)boundsInLocal.get().getWidth(), (int)boundsInLocal.get().getHeight());
        }
    }
    
    /**
     * This can be used to hit test pointer events against this node.  It checks whether
     * a given absolute (x, y) coordinate hits the node.
     * @param x
     * @param y
     * @return 
     */
    public boolean contains(int x, int y) {
        GeneralPath p = GeneralPath.createFromPool();
        try {
            p.setRect(getPaintingRect(), getLocalToScreenTransform());
            return p.contains(x, y);
        } finally {
            GeneralPath.recycle(p);
        }
    }
    
    private void findNodesWithTag(Set out, String tag) {
        if (hasTag(tag)) {
            out.add(this);
        }
        for (Node child : getChildNodes()) {
            child.findNodesWithTag(out, tag);
        }
    }
    
    public Collection findNodesWithTag(String tag) {
        Set out = new HashSet();
        findNodesWithTag(out, tag);
        return out;
    }
    
    public Transform getLocalToScreenTransform() {
        Transform newT = Transform.isPerspectiveSupported() && scene.camera.get() != null ? 
                scene.camera.get().getTransform() : Transform.makeIdentity();
        newT.translate(getScene().getAbsoluteX(), getScene().getAbsoluteY());
        newT.concatenate(getLocalToSceneTransform());
        return newT;
    }
    
    /**
     * Renders the node onto a graphics context.
     * @param g 
     */
    public void render(Graphics g) {
        if (!visible.get()) {
            return;
        }
        Transform existingT = Transform.makeIdentity();
        g.getTransform(existingT);
        
        Transform newT = Transform.isPerspectiveSupported() && scene.camera.get() != null ? 
                scene.camera.get().getTransform() :
                Transform.makeIdentity();
        newT.translate(getScene().getAbsoluteX(), getScene().getAbsoluteY());
        newT.concatenate(getLocalToSceneTransform());
        newT.translate(-scene.getAbsoluteX(), -scene.getAbsoluteY());
        g.setTransform(newT);
        int alpha = g.getAlpha();
        int nodeAlpha = (int)Math.round(alpha * opacity.get());
        nodeAlpha = Math.max(Math.min(255, nodeAlpha), 0);
        g.setAlpha(nodeAlpha);
        try {
            
            if (renderer != null) {
                Rectangle paintingRect = getPaintingRect();
                //System.out.println("Rendering on paint rect "+paintingRect);
                //System.out.println("Renderer: "+renderer);
                //rendererCnt.setX(paintingRect.getX());
                //rendererCnt.setY(paintingRect.getY());
                //rendererCnt.setWidth(Math.max(0, paintingRect.getWidth()));
                //rendererCnt.setHeight(Math.max(paintingRect.getHeight(), 0));
                //rendererCnt.layoutContainer();
                
                
                if (renderAsImage) {
                    
                    Image im = Image.createImage(paintingRect.getWidth(), paintingRect.getHeight(), 0);
                    Graphics ig = im.getGraphics();
                    renderer.paint(ig, new Rectangle(0, 0, paintingRect.getWidth(), paintingRect.getHeight()), this);
                    
                    g.drawImage(im, paintingRect.getX(), paintingRect.getY());
                    
                } else {
                    //rendererCnt.render(g);
                    renderer.paint(g, paintingRect, this);
                }
                
            }
            layoutChildrenInternal();
            renderChildren(g);
        } finally {
            g.setAlpha(alpha);
            g.setTransform(existingT);
        }
        
    }
    
    /**
     * Renders the node's children.
     * @param g 
     */
    public void renderChildren(Graphics g) {
        if (children != null) {
            for (Node child : children) {
                child.render(g);
            }
        }
    }
    
    /**
     * Gets the renderer component for this node.
     * @return 
     */
    public NodePainter getRenderer() {
        return renderer;
    }
    
    /**
     * Adds a child node.
     * @param child 
     */
    public void add(Node child) {
        if (children == null) {
            children = new ArrayList();
        }
        children.add(child);
        child.parent = this;
        child.setScene(getScene());
    }
    
    /**
     * Removes a child node.
     * @param child 
     */
    public void remove(Node child) {
        if (child.parent != this) {
            return;
        }
        if (children != null) {
            children.remove(child);
            child.parent = null;
            child.setScene(null);
        }
    }
    
    private void layoutChildrenInternal() {
        if (isNeedsLayout()) {
            setNeedsLayout(false);
            layoutChildren();
        }
    }
    
    /**
     * Can be overridden by subclasses to layout children.  Called before node is rendered.
     */
    protected void layoutChildren() {
        
    }
    
    /**
     * Gets the child nodes of this node.
     * @return 
     */
    public Iterable getChildNodes() {
        return children;
    }
    
    /**
     * Gets number of children in this node.
     * @return 
     */
    public int getChildCount() {
        if (children == null) {
            return 0;
        }
        return children.size();
    }
    
    /**
     * Gets child node at index.
     * @param index
     * @return 
     */
    public Node getChildAt(int index) {
        if (children == null) {
            return null;
        }
        return children.get(index);
    }
    
    /**
     * Checks if node has children.
     * @return 
     */
    public boolean hasChildren() {
        return children != null && !children.isEmpty();
    
    }
    
    
    /**
     * Removes all child nodes.
     */
    public void removeAll() {
        if (children != null) {
            children.clear();
        }
    }
}
    




© 2015 - 2025 Weber Informatics LLC | Privacy Policy