Package squidpony.squidgrid.gui

Source Code of squidpony.squidgrid.gui.SwingPane

package squidpony.squidgrid.gui;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.LinkedList;
import java.util.concurrent.ConcurrentLinkedQueue;
import javax.swing.JLayeredPane;
import squidpony.squidcolor.SColor;
import squidpony.squidgrid.gui.animation.Animation;
import squidpony.squidgrid.gui.animation.AnimationManager;
import squidpony.squidgrid.gui.animation.BumpAnimation;
import squidpony.squidgrid.gui.animation.SlideAnimation;
import squidpony.squidgrid.gui.animation.WiggleAnimation;
import squidpony.squidgrid.util.DirectionIntercardinal;

/**
* Displays text and images in a grid pattern. Supports basic animations.
*
* When text is placed, the background color is set separately from the foreground character. When moved, only the
* foreground character is moved.
*
* @author Eben Howard - http://squidpony.com - howard@squidpony.com
*/
public class SwingPane extends JLayeredPane {

    private static int DEFAULT_ANIMATION_DURATION = 2000;
    private static Font DEFAULT_FONT = new Font("Helvetica", Font.PLAIN, 22);
    private AnimationManager animationManager;
    private final ConcurrentLinkedQueue<Animation> animations = new ConcurrentLinkedQueue<>();
    private BufferedImage[][] contents;
    private boolean[][] imageChanged;
    private BufferedImage contentsImage = new BufferedImage(20, 20, BufferedImage.TYPE_4BYTE_ABGR);
    private Color defaultForeground = SColor.WHITE;
    private final int gridWidth, gridHeight, cellWidth, cellHeight;
    private Dimension panelDimension;
    private final TextCellFactory textFactory;
    private final ImageCellMap imageCellMap;

    /**
     * Creates a panel with the given grid and cell size. Uses a default font.
     *
     * @param gridWidth
     * @param gridHeight
     * @param cellWidth
     * @param cellHeight
     */
    public SwingPane(int gridWidth, int gridHeight, int cellWidth, int cellHeight) {
        this(gridWidth, gridHeight, new TextCellFactory(DEFAULT_FONT, cellWidth, cellHeight), null);
    }

    /**
     * Builds a panel with the given grid size and all other parameters determined by the factory. Even if sprite images
     * are being used, a TextCellFactory is still needed to perform sizing and other utility functions.
     *
     * For proper display, gridWidth should be an even multiple of the cellWidth in the factory, and likewise for the
     * gridHeight and cellHeight. Additionally the imageMap (if not null) should have the same cell size as the factory.
     *
     * @param gridWidth
     * @param gridHeight
     * @param factory
     * @param imageMap can be null if no explicit images will be used
     */
    public SwingPane(int gridWidth, int gridHeight, TextCellFactory factory, ImageCellMap imageMap) {
        this.gridWidth = gridWidth;
        this.gridHeight = gridHeight;
        textFactory = factory;
        cellWidth = factory.width();
        cellHeight = factory.height();
        setFont(textFactory.font());
        if (imageMap == null) {
            imageCellMap = new ImageCellMap(cellWidth, cellHeight);
        } else {
            imageCellMap = imageMap;
        }

        setOpaque(false);
        contents = new BufferedImage[gridWidth][gridHeight];
        imageChanged = new boolean[gridWidth][gridHeight];
        for (int x = 0; x < gridWidth; x++) {
            for (int y = 0; y < gridHeight; y++) {
                imageChanged[x][y] = true;
            }
        }
        int w = gridWidth * cellWidth;
        int h = gridHeight * cellHeight;
        panelDimension = new Dimension(w, h);
        setSize(panelDimension);
        setMinimumSize(panelDimension);
        setPreferredSize(panelDimension);
        contentsImage = new BufferedImage(w, h, BufferedImage.TYPE_4BYTE_ABGR);
        redraw();
        repaint();
    }

    /**
     * Returns the image being displayed at the given coordinates. Because this is the actual image and not a copy, any
     * changes to it will be reflected in the original during the next GUI update.
     *
     * @param x
     * @param y
     * @return
     */
    public BufferedImage getImage(int x, int y) {
        return contents[x][y];
    }

    @Override
    public void paintComponent(Graphics g) {
        g.drawImage(contentsImage, 0, 0, null);
        paintComponents(g);
        Toolkit.getDefaultToolkit().sync();
    }

    /**
     * Places the given characters into the grid starting at 0,0.
     *
     * @param chars
     */
    public void put(char[][] chars) {//TODO - convert this to work with code points
        SwingPane.this.put(0, 0, chars);
    }

    public void put(int x, int y, BufferedImage image) {
        contents[x][y] = image;
        imageChanged[x][y] = true;
    }

    public void putImage(int x, int y, String key) {
        BufferedImage image = imageCellMap.get(key);
        if (image == null) {
            image = imageCellMap.getNullImage();
        }
        contents[x][y] = image;
        imageChanged[x][y] = true;
    }

    public void put(int xOffset, int yOffset, char[][] chars) {
        SwingPane.this.put(xOffset, yOffset, chars, defaultForeground);
    }

