This is an incomplete implementation of something similar to scratch. It's incomplete because right now, I'm working purely on the user interface.

I used Swing to make the basics of it, but then hacked my own code for the rest of it, because I wasn't sure if Swing could give me what I wanted. Basically, I wanted to be able to drag around the pieces (which I could do with Swing) but also drag the background around.

demo

You can see it better here

I'm looking for a way to improve this code and also whether I took the right approach or whether I should rewrite it in Swing (and how).

MainPanel.java

public class MainPanel extends JPanel {

    private static final int SEPARATOR_DISTANCE = 100;
    private static final int NUMBER_DISTANCE = 200;
    private final List<Piece> pieces = new ArrayList<>();
    private int x;
    private int y;

    public MainPanel() {
        super();
        pieces.add(new NumberConstant(5, 100, 10));
        x = y = 0;
        final MainInputHandler input = new MainInputHandler(this);

        addMouseListener(input);
        addMouseMotionListener(input);
    }

    @Override
    protected void paintComponent(final Graphics g) {
        GraphicsUtils.prettyGraphics((Graphics2D) g);

        super.paintComponent(g);
        g.setColor(GraphicsConstants.MAIN_BACKROUND_COLOR);
        g.fillRect(0, 0, getWidth(), getHeight());

        drawGrid(g);

        g.translate(-x, -y);
        for (final Piece p : pieces) {
            p.draw((Graphics2D) g);
        }

    }

    private void drawGrid(final Graphics g) {
        final int textSpaceBuffer = 5;

        g.setColor(GraphicsConstants.MAIN_GRID_COLOR);

        // Draws vertical lines
        for (int sepX = -(x % SEPARATOR_DISTANCE) - SEPARATOR_DISTANCE; sepX < getWidth(); sepX += SEPARATOR_DISTANCE) {
            if (sepX + x == 0) {
                g.setColor(GraphicsConstants.MAIN_GRID_ORIGIN_COLOR);
            } else {
                g.setColor(GraphicsConstants.MAIN_GRID_COLOR);
            }

            g.drawLine(sepX, 0, sepX, getHeight());
            // draws line coordinates if neccesary
            if ((sepX + x) % NUMBER_DISTANCE == 0) {
                g.setColor(GraphicsConstants.MAIN_GRID_COLOR);
                g.drawString(String.valueOf(sepX + x), sepX + textSpaceBuffer, g.getFontMetrics().getMaxAscent() + textSpaceBuffer);
            }
        }
        // draws horizontal lines
        for (int sepY = -(y % SEPARATOR_DISTANCE); sepY < getHeight() + SEPARATOR_DISTANCE; sepY += SEPARATOR_DISTANCE) {
            if (sepY + y == 0) {
                g.setColor(GraphicsConstants.MAIN_GRID_ORIGIN_COLOR);
            } else {
                g.setColor(GraphicsConstants.MAIN_GRID_COLOR);
            }

            g.drawLine(0, sepY, getWidth(), sepY);
            // draws line coordinates if necessary
            if ((sepY + y) % NUMBER_DISTANCE == 0) {
                g.setColor(GraphicsConstants.MAIN_GRID_COLOR);
                g.drawString(String.valueOf(sepY + y), textSpaceBuffer, sepY - textSpaceBuffer);
            }
        }
    }

    public void centerOnOrigin() {
        x = -getWidth() / 2;
        y = -getHeight() / 2;
    }

    public void setSpacePosition(final int x, final int y) {
        this.x = x;
        this.y = y;
    }

    public int getSpaceX() {
        return x;
    }

    public int getSpaceY() {
        return y;
    }

    public Point getSpacePosition() {
        return new Point(x, y);
    }

    public Point getWorldCoordFromMouse(final Point p) {
        return new Point(x + p.x, y + p.y);
    }

    public List<Piece> getPieces() {
        return pieces;
    }

}

Piece.java

/**
 * Abstract class. To implement this class, you must add a method with the signature<br>
 * <code>public static String name()</code>
 * */
public abstract class Piece {

    public static final String MAX_LENGTH_STRING;
    private static final int PORT_SIZE = 20;
    private static final int GAP_SIZE = 10;
    private static final int BORDER_SPACE = 5;

    private static List<Class<? extends Piece>> pieces = new ArrayList<>();
    private static Map<Class<? extends Piece>, String> pieceNames = new HashMap<>();

