Здесь вы go ... Около 463 строк кода, чтобы вы начали ...
public class Final {
public static enum MouseEventFlag {
KEY_ALT, KEY_ALTGRAPH, KEY_CTRL, KEY_META, KEY_SHIFT, BUTTON_LEFT, BUTTON_MIDDLE, BUTTON_RIGHT;
public static EnumSet<MouseEventFlag> buttonSetOf(final MouseEvent mevt) {
final EnumSet<MouseEventFlag> set = EnumSet.noneOf(MouseEventFlag.class);
if (SwingUtilities.isLeftMouseButton(mevt)) set.add(BUTTON_LEFT);
if (SwingUtilities.isMiddleMouseButton(mevt)) set.add(BUTTON_MIDDLE);
if (SwingUtilities.isRightMouseButton(mevt)) set.add(BUTTON_RIGHT);
return set;
}
}
//Converts EnumSet to mask:
public static long pack(final EnumSet<?> set) { //Supports Enums with up to 64 values.
//return set.stream().mapToLong(e -> (1L << e.ordinal())).reduce(0L, (L1, L2) -> L1 | L2);
long L = 0;
for (final Enum e: set)
L = L | (1L << e.ordinal());
return L;
}
//Converts Enums to mask:
public static <E extends Enum<E>> long pack(final E... ez) { //Supports Enums with up to 64 values.
long L = 0;
for (final Enum e: ez)
L = L | (1L << e.ordinal());
return L;
}
public static Color transparent(final Color c, final int alpha) {
return new Color(c.getRed(), c.getGreen(), c.getBlue(), alpha);
}
public static void setOperatedSize(final Dimension input, final BiFunction<Integer, Integer, Integer> operator, final Dimension inputOutput) {
inputOutput.setSize(operator.apply(input.width, inputOutput.width), operator.apply(input.height, inputOutput.height));
}
//Prompts user to input some text.
public static void inputTitle(final Component parent, final String titleID, final Consumer<String> consumer) {
final String userIn = JOptionPane.showInputDialog(parent, "Enter " + titleID.toLowerCase() + "'s title:", "Enter title", JOptionPane.QUESTION_MESSAGE);
if (userIn != null) {
if (userIn.isEmpty())
JOptionPane.showMessageDialog(parent, titleID + "'s name cannot be empty...", "Oups!", JOptionPane.INFORMATION_MESSAGE);
else
consumer.accept(userIn);
}
}
//Applies an action to every child component of the container recursively as well as to the given Container.
public static void consumeComponentsRecursively(final Container container, final Consumer<Component> consumer) {
for (final Component child: container.getComponents())
if (child instanceof Container)
consumeComponentsRecursively((Container) child, consumer);
else
consumer.accept(child);
consumer.accept(container);
}
public static Dimension getGoodEnoughSize(final Component comp, final Dimension defaultSize) {
final Dimension dim = new Dimension(defaultSize);
if (comp != null) { // && comp.isVisible()) {
/*Start with default size, and then listen to max and min
(if both max and min are set, we prefer the min one):*/
if (comp.isMaximumSizeSet())
setOperatedSize(comp.getMaximumSize(), Math::min, dim);
if (comp.isMinimumSizeSet())
setOperatedSize(comp.getMinimumSize(), Math::max, dim);
}
return dim;
}
public static class ManualLayout implements LayoutManager, Serializable {
public Dimension getLayoutComponentSize(final Component comp) {
return getGoodEnoughSize(comp, (comp.getWidth() <= 0 && comp.getHeight() <= 0)? comp.getPreferredSize(): comp.getSize());
}
@Override public void addLayoutComponent(final String name, final Component comp) { }
@Override public void removeLayoutComponent(final Component comp) { }
@Override public Dimension preferredLayoutSize(final Container parent) { return minimumLayoutSize(parent); } //Preferred and minimum coincide for simplicity.
@Override
public Dimension minimumLayoutSize(final Container parent) {
final Component[] comps = parent.getComponents();
if (comps == null || comps.length <= 0)
return new Dimension();
final Rectangle totalBounds = new Rectangle(comps[0].getLocation(), getLayoutComponentSize(comps[0]));
for (int i = 1; i < comps.length; ++i)
totalBounds.add(new Rectangle(comps[i].getLocation(), getLayoutComponentSize(comps[i])));
return new Dimension(totalBounds.x + totalBounds.width, totalBounds.y + totalBounds.height);
}
@Override
public void layoutContainer(final Container parent) {
for (final Component comp: parent.getComponents())
comp.setSize(getLayoutComponentSize(comp)); //Just set the size. The locations are taken care by the class's client supposedly.
}
}
public static abstract class RectangularPanel<R extends RectangularShape> extends JPanel {
private R shape;
private Area cache; /*Use a cache so as not to have to create new Areas every time we need
to intersect the clip (see 'createClip' method). Also, the client can modify the current
shape but not the cache upon which the shape and painting of the panel depend.*/
public RectangularPanel(final double width, final double height) {
super();
super.setOpaque(false);
cache = new Area(shape = createShape(0, 0, width, height));
super.addComponentListener(new ComponentAdapter() {
@Override
public void componentResized(final ComponentEvent cevt) {
cache = new Area(shape = createShape(0, 0, getWidth(), getHeight()));
revalidate();
repaint();
}
});
}
protected abstract R createShape(final double x, final double y, final double width, final double height);
protected abstract double getArcWidth();
protected abstract double getArcHeight();
protected final R getShape() { return shape; }
@Override public boolean contains(final int x, final int y) { return cache.contains(x, y); }
protected Shape createClip(final Shape originalClip) {
if (originalClip == null)
return cache;
final Area clip = new Area(originalClip);
clip.intersect(cache);
return clip;
}
@Override
public void paint(final Graphics g) { //print() and update() rely on paint(), so we only need to override this one...
g.setClip(createClip(g.getClip()));
super.paint(g);
}
}
public static class VennTool implements Runnable {
protected static final Object LAYER_USER_CONTROLS = JLayeredPane.DEFAULT_LAYER, LAYER_VENN_SET = JLayeredPane.PALETTE_LAYER, LAYER_VENN_LABEL = JLayeredPane.MODAL_LAYER;
public class VennDrawPanel extends JPanel {
private final JLayeredPane pane;
private final int drawingOffsetY, drawingOffsetX;
private final JCheckBox attachMode, collisionMode;
private final JPanel ctrl;
public VennDrawPanel(final GraphicsConfiguration gconf) {
super(new BorderLayout());
pane = new JLayeredPane();
super.add(pane, BorderLayout.CENTER);
final ManualLayout layout = new ManualLayout();
pane.setLayout(layout);
final Dimension prefsz = new Dimension(gconf.getBounds().getSize());
prefsz.width = (2 * prefsz.width) / 3;
prefsz.height = (2 * prefsz.height) / 3;
final JButton createLabel = new JButton("Create label");
final JButton createSet = new JButton("Create set");
attachMode = new JCheckBox("Attach mode", true);
collisionMode = new JCheckBox("Collision mode", true);
ctrl = new JPanel(new FlowLayout(FlowLayout.LEADING, 0, 5));
ctrl.add(createLabel);
ctrl.add(createSet);
ctrl.add(attachMode);
ctrl.add(collisionMode);
drawingOffsetX = layout.getLayoutComponentSize(ctrl).width;
prefsz.width = Math.max(prefsz.width, drawingOffsetX);
drawingOffsetY = prefsz.height / 8;
pane.setPreferredSize(prefsz);
pane.add(ctrl, LAYER_USER_CONTROLS);
createLabel.addActionListener(e -> inputTitle(this, "Label", VennTool.this::createLabel));
createSet.addActionListener(e -> inputTitle(this, "Set", VennTool.this::createSet));
}
protected void setControlsEnabled(final boolean enable) { consumeComponentsRecursively(ctrl, c -> c.setEnabled(enable)); }
public boolean isAttachModeSelected() { return attachMode.isSelected(); }
public boolean isCollisionModeSelected() { return collisionMode.isSelected(); }
protected Point getCreationLocation() { return new Point(drawingOffsetX + 50, drawingOffsetY / 2); }
public void addSet(final VennSet set) {
set.setLocation(getCreationLocation());
pane.add(set, LAYER_VENN_SET, 0);
pane.revalidate();
pane.repaint();
}
public void addLabel(final VennLabel label) {
label.setLocation(getCreationLocation());
pane.add(label, LAYER_VENN_LABEL, 0);
pane.revalidate();
pane.repaint();
}
}
protected static class VennBorder extends LineBorder {
public VennBorder(final int thickness) { super(Color.BLACK, thickness, true); }
@Override
public void paintBorder(final Component c, final Graphics g, final int x, final int y, final int width, final int height) {
if (c instanceof VennControl) {
final VennControl ctrl = (VennControl) c;
Graphics2D g2d = (Graphics2D) g.create();
try {
g2d.setColor(ctrl.getBorderColor());
final int t2 = thickness + thickness;
final int aw = (int) Math.round(Math.min(width, ctrl.getArcWidth())), ah = (int) Math.round(Math.min(height, ctrl.getArcHeight()));
final Path2D path = new Path2D.Float(Path2D.WIND_EVEN_ODD);
path.append(new RoundRectangle2D.Float(x, y, width, height, aw, ah), false);
path.append(new RoundRectangle2D.Double(x + thickness, y + thickness, width - t2, height - t2, aw, ah), false);
g2d.fill(path);
}
finally {
g2d.dispose();
}
}
else
super.paintBorder(c, g, x, y, width, height);
}
}
public static <C1 extends VennControl<C1, C2, ?, ?>, C2 extends VennControl<C2, C1, ?, ?>> void attach(final C1 c1, final C2 c2) { //Utility method.
c1.getAttachments().add(c2);
c2.getAttachments().add(c1);
}
public static <C1 extends VennControl<C1, C2, ?, ?>, C2 extends VennControl<C2, C1, ?, ?>> void detach(final C1 c1, final C2 c2) { //Utility method.
c1.getAttachments().remove(c2);
c2.getAttachments().remove(c1);
}
protected abstract class VennControl<C1 extends VennControl<C1, C2, R1, R2>, C2 extends VennControl<C2, C1, R2, R1>, R1 extends RectangularShape, R2 extends RectangularShape> extends RectangularPanel<R1> {
private Color bg;
private boolean highlighted, selected;
private final LinkedHashSet<C2> attachments;
public VennControl(final String title, final double width, final double height) {
super(width, height);
super.setLayout(new GridBagLayout());
super.add(new JLabel(Objects.toString(title), JLabel.CENTER));
super.setBorder(new VennBorder(2));
super.setSize((int) Math.ceil(width), (int) Math.ceil(height));
attachments = new LinkedHashSet<>();
bg = transparent(Color.LIGHT_GRAY, 127);
highlighted = selected = false;
final MouseAdapter relocationMA = new MouseAdapter() {
private Point moveAnchor;
private LinkedHashSet<C2> intersections;
@Override
public void mousePressed(final MouseEvent mevt) {
if (pack(MouseEventFlag.buttonSetOf(mevt)) == pack(MouseEventFlag.BUTTON_LEFT) && moveAnchor == null && intersections == null) {
VennTool.this.drawPanel.setControlsEnabled(false);
moveAnchor = mevt.getPoint();
setSelected(true);
intersections = findIntersections(0, 0);
intersections.forEach(c2 -> c2.setHighlighted(true));
}
}
@Override
public void mouseDragged(final MouseEvent mevt) {
final int dx = mevt.getX() - moveAnchor.x, dy = mevt.getY() - moveAnchor.y;
final boolean attach = VennTool.this.drawPanel.isAttachModeSelected(), collisions = VennTool.this.drawPanel.isCollisionModeSelected();
final LinkedHashSet<C2> newIntersections = findIntersections(dx, dy);
if (MouseEventFlag.buttonSetOf(mevt).contains(MouseEventFlag.BUTTON_LEFT) && moveAnchor != null && intersections != null && (!attach || newIntersections.containsAll(getAttachments())) && (!collisions || !collides(dx, dy))) {
setLocation(getX() + dx, getY() + dy);
LinkedHashSet<C2> setHighlight = (LinkedHashSet<C2>) intersections.clone();
setHighlight.removeAll(newIntersections);
if (!attach)
setHighlight.forEach(c2 -> detach(c2, (C1) VennControl.this));
setHighlight.forEach(c2 -> c2.setHighlighted(false));
setHighlight = (LinkedHashSet<C2>) newIntersections.clone();
setHighlight.removeAll(intersections);
setHighlight.forEach(c2 -> c2.setHighlighted(true));
intersections = newIntersections;
}
}
@Override
public void mouseReleased(final MouseEvent mevt) {
if (pack(MouseEventFlag.buttonSetOf(mevt)) == pack(MouseEventFlag.BUTTON_LEFT) && moveAnchor != null && intersections != null) {
intersections.forEach(c2 -> c2.setHighlighted(false));
final VennDrawPanel vdp = VennTool.this.drawPanel;
if (vdp.isAttachModeSelected())
intersections.forEach(c2 -> attach(c2, (C1) VennControl.this));
moveAnchor = null;
intersections = null;
setSelected(false);
vdp.setControlsEnabled(true);
}
}
};
super.addMouseListener(relocationMA);
super.addMouseMotionListener(relocationMA);
}
protected LinkedHashSet<C2> findIntersections(final double dx, final double dy) {
final R1 r1tmp = getShape();
final R1 r1 = createShape(getX() + dx + r1tmp.getX(), getY() + dy + r1tmp.getY(), r1tmp.getWidth(), r1tmp.getHeight());
final LinkedHashSet<C2> intersections = new LinkedHashSet<>(), possibilities = getPossibleIntersections();
possibilities.forEach(c2 -> {
final R2 r2tmp = c2.getShape();
final R2 r2 = c2.createShape(c2.getX() + r2tmp.getX(), c2.getY() + r2tmp.getY(), r2tmp.getWidth(), r2tmp.getHeight());
if (intersect(r1, r2))
intersections.add(c2);
});
return intersections;
}
public LinkedHashSet<C2> getAttachments() { return attachments; }
protected abstract boolean intersect(final R1 r1, final R2 r2);
protected abstract LinkedHashSet<C2> getPossibleIntersections();
protected abstract boolean collides(final double dx, final double dy);
public void setHighlighted(final boolean highlighted) {
if (highlighted != this.highlighted) {
this.highlighted = highlighted;
repaint();
}
}
public void setSelected(final boolean selected) {
if (selected != this.selected) {
this.selected = selected;
repaint();
}
}
public void setColor(final Color c) {
if (!bg.equals(c)) {
bg = Objects.requireNonNull(c);
repaint();
}
}
public Color getBorderColor() {
return selected? Color.GREEN: (highlighted? Color.CYAN: Color.BLACK);
}
@Override
protected void paintComponent(final Graphics g) {
super.paintComponent(g);
g.setColor(bg);
g.fillRect(0, 0, getWidth(), getHeight());
}
}
protected class VennLabel extends VennControl<VennLabel, VennSet, Rectangle2D, Ellipse2D> {
public VennLabel(final String title) { super(title, 0, 0); }
@Override protected Rectangle2D createShape(double x, double y, double width, double height) { return new Rectangle2D.Double(x, y, width, height); }
@Override protected double getArcWidth() { return 0; }
@Override protected double getArcHeight() { return 0; }
@Override protected boolean intersect(final Rectangle2D r1, final Ellipse2D r2) { return r2.intersects(r1); }
@Override protected LinkedHashSet<VennSet> getPossibleIntersections() { return VennTool.this.sets; }
@Override
protected boolean collides(final double dx, final double dy) {
Rectangle2D tmp = getShape();
final Rectangle2D thisShape = createShape(getX() + dx + tmp.getX(), getY() + dy + tmp.getY(), tmp.getWidth(), tmp.getHeight());
for (final VennLabel label: VennTool.this.labels)
if (label != this) {
tmp = label.getShape();
tmp = label.createShape(label.getX() + tmp.getX(), label.getY() + tmp.getY(), tmp.getWidth(), tmp.getHeight());
if (tmp.intersects(thisShape))
return true;
}
return false;
}
}
protected class VennSet extends VennControl<VennSet, VennLabel, Ellipse2D, Rectangle2D> {
public VennSet(final String title, final double radius) {
super(title, radius + radius, radius + radius);
final Dimension sz = super.getPreferredSize();
sz.width = sz.height = Math.max(Math.max(sz.width, super.getWidth()), Math.max(sz.height, super.getHeight()));
super.setSize(sz);
}
@Override protected Ellipse2D createShape(double x, double y, double width, double height) { return new Ellipse2D.Double(x, y, width, height); }
@Override protected double getArcWidth() { return getWidth(); }
@Override protected double getArcHeight() { return getHeight(); }
@Override protected boolean intersect(final Ellipse2D r1, final Rectangle2D r2) { return r1.intersects(r2); }
@Override protected LinkedHashSet<VennLabel> getPossibleIntersections() { return VennTool.this.labels; }
@Override protected boolean collides(final double dx, final double dy) { return false; } //Never collides with anything.
}
private final JFrame frame;
private final VennDrawPanel drawPanel;
private final LinkedHashSet<VennSet> sets;
private final LinkedHashSet<VennLabel> labels;
public VennTool(final GraphicsConfiguration gconf) {
drawPanel = new VennDrawPanel(gconf);
frame = new JFrame("Collisionless Venn Tool", gconf);
sets = new LinkedHashSet<>();
labels = new LinkedHashSet<>();
}
public void createSet(final String title) {
final VennSet set = new VennSet(title, 100);
sets.add(set);
drawPanel.addSet(set);
drawPanel.revalidate();
drawPanel.repaint();
}
public void createLabel(final String title) {
final VennLabel label = new VennLabel(title);
labels.add(label);
drawPanel.addLabel(label);
drawPanel.revalidate();
drawPanel.repaint();
}
@Override
public void run() {
if (SwingUtilities.isEventDispatchThread()) {
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(drawPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
else
SwingUtilities.invokeLater(this);
}
}
public static void main(final String[] args) {
final VennTool tool = new VennTool(GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration());
SwingUtilities.invokeLater(() -> {
tool.createSet("Drag this set!");
tool.createLabel("Drag this label!");
});
tool.run();
}
}
Почему так много строк?
В основном потому, что вы сказали, что у вас есть реализовано, но не предоставило никакого кода. Поэтому я должен был разработать все с нуля. От диспетчера нестандартной компоновки, панелей и рамок произвольной формы до фактических представлений VennTool , VennDrawPanel , VennSet и VennLabel .
Так что же это включает в себя?
Вот что-то вроде диаграммы классов, которая является моим лучшим усилием на данный момент для ваших требований:
Коробки представляют классы, указатели представляют наследование, а вложенные блоки представляют вложенные классы. Черный текст представляет имена классов, а голубой текст представляет комментарии / подробности.
Код начинается с, казалось бы, не связанных классов, но они действуют как платформа для работы с VennTool.
Для начала, MouseEventFlag
enum как раз для преобразования MouseEvent
s в константы enum, чтобы проверить, какая кнопка / клавиша нажата для каждого события мыши.
Затем существуют некоторые методы stati c, которые выполняют общие действия. как упаковка от EnumSet
s до long
масок, преобразование цветов в прозрачные, получение и работа с Component
размерами и c ...
Затем существует пользовательский класс LayoutManager
с именем ManualLayout
. Как следует из названия, вы должны поместить Component
s Container
с ManualLayout
туда, где вы хотите, чтобы они были (например) setBounds
или setLocation
методами. Затем макет вычисляет предпочтительный размер для каждого Component
в Container
, чтобы вернуть минимальный и предпочтительный размеры макета. Метод layoutContainer
просто устанавливает размеры включенных Component
с и не перемещает их местоположение. ManualLayout
учитывает все размеры каждого Component
(ie текущий размер, предпочтительный, минимальный и максимальный размер). Экземпляр такого макета используется для размещения Component
s на панели рисования (об этом поговорим в этом посте).
Затем существует класс RectangularPanel
, который имеет пользовательскую форму JPanel
которая поддерживает любую java.awt.geom.RectangularShape
форму, чтобы придать форму самой панели и нарисовать ее. Это суперкласс компонентов, которые составляют набор Венна (например, сам набор Венна и метки, добавленные на панель рисования).
Затем запускается класс VennTool
. Этот класс реализует Runnable
для запуска моделирования . Он включает в себя все классы, представляющие компоненты / элементы Venn, а также панель рисования. Предполагается, что клиентские классы видят только класс VennTool
(который предоставляет методы для создания наборов и меток Венна) и ничего больше (кроме случаев, когда они являются его подклассами). Обработка и модификация наборов Venn зависит от пользователя. Конечно, вы можете изменить класс так, чтобы он возвращал все компоненты Venn, уже находящиеся в симуляции, в любое время, когда вам необходимо сохранить их как проект, например, позже, но такая функциональность здесь не реализована. Просто добавьте несколько геттеров и файловый ввод / вывод, чтобы сделать это самостоятельно.
Затем внутри VennTool
сейчас находится VennDrawPanel
, то есть панель для рисования, о которой я говорил выше, которая отвечает для макета всего моделирования. Здесь мы добавляем наборы Venn, метки и пользовательские элементы управления (например, кнопки, позволяющие пользователям определять свои действия). VennDrawPanel
имеет JLayeredPane
, который использует ManualLayout
, достигая разных слоев и местоположений для каждого компонента. В частности, первый слой состоит из кнопок. Поверх этого слоя находятся наборы Венна, и, наконец, поверх них - ярлыки Венна. В качестве будущей идеи вы можете реализовать новый слой, на котором перетаскиваемый компонент будет перетаскивать go после перетаскивания (например, вам может понадобиться набор Венна, который пользователь в данный момент перетаскивает, чтобы он находился на уровне, который выше всех остальных).
Затем существует пользовательский класс Border
, названный (как вы уже догадались) VennBorder
. Он заключен в VennTool
только для того, чтобы показать его масштаб и позволить ему взаимодействовать с каждым компонентом Venn. Он просто вдохновлен и расширяет класс javax.swing.border.LineBorder
.
Затем существует класс VennControl
, который представляет каждый компонент Венна (ie VennSet
с и VennLabel
с). Он отвечает за использование мыши (например, перетаскивание набора Венна), за обнаружение столкновений и разрешение меток, а также за присоединение / отсоединение наборов к меткам и меток к наборам.
Пользовательский VennBorder
и VennControl
классы определяются формой типа java.awt.geom.RectangularShape
. Это позволяет каждому подклассу иметь, например, форму Ellipse2D
(как в случае VennSet
s, которые являются круглыми) или Rectangle2D
(как в случае VennLabel
s). Он также определяет ширину и высоту ar c для поддержки RoundRectangle2D
, но она только что пришла по дороге, чтобы поддержать Ellipse2D
и Rectangle2D
в одной панели / рамке.
Наконец, есть классы VennSet
и VennLabel
, которые представляют набор Venn и метку, которая должна быть добавлена в набор Venn соответственно.
Режим присоединения / отсоединения:
При включении, каждое нажатие мыши будет прикрепите ярлык, который вы держите со всеми перекрывающимися наборами (или набор, который вы держите со всеми перекрывающимися ярлыками). Это также не позволит меткам / наборам go не перекрываться со своими прикрепленными наборами / метками. Отключите этот режим, чтобы свободно перемещать любой VennControl
вокруг его отсоединения при отпускании мыши.
Режим обнаружения столкновений:
Как следует из названия, при включении метки будут сталкиваться и не перекрываться с друг с другом. Хотя при отключении они не будут.