    public void put(int xOffset, int yOffset, char[][] chars, Color foreground) {
        for (int x = xOffset; x < xOffset + chars.length; x++) {
            for (int y = yOffset; y < yOffset + chars[0].length; y++) {
                if (x >= 0 && y >= 0 && x < gridWidth && y < gridHeight) {//check for valid input
                    SwingPane.this.put(x, y, chars[x - xOffset][y - yOffset], foreground);
                }
            }
        }
    }

    /**
     * Puts the given string horizontally with the first character at the given offset.
     *
     * Does not word wrap. Characters that are not renderable (due to being at negative offsets or offsets greater than
     * the grid size) will not be shown but will not cause any malfunctions.
     *
     * Will use the default color for this component to draw the characters.
     *
     * @param xOffset the x coordinate of the first character
     * @param yOffset the y coordinate of the first character
     * @param string the characters to be displayed
     */
    public void put(int xOffset, int yOffset, String string) {
        SwingPane.this.put(xOffset, yOffset, string, defaultForeground);
    }

    /**
     * Puts the given string horizontally with the first character at the given offset.
     *
     * Does not word wrap. Characters that are not renderable (due to being at negative offsets or offsets greater than
     * the grid size) will not be shown but will not cause any malfunctions.
     *
     * @param xOffset the x coordinate of the first character
     * @param yOffset the y coordinate of the first character
     * @param string the characters to be displayed
     * @param foreground the color to draw the characters
     */
    public void put(int xOffset, int yOffset, String string, Color foreground) {//TODO - make this work with code points
        char[][] temp = new char[string.length()][1];
        for (int i = 0; i < string.length(); i++) {
            temp[i][0] = string.charAt(i);
        }
        SwingPane.this.put(xOffset, yOffset, temp, foreground);
    }

    /**
     * Puts the given string horizontally with the first character at the given offset.
     *
     * Does not word wrap. Characters that are not renderable (due to being at negative offsets or offsets greater than
     * the grid size) will not be shown but will not cause any malfunctions.
     *
     * Will use the default color for this component to draw the characters.
     *
     * @param xOffset the x coordinate of the first character
     * @param yOffset the y coordinate of the first character
     * @param string the characters to be displayed
     * @param vertical true if the text should be written vertically, from top to bottom
     */
    public void placeVerticalString(int xOffset, int yOffset, String string, boolean vertical) {
        SwingPane.this.put(xOffset, yOffset, string, defaultForeground, vertical);
    }

    /**
     * Puts the given string horizontally with the first character at the given offset.
     *
     * Does not word wrap. Characters that are not renderable (due to being at negative offsets or offsets greater than
     * the grid size) will not be shown but will not cause any malfunctions.
     *
     * @param xOffset the x coordinate of the first character
     * @param yOffset the y coordinate of the first character
     * @param string the characters to be displayed
     * @param foreground the color to draw the characters
     * @param vertical true if the text should be written vertically, from top to bottom
     */
    public void put(int xOffset, int yOffset, String string, Color foreground, boolean vertical) {//TODO - make this use any Direction
        if (vertical) {
            SwingPane.this.put(xOffset, yOffset, new char[][]{string.toCharArray()}, foreground);
        } else {
            SwingPane.this.put(xOffset, yOffset, string, foreground);
        }
    }

    /**
     * Erases the entire panel, leaving only a transparent space.
     */
    public void erase() {
        Graphics2D g = contentsImage.createGraphics();

        Composite c = g.getComposite();
        g.setComposite(AlphaComposite.Clear);
        g.fillRect(0, 0, getWidth(), getHeight());
        g.setComposite(c);

        redraw();
    }

    /**
     * Removes the contents of this cell, leaving a transparent space.
     *
     * @param x
     * @param y
     */
    public void clear(int x, int y) {
        this.put(x, y, SColor.TRANSPARENT);
    }

    public void put(int x, int y, SColor color) {
        put(x, y, textFactory.getSolid(color));
    }

    public void put(int x, int y, char c) {
        put(x, y, c, defaultForeground);
    }
   
    /**
     * Takes a unicode codepoint for input.
     *
     * @param x
     * @param y
     * @param code
     */
    public void put(int x, int y, int code){
        put(x, y, code, defaultForeground);
    }

    public void put(int x, int y, char c, Color color) {
        put(x, y, (int)c, color);
    }
   
   
    /**
     * Takes a unicode codepoint for input.
     *
     * @param x
     * @param y
     * @param code
     * @param color
     */
    public void put(int x, int y, int code, Color color) {
        if (x < 0 || x >= gridWidth || y < 0 || y >= gridHeight) {
            return;//skip if out of bounds
        }
        contents[x][y] = textFactory.get(code, color);
        imageChanged[x][y] = true;
    }

    public int cellWidth() {
        return cellWidth;
    }

    public int cellHeight() {
        return cellHeight;
    }

    public int gridHeight() {
        return gridHeight;
    }

    public int gridWidth() {
        return gridWidth;
    }

    public void refresh() {
        trimAnimations();
        redraw();
        repaint();
    }

