/** * Copy dirty layer references to array for further painting. The copying is needed to not keep * lock on layers list when layers painting will happen. Dirty states of the layers are cleaned * after the copying. Layers painting can change layers state again, but it will be served on next * repaint only. */ private void copyAndCleanDirtyLayers() { if (CGraphicsQ.DEBUG) { System.err.println("[Copy dirty layers]"); } CLayer l; dirtyCount = 0; int layersCount = layers.size(); // Heuristics to increase array for copied dirty layers if (layersCount > dirtyMaxCount) { dirtyMaxCount += layersCount; dirtyLayers = new CLayer[dirtyMaxCount]; } // Copy dirty layer references and reset dirty layer states for (CLayerElement le = layers.getBottom(); le != null; le = le.getUpper()) { l = le.getLayer(); if (l.visible && l.isDirty()) { l.copyAndCleanDirtyState(); dirtyLayers[dirtyCount++] = l; } else { // !(visible && dirty) if (CGraphicsQ.DEBUG) { System.err.println("Skip Layer: " + l); } } // if } // for }
/** * First Pass: We do sweep and mark of all layers requiring a repaint, the areas behind a visible * opaque layers need no repaint */ private void sweepAndMarkLayers() { if (CGraphicsQ.DEBUG) { System.err.println("[Sweep and mark layers]"); } CLayer l; CLayerElement changed; CLayerElement le = layers.getTop(); while (le != null) { l = le.getLayer(); if (l.visible && l.opaque) { cleanLowerDirtyRegions(le); } // The dirty layer can be invisible, that means it // has been hidden since the previous paint. if (l.isDirty()) { // In the case higher layer was changed we need to // restart all the algorithm from the changed layer changed = sweepAndMarkDirtyLayer(le, !l.visible); if (changed != null) { if (CGraphicsQ.DEBUG) { System.err.println("Restart sweep and mark: " + changed.getLayer()); } le = changed; changed = null; continue; } } // Go to next lower layer le = le.getLower(); } }
/** * Sets all visible layers to dirty state. The method is needed on system events like screen * rotation, when generic layers system is not capabel to properly analyze layers changes, e.g. of * move/resize kind. It could be fixed in the future and this method will be out of use. */ public void setAllDirty() { synchronized (layers) { CLayer l; for (CLayerElement le = layers.getBottom(); le != null; le = le.getUpper()) { l = le.getLayer(); if (l.visible) { l.addDirtyRegion(); } // if } // for } // synchronized }
/** * Handle input from some type of device-dependent input method. This could be input from * something such as T9, or a phonebook lookup, etc. * * @param str the text to handle as direct input * @return true if this window or one of its layers processed the event */ public boolean methodInput(String str) { CLayer layer; synchronized (layers) { for (CLayerElement le = layers.getTop(); le != null; le = le.getLower()) { layer = le.getLayer(); if (layer.visible && layer.supportsInput && layer.methodInput(str)) { return true; } } } // sync return false; }
/** * Allow this window to process key input. The type of key input will be press, release, repeat, * etc. The key code will identify which key generated the event. This method will return true if * the event was processed by this window or one of its layers, false otherwise. * * @param type the type of key event (press, release, repeat) * @param keyCode the identifier of the key which generated the event * @return true if this window or one of its layers processed the event */ public boolean keyInput(int type, int keyCode) { CLayer layer; synchronized (layers) { for (CLayerElement le = layers.getTop(); le != null; le = le.getLower()) { layer = le.getLayer(); if (layer.supportsInput && layer.keyInput(type, keyCode)) { return true; } } } // sync return false; }
/** * Subtract this layer area from an underlying dirty regions. The method is designed to reduce * dirty regions of a layres below the opaque visible layer. * * @param le layer list element */ private void cleanLowerDirtyRegions(CLayerElement le) { if (CGraphicsQ.DEBUG) { System.err.println("Clean dirty regions under opaque layer: " + le.getLayer()); } CLayer l = le.getLayer(); for (CLayerElement le2 = le.getLower(); le2 != null; le2 = le2.getLower()) { CLayer l2 = le2.getLayer(); if (l2.isDirty()) { l2.subDirtyRegion( l.bounds[X] - l2.bounds[X], l.bounds[Y] - l2.bounds[Y], l.bounds[W], l.bounds[H]); } } }
/** * Check whether layer is overlapped with a higher visible layer in the layer stack of the window * * @param l layer to check overlapping * @return true if overlapped, false otherwise */ public boolean isOverlapped(CLayer l) { synchronized (layers) { CLayerElement le = layers.find(l); if (le != null) { CLayer l2; for (le = le.getUpper(); le != null; le = le.getUpper()) { l2 = le.getLayer(); if (l2.isVisible() && l.intersects(l2)) { return true; } } } } return false; }
/** * Remove a layer from this CWindow. This method will remove the given layer from the "deck" of * layers associated with this CWindow. If successfull, this method will return true, false * otherwise (for example, if the layer does not belong to this window). * * @param layer the layer to remove from this window * @return true if successful, false otherwise */ public boolean removeLayer(CLayer layer) { synchronized (layers) { CLayerElement le = sweepLayer(layer); if (le != null) { if (CGraphicsQ.DEBUG) { System.err.println("Remove Layer: " + layer); } layer.owner = null; requestRepaint(); layers.removeLayerElement(le); layer.removeNotify(this); return true; } } return false; }
/** * Allow this window to process pointer input. The type of pointer input will be press, release, * drag, etc. The x and y coordinates will identify the point at which the pointer event occurred * in the coordinate system of this window. This window will translate the coordinates * appropriately for each layer contained in this window. This method will return true if the * event was processed by this window or one of its layers, false otherwise. * * @param type the type of pointer event (press, release, drag) * @param x the x coordinate of the location of the event * @param y the y coordinate of the location of the event * @return true if this window or one of its layers processed the event */ public boolean pointerInput(int type, int x, int y) { CLayer layer; synchronized (layers) { for (CLayerElement le = layers.getTop(); le != null; le = le.getLower()) { layer = le.getLayer(); if (layer.visible && layer.supportsInput && layer.handlePoint(x, y)) { // If the layer is visible, supports input, and // contains the point of the pointer press, we translate // the point into the layer's coordinate space and // pass on the input if (layer.pointerInput(type, x - layer.bounds[X], y - layer.bounds[Y])) { return true; } } } } // sync return false; }
/** * Add a new CLayer to the "deck" of layers associated with this CWindow. This method will * sequentially add layers to the window, placing subsequently added layers on top of previously * added layers. * * @param layer the new layer to add to this window * @return true if new layer was added, false otherwise */ public boolean addLayer(CLayer layer) { if (layer != null) { if (CGraphicsQ.DEBUG) { System.err.println("Add Layer: " + layer); } synchronized (layers) { if (layers.find(layer) == null) { layer.owner = this; layers.addLayer(layer); layer.addDirtyRegion(); requestRepaint(); layer.addNotify(); return true; } } } return false; }
/** * Second Pass: We sweep through the layers from the bottom to the top and paint each one that is * marked as dirty * * <p>Note, that the painting for copied layers is done here to not hold the layers lock during * the painting. * * @param g The graphics object to use to paint this window. * @param refreshQ The custom queue which holds the set of refresh regions needing to be blitted * to the screen */ private void paintLayers(Graphics g, CGraphicsQ refreshQ) { if (CGraphicsQ.DEBUG) { System.err.println("[Paint dirty layers]"); } for (int i = 0; i < dirtyCount; i++) { CLayer l = dirtyLayers[i]; // Prepare relative dirty region coordinates // of the current layer int dx = l.dirtyBoundsCopy[X]; int dy = l.dirtyBoundsCopy[Y]; int dw = l.dirtyBoundsCopy[W]; int dh = l.dirtyBoundsCopy[H]; // Before we call into the layer to paint, we // translate the graphics context into the layer's // coordinate space g.translate(l.boundsCopy[X], l.boundsCopy[Y]); if (CGraphicsQ.DEBUG) { System.err.println("Painting Layer: " + l); System.err.println("\tClip: " + dx + ", " + dy + ", " + dw + ", " + dh); } // Clip the graphics to only contain the dirty region of // the layer (if the dirty region isn't set, clip to the // whole layer contents). g.clipRect(dx, dy, dw, dh); refreshQ.queueRefresh(l.boundsCopy[X] + dx, l.boundsCopy[Y] + dy, dw, dh); l.paint(g); // We restore our graphics context to prepare // for the next layer g.translate(-g.getTranslateX(), -g.getTranslateY()); g.translate(tranX, tranY); // We reset our clip to this window's bounds again. g.setClip(bounds[X], bounds[Y], bounds[W], bounds[H]); g.setFont(font); g.setColor(color); } // for }
/** * Move layer to anotger location * * @param newBounds new bounds for this layer * @param x New 'x' coordinate of the layer's origin * @param y New 'y' coordinate of the layer's origin * @param w New width of the layer * @param h New height of the layer * @return true if successful, false otherwise */ public boolean relocateLayer(CLayer layer, int x, int y, int w, int h) { if (layer != null) { synchronized (layers) { if (sweepLayer(layer) != null) { if (CGraphicsQ.DEBUG) { System.err.println("Relocate Layer: " + layer); } int[] oldBounds = {layer.bounds[X], layer.bounds[Y], layer.bounds[W], layer.bounds[H]}; if (oldBounds[X] != x || oldBounds[Y] != y || oldBounds[W] != w || oldBounds[H] != h) { layer.setBounds(x, y, w, h); layer.addDirtyRegion(); requestRepaint(); layer.relocateNotify(oldBounds); return true; } } } } return false; }
/** * Propagate dirty region of the layer to other layers in the stack. The method should be called * for dirty layers only. The dirty layer can be invisible in the case it has been hidden since * the previous paint. * * <p>IMPL_NOTE: The layer been removed or set to invisible state since the previous paint is * considered as "hidden", thus it should be entirely dirty and must affect other visible layers * accordingly. * * @param le dirty layer element to be propagated to other layers * @param hidden indicates whether the dirty layer has been hidden since the previous repaint * @return the highest layer element above le with modified dirty region, or null if none */ private CLayerElement sweepAndMarkDirtyLayer(CLayerElement le, boolean hidden) { if (CGraphicsQ.DEBUG) { System.err.println("Sweep and mark dirty layer: " + le.getLayer()); } CLayer l2; CLayerElement res = null; CLayer l = le.getLayer(); // Prepare absolute dirty region coordinates of layer l int dx = l.bounds[X]; int dy = l.bounds[Y]; int dh, dw; if (l.isEmptyDirtyRegions()) { dw = l.bounds[W]; dh = l.bounds[H]; } else { dx += l.dirtyBounds[X]; dy += l.dirtyBounds[Y]; dw = l.dirtyBounds[W]; dh = l.dirtyBounds[H]; } // Sweep dirty region to upper layers for (CLayerElement le2 = le.getUpper(); le2 != null; le2 = le2.getUpper()) { l2 = le2.getLayer(); if (l2.visible) { if (l2.addDirtyRegion(dx - l2.bounds[X], dy - l2.bounds[Y], dw, dh)) { // Remember the highest changed layer res = le2; } } } // Sweep non-opaque dirty region to undelying layers if (!l.opaque || hidden) { for (CLayerElement le2 = le.getLower(); le2 != null; le2 = le2.getLower()) { l2 = le2.getLayer(); if (l2.visible) { l2.addDirtyRegion(dx - l2.bounds[X], dy - l2.bounds[Y], dw, dh); } } // A newly hidden layer should be dirty only for the first // succeeded paint, it should be cleaned as soon as underlying // layers are properly marked as dirty. if (hidden) { l.cleanDirty(); } } return res; }
/** * Update dirty regions of all visible layers in the stack regarding the entire area of the given * layer as being dirty. The method is needed to perform layer move/resize/remove opertion, since * other layers should be informed of changed area. * * @param layer the layer whose area should be reported as dirty to other stack layers * @return element of the window layers list that contains swept layer, it can be used for further * layer processing */ CLayerElement sweepLayer(CLayer layer) { if (layer != null) { if (CGraphicsQ.DEBUG) { System.err.println("Sweep Layer: " + layer); } synchronized (layers) { CLayerElement le = layers.find(layer); if (le != null) { // IMPL NOTE: when a layer gets removed (or has its setVisible(false)) // called, the parent window must loop through all the other // layers and mark them as dirty if they intersect with the // layer being removed (or having its visibility changed). layer.addDirtyRegion(); sweepAndMarkDirtyLayer(le, true); return le; } } } return null; }