    private final ProgramValue[] inputs;
    private final Connection[] outputs;

    private int x;
    private int y;
    private FontMetrics fontMetrics;

    static {
        addPiece(NumberConstant.class);

        String longestString = "";
        final Iterator<String> it = getPieceNames().values().iterator();
        while (it.hasNext()) {
            final String next = it.next();
            if (next.length() > longestString.length()) {
                longestString = next;
            }
        }
        MAX_LENGTH_STRING = longestString;
    }

    protected Piece(final int inputs, final int outputs, final int x, final int y) {
        this.inputs = new ProgramValue[inputs];
        this.outputs = new Connection[outputs];
        for (int i = 0; i < outputs; i++) {
            getOutputs()[i] = new Connection(this, i, null, 0);
        }
        this.x = x;
        this.y = y;
    }

    // Piece should take inputs and figure out its output
    public abstract void update(ProgramContext pc);

    /**
     * Assumes that this should draw at (0,0)
     * */
    public void draw(final Graphics2D g) {
        g.translate(x, y);
        g.setColor(GraphicsConstants.PIECE_BACKGROUND);
        final String name = pieceNames.get(getClass());
        // Store this variable so other methods can use it without accessing graphics
        fontMetrics = g.getFontMetrics();
        final int nameWidth = getNameWidth(name);
        g.fill(getBodyShape(nameWidth));

        final int nameHeight = fontMetrics.getMaxAscent();

        g.setColor(GraphicsConstants.PIECE_TEXT);
        g.drawString(name, BORDER_SPACE, nameHeight);

        for (int i = 0; i < inputs.length; i++) {
            g.drawOval(BORDER_SPACE, nameHeight + GAP_SIZE + (PORT_SIZE + GAP_SIZE) * i, PORT_SIZE, PORT_SIZE);
        }
        for (int i = 0; i < outputs.length; i++) {
            g.drawOval(nameWidth - PORT_SIZE - BORDER_SPACE, nameHeight + GAP_SIZE + (PORT_SIZE + GAP_SIZE) * i, PORT_SIZE, PORT_SIZE);
        }

    }

    /**
     * @param a
     *            point in world space
     * @return the connection that was clicked on
     * */
    public Optional<Connection> outputPortContainingPoint(final Point worldCoord) {
        final Point worldCoordCopy = new Point(worldCoord);
        // the body shape is at 0,0 so we have to translate that by its x and y OR translate our point by -x and -y
        worldCoordCopy.translate(-x, -y);

        final int nameWidth = getNameWidth(pieceNames.get(getClass()));
        final int nameHeight = fontMetrics.getMaxAscent();

        for (int i = 0; i < outputs.length; i++) {
            if (new Ellipse2D.Float(nameWidth - PORT_SIZE - BORDER_SPACE, nameHeight + GAP_SIZE + (PORT_SIZE + GAP_SIZE) * i, PORT_SIZE, PORT_SIZE).contains(worldCoordCopy)) {
                return Optional.of(outputs[i]);
            }
        }
        return Optional.empty();
    }

    private int getNameWidth(final String name) {
        return (int) (fontMetrics.stringWidth(name) * 1.5);
    }

    public static List<Class<? extends Piece>> values() {
        return pieces;
    }

