3
\$\begingroup\$

The previous and initial iteration at Simulating a GUI windowing system

I have essentially cleaned the code based on the only answer.

Now I have:

MyCanvas.java:

package net.coderodde.gui.simulation;

import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.util.List;

class MyCanvas extends Canvas {

    private final int width;
    private final int height;
    private final List<MyWindow> windowList;

    MyCanvas(final int width, 
             final int height, 
             final List<MyWindow> wndList,
             final MyScreen screen) {
        this.width = width;
        this.height = height;
        setSize(width, height);
        this.windowList = wndList;
        final MyMouseListener mml = new MyMouseListener(screen);
        addMouseListener(mml);
        addMouseMotionListener(mml);
    }

    @Override
    public void paint(final Graphics g) {
        g.setColor(Color.BLACK);
        g.fillRect(0, 0, width, height);

        for (final MyWindow wnd : windowList) {
            wnd.draw(g);
        }
    }

    @Override
    public void update(final Graphics g) {
        paint(g);
    }

    @Override
    public Dimension getPreferredSize() {
        return new Dimension(width, height);
    }

    static class MyMouseListener implements MouseListener,
                                            MouseMotionListener {

        private final MyScreen screen;

        MyMouseListener(final MyScreen screen) {
            this.screen = screen;
        }

        @Override
        public void mouseClicked(MouseEvent e) {

        }

        @Override
        public void mousePressed(MouseEvent e) {
            screen.press(e.getX(), e.getY());
        }

        @Override
        public void mouseReleased(MouseEvent e) {
            screen.releaseMouse();
        }

        @Override
        public void mouseEntered(MouseEvent e) {
            screen.allowDrag();
        }

        @Override
        public void mouseExited(MouseEvent e) {
            screen.disallowDrag();
        }    

        @Override
        public void mouseDragged(MouseEvent e) {
            screen.drag(e.getX(), e.getY());
        }

        @Override
        public void mouseMoved(MouseEvent e) {
           screen.move(e.getX(), e.getY());
        }
    }
}

MyScreen:

package net.coderodde.gui.simulation;

import java.awt.Canvas;
import java.awt.Dimension;
import java.util.List;
import java.awt.Toolkit;
import java.util.ArrayList;
import javax.swing.JFrame;

public class MyScreen {

    private final static int FRAME_WIDTH = 1024;
    private final static int FRAME_HEIGHT = 768;

    private final JFrame frame;
    private final Canvas canvas;
    private final List<MyWindow> windowList;
    private MyWindow activeWindow;
    private MyWindow draggedWindow;
    private int draggedWindowOffsetX;
    private int draggedWindowOffsetY;
    private boolean topBorderLocked;
    private boolean rightBorderLocked;
    private boolean bottomBorderLocked;
    private boolean leftBorderLocked;
    private MyWindow highlightedWindow;
    private boolean disallowDrag;

    public MyScreen() {
        windowList = new ArrayList<>();
        frame = new JFrame("coderodde GUI simulation");
        frame.setSize(FRAME_WIDTH, FRAME_HEIGHT);
        frame.setResizable(false);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        final Dimension realScreenDimension = 
                Toolkit.getDefaultToolkit().getScreenSize();

        frame.setLocation((realScreenDimension.width - frame.getWidth()) / 2, 
                          (realScreenDimension.height - frame.getHeight()) / 2);

        canvas = new MyCanvas(frame.getWidth(), 
                              frame.getHeight(), 
                              windowList, 
                              this);

        frame.add(canvas);
        frame.setVisible(true);
    }

    public void addWindow(final MyWindow window) {
        windowList.add(window);

        if (activeWindow != null) {
            activeWindow.setActive(false);
        }

        window.setActive(true);
        activeWindow = window;
    }

