All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.alee.extended.menu.WebDynamicMenu Maven / Gradle / Ivy

There is a newer version: 1.2.14
Show newest version
/*
 * This file is part of WebLookAndFeel library.
 *
 * WebLookAndFeel library is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * WebLookAndFeel library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with WebLookAndFeel library.  If not, see .
 */

package com.alee.extended.menu;

import com.alee.extended.image.WebImage;
import com.alee.global.StyleConstants;
import com.alee.laf.rootpane.WebWindow;
import com.alee.managers.focus.GlobalFocusListener;
import com.alee.utils.GeometryUtils;
import com.alee.utils.GraphicsUtils;
import com.alee.utils.swing.WebHeavyWeightPopup;
import com.alee.utils.swing.WebTimer;
import com.alee.utils.swing.WindowFollowAdapter;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import java.util.List;

/**
 * Custom dynamic menu with pretty display/hide animations.
 *
 * @author Mikle Garin
 */

public class WebDynamicMenu extends WebHeavyWeightPopup
{
    /**
     * todo 1. Add sliding down vertical menu (with text and selection background)
     */

    /**
     * Radius of the menu background in px.
     */
    protected int radius;

    /**
     * First element position angle in degrees.
     * Counted from the circle top-most point.
     */
    protected double startingAngle;

    /**
     * Angle which is available for menu elements.
     * It is 360 degress by default (whole circle available).
     */
    protected double angleRange;

    /**
     * Single animation step progress.
     * Making this value bigger will speedup the animation, reduce required resources but will also make it less soft.
     */
    protected float stepProgress;

    /**
     * Menu animation type.
     */
    protected DynamicMenuType type;

    /**
     * Menu hide animation type.
     */
    protected DynamicMenuType hideType;

    /**
     * Menu animation direction.
     */
    protected boolean clockwise;

    /**
     * Menu items list.
     */
    protected List items = new ArrayList ();

    /**
     * Menu display progress.
     */
    protected float currentProgress = 0f;

    /**
     * Actions synchronization object.
     */
    protected final Object sync = new Object ();

    /**
     * Animation timer.
     */
    protected WebTimer animator = null;

    /**
     * Invoker window follow adapter.
     */
    protected WindowFollowAdapter followAdapter;

    /**
     * Whether menu is being displayed or not.
     */
    protected boolean displaying = false;

    /**
     * Whether menu is being hidden or not.
     */
    protected boolean hiding = false;

    /**
     * Index of menu item that caused menu to close.
     * This might affect the hiding animation.
     */
    protected int hidingCause = -1;

    /**
     * Custom global mouse listener that closes menu.
     */
    protected AWTEventListener mouseListener;

    /**
     * Custom global focus listener that closes menu.
     */
    protected GlobalFocusListener focusListener;

    /**
     * Listeners synchronization object.
     */
    protected final Object lsync = new Object ();

    /**
     * Actions to perform on full display.
     */
    protected final List onFullDisplay = new ArrayList ();

    /**
     * Actions to perform on full hide.
     */
    protected final List onFullHide = new ArrayList ();

    /**
     * Constructs new dynamic menu.
     */
    public WebDynamicMenu ()
    {
        super ( "transparent", new DynamicMenuLayout () );
        setRadius ( 60 );
        setStartingAngle ( 0 );
        setAngleRange ( 360 );
        setStepProgress ( 0.04f );
        setType ( DynamicMenuType.shutter );
        setHideType ( null );
        setClockwise ( true );
        setWindowOpaque ( false );
        setFollowInvoker ( true );
    }

    public int getRadius ()
    {
        return radius;
    }

    public void setRadius ( final int radius )
    {
        this.radius = radius;
    }

    public double getStartingAngle ()
    {
        return startingAngle;
    }

    public void setStartingAngle ( final double startingAngle )
    {
        this.startingAngle = startingAngle;
    }

    public double getAngleRange ()
    {
        return angleRange;
    }

    public void setAngleRange ( final double angleRange )
    {
        this.angleRange = angleRange;
    }

    public float getStepProgress ()
    {
        return stepProgress;
    }

    public void setStepProgress ( final float stepProgress )
    {
        this.stepProgress = stepProgress;
    }

    public DynamicMenuType getType ()
    {
        return type;
    }

    public void setType ( final DynamicMenuType type )
    {
        this.type = type;
    }

    public DynamicMenuType getHideType ()
    {
        return hideType != null ? hideType : type;
    }

