/*
*
* Copyright 2014 http://Bither.net
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* /
*/
package net.bither.viewsystem.panels;
import com.google.common.base.Preconditions;
import net.bither.Bither;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
/**
* <p>Component to provide the following to UI:</p>
* <ul>
* <li>Provision of a light box effect where the background is dimmed</li>
* </ul>
*
* @since 0.0.1
*/
public class LightBoxPanel extends JPanel {
private final JPanel screenPanel;
private static final Logger log = LoggerFactory.getLogger(LightBoxPanel.class);
/**
* @param screenPanel The panel containing the light box components (e.g. a wizard screen panel)
* @param layer The layer which to place the panel (JLayeredPane.MODAL_LAYER for wizards, DRAG_LAYER for popovers within wizards)
*/
public LightBoxPanel(JPanel screenPanel, Integer layer) {
Preconditions.checkNotNull(screenPanel, "'panel' must be present");
Preconditions.checkState(screenPanel.getWidth() > 0, "'width' must be greater than zero");
Preconditions.checkState(screenPanel.getHeight() > 0, "'height' must be greater than zero");
this.screenPanel = screenPanel;
// Ensure we set the opacity (platform dependent)
setOpaque(false);
// Ensure we are visible
setVisible(true);
// Ensure this panel covers all the available frame area
setSize(Bither.getMainFrame().getWidth() + 100, Bither.getMainFrame().getHeight() + 100);
// Prevent mouse events reaching through the darkened border
addMouseListener(new ModalMouseListener());
// Add this panel to the frame's layered panel as the palette layer (directly above the default)
if (JLayeredPane.MODAL_LAYER.equals(layer)) {
Bither.getMainFrame().getLayeredPane().add(this, JLayeredPane.PALETTE_LAYER);
} else {
Bither.getMainFrame().getLayeredPane().add(this, JLayeredPane.POPUP_LAYER);
}
// Provide a starting position
calculatePosition();
// Add the light box panel to the frame
Bither.getMainFrame().getLayeredPane().add(screenPanel, layer);
}
/**
* <p>Calculate the position of the center panel</p>
*/
private void calculatePosition() {
int currentFrameWidth = Bither.getMainFrame().getWidth();
int currentFrameHeight = Bither.getMainFrame().getHeight();
// Ensure this panel covers all the available frame area allowing for fast dragging
setSize(currentFrameWidth * 2, currentFrameHeight * 2);
// Center the light box panel in the frame
int x = (currentFrameWidth - screenPanel.getWidth()) / 2;
int y = ((currentFrameHeight - screenPanel.getHeight()) / 2) - 10; // The 10 offset is a magic number
// Avoid any negative values if resizing gets cramped
x = x < 0 ? 0 : x;
y = y < 0 ? 0 : y;
screenPanel.setLocation(x, y);
}
/**
* <p>Close the light box</p>
* <p/>
* <p>During handover the dark panel is allowed to remain to give visual continuity</p>
*/
public void close() {
Preconditions.checkState(SwingUtilities.isEventDispatchThread(), "Must be on the EDT");
// NOTE: We cannot remove by reference reliably so this approach is used
// The light box panels are stacked in visual order therefore the usual index positions are
// 0: Wizard content panel (JPanel.WizardCardLayout)
// 1: Dark panel (LightBoxPanel)
// 2: Main content (JPanel.JRootPane)
//
// If a popover is present it is stacked over the light box leading to
// 0: Popover content panel (RoundedPanel)
// 1: Popover dark panel (LightBoxPanel)
// 2: Wizard content panel (JPanel.WizardCardLayout)
// 3: Dark panel (LightBoxPanel)
// 4: Main content (JPanel.JRootPane)
JLayeredPane layeredPane = Bither.getMainFrame().getLayeredPane();
Component[] components = layeredPane.getComponents();
if (log.isDebugEnabled()) {
for (int i = 0; i < components.length; i++) {
//log.debug("[{}]: {}", i, components[i].getClass().getSimpleName());
}
}
boolean popoverPresent = components.length > 3;
// Check for tooltips
if (components.length == 4 || components.length == 6) {
layeredPane.remove(0);
}
if (components.length > 2 && components.length < 7) {
// Remove the dark panel
layeredPane.remove(1);
// Remove the content panel (components will have shuffled)
layeredPane.remove(0);
if (popoverPresent) {
// log.debug("Popover light boxane panel removed from application frame");
} else {
// log.debug("Wizard light box panel removed from application frame");
}
} else {
// log.error("Unknown component hierarchy in light box.");
for (int i = 0; i < components.length; i++) {
// log.error("[{}]: {}", i, components[i].getClass().getSimpleName());
}
}
// Repaint
Bither.getMainFrame().validate();
Bither.getMainFrame().repaint();
}
@Override
protected void paintComponent(Graphics graphics) {
// Reposition the center panel on the fly
calculatePosition();
Graphics2D g = (Graphics2D) graphics;
// Always use black even for light themes
g.setPaint(Color.BLACK);
// Set the opacity
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f));
// Create the darkened border rectangle (will appear beneath the panel layer)
g.fillRect(0, 0, Bither.getMainFrame().getWidth(), Bither.getMainFrame().getHeight());
}
/**
* Prevent mouse events reaching through the light box border
*/
private class ModalMouseListener implements MouseListener {
@Override
public void mouseClicked(MouseEvent e) {
// Do nothing
}
@Override
public void mousePressed(MouseEvent e) {
// Do nothing
}
@Override
public void mouseReleased(MouseEvent e) {
// Do nothing
}
@Override
public void mouseEntered(MouseEvent e) {
// Do nothing
}
@Override
public void mouseExited(MouseEvent e) {
// Do nothing
}
}
}