    public void move(final int x, final int y) {
        MyWindow target = null;

        for (int i = windowList.size() - 1; i >= 0; --i) {
            if (windowList.get(i).windowContainsPoint(x, y)) {
                target = windowList.get(i);
                break;
            }
        }

        if (target == null) {
            if (highlightedWindow != null) {
                highlightedWindow.clearBorderHighlights();
                highlightedWindow = null;
                canvas.repaint();
            }

            return;
        }

        if (highlightedWindow != null) {
            highlightedWindow.clearBorderHighlights();
            highlightedWindow = null;
        }

        if (target.topBorderContainsPoint(x, y)) {
            target.highlightBorder(MyWindow.Border.TOP);
            highlightedWindow = target;
        } else if (target.bottomBorderContainsPoint(x, y)) {
            target.highlightBorder(MyWindow.Border.BOTTOM);
            highlightedWindow = target;
        }

        if (target.rightBorderContainsPoint(x, y)) {
            target.highlightBorder(MyWindow.Border.RIGHT);
            highlightedWindow = target;
        } else if (target.leftBorderContainsPoint(x, y)) {
            target.highlightBorder(MyWindow.Border.LEFT);
            highlightedWindow = target;
        }

        canvas.repaint();
    }

    public void press(final int x, final int y) {
        MyWindow newActiveWindow = null;

        for (int i = windowList.size() - 1; i >= 0; --i) {
            if (windowList.get(i).windowContainsPoint(x, y)) {
                newActiveWindow = windowList.remove(i);
                break;
            }
        }

        if (newActiveWindow == null) {
            return;
        }

        activeWindow.setActive(false);
        activeWindow = newActiveWindow;
        windowList.add(activeWindow);
        activeWindow.setActive(true);

        boolean doResize = false;

        if (activeWindow.topBorderContainsPoint(x, y)) {
            topBorderLocked = true;
            doResize = true;
        } else if (activeWindow.bottomBorderContainsPoint(x, y)) {
            bottomBorderLocked = true;
            doResize = true;
        }

        if (activeWindow.leftBorderContainsPoint(x, y)) {
            leftBorderLocked = true;
            doResize = true;
        } else if (activeWindow.rightBorderContainsPoint(x, y)) {
            rightBorderLocked = true;
            doResize = true;
        }

        if (!doResize && activeWindow.titleBarContainsPoint(x, y)) {
            draggedWindow = activeWindow;
            draggedWindowOffsetX = x - draggedWindow.getX();
            draggedWindowOffsetY = y - draggedWindow.getY();
        } 

        if (doResize) {
            draggedWindow = activeWindow;
        }

        canvas.repaint();
    }

    public void allowDrag() {
        disallowDrag = false;
    }

    public void disallowDrag() {
        disallowDrag = true;
    }

    public void drag(final int x, final int y) {
        if (draggedWindow == null) {
            return;
        }

        if (disallowDrag) {
            return;
        }

        if (!(topBorderLocked 
                || rightBorderLocked 
                || bottomBorderLocked
                || leftBorderLocked)) {
            // Move the entire window.
            draggedWindow.setX(x - draggedWindowOffsetX);
            draggedWindow.setY(y - draggedWindowOffsetY);
            canvas.repaint();
        }

        // Resize the window.
        final int originalX = draggedWindow.getX();
        final int originalY = draggedWindow.getY();

        if (topBorderLocked) {
            draggedWindow.resizeAtTop(y - originalY);
        } else if (bottomBorderLocked) {   
            draggedWindow.setHeight(y - originalY);
        }

        if (leftBorderLocked) {
            draggedWindow.resizeAtLeft(x - originalX);
        } else if (rightBorderLocked) {
            draggedWindow.setWidth(x - originalX);
        }

        canvas.repaint();
    }

    public void releaseMouse() {
        draggedWindow = null;
        topBorderLocked = false;
        leftBorderLocked = false;
        bottomBorderLocked = false;
        rightBorderLocked = false;
    }

    List<MyWindow> getWindows() {
        return windowList;
    }
}

MyWindow.java:

package net.coderodde.gui.simulation;

