git-svn-id: https://samskivert.googlecode.com/svn/trunk@1098 6335cc39-0255-0410-8fd6-9bcaacd3b74c
This commit is contained in:
@@ -0,0 +1,172 @@
|
|||||||
|
//
|
||||||
|
// $Id: LabelSausage.java,v 1.1 2003/04/15 20:28:36 mdb Exp $
|
||||||
|
|
||||||
|
package com.samskivert.swing;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Dimension;
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
|
||||||
|
import javax.swing.Icon;
|
||||||
|
|
||||||
|
import com.samskivert.swing.util.SwingUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An abstract lightweight renderer that sizes and renders a label (with
|
||||||
|
* optional icon) in a roundy-ended sausage.
|
||||||
|
*/
|
||||||
|
public abstract class LabelSausage
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Constructs a label sausage.
|
||||||
|
*/
|
||||||
|
protected LabelSausage (Label label, Icon icon)
|
||||||
|
{
|
||||||
|
_label = label;
|
||||||
|
_icon = icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lays out the label sausage. It is assumed that the desired label
|
||||||
|
* font is already set in the label.
|
||||||
|
*/
|
||||||
|
protected void layout (Graphics2D gfx, int extraPadding)
|
||||||
|
{
|
||||||
|
// if we have an icon, let that dictate our size; otherwise just
|
||||||
|
// lay out our label all on one line
|
||||||
|
int sqwid, sqhei;
|
||||||
|
if (_icon == null) {
|
||||||
|
sqwid = sqhei = 0;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
sqwid = _icon.getIconWidth();
|
||||||
|
sqhei = _icon.getIconHeight();
|
||||||
|
_label.setTargetHeight(sqhei);
|
||||||
|
}
|
||||||
|
|
||||||
|
// lay out our label
|
||||||
|
_label.layout(gfx);
|
||||||
|
Dimension lsize = _label.getSize();
|
||||||
|
|
||||||
|
// if we have no icon, make sure that the label has enough room
|
||||||
|
if (_icon == null) {
|
||||||
|
sqhei = lsize.height + extraPadding * 2;
|
||||||
|
sqwid = extraPadding * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// compute the diameter of the circle that perfectly encompasses our
|
||||||
|
// icon
|
||||||
|
int hhei = sqhei / 2;
|
||||||
|
int hwid = sqwid / 2;
|
||||||
|
_dia = (int) (Math.sqrt(hwid * hwid + hhei * hhei) * 2);
|
||||||
|
|
||||||
|
// compute the x and y offsets at which we'll start rendering
|
||||||
|
_xoff = (_dia - sqwid) / 2;
|
||||||
|
_yoff = (_dia - sqhei) / 2;
|
||||||
|
|
||||||
|
// and for the label
|
||||||
|
_lxoff = _dia - _xoff;
|
||||||
|
_lyoff = (_dia - lsize.height) / 2;
|
||||||
|
|
||||||
|
// now compute our closed and open sizes
|
||||||
|
_size.height = _dia;
|
||||||
|
|
||||||
|
// width is the diameter of the circle that contains
|
||||||
|
// the icon plus space for the label when we're open
|
||||||
|
_size.width = _dia + lsize.width + _xoff;
|
||||||
|
|
||||||
|
// and if we are actually rendering the icon, we need to
|
||||||
|
// account for the space between it and the label.
|
||||||
|
if (_icon != null) {
|
||||||
|
_size.width += _xoff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Paints the label sausage.
|
||||||
|
*/
|
||||||
|
protected void paint (
|
||||||
|
Graphics2D gfx, int x, int y, Color background, Object cliData)
|
||||||
|
{
|
||||||
|
// turn on anti-aliasing
|
||||||
|
Object oalias = SwingUtil.activateAntiAliasing(gfx);
|
||||||
|
|
||||||
|
// draw the base sausage
|
||||||
|
gfx.setColor(background);
|
||||||
|
drawBase(gfx, x, y);
|
||||||
|
|
||||||
|
// render our icon if we've got one
|
||||||
|
if (_icon != null) {
|
||||||
|
_icon.paintIcon(null, gfx, x + _xoff, y + _yoff);
|
||||||
|
}
|
||||||
|
|
||||||
|
drawLabel(gfx, x, y);
|
||||||
|
drawBorder(gfx, x, y);
|
||||||
|
|
||||||
|
drawExtras(gfx, x, y, cliData);
|
||||||
|
|
||||||
|
// restore original hints
|
||||||
|
SwingUtil.restoreAntiAliasing(gfx, oalias);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draws the base sausage within which all the other decorations are
|
||||||
|
* added.
|
||||||
|
*/
|
||||||
|
protected void drawBase (Graphics2D gfx, int x, int y)
|
||||||
|
{
|
||||||
|
gfx.fillRoundRect(
|
||||||
|
x, y, _size.width - 1, _size.height - 1, _dia, _dia);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draws the label.
|
||||||
|
*/
|
||||||
|
protected void drawLabel (Graphics2D gfx, int x, int y)
|
||||||
|
{
|
||||||
|
_label.render(gfx, x + _lxoff, y + _lyoff);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draws the black outer border.
|
||||||
|
*/
|
||||||
|
protected void drawBorder (Graphics2D gfx, int x, int y)
|
||||||
|
{
|
||||||
|
// draw the black outer border
|
||||||
|
gfx.setColor(Color.black);
|
||||||
|
gfx.drawRoundRect(x, y, _size.width - 1, _size.height - 1,
|
||||||
|
_dia, _dia);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draws any extras that may be required.
|
||||||
|
*/
|
||||||
|
protected void drawExtras (Graphics2D gfx, int x, int y, Object cliData)
|
||||||
|
{
|
||||||
|
// nothing by default
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The label. */
|
||||||
|
protected Label _label;
|
||||||
|
|
||||||
|
/** The optional icon. */
|
||||||
|
protected Icon _icon;
|
||||||
|
|
||||||
|
/** The size of this label sausage. */
|
||||||
|
protected Dimension _size = new Dimension();
|
||||||
|
|
||||||
|
/** The diameter of the circle that perfectly surrounds our icon. */
|
||||||
|
protected int _dia = 0;
|
||||||
|
|
||||||
|
/** The x offset of the icon. */
|
||||||
|
protected int _xoff = 0;
|
||||||
|
|
||||||
|
/** The y offset of the icon. */
|
||||||
|
protected int _yoff = 0;
|
||||||
|
|
||||||
|
/** The y offset for the label. */
|
||||||
|
protected int _lyoff = 0;
|
||||||
|
|
||||||
|
/** The x offset for the label. */
|
||||||
|
protected int _lxoff = 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,195 @@
|
|||||||
|
//
|
||||||
|
// $Id: RadialLabelSausage.java,v 1.1 2003/04/15 20:28:36 mdb Exp $
|
||||||
|
|
||||||
|
package com.samskivert.swing;
|
||||||
|
|
||||||
|
import java.awt.AlphaComposite;
|
||||||
|
import java.awt.BasicStroke;
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Composite;
|
||||||
|
import java.awt.Font;
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
import java.awt.Rectangle;
|
||||||
|
import java.awt.Stroke;
|
||||||
|
|
||||||
|
import javax.swing.Icon;
|
||||||
|
import javax.swing.UIManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specializes the label sausage to suit the needs of items placed within
|
||||||
|
* a radial menu. The colors used to render the radial label sausage must
|
||||||
|
* be configured via the {@link UIManager}. These colors are:
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* RadialLabelSausage.activeBorder
|
||||||
|
* RadialLabelSausage.inactiveBorder
|
||||||
|
* RadialLabelSausage.background
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* This would be configured in a custom look and feel like so:
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* protected void initComponentDefaults (UIDefaults table)
|
||||||
|
* {
|
||||||
|
* super.initComponentDefaults(table);
|
||||||
|
* Object[] defaults = {
|
||||||
|
* "RadialLabelSausage.inactiveBorder", new ColorUIResource(0x478ABA),
|
||||||
|
* "RadialLabelSausage.activeBorder", new ColorUIResource(0xE0A000),
|
||||||
|
* "RadialLabelSausage.background", new ColorUIResource(0xD5E7E7),
|
||||||
|
* };
|
||||||
|
* table.putDefaults(defaults);
|
||||||
|
* }
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* An application that is not using a custom look and feel can simply set
|
||||||
|
* these values with {@link UIManager#put}.
|
||||||
|
*/
|
||||||
|
public class RadialLabelSausage extends LabelSausage
|
||||||
|
{
|
||||||
|
/** The dimensions of this item when we are in our closed state as
|
||||||
|
* well as our most recently laid out coordinates. This is only valid
|
||||||
|
* after a call to {@link #layout}. */
|
||||||
|
public Rectangle closedBounds = new Rectangle();
|
||||||
|
|
||||||
|
/** The dimensions of this item when we are in open state. This is
|
||||||
|
* only valid after a call to {@link #layout}. */
|
||||||
|
public Rectangle openBounds = new Rectangle();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a radial label sausage.
|
||||||
|
*/
|
||||||
|
public RadialLabelSausage (String label, Icon icon)
|
||||||
|
{
|
||||||
|
super(new Label(label), icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if this menu item should be enabled when it is
|
||||||
|
* displayed. The default implementation always returns true.
|
||||||
|
*/
|
||||||
|
public boolean isEnabled (RadialMenu menu)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether the label is to be rendered as an active label.
|
||||||
|
*/
|
||||||
|
public void setActive (boolean active)
|
||||||
|
{
|
||||||
|
_active = active;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes the dimensions of this label based on the specified font
|
||||||
|
* and in the specified graphics context.
|
||||||
|
*/
|
||||||
|
public void layout (Graphics2D gfx, Font font)
|
||||||
|
{
|
||||||
|
_label.setFont(font);
|
||||||
|
layout(gfx, BORDER_THICKNESS);
|
||||||
|
|
||||||
|
openBounds.width = _size.width;
|
||||||
|
openBounds.height = _size.height;
|
||||||
|
|
||||||
|
// and closed up, we're just a circle
|
||||||
|
closedBounds.height = closedBounds.width = _size.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
// documentation inherited
|
||||||
|
protected void drawLabel (Graphics2D gfx, int x, int y)
|
||||||
|
{
|
||||||
|
if (_active) {
|
||||||
|
super.drawLabel(gfx, x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// documentation inherited
|
||||||
|
protected void drawBorder (Graphics2D gfx, int x, int y)
|
||||||
|
{
|
||||||
|
// then around all that draw the borders
|
||||||
|
Stroke ostroke = gfx.getStroke();
|
||||||
|
gfx.setStroke(BORDER_STROKE);
|
||||||
|
if (_active) {
|
||||||
|
// draw the active border
|
||||||
|
gfx.setColor(UIManager.getColor("RadialLabelSausage.activeBorder"));
|
||||||
|
gfx.drawRoundRect(
|
||||||
|
x + (BORDER_THICKNESS / 2), y + (BORDER_THICKNESS / 2),
|
||||||
|
openBounds.width - 1 - BORDER_THICKNESS,
|
||||||
|
openBounds.height - 1 - BORDER_THICKNESS,
|
||||||
|
_dia - BORDER_THICKNESS, _dia - BORDER_THICKNESS);
|
||||||
|
|
||||||
|
// draw the black outer border
|
||||||
|
gfx.setStroke(ostroke);
|
||||||
|
super.drawBorder(gfx, x, y);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// draw the inactive border
|
||||||
|
gfx.setColor(UIManager.getColor(
|
||||||
|
"RadialLabelSausage.inactiveBorder"));
|
||||||
|
gfx.drawOval(
|
||||||
|
x + (BORDER_THICKNESS / 2), y + (BORDER_THICKNESS / 2),
|
||||||
|
closedBounds.width - 1 - BORDER_THICKNESS,
|
||||||
|
closedBounds.height - 1 - BORDER_THICKNESS);
|
||||||
|
|
||||||
|
// draw the black outer border
|
||||||
|
gfx.setStroke(ostroke);
|
||||||
|
gfx.setColor(Color.black);
|
||||||
|
gfx.drawOval(x, y, closedBounds.width - 1, closedBounds.height - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// documentation inherited
|
||||||
|
protected void drawExtras (Graphics2D gfx, int x, int y, Object cliData)
|
||||||
|
{
|
||||||
|
RadialMenu menu = (RadialMenu) cliData;
|
||||||
|
// finally, if we're dimmed out: dim us out
|
||||||
|
if (!isEnabled(menu)) {
|
||||||
|
Composite ocomp = gfx.getComposite();
|
||||||
|
gfx.setComposite(DISABLED_ALPHA);
|
||||||
|
gfx.setColor(Color.black);
|
||||||
|
drawBase(gfx, x, y);
|
||||||
|
gfx.setComposite(ocomp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draw the base circle or sausage within which all the other
|
||||||
|
* decorations are added.
|
||||||
|
*/
|
||||||
|
protected void drawBase (Graphics2D gfx, int x, int y)
|
||||||
|
{
|
||||||
|
if (_active) {
|
||||||
|
// render the sausage
|
||||||
|
super.drawBase(gfx, x, y);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// render the circle
|
||||||
|
gfx.fillOval(
|
||||||
|
x, y, closedBounds.width - 1, closedBounds.height - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Paints the radial label sausage.
|
||||||
|
*/
|
||||||
|
protected void paint (Graphics2D gfx, int x, int y, RadialMenu menu)
|
||||||
|
{
|
||||||
|
paint(gfx, x, y, UIManager.getColor(
|
||||||
|
"RadialLabelSausage.background"), menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Indicates whether or not this label is active. */
|
||||||
|
protected boolean _active = false;
|
||||||
|
|
||||||
|
/** The thickness of the colored active/inactive border. */
|
||||||
|
protected static final int BORDER_THICKNESS = 4;
|
||||||
|
|
||||||
|
/** The stroke to use when drawing the selected/unselected color. */
|
||||||
|
protected static final Stroke BORDER_STROKE =
|
||||||
|
new BasicStroke(BORDER_THICKNESS);
|
||||||
|
|
||||||
|
/** The alpha level for how much we dim an inactive menu item by. */
|
||||||
|
protected static final Composite DISABLED_ALPHA =
|
||||||
|
AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f);
|
||||||
|
}
|
||||||
@@ -0,0 +1,634 @@
|
|||||||
|
//
|
||||||
|
// $Id: RadialMenu.java,v 1.1 2003/04/15 20:28:36 mdb Exp $
|
||||||
|
|
||||||
|
package com.samskivert.swing;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Component;
|
||||||
|
import java.awt.Composite;
|
||||||
|
import java.awt.Font;
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
import java.awt.Image;
|
||||||
|
import java.awt.Point;
|
||||||
|
import java.awt.Rectangle;
|
||||||
|
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.awt.event.ActionListener;
|
||||||
|
import java.awt.event.MouseEvent;
|
||||||
|
import java.awt.event.MouseListener;
|
||||||
|
import java.awt.event.MouseMotionListener;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import javax.swing.Icon;
|
||||||
|
import javax.swing.ImageIcon;
|
||||||
|
import javax.swing.UIManager;
|
||||||
|
|
||||||
|
import com.samskivert.swing.event.CommandEvent;
|
||||||
|
import com.samskivert.swing.util.MouseHijacker;
|
||||||
|
import com.samskivert.swing.util.SwingUtil;
|
||||||
|
import com.samskivert.util.ObserverList;
|
||||||
|
import com.samskivert.util.StringUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a radial menu with iconic menu items that expand to include
|
||||||
|
* textual descriptions when moused over. Menu selections are posted as
|
||||||
|
* controller commands.
|
||||||
|
*/
|
||||||
|
public class RadialMenu
|
||||||
|
implements MouseMotionListener, MouseListener
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The interface used to communicate back to the component that hosts
|
||||||
|
* this menu.
|
||||||
|
*/
|
||||||
|
public static interface Host
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Returns the component on which we will be rendered.
|
||||||
|
*/
|
||||||
|
public Component getComponent ();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requests that the appropriate region of the host be repainted.
|
||||||
|
*/
|
||||||
|
public void repaintRect (int x, int y, int width, int height);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instructs the host component to stop rendering this menu
|
||||||
|
* because it has been popped down.
|
||||||
|
*/
|
||||||
|
public void menuDeactivated (RadialMenu menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to determine at the time that a menu is shown, whether its
|
||||||
|
* items should be included and/or enabled. Predicate methods are
|
||||||
|
* called immediately before showing a radial menu and thus should not
|
||||||
|
* perform complicated computations. They generally will check some
|
||||||
|
* simple model to determine a menu item's status.
|
||||||
|
*/
|
||||||
|
public static interface Predicate
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* If true, the menu will be included in the character menu when
|
||||||
|
* it is displayed.
|
||||||
|
*
|
||||||
|
* @param menu the menu that will include or not include the item
|
||||||
|
* in question.
|
||||||
|
* @param item the menu item that will or will not be included.
|
||||||
|
*/
|
||||||
|
public boolean isIncluded (RadialMenu menu, RadialMenuItem item);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If true, the menu will be enabled when displayed in a
|
||||||
|
* character's menu.
|
||||||
|
*
|
||||||
|
* @param menu the menu that contains the item in question.
|
||||||
|
* @param item the menu item that will or will not be enabled.
|
||||||
|
*/
|
||||||
|
public boolean isEnabled (RadialMenu menu, RadialMenuItem item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a radial menu.
|
||||||
|
*/
|
||||||
|
public RadialMenu ()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a menu item to the menu. The menu should not currently be
|
||||||
|
* active.
|
||||||
|
*
|
||||||
|
* @param command the command to be issued when the item is selected.
|
||||||
|
* @param label the textual label to be displayed with the menu item.
|
||||||
|
* @param icon the icon to display next to the menu text or null if no
|
||||||
|
* icon is desired.
|
||||||
|
* @param predicate a predicate that will be used to determine whether
|
||||||
|
* or not this menu item should be included in the menu and enabled
|
||||||
|
* when it is shown.
|
||||||
|
*
|
||||||
|
* @return the item that was added to the menu. It can be modified
|
||||||
|
* while the menu is not being displayed.
|
||||||
|
*/
|
||||||
|
public RadialMenuItem addMenuItem (String command, String label,
|
||||||
|
Image icon, Predicate predicate)
|
||||||
|
{
|
||||||
|
RadialMenuItem item = new RadialMenuItem(
|
||||||
|
command, label, new ImageIcon(icon), predicate);
|
||||||
|
addMenuItem(item);
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an already constructed menu item to the menu. The menu should
|
||||||
|
* not currently be active.
|
||||||
|
*
|
||||||
|
* @param item the menu item instance to be added.
|
||||||
|
*/
|
||||||
|
public void addMenuItem (RadialMenuItem item, boolean makeDefault)
|
||||||
|
{
|
||||||
|
addMenuItem(item);
|
||||||
|
if (makeDefault) {
|
||||||
|
_defaultItem = item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an already constructed menu item to the menu. The menu should
|
||||||
|
* not currently be active.
|
||||||
|
*
|
||||||
|
* @param item the menu item instance to be added.
|
||||||
|
*/
|
||||||
|
public void addMenuItem (RadialMenuItem item)
|
||||||
|
{
|
||||||
|
_items.add(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the specified menu item from this menu.
|
||||||
|
*/
|
||||||
|
public void removeMenuItem (RadialMenuItem item)
|
||||||
|
{
|
||||||
|
_items.remove(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the first menu item that matches the specified command from
|
||||||
|
* this menu.
|
||||||
|
*
|
||||||
|
* @return true if a matching menu item was removed, false if not.
|
||||||
|
*/
|
||||||
|
public boolean removeMenuItem (String command)
|
||||||
|
{
|
||||||
|
int icount = _items.size();
|
||||||
|
for (int i = 0; i < icount; i++) {
|
||||||
|
RadialMenuItem item = (RadialMenuItem)_items.get(i);
|
||||||
|
if (item.command.equals(command)) {
|
||||||
|
_items.remove(i);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a listener that will be notified when a menu item is
|
||||||
|
* selected. When the the menu is popped down, all listeners will be
|
||||||
|
* cleared.
|
||||||
|
*/
|
||||||
|
public void addActionListener (ActionListener listener)
|
||||||
|
{
|
||||||
|
_actlist.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the optional centerpiece icon to be displayed in the center of
|
||||||
|
* the menu.
|
||||||
|
*/
|
||||||
|
public void setCenterIcon (Icon icon)
|
||||||
|
{
|
||||||
|
_centerIcon = icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activates the radial menu, rendering a menu around the prescribed
|
||||||
|
* bounds. It is expected that the host component will subsequently
|
||||||
|
* call {@link #render} if the menu invalidates the region of the
|
||||||
|
* component occupied by the menu and requests it to repaint.
|
||||||
|
*
|
||||||
|
* @param host the host component within which the radial menu is
|
||||||
|
* displayed.
|
||||||
|
* @param bounds the bounds of the object that was clicked to activate
|
||||||
|
* the menu.
|
||||||
|
* @param argument a reference to an object that will be provided
|
||||||
|
* along with the command that is issued if the user selects a menu
|
||||||
|
* item (unless that item has an overriding argument, in which case
|
||||||
|
* the overriding argument will be used).
|
||||||
|
*/
|
||||||
|
public void activate (Host host, Rectangle bounds, Object argument)
|
||||||
|
{
|
||||||
|
setActivationArgument(argument);
|
||||||
|
activate(host, bounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activates the radial menu, rendering a menu around the prescribed
|
||||||
|
* bounds. It is expected that the host component will subsequently
|
||||||
|
* call {@link #render} if the menu invalidates the region of the
|
||||||
|
* component occupied by the menu and requests it to repaint.
|
||||||
|
*
|
||||||
|
* @param host the host component within which the radial menu is
|
||||||
|
* displayed.
|
||||||
|
* @param bounds the bounds of the object that was clicked to activate
|
||||||
|
* the menu.
|
||||||
|
*/
|
||||||
|
public void activate (Host host, Rectangle bounds)
|
||||||
|
{
|
||||||
|
_host = host;
|
||||||
|
_tbounds = bounds;
|
||||||
|
_poptime = System.currentTimeMillis();
|
||||||
|
_msMode = false;
|
||||||
|
|
||||||
|
Component comp = _host.getComponent();
|
||||||
|
_hijacker = new MouseHijacker(comp);
|
||||||
|
|
||||||
|
// start listening to the host component
|
||||||
|
comp.addMouseListener(this);
|
||||||
|
comp.addMouseMotionListener(this);
|
||||||
|
|
||||||
|
// lay ourselves out and repaint
|
||||||
|
layout();
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the argument that will be defaultly posted along with commands.
|
||||||
|
*/
|
||||||
|
public void setActivationArgument (Object arg)
|
||||||
|
{
|
||||||
|
_argument = arg;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the argument provided to this menu when {@link #activate}
|
||||||
|
* was called. This will only be non-null while the menu is active and
|
||||||
|
* only then if an argument was provided in the call to {@link
|
||||||
|
* #activate}.
|
||||||
|
*/
|
||||||
|
public Object getActivationArgument ()
|
||||||
|
{
|
||||||
|
return _argument;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deactivates the menu.
|
||||||
|
*/
|
||||||
|
public void deactivate ()
|
||||||
|
{
|
||||||
|
Component comp = _host.getComponent();
|
||||||
|
|
||||||
|
// unwire ourselves from the host component
|
||||||
|
comp.removeMouseListener(this);
|
||||||
|
comp.removeMouseMotionListener(this);
|
||||||
|
|
||||||
|
// reinstate the previous mouse listeners
|
||||||
|
_hijacker.release();
|
||||||
|
_hijacker = null;
|
||||||
|
|
||||||
|
// tell our host that we are no longer
|
||||||
|
_host.menuDeactivated(this);
|
||||||
|
|
||||||
|
// fire off a last repaint to clean up after ourselves
|
||||||
|
repaint();
|
||||||
|
|
||||||
|
// clear out our references
|
||||||
|
_host = null;
|
||||||
|
_tbounds = null;
|
||||||
|
_argument = null;
|
||||||
|
|
||||||
|
// clear out our action listeners
|
||||||
|
_actlist.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the current configuration of this menu.
|
||||||
|
*/
|
||||||
|
public void render (Graphics2D gfx)
|
||||||
|
{
|
||||||
|
Component host = _host.getComponent();
|
||||||
|
|
||||||
|
int x = _bounds.x, y = _bounds.y;
|
||||||
|
|
||||||
|
if (_centerLabel != null) {
|
||||||
|
// render the centerpiece label
|
||||||
|
_centerLabel.paint(gfx, x + _centerLabel.closedBounds.x,
|
||||||
|
y + _centerLabel.closedBounds.y, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// render each of our items in turn
|
||||||
|
int icount = _items.size();
|
||||||
|
for (int i = 0; i < icount; i++) {
|
||||||
|
RadialMenuItem item = (RadialMenuItem)_items.get(i);
|
||||||
|
if (!item.isIncluded(this)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// we have to wait and render the active item last
|
||||||
|
if (item != _activeItem) {
|
||||||
|
item.render(host, this, gfx, x + item.closedBounds.x,
|
||||||
|
y + item.closedBounds.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_activeItem != null) {
|
||||||
|
_activeItem.render(host, this, gfx, x + _activeItem.closedBounds.x,
|
||||||
|
y + _activeItem.closedBounds.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
// render our bounds
|
||||||
|
// gfx.setColor(Color.green);
|
||||||
|
// gfx.draw(_bounds);
|
||||||
|
// gfx.setColor(Color.blue);
|
||||||
|
// gfx.draw(_tbounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
// documentation inherited
|
||||||
|
public void mouseDragged (MouseEvent event)
|
||||||
|
{
|
||||||
|
// same as moved
|
||||||
|
mouseMoved(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
// documentation inherited
|
||||||
|
public void mouseMoved (MouseEvent event)
|
||||||
|
{
|
||||||
|
int x = event.getX(), y = event.getY();
|
||||||
|
// translate the coords into our space
|
||||||
|
x -= _bounds.x;
|
||||||
|
y -= _bounds.y;
|
||||||
|
boolean repaint = false;
|
||||||
|
|
||||||
|
// see if we dragged out of the active item
|
||||||
|
if (_activeItem != null) {
|
||||||
|
// oldway: if (!_activeItem.openBounds.contains(x, y)) {
|
||||||
|
if (!_activeItem.closedBounds.contains(x, y)) {
|
||||||
|
_activeItem.setActive(false);
|
||||||
|
_activeItem = null;
|
||||||
|
repaint = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// see if we dragged into a new menu item
|
||||||
|
if ((_activeItem == null) &&
|
||||||
|
(event.getWhen() > _poptime + DEBOUNCE_DELAY)) {
|
||||||
|
|
||||||
|
int icount = _items.size();
|
||||||
|
for (int i = 0; i < icount; i++) {
|
||||||
|
RadialMenuItem item = (RadialMenuItem)_items.get(i);
|
||||||
|
if (item.isIncluded(this) &&
|
||||||
|
item.closedBounds.contains(x, y)) {
|
||||||
|
_activeItem = item;
|
||||||
|
_activeItem.setActive(true);
|
||||||
|
repaint = true;
|
||||||
|
|
||||||
|
// if we got here with a mouse moved, we've definately
|
||||||
|
// made the decision to be in non drag mode
|
||||||
|
if (event.getID() == MouseEvent.MOUSE_MOVED) {
|
||||||
|
_msMode = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// repaint ourselves if something changed
|
||||||
|
if (repaint) {
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// documentation inherited
|
||||||
|
public void mouseClicked (MouseEvent event)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// documentation inherited
|
||||||
|
public void mouseEntered (MouseEvent event)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// documentation inherited
|
||||||
|
public void mouseExited (MouseEvent event)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// documentation inherited
|
||||||
|
public void mousePressed (MouseEvent event)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// documentation inherited
|
||||||
|
public void mouseReleased (MouseEvent event)
|
||||||
|
{
|
||||||
|
if (_activeItem == null) {
|
||||||
|
long when = event.getWhen();
|
||||||
|
if (!_msMode &&
|
||||||
|
(when < _poptime + DRAGMODE_DELAY)) {
|
||||||
|
// if the dragmode delay time hasn't passed, then we're
|
||||||
|
// going to leave the menu up because the user wants it
|
||||||
|
// to behave like a MS Windows menu.
|
||||||
|
_msMode = true;
|
||||||
|
return;
|
||||||
|
|
||||||
|
// if they've double clicked, then activate the default item
|
||||||
|
} else if (_msMode && (when < _poptime + DOUBLECLICK_DELAY)) {
|
||||||
|
_activeItem = _defaultItem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// deactivate the active item and post a command for it
|
||||||
|
if (_activeItem != null) {
|
||||||
|
// only process the selection if the item is enabled
|
||||||
|
if (_activeItem.isEnabled(this)) {
|
||||||
|
// if the item has an overriding argument, use that
|
||||||
|
Object itemArg = _activeItem.argument;
|
||||||
|
Object arg = (itemArg == null) ? _argument : itemArg;
|
||||||
|
|
||||||
|
// post the action
|
||||||
|
final CommandEvent evt = new CommandEvent(
|
||||||
|
_host.getComponent(), _activeItem.command, arg);
|
||||||
|
Controller.postAction(evt);
|
||||||
|
|
||||||
|
// and notify our listeners
|
||||||
|
_actlist.apply(new ObserverList.ObserverOp() {
|
||||||
|
public boolean apply (Object obs) {
|
||||||
|
((ActionListener)obs).actionPerformed(evt);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_activeItem.setActive(false);
|
||||||
|
_activeItem = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// deactive ourselves
|
||||||
|
deactivate();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lays the radial menu items out based on the currently configured
|
||||||
|
* bounds information.
|
||||||
|
*/
|
||||||
|
protected void layout ()
|
||||||
|
{
|
||||||
|
Graphics2D gfx = (Graphics2D)_host.getComponent().getGraphics();
|
||||||
|
Font font = gfx.getFont();
|
||||||
|
_bounds = new Rectangle();
|
||||||
|
|
||||||
|
// figure out which items are included
|
||||||
|
int icount = _items.size();
|
||||||
|
ArrayList items = new ArrayList();
|
||||||
|
for (int i = 0; i < icount; i++) {
|
||||||
|
RadialMenuItem item = (RadialMenuItem)_items.get(i);
|
||||||
|
if (item.isIncluded(this)) {
|
||||||
|
items.add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// lay out all of our menu items
|
||||||
|
int maxwid = 0, maxhei = 0;
|
||||||
|
icount = items.size();
|
||||||
|
for (int i = 0; i < icount; i++) {
|
||||||
|
RadialMenuItem item = (RadialMenuItem)items.get(i);
|
||||||
|
item.layout(gfx, font);
|
||||||
|
|
||||||
|
// track maximum menu item size
|
||||||
|
if (item.closedBounds.width > maxwid) {
|
||||||
|
maxwid = item.closedBounds.width;
|
||||||
|
}
|
||||||
|
if (item.closedBounds.height > maxhei) {
|
||||||
|
maxhei = item.closedBounds.height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
gfx.dispose();
|
||||||
|
|
||||||
|
// use the maximum of either width or height and make a circle
|
||||||
|
// around that
|
||||||
|
double radius = Math.max(_tbounds.height, _tbounds.width) / 2;
|
||||||
|
|
||||||
|
// be sure to add a gap and space for the menu item itself
|
||||||
|
radius += (5 + maxwid/2);
|
||||||
|
|
||||||
|
// compute the angle between menu items (we use the diameter of
|
||||||
|
// the menu items as an approximate measure of the distance along
|
||||||
|
// the circumference)
|
||||||
|
double theta = (maxwid + 10) / radius ;
|
||||||
|
|
||||||
|
// now position each item accordingly
|
||||||
|
double angle = -Math.PI/2;
|
||||||
|
for (int i = 0; i < icount; i++) {
|
||||||
|
RadialMenuItem item = (RadialMenuItem)items.get(i);
|
||||||
|
int ix = (int)(radius * Math.cos(angle));
|
||||||
|
int iy = (int)(radius * Math.sin(angle));
|
||||||
|
item.openBounds.x = item.closedBounds.x = ix - maxwid/2;
|
||||||
|
item.openBounds.y = item.closedBounds.y = iy - maxhei/2;
|
||||||
|
|
||||||
|
// move along the circle
|
||||||
|
angle += theta;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create and position the centerpiece label
|
||||||
|
if (_centerIcon != null) {
|
||||||
|
_centerLabel = new RadialLabelSausage("", _centerIcon);
|
||||||
|
_centerLabel.layout(gfx, font);
|
||||||
|
_centerLabel.openBounds.x = _centerLabel.closedBounds.x =
|
||||||
|
-(_centerLabel.closedBounds.width / 2);
|
||||||
|
_centerLabel.openBounds.y = _centerLabel.closedBounds.y =
|
||||||
|
-(_centerLabel.closedBounds.height / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// now compute the rectangle that encloses the entire menu
|
||||||
|
|
||||||
|
// include the bounds for the centerpiece label
|
||||||
|
if (_centerLabel != null) {
|
||||||
|
_bounds.add(_centerLabel.openBounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
// include the bounds for all menu items
|
||||||
|
for (int i = 0; i < icount; i++) {
|
||||||
|
RadialMenuItem item = (RadialMenuItem)items.get(i);
|
||||||
|
// we need the open bounds rather than the closed ones
|
||||||
|
_bounds.add(item.openBounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
// now translate everything from the center of the target bounds
|
||||||
|
// to the upper left of the menu bounds
|
||||||
|
if (_centerLabel != null) {
|
||||||
|
_centerLabel.openBounds.translate(-_bounds.x, -_bounds.y);
|
||||||
|
_centerLabel.closedBounds.translate(-_bounds.x, -_bounds.y);
|
||||||
|
}
|
||||||
|
for (int i = 0; i < icount; i++) {
|
||||||
|
RadialMenuItem item = (RadialMenuItem)items.get(i);
|
||||||
|
item.openBounds.translate(-_bounds.x, -_bounds.y);
|
||||||
|
item.closedBounds.translate(-_bounds.x, -_bounds.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
// the origin was at the center of the encircled rectangle; we
|
||||||
|
// need to translate it such that the origin of our bounds are in
|
||||||
|
// screen coordinates and at the upper left rather than the center
|
||||||
|
_bounds.x += (_tbounds.x + _tbounds.width/2);
|
||||||
|
_bounds.y += (_tbounds.y + _tbounds.height/2);
|
||||||
|
|
||||||
|
// now make sure the whole shebang is fully visible within the
|
||||||
|
// host component
|
||||||
|
Rectangle hbounds = new Rectangle(_host.getComponent().getSize());
|
||||||
|
Point pos = SwingUtil.fitRectInRect(_bounds, hbounds);
|
||||||
|
_bounds.setLocation(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requests that our host component repaint the part of itself that we
|
||||||
|
* occupy.
|
||||||
|
*/
|
||||||
|
protected void repaint ()
|
||||||
|
{
|
||||||
|
// only repaint the area that we overlap
|
||||||
|
_host.repaintRect(_bounds.x, _bounds.y,
|
||||||
|
_bounds.width+1, _bounds.height+1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Our host component. */
|
||||||
|
protected Host _host;
|
||||||
|
|
||||||
|
/** The bounds around which we're rendering a menu. */
|
||||||
|
protected Rectangle _tbounds;
|
||||||
|
|
||||||
|
/** The bounds that all of our menu items occupy. */
|
||||||
|
protected Rectangle _bounds;
|
||||||
|
|
||||||
|
/** The argument object, which will be delivered along with a posted
|
||||||
|
* command when a menu item is selected. */
|
||||||
|
protected Object _argument;
|
||||||
|
|
||||||
|
/** The centerpiece icon or null if there is none. */
|
||||||
|
protected Icon _centerIcon;
|
||||||
|
|
||||||
|
/** The label used to render the centerpiece icon if there is one. */
|
||||||
|
protected RadialLabelSausage _centerLabel;
|
||||||
|
|
||||||
|
/** Our menu items. */
|
||||||
|
protected ArrayList _items = new ArrayList();
|
||||||
|
|
||||||
|
/** The item that has the mouse over it presently, and the default item
|
||||||
|
* if we're double clicked. */
|
||||||
|
protected RadialMenuItem _activeItem, _defaultItem;
|
||||||
|
|
||||||
|
/** The amount of time the user must hold down the mouse in order
|
||||||
|
* to put it in drag mode, after which the first mouse-up will pop
|
||||||
|
* down the menu. */
|
||||||
|
protected static final long DRAGMODE_DELAY = 1000L;
|
||||||
|
|
||||||
|
/** The amount of time after the menu has been popped up before we
|
||||||
|
* accept selections. */
|
||||||
|
protected static final long DEBOUNCE_DELAY = 200L;
|
||||||
|
|
||||||
|
/** How quickly a second click must arrive for us to count it as a double
|
||||||
|
* click and not two single clicks. */
|
||||||
|
protected static final long DOUBLECLICK_DELAY = 400L;
|
||||||
|
|
||||||
|
/** The time at which the menu was popped up. */
|
||||||
|
protected long _poptime;
|
||||||
|
|
||||||
|
/** Whether we're popping the menu up in microsoft mode, where the
|
||||||
|
* user does not need to drag to the item they want to select. */
|
||||||
|
protected boolean _msMode = false;
|
||||||
|
|
||||||
|
/** This hijacks and holds onto the previous mouse listeners. */
|
||||||
|
protected MouseHijacker _hijacker;
|
||||||
|
|
||||||
|
/** Maintains a list of action listeners. */
|
||||||
|
protected ObserverList _actlist = new ObserverList(
|
||||||
|
ObserverList.SAFE_IN_ORDER_NOTIFY);
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
//
|
||||||
|
// $Id: RadialMenuItem.java,v 1.1 2003/04/15 20:28:36 mdb Exp $
|
||||||
|
|
||||||
|
package com.samskivert.swing;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Component;
|
||||||
|
import java.awt.Font;
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
|
||||||
|
import javax.swing.Icon;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to track info for each menu item in a {@link RadialMenu}.
|
||||||
|
*/
|
||||||
|
public class RadialMenuItem extends RadialLabelSausage
|
||||||
|
{
|
||||||
|
/** The command to issue if this item is selected. */
|
||||||
|
public String command;
|
||||||
|
|
||||||
|
/** A special argument to be used when this menu item is selected,
|
||||||
|
* rather than the argument provided to the radial menu when it was
|
||||||
|
* activated. */
|
||||||
|
public Object argument;
|
||||||
|
|
||||||
|
/** Used to determine whether or not this menu item should be included
|
||||||
|
* in a menu and whether or not it should be enabled. If no predicate
|
||||||
|
* is available, a menu item is assumed always to be included and
|
||||||
|
* enabled. */
|
||||||
|
public RadialMenu.Predicate predicate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a radial menu item with the specified command and label.
|
||||||
|
* No icon or menu predicate will be used.
|
||||||
|
*/
|
||||||
|
public RadialMenuItem (String command, String label)
|
||||||
|
{
|
||||||
|
this(command, label, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a radial menu item with the specified command, label and
|
||||||
|
* icon. No menu predicate will be used.
|
||||||
|
*/
|
||||||
|
public RadialMenuItem (String command, String label, Icon icon)
|
||||||
|
{
|
||||||
|
this(command, label, icon, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a radial menu item with the specified command, label and
|
||||||
|
* icon.
|
||||||
|
*/
|
||||||
|
public RadialMenuItem (String command, String label, Icon icon,
|
||||||
|
RadialMenu.Predicate predicate)
|
||||||
|
{
|
||||||
|
super(label, icon);
|
||||||
|
|
||||||
|
_label.setTextColor(Color.black);
|
||||||
|
|
||||||
|
this.command = command;
|
||||||
|
this.predicate = predicate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if this menu item should be included in a menu when it
|
||||||
|
* is displayed. Calls through to the {@link RadialMenu.Predicate} if
|
||||||
|
* we have one.
|
||||||
|
*/
|
||||||
|
public boolean isIncluded (RadialMenu menu)
|
||||||
|
{
|
||||||
|
return (predicate == null || predicate.isIncluded(menu, this));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if this menu item should be enabled when it is
|
||||||
|
* displayed. Calls through to the {@link RadialMenu.Predicate} if we
|
||||||
|
* have one.
|
||||||
|
*/
|
||||||
|
public boolean isEnabled (RadialMenu menu)
|
||||||
|
{
|
||||||
|
return (predicate == null || predicate.isEnabled(menu, this));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Menu items are equal if their commands are equal. We also
|
||||||
|
* declare ourselves to be equal to a string with the same value
|
||||||
|
* as our command.
|
||||||
|
*/
|
||||||
|
public boolean equals (Object other)
|
||||||
|
{
|
||||||
|
if (other instanceof RadialMenuItem) {
|
||||||
|
return command.equals(((RadialMenuItem)other).command);
|
||||||
|
} else if (other instanceof String) {
|
||||||
|
return command.equals(other);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders this menu item at the specified location.
|
||||||
|
*/
|
||||||
|
public void render (
|
||||||
|
Component host, RadialMenu menu, Graphics2D gfx, int x, int y)
|
||||||
|
{
|
||||||
|
paint(gfx, x, y, menu);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user