0

I really don't know how to do this. I want to have a panel "slide out" from underneath the current one when the user clicks a button. Then, when they click the button again, it will slide back in.

Here is an example of what I mean. Notice how it looks like it's "under" the window on the left and how it doesn't extend past completely (but almost) to the bottom and the top:

enter image description here

Here's a picture of it when it is not out (the second window in the background is just the first image [the one above] because the browser was in the frame when I took the screen shot): enter image description here

sinθ
  • 11,093
  • 25
  • 85
  • 121
  • [*Mac Widgets for Java*](http://code.google.com/p/macwidgets/) or cross-platform? – trashgod May 10 '13 at 14:27
  • One problem is that Swing can't paint outside of the JFrame. You might have better luck if your sliding area slides over the panel, rather than under. Or else you're going to have to have a blank area in your JFrame to reserve the space for the sliding area. – Gilbert Le Blanc May 10 '13 at 19:51
  • @GilbertLeBlanc Ok, lets say that it slid in from the side like a – sinθ May 10 '13 at 21:22

2 Answers2

2

Two approaches:

  1. Use a JLayeredPane. Upside is that you get the overlap effect that you're looking for. Downside is that JLP uses a null layout, which means your UI won't resize nicely without extra effort.

  2. Use a CardLayout. Upside is that your layout will behave as you asked if resized. Downside is that you won't get the overlap effect. You're slide-out panel will just have blank space to right of it.

EDIT: I just noticed that your slide-out isn't overlapping, but extending outward to the right. I originally thought you meant something like the old Outlook UI. In that case, can it be any slide-out, or does it have to be anchored to the frame?

Here's a crude demo that simply uses a BorderLayout for the effect. The big difference I see between this demo and the screenshots are:

  1. The frame's border extends with the slide-out in the demo
  2. The slide-out panel isn't offset in the demo.

Code:

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.*;

public class SlideOutPanelDemo
{
  private JPanel pnlMain;
  private JPanel pnlTools;
  private JFrame frame;

  public static void main(String[] args)
  {
    SwingUtilities.invokeLater(new Runnable()
    {
      public void run()
      {
        new SlideOutPanelDemo().createAndShowGUI();
      }
    });
  }

  public void createAndShowGUI()
  {
    JButton button = new JButton("Tools");    
    button.addActionListener(new ActionListener(){
      @Override
      public void actionPerformed(ActionEvent event)
      {
        boolean visible = pnlTools.isVisible();
        pnlTools.setVisible(! visible);
        frame.pack();
      }
    });

    pnlTools = createToolsPanel();
    pnlMain = createMainPanel();

    JToolBar toolBar = new JToolBar();
    toolBar.add(button);

    JPanel contentPane = new JPanel();
    contentPane.setLayout(new BorderLayout());
    contentPane.setOpaque(true);
    contentPane.add(toolBar, BorderLayout.NORTH);
    contentPane.add(pnlMain, BorderLayout.WEST);
    contentPane.add(pnlTools, BorderLayout.EAST);

    pnlMain.setVisible(true);
    pnlTools.setVisible(false);

    JFrame.setDefaultLookAndFeelDecorated(true);
    frame = new JFrame("Slide Out Panel Demo");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setContentPane(contentPane);
    frame.pack();
    frame.setLocationRelativeTo(null);
    frame.setVisible(true);
  }

  private JPanel createMainPanel()
  {
    JPanel panel = new JPanel();
    panel.setBorder(BorderFactory.createTitledBorder("Main"));
    panel.add(new JLabel("Field 1"));
    panel.add(new JTextField(20));
    panel.add(new JLabel("Field 2"));
    panel.add(new JTextField(20));
    panel.setSize(1000, 600);

    return panel;
  }

  private JPanel createToolsPanel()
  {
    JPanel panel = new JPanel();
    panel.setBackground(Color.YELLOW);
    Border b1 = BorderFactory.createTitledBorder("Tools");
    Border b2 = BorderFactory.createLineBorder(Color.BLUE, 2);
    panel.setBorder(BorderFactory.createCompoundBorder(b2, b1));
    panel.add(new JLabel("Thing 1"));
    panel.add(new JLabel("Thing 2"));
    panel.setSize(400, 600);

    return panel;
  }
}
splungebob
  • 5,357
  • 2
  • 22
  • 45
  • +1 (but maybe four:-) 3. JSplitPane without divider, invoked from Swing Timer, 4. JLayer (based on JXayer - Java6) – mKorbel May 10 '13 at 14:28
  • 1
    I don't know; that looks like peer component real estate. – trashgod May 10 '13 at 14:29
  • It is anchored to the main frame, as it it should move around with it. It does not need, nor should it be, resizable, if that helps. – sinθ May 10 '13 at 14:32
  • And how would I make it slide out. – sinθ May 10 '13 at 14:33
  • @trashgod `... peer component real estate.`, good point, then un_decorated JDialog or ??? – mKorbel May 10 '13 at 14:35
  • 1
    @mKorbel For cross-platform, maybe. More on sliding [here](http://stackoverflow.com/q/16316132/230513); more on split pane animation [here](http://stackoverflow.com/a/5071109/230513). – trashgod May 10 '13 at 14:41
  • splungebob: +1 for cross-platform alternatives; feel free to migrate any useful citations to you answer. – trashgod May 10 '13 at 14:42
  • [shots to the dark](http://stackoverflow.com/questions/8166033/how-to-prevent-boxlayout-box-from-stretching-children-components/8166223#8166223), better into undecorated JDialog – mKorbel May 10 '13 at 14:52
2

With the help of a book, Swing Hacks, I created a sliding JFrame and a sliding JDialog. It took me a few hours to debug the code.

Here's a test run showing a JOptionPane slid part way out.

Sheet Test Partial

And all the way out.

Sheet Test Out

Basically, I took the content pane of the JOptionPane, and animated it as an image.

You left click the slide sheet button to slide the JOptionPane out. You left click Yes or No on the JOptionPane to slide the JOptionPane back.

Here's the code to create the animated JFrame and animated JDialog.

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsEnvironment;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;

import javax.swing.Box;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.border.LineBorder;

public class AnimatedJFrame extends JFrame implements ActionListener {

    private static final long   serialVersionUID    = 6462856212447879086L;

    public static final int     INCOMING            = 1;
    public static final int     OUTGOING            = -1;
    public static final float   ANIMATION_DURATION  = 600F;
    public static final int     ANIMATION_SLEEP     = 5;

    JComponent                  sheet;
    JPanel                      glass;
    AnimatingSheet              animatingSheet;
    boolean                     animating;
    int                         animationDirection;
    Timer                       animationTimer;
    long                        animationStart;
    BufferedImage               offscreenImage;

    public AnimatedJFrame(String name) {
        super(name);
        glass = (JPanel) getGlassPane();
        glass.setLayout(new GridBagLayout());
        animatingSheet = new AnimatingSheet();
        animatingSheet.setBorder(new LineBorder(Color.BLACK, 1));
    }

    public JComponent showJDialogAsSheet(JDialog dialog) {
        sheet = (JComponent) dialog.getContentPane();
        sheet.setBorder(new LineBorder(Color.BLACK, 1));
        glass.removeAll();
        animationDirection = INCOMING;
        startAnimation();
        return sheet;
    }

    public void hideSheet() {
        animationDirection = OUTGOING;
        startAnimation();
    }

    private void startAnimation() {
//      glass.repaint();

        // Clear glass pane and set up animatingSheet
        animatingSheet.setSource(sheet);
        glass.removeAll();
        setGridBagConstraints(animatingSheet);
        glass.setVisible(true);

        // Start animation timer
        animationStart = System.currentTimeMillis();
        if (animationTimer == null) {
            animationTimer = new Timer(ANIMATION_SLEEP, this);
        }
        animating = true;
        animationTimer.start();
    }

    private void stopAnimation() {
        animationTimer.stop();
        animating = false;
    }

    @Override
    public void actionPerformed(ActionEvent event) {
        if (animating) {
            // Calculate height to show
            float animationPercent = (System.currentTimeMillis() - animationStart)
                    / ANIMATION_DURATION;
            animationPercent = Math.min(1.0F, animationPercent);
            int animatingWidth = 0;
            if (animationDirection == INCOMING) {
                animatingWidth = (int) (animationPercent * sheet.getWidth());
            } else {
                animatingWidth = (int) ((1.0F - animationPercent) * sheet
                        .getWidth());
            }

            // Clip off that much from the sheet and blit it
            // into the animatingSheet
            animatingSheet.setAnimatingWidth(animatingWidth);
            animatingSheet.repaint();

            if (animationPercent >= 1.0F) {
                stopAnimation();
                if (animationDirection == INCOMING) {
                    finishShowingSheet();
                } else {
                    glass.removeAll();
                    glass.setVisible(false);
                }
            }
        }
    }

    private void finishShowingSheet() {
        glass.removeAll();
        setGridBagConstraints(sheet);
        glass.revalidate();
        glass.repaint();
    }

    private void setGridBagConstraints(JComponent sheet) {
        GridBagConstraints gbc = new GridBagConstraints();
        gbc.anchor = GridBagConstraints.NORTHWEST;
        glass.add(sheet, gbc);
        gbc.gridy = 1;
        gbc.weighty = Integer.MAX_VALUE;
        glass.add(Box.createGlue(), gbc);
    }

}

class AnimatingSheet extends JPanel {

    private static final long   serialVersionUID    = 3958155417286820827L;

    Dimension       animatingSize   = new Dimension(0, 1);
    JComponent      source;
    BufferedImage   offscreenImage;

    public AnimatingSheet() {
        super();
        setOpaque(true);
    }

    public void setSource(JComponent source) {
        this.source = source;
        animatingSize.height = source.getHeight();
        makeOffscreenImage(source);
    }

    public void setAnimatingWidth(int width) {
        animatingSize.width = width;
        setSize(animatingSize);
    }

    private void makeOffscreenImage(JComponent source) {
        GraphicsConfiguration gfxConfig = GraphicsEnvironment
                .getLocalGraphicsEnvironment().getDefaultScreenDevice()
                .getDefaultConfiguration();
        offscreenImage = gfxConfig.createCompatibleImage(source.getWidth(),
                source.getHeight());
        Graphics2D offscreenGraphics = (Graphics2D) offscreenImage
                .getGraphics();
        source.paint(offscreenGraphics);
        offscreenGraphics.dispose();
    }

    @Override
    public Dimension getPreferredSize() {
        return animatingSize;
    }

    @Override
    public Dimension getMinimumSize() {
        return animatingSize;
    }

    @Override
    public Dimension getMaximumSize() {
        return animatingSize;
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        BufferedImage fragment = offscreenImage.getSubimage(
                offscreenImage.getWidth() - animatingSize.width, 0,
                animatingSize.width, source.getHeight());
        g.drawImage(fragment, 0, 0, this);
    }
}

Here's the code to test the animated JFrame.

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class SheetTest implements PropertyChangeListener, Runnable {

    JOptionPane     optionPane;
    AnimatedJFrame  frame;

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new SheetTest());
    }

    @Override
    public void run() {
        // Build JOptionPane dialog
        optionPane = new JOptionPane("Do you want to save?",
                JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_NO_OPTION);
        optionPane.addPropertyChangeListener(this);

        frame = new AnimatedJFrame("Sheet Test");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        // Put an image in the frame's content pane
        ImageIcon icon = new ImageIcon("images/Google Tile.jpg");
        JLabel label = new JLabel(icon);
        frame.getContentPane().add(label, BorderLayout.CENTER);

        JButton dropButton = new JButton("Slide sheet");
        dropButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                JDialog dialog = optionPane.createDialog(frame, "Irrelevant");
                frame.showJDialogAsSheet(dialog);
            }
        });

        frame.getContentPane().add(dropButton, BorderLayout.SOUTH);
        frame.pack();
        frame.setVisible(true);
    }

    @Override
    public void propertyChange(PropertyChangeEvent event) {
        if (event.getPropertyName().equals(JOptionPane.VALUE_PROPERTY)) {
            frame.hideSheet();
        }

    }

}

And here's the image I used to create the test JFrame.

Google Tile

Gilbert Le Blanc
  • 50,182
  • 6
  • 67
  • 111