import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;

public class MyWindow {

    public static enum Border {
        TOP,
        RIGHT,
        BOTTOM,
        LEFT
    }

    public final static int MINIMUM_WIDTH = 50;
    public final static int MINIMUM_HEIGHT = 50;

    private final static int TITLE_BAR_HEIGHT = 30;
    private final static Color ACTIVE_TITLE_BAR_BACKGROUND = new Color(255, 150, 100);
    private final static Color TITLE_BAR_TEXT_COLOR = Color.WHITE;
    private final static Color PASSIVE_TITLE_BAR_BACKGROUND = new Color(200, 200, 200);
    private final static Color BORDER_HIGHLIGHT_COLOR = Color.GREEN;
    private final static Color BODY_COLOR = new Color(50, 50, 50);
    private final static Font TITLE_FONT = new Font("Monospaced", Font.BOLD, 12);
    private final static int TITLE_PADDING = 14;

    private String title;
    private int width;
    private int height;
    private int x;
    private int y;
    private boolean active;
    private boolean topBorderHightlighted;
    private boolean rightBorderHighlighted;
    private boolean bottomBorderHightlighted;
    private boolean leftBorderHighlighted;
    private boolean debug;

    public MyWindow(final String title, 
                    final int width, 
                    final int height,
                    final int x,
                    final int y) {
        this.title = title;
        this.width = Math.max(width, MINIMUM_WIDTH);
        this.height = Math.max(height, MINIMUM_HEIGHT) + TITLE_BAR_HEIGHT;
        this.x = x;
        this.y = y;
        this.active = false;
    }

    public void setDebug(final boolean debug) {
        this.debug = debug;
    }

    public boolean isActive() {
        return active;
    }

    public void setActive(final boolean active) {
        this.active = active;
    }

    public void clearBorderHighlights() {
        topBorderHightlighted = false;
        rightBorderHighlighted = false;
        bottomBorderHightlighted = false;
        leftBorderHighlighted = false;
    }