    private static void addPiece(final Class<? extends Piece> p) {
        pieces.add(p);
        try {
            // Assumes subclass has a static method called name
            getPieceNames().put(p, p.getMethod("name").invoke(null).toString());
        } catch (final NoSuchMethodException e) {
            e.printStackTrace();
            // if they didn't supply a name method, use the class name instead
            getPieceNames().put(p, p.getSimpleName());
        } catch (final SecurityException e) {
            e.printStackTrace();
        } catch (final IllegalAccessException e) {
            e.printStackTrace();
        } catch (final IllegalArgumentException e) {
            e.printStackTrace();
        } catch (final InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    private RoundRectangle2D getBodyShape(final int nameWidth) {
        final int curve = 5;
        final int height = fontMetrics.getMaxAscent() + GAP_SIZE + (PORT_SIZE + GAP_SIZE) * Math.max(inputs.length, outputs.length);
        return new RoundRectangle2D.Float(0, 0, nameWidth, height, curve, curve);
    }

    public boolean containsPoint(final Point worldCoord) {
        final Point worldCoordCopy = new Point(worldCoord);
        // the body shape is at 0,0 so we have to translate that by its x and y OR translate our point by -x and -y
        worldCoordCopy.translate(-x, -y);
        return getBodyShape(getNameWidth(pieceNames.get(getClass()))).contains(worldCoordCopy);
    }

    public static Map<Class<? extends Piece>, String> getPieceNames() {
        return pieceNames;
    }

    public void changeInput(final int inputPort, final ProgramValue value) {
        assert value != null;
        inputs[inputPort] = value;
    }

    protected Connection[] getOutputs() {
        return outputs;
    }

    public void setPosition(final int x, final int y) {
        this.x = x;
        this.y = y;
    }

    public Point getPosition() {
        return new Point(x, y);
    }

}

MainInputHandler.java

public class MainInputHandler implements MouseListener, MouseMotionListener {

    private final MainPanel mainPanel;

    private Optional<Point> pressedPosition = Optional.empty();
    private Optional<Point> initialPosition = Optional.empty();
    private Optional<Piece> pieceDragged = Optional.empty();
    private Optional<Point> pieceInitialPosition = Optional.empty();

    private Optional<Connection> portSelected = Optional.empty();

    public MainInputHandler(final MainPanel mainPanel) {
        this.mainPanel = mainPanel;
    }

    @Override
    public void mousePressed(final MouseEvent e) {
        final List<Piece> collidingPieces = new ArrayList<>(mainPanel.getPieces());
        final Point worldCoord = mainPanel.getWorldCoordFromMouse(e.getPoint());
        collidingPieces.removeIf((final Piece p) -> !p.containsPoint(worldCoord));
        if (!collidingPieces.isEmpty()) {
            // we are clicking on one or more pieces, drag the top piece (last in the list)
            final Piece selected = collidingPieces.get(collidingPieces.size() - 1);

            final Optional<Connection> outputPortSelected = selected.outputPortContainingPoint(worldCoord);
            if (outputPortSelected.isPresent()) {
                portSelected = outputPortSelected;
            } else {
                // we didn't select a port, we selected th body
                pieceDragged = Optional.of(selected);
                pieceInitialPosition = Optional.of(pieceDragged.get().getPosition());
            }
        } else {
            pieceDragged = Optional.empty();
            pieceInitialPosition = Optional.empty();
        }

        pressedPosition = Optional.of(e.getPoint());
        initialPosition = Optional.of(mainPanel.getSpacePosition());
    }

    @Override
    public void mouseReleased(final MouseEvent e) {
        pressedPosition = Optional.empty();
        initialPosition = Optional.empty();
        pieceDragged = Optional.empty();
        pieceInitialPosition = Optional.empty();
        portSelected = Optional.empty();
    }

    @Override
    public void mouseDragged(final MouseEvent e) {
        if (portSelected.isPresent()) {
            // Drag a connection
            // TODO
        } else if (pieceDragged.isPresent() && pieceInitialPosition.isPresent()) {
            // Drag a piece

            final int x = pieceInitialPosition.get().x + e.getPoint().x - pressedPosition.get().x;
            final int y = pieceInitialPosition.get().y + e.getPoint().y - pressedPosition.get().y;
            pieceDragged.get().setPosition(x, y);
            mainPanel.repaint();
        } else if (pressedPosition.isPresent() && initialPosition.isPresent()) {
            // Move the background
            final int x = initialPosition.get().x + pressedPosition.get().x - e.getPoint().x;
            final int y = initialPosition.get().y + pressedPosition.get().y - e.getPoint().y;
            mainPanel.setSpacePosition(x, y);
            mainPanel.repaint();
        }
    }

    @Override
    public void mouseMoved(final MouseEvent e) {

    }

    @Override
    public void mouseEntered(final MouseEvent e) {

    }

    @Override
    public void mouseExited(final MouseEvent e) {

    }

    @Override
    public void mouseClicked(final MouseEvent e) {

    }

}
share|improve this question
    
Can you post the rest of the files? – motoku Jun 21 '15 at 23:16
    
@MotokoKusanagi github.com/kyranstar/FlowchartProgramming – Kyranstar Jun 21 '15 at 23:32

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Browse other questions tagged or ask your own question.