    public void setHideType ( final DynamicMenuType hideType )
    {
        this.hideType = hideType;
    }

    public boolean isClockwise ()
    {
        return clockwise;
    }

    public void setClockwise ( final boolean clockwise )
    {
        this.clockwise = clockwise;
    }

    public List getItems ()
    {
        return items;
    }

    public WebImage addItem ( final ImageIcon icon )
    {
        return addItem ( icon, null );
    }

    public WebImage addItem ( final ImageIcon icon, final ActionListener action )
    {
        return addItem ( new WebDynamicMenuItem ( icon, action ) );
    }

    public WebImage addItem ( final WebDynamicMenuItem item )
    {
        items.add ( item );

        final WebImage menuItem = new WebImage ( item.getIcon () )
        {
            @Override
            protected void paintComponent ( final Graphics g )
            {
                if ( item.isDrawBorder () )
                {
                    final Graphics2D g2d = ( Graphics2D ) g;
                    final Object aa = GraphicsUtils.setupAntialias ( g2d );
                    g2d.setPaint ( isEnabled () ? item.getBorderColor () : item.getDisabledBorderColor () );
                    g2d.fillOval ( 0, 0, getWidth (), getHeight () );
                    g2d.setColor ( Color.WHITE );
                    g2d.fillOval ( 2, 2, getWidth () - 4, getHeight () - 4 );
                    GraphicsUtils.restoreAntialias ( g2d, aa );
                }

                super.paintComponent ( g );
            }
        };
        menuItem.setEnabled ( item.getAction () != null );
        menuItem.setMargin ( item.getMargin () );

        menuItem.addMouseListener ( new MouseAdapter ()
        {
            @Override
            public void mousePressed ( final MouseEvent e )
            {
                final ActionListener action = item.getAction ();
                if ( action != null )
                {
                    action.actionPerformed ( new ActionEvent ( e.getSource (), 0, "Action performed" ) );
                }
                hideMenu ( getComponentZOrder ( menuItem ) );
            }
        } );
        add ( menuItem );

        return menuItem;
    }

    public float getCurrentProgress ()
    {
        return currentProgress;
    }

    public WebTimer getAnimator ()
    {
        return animator;
    }

    public boolean isDisplaying ()
    {
        return displaying;
    }

    public boolean isHiding ()
    {
        return hiding;
    }

    /**
     * Returns index of menu item that caused menu to close.
     *
     * @return index of menu item that caused menu to close
     */
    public int getHidingCause ()
    {
        return hidingCause;
    }

    /**
     * Displays dynamic menu for the specified invoker location.
     *
     * @param invoker  menu invoker
     * @param location menu location
     */
    public void showMenu ( final Component invoker, final Point location )
    {
        synchronized ( sync )
        {
            if ( displaying || window != null || getComponentCount () == 0 )
            {
                return;
            }

            displaying = true;

            // Stop displaying if we were
            if ( hiding )
            {
                if ( animator != null )
                {
                    animator.stop ();
                }
                hiding = false;
                hidingCause = -1;
            }

            // Creating menu and displaying it
            displayMenuWindow ( invoker, location );

            // Displaying menu softly
            animator = WebTimer.repeat ( StyleConstants.fastAnimationDelay, 0L, new ActionListener ()
            {
                @Override
                public void actionPerformed ( final ActionEvent e )
                {
                    synchronized ( sync )
                    {
                        if ( currentProgress < 1f )
                        {
                            currentProgress = Math.min ( currentProgress + stepProgress, 1f );
                            revalidate ();
                            setWindowOpacity ( currentProgress );
                        }
                        else
                        {
                            animator.stop ();
                            animator = null;
                            displaying = false;
                            fullyDisplayed ();
                        }
                    }
                }
            } );
        }
    }