    private void redraw() {
        Graphics2D g = contentsImage.createGraphics();

        for (int x = 0; x < gridWidth; x++) {
            for (int y = 0; y < gridHeight; y++) {
                if (imageChanged[x][y]) {
                    Composite c = g.getComposite();
                    g.setComposite(AlphaComposite.Clear);
                    g.fillRect(x * cellWidth, y * cellHeight, cellWidth, cellHeight);
                    g.setComposite(c);

                    if (contents[x][y] != null) {
                        g.drawImage(contents[x][y], null, x * cellWidth, y * cellHeight);
                    }
                    imageChanged[x][y] = false;
                }
            }
        }
    }

    public void setDefaultForeground(Color defaultForeground) {
        this.defaultForeground = defaultForeground;
    }

    /**
     * Starts a bumping animation in the direction provided.
     *
     * @param location
     * @param direction
     */
    public void bump(Point location, DirectionIntercardinal direction) {
        bump(location, new Point(direction.deltaX, direction.deltaY));
    }

    /**
     * Starts a bumping animation in the direction provided.
     *
     * @param location
     * @param direction
     */
    public void bump(Point location, Point direction) {
        if (contents[location.x][location.y] != null) {
            int duration = 20;
            Animation anim = new BumpAnimation(contents[location.x][location.y], new Point(location.x * cellWidth, location.y * cellHeight), new Dimension(cellWidth / 3, cellHeight / 3), direction, duration);
            contents[location.x][location.y] = null;
            imageChanged[location.x][location.y] = true;
            redraw();
            animations.add(anim);
            if (animationManager == null) {
                animationManager = AnimationManager.startNewAnimationManager(this);
            }
            animationManager.add(anim);
        }
    }

    /**
     * Starts a movement animation for the object at the given grid location at the default speed.
     *
     * @param start
     * @param end
     */
    public void slide(Point start, Point end) {
        slide(start, end, DEFAULT_ANIMATION_DURATION);
    }

    /**
     * Starts a movement animation for the object at the given grid location at the default speed for one grid square in
     * the direction provided.
     *
     * @param start
     * @param direction
     */
    public void slide(Point start, DirectionIntercardinal direction) {
        slide(start, new Point(direction.deltaX + start.x, direction.deltaY + start.y), DEFAULT_ANIMATION_DURATION);
    }

    /**
     * Starts a sliding movement animation for the object at the given location at the provided speed. The duration is
     * how many milliseconds should pass for the entire animation.
     *
     * @param start
     * @param end
     * @param duration
     */
    public void slide(Point start, Point end, int duration) {
        if (contents[start.x][start.y] != null) {
            Animation anim = new SlideAnimation(contents[start.x][start.y], new Point(start.x * cellWidth, start.y * cellHeight), new Point(end.x * cellWidth, end.y * cellHeight), duration);
            contents[start.x][start.y] = null;
            imageChanged[start.x][start.y] = true;
            redraw();
            animations.add(anim);
            if (animationManager == null) {
                animationManager = AnimationManager.startNewAnimationManager(this);
            }
            animationManager.add(anim);
        }
    }

    /**
     * Starts an wiggling animation for the object at the given location.
     *
     * @param location
     */
    public void wiggle(Point location) {
        if (contents[location.x][location.y] != null) {
            Animation anim = new WiggleAnimation(contents[location.x][location.y], new Point(location.x * cellWidth, location.y * cellHeight), 0.3, new Point(cellWidth / 4, cellHeight / 4), 160);
            contents[location.x][location.y] = null;
            imageChanged[location.x][location.y] = true;
            redraw();
            animations.add(anim);
            if (animationManager == null) {
                animationManager = AnimationManager.startNewAnimationManager(this);
            }
            animationManager.add(anim);
        }
    }

    /**
     * Drops any finished animations from the animation list.
     */
    private void trimAnimations() {
        if (animationManager == null) {
            return;//no manager means nothing to trim
        }
        LinkedList<Animation> removals = new LinkedList<>();
        for(Animation anim : animations){
            if (!anim.isActive()){
                removals.add(anim);
            }
        }
        animations.removeAll(removals);
        for(Animation anim : removals){
            animationManager.stopAnimation(anim);
            anim.remove();
            contents[anim.getLocation().x / cellWidth][anim.getLocation().y / cellHeight] = anim.getImage();
            imageChanged[anim.getLocation().x / cellWidth][anim.getLocation().y / cellHeight] = true;
        }
    }

    /**
     * Causes the component to stop responding to input until all current animations are finished.
     *
     * Note that if an animation is set to not stop then this method will never return.
     */
    public void waitForAnimations() {
        while (!animations.isEmpty()) {
            trimAnimations();
        }
        redraw();
        repaint();
    }

    /**
     * Returns true if there are animations running when this method is called.
     *
     * Note that due to the nature of animations ending at various times, this result is not guaranteed to be accurate.
     *
     * To fully ensure no animations are running, waitForAnimations() must be used.
     *
     * @return
     */
    public boolean hasActiveAnimations() {
        return animations == null ? false : !animations.isEmpty();
    }

}
TOP

Related Classes of squidpony.squidgrid.gui.SwingPane

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.