    public void highlightBorder(Border border) {
        switch (border) {
            case TOP:
                topBorderHightlighted = true;
                return;

            case RIGHT:
                rightBorderHighlighted = true;
                return;

            case BOTTOM:
                bottomBorderHightlighted = true;
                return;

            case LEFT:
                leftBorderHighlighted = true;
                return;
        }
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(final String title) {
        this.title = title;
    }

    public int getWidth() {
        return width;
    }

    public void setWidth(final int width) {
        this.width = Math.max(width, MINIMUM_WIDTH);
    }

    public int getHeight() {
        return height;
    }

    public void setHeight(final int height) {
        if (debug) {
            System.out.println("Height changed from " + getHeight() + " to " +
                               height);
        }

        this.height = Math.max(height, MINIMUM_HEIGHT);
    }

    public int getX() {
        return x;
    }

    public int getY() {
        return y;
    }

    public void setX(final int x) {
        this.x = x;
    }

    public void setY(final int y) {
        this.y = y;
    }

    public boolean titleBarContainsPoint(final int x, final int y) {
        return getX() <= x 
                && x < getX() + width
                && getY() <= y
                && y < getY() + TITLE_BAR_HEIGHT;
    }

    public boolean windowContainsPoint(final int x, final int y) {
        return getX() <= x 
                && x < getX() + width 
                && getY() <= y
                && y < getY() + height;
    }

    public boolean topBorderContainsPoint(final int x, final int y) {
        return getX() <= x 
                && x < getX() + width 
                && (y == getY() || y == getY() + 1);
    }

    public boolean rightBorderContainsPoint(final int x, final int y) {
        return (x == getX() + width - 1 || x == getX() + width - 2) 
                && getY() <= y 
                && y < getY() + height;
    }

    public boolean bottomBorderContainsPoint(final int xx, final int yy) {
        return getX() <= xx 
                && xx < getX() + width 
                && (yy == getY() + height - 1 || yy == getY() + height - 2);
    }

    public boolean leftBorderContainsPoint(final int x, final int y) {
        return (x == getX() || x == getX() + 1) 
                && getY() <= y 
                && y < getY() + height;
    }

    public void draw(final Graphics g) {
        final Color titleBarColor = isActive() ?
                ACTIVE_TITLE_BAR_BACKGROUND :
                PASSIVE_TITLE_BAR_BACKGROUND;

        // Draw title bar.
        g.setColor(titleBarColor);
        g.fillRect(x, y, width, TITLE_BAR_HEIGHT);
        g.setColor(TITLE_BAR_TEXT_COLOR);
        g.setFont(TITLE_FONT);
        g.drawString(getContractedTitle(g.getFontMetrics(TITLE_FONT)), 
                                        x + TITLE_PADDING,
                                        y + 18);

        // Fill body rectangle.
        g.setColor(BODY_COLOR);
        g.fillRect(x, y + TITLE_BAR_HEIGHT, width, height - TITLE_BAR_HEIGHT);

        // Draw left, right and bottom colors.
        g.setColor(titleBarColor);
            // Draw left border.
        g.drawLine(x, y, x, y + height - 1);
            // Draw right border.
        g.drawLine(x + width - 1, y, x + width - 1, y + height - 1);
            // Draw bottom border
        g.drawLine(x, y + height - 1, x + width - 1, y + height - 1);

        // Try draw border highlights.
        g.setColor(BORDER_HIGHLIGHT_COLOR);

            // Top border.
        if (topBorderHightlighted) {
            g.fillRect(x, y, width, 2);
        }
            // Left border.
        if (leftBorderHighlighted) {
            g.fillRect(x, y, 2, height);
        }
            // Right border.
        if (rightBorderHighlighted) {
            g.fillRect(x + width - 2, y, 2, height);
        }
            // Bottom border.
        if (bottomBorderHightlighted) {
            g.fillRect(x, y + height - 2, width, 2);
        }
    }

    public void resizeAtTop(final int delta) {
        final int oldHeight = height;
        height = Math.max(MINIMUM_HEIGHT, height - delta);
        y += (oldHeight - height);
    }

    public void resizeAtLeft(final int delta) {
        final int oldWidth = width;
        width = Math.max(MINIMUM_WIDTH, width - delta);
        x += (oldWidth - width);
    }

    private String getContractedTitle(final FontMetrics fm) {
        final int allowedWidth = getWidth() - 2 * TITLE_PADDING;

        if (fm.stringWidth(title) <= allowedWidth) {
            // Once here, the window title fits entirely in the title bar.
            return title;
        }

        // Once here, we need to contract the string from right.
        for (int i = title.length() - 1; i > 0; --i) {
            final String contractedTitle = title.substring(0, i);
            final String titleWithDots = contractedTitle + "...";
            if (fm.stringWidth(titleWithDots) <= allowedWidth) {
                return titleWithDots;
            }
        }

        if (fm.stringWidth("...") <= allowedWidth) {
            return "...";
        }

        if (fm.stringWidth("..") <= allowedWidth) {
            return "..";
        }

        return "";
    }
}

The Demo.java is not changed.

Now, is the code now kewl and funky enough?

\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

I would go into more abstraction and less monolithic code, for example a window could be a really bare bone window (no title, no border, nothing but a rectangle where you can draw into) and use inheritance for composed windows, for example an AppWindow or so that has a title bar, border (resizeable or fixed), a menu window, and so on.

I would also try to abstract each attribute into it's own class (the ones that makes sense of course), for example a Border class that encapsulates it's thickness / insets, colors and so on.

I did a mini windowing system to proof some ideas sometime ago (some info here but did it in python: http://valkoinenrapu2.blogspot.fi/2015/03/the-sorry-state-of-ui-and-some-hope-for.html)

In the long run the code will be more compact, easier to maintain and each component (class) more focused on it's own domain

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.