    /**
     * Creates new menu window.
     *
     * @param invoker  menu invoker
     * @param location menu location
     */
    protected void displayMenuWindow ( final Component invoker, final Point location )
    {
        // Updating opacity
        setWindowOpacity ( currentProgress );

        // Displaying popup
        final Dimension size = getPreferredSize ();
        showPopup ( invoker, location.x - size.width / 2, location.y - size.height / 2 );
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public WebHeavyWeightPopup hidePopup ()
    {
        hideMenu ();
        return this;
    }

    /**
     * Hides dynamic menu.
     */
    public void hideMenu ()
    {
        hideMenu ( -1 );
    }

    /**
     * Hides dynamic menu.
     *
     * @param index menu item that forced menu to hide
     */
    public void hideMenu ( final int index )
    {
        synchronized ( sync )
        {
            if ( hiding )
            {
                return;
            }

            hiding = true;
            hidingCause = index;

            // Stop displaying if we were
            if ( displaying )
            {
                if ( animator != null )
                {
                    animator.stop ();
                }
                displaying = false;
            }

            // Hiding menu softly
            animator = WebTimer.repeat ( StyleConstants.fastAnimationDelay, 0L, new ActionListener ()
            {
                @Override
                public void actionPerformed ( final ActionEvent e )
                {
                    synchronized ( sync )
                    {
                        if ( currentProgress > 0f )
                        {
                            currentProgress = Math.max ( currentProgress - stepProgress, 0f );
                            revalidate ();
                            setWindowOpacity ( currentProgress );
                        }
                        else
                        {
                            destroyMenuWindow ();
                            animator.stop ();
                            animator = null;
                            hiding = false;
                            hidingCause = -1;
                            fullyHidden ();
                        }
                    }
                }
            } );
        }
    }

    /**
     * Disposes old menu window.
     */
    protected void destroyMenuWindow ()
    {
        // Disposing of menu window
        super.hidePopup ();
    }

    /**
     * Performs provided action when menu is fully displayed.
     * Might be useful to display sub-menus or perform some other actions.
     * Be aware that this action will be performed only once and then removed from the actions list.
     *
     * @param action action to perform
     */
    public void onFullDisplay ( final Runnable action )
    {
        synchronized ( lsync )
        {
            if ( !isShowing () || isDisplaying () )
            {
                onFullDisplay.add ( action );
            }
            else if ( isShowing () && !isHiding () )
            {
                action.run ();
            }
        }
    }

    /**
     * Performs actions waiting for menu display animation finish.
     */
    public void fullyDisplayed ()
    {
        synchronized ( lsync )
        {
            for ( final Runnable runnable : onFullDisplay )
            {
                runnable.run ();
            }
            onFullDisplay.clear ();
        }
    }

    /**
     * Performs provided action when menu is fully hidden.
     * Be aware that this action will be performed only once and then removed from the actions list.
     *
     * @param action action to perform
     */
    public void onFullHide ( final Runnable action )
    {
        synchronized ( lsync )
        {
            if ( isShowing () )
            {
                onFullHide.add ( action );
            }
            else
            {
                action.run ();
            }
        }
    }

    /**
     * Performs actions waiting for menu hide animation finish.
     */
    public void fullyHidden ()
    {
        synchronized ( lsync )
        {
            for ( final Runnable runnable : onFullHide )
            {
                runnable.run ();
            }
            onFullHide.clear ();
        }
    }

    /**
     * Returns menu item angle relative to vertical axis.
     *
     * @param item menu item
     * @return menu item center point angle
     */
    public double getItemAngle ( final Component item )
    {
        return getItemAngle ( getComponentZOrder ( item ) );
    }

    /**
     * Returns menu item angle relative to vertical axis.
     *
     * @param index menu item index
     * @return menu item center point angle
     */
    public double getItemAngle ( final int index )
    {
        return GeometryUtils.toDegrees ( getActualLayout ().getItemAngle ( this, index ) );
    }

    /**
     * Returns actual menu layout manager.
     *
     * @return actual menu layout manager
     */
    public DynamicMenuLayout getActualLayout ()
    {
        return ( DynamicMenuLayout ) getLayout ();
    }

    /**
     * Returns whether any dynamic menu is currently displayed or not.
     *
     * @return true if any dynamic menu is currently displayed, false otherwise
     */
    public static boolean isAnyDynamicMenuDisplayed ()
    {
        for ( final Window window : Window.getWindows () )
        {
            if ( window.isShowing () && window instanceof WebWindow &&
                    ( ( WebWindow ) window ).getContentPane () instanceof WebDynamicMenu )
            {
                return true;
            }
        }
        return false;
    }

    /**
     * Hides all visible dynamic menus.
     */
    public static void hideAllDynamicMenus ()
    {
        for ( final Window window : Window.getWindows () )
        {
            if ( window.isShowing () && window instanceof WebWindow &&
                    ( ( WebWindow ) window ).getContentPane () instanceof WebDynamicMenu )
            {
                final WebDynamicMenu menu = ( WebDynamicMenu ) ( ( WebWindow ) window ).getContentPane ();
                menu.hideMenu ();
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy