+ Extracted Listeners out of MyComponent

+ a lot of cleanup
This commit is contained in:
Patrick Haßel 2023-02-28 10:19:46 +01:00
parent 7cc298638c
commit d5660e9265
27 changed files with 674 additions and 340 deletions

View File

@ -12,6 +12,14 @@ public class CONFIG {
public static final BasicStroke STROKE_HIGHLIGHT = new BasicStroke(STROKE_HIGHLIGHT_WIDTH);
public static final int SIDEBAR_WIDTH = 300;
public static final int TOOLBOX_BUTTON_SIZE = 50;
public static final int STAMP_GROUP_BUTTON_SIZE = 40;
public static final int STAMP_GROUP_BUTTON_ICON_REAL_SIZE = STAMP_GROUP_BUTTON_SIZE - STROKE_HIGHLIGHT_WIDTH * 2;
public static final int STAMP_BUTTON_SIZE = 75;
public static final int STAMP_BUTTON_ICON_REAL_SIZE = STAMP_BUTTON_SIZE - STROKE_HIGHLIGHT_WIDTH * 2;

View File

@ -0,0 +1,244 @@
package de.ph87.kindermalen;
import java.awt.*;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.util.function.Consumer;
@SuppressWarnings("UnusedReturnValue")
public class ComponentListener {
private Consumer<MouseEvent> mouseClicked;
private Consumer<MouseEvent> mousePressed;
private Consumer<MouseEvent> mouseReleased;
private Consumer<MouseEvent> mouseEntered;
private Consumer<MouseEvent> mouseExited;
private Consumer<MouseEvent> mouseDragged;
private Consumer<MouseEvent> mouseMoved;
private Consumer<ComponentEvent> componentResized;
private Consumer<ComponentEvent> componentMoved;
private Consumer<ComponentEvent> componentShown;
private Consumer<ComponentEvent> componentHidden;
public ComponentListener(final Component component) {
component.addMouseListener(new MouseListenerImpl());
component.addMouseMotionListener(new MouseMotionListenerImpl());
component.addComponentListener(new ComponentListenerImpl());
}
public static ComponentListener on(final Component component) {
return new ComponentListener(component);
}
public ComponentListener mouseClicked(final Consumer<MouseEvent> consumer) {
mouseClicked = consumer;
return this;
}
public ComponentListener mousePressed(final Consumer<MouseEvent> consumer) {
mousePressed = consumer;
return this;
}
public ComponentListener mouseReleased(final Consumer<MouseEvent> consumer) {
mouseReleased = consumer;
return this;
}
public ComponentListener mouseEntered(final Consumer<MouseEvent> consumer) {
mouseEntered = consumer;
return this;
}
public ComponentListener mouseExited(final Consumer<MouseEvent> consumer) {
mouseExited = consumer;
return this;
}
public ComponentListener mouseDragged(final Consumer<MouseEvent> consumer) {
mouseDragged = consumer;
return this;
}
public ComponentListener mouseMoved(final Consumer<MouseEvent> consumer) {
mouseMoved = consumer;
return this;
}
public ComponentListener componentResized(final Consumer<ComponentEvent> consumer) {
componentResized = consumer;
return this;
}
public ComponentListener componentMoved(final Consumer<ComponentEvent> consumer) {
componentMoved = consumer;
return this;
}
public ComponentListener componentShown(final Consumer<ComponentEvent> consumer) {
componentShown = consumer;
return this;
}
public ComponentListener componentHidden(final Consumer<ComponentEvent> consumer) {
componentHidden = consumer;
return this;
}
public ComponentListener mouseClicked(final Runnable runnable) {
mousePressed = mouseEvent -> runnable.run();
return this;
}
public ComponentListener mousePressed(final Runnable runnable) {
mousePressed = mouseEvent -> runnable.run();
return this;
}
public ComponentListener mouseReleased(final Runnable runnable) {
mouseReleased = mouseEvent -> runnable.run();
return this;
}
public ComponentListener mouseEntered(final Runnable runnable) {
mouseEntered = mouseEvent -> runnable.run();
return this;
}
public ComponentListener mouseExited(final Runnable runnable) {
mouseExited = mouseEvent -> runnable.run();
return this;
}
public ComponentListener mouseDragged(final Runnable runnable) {
mouseDragged = mouseEvent -> runnable.run();
return this;
}
public ComponentListener mouseMoved(final Runnable runnable) {
mouseMoved = mouseEvent -> runnable.run();
return this;
}
public ComponentListener componentResized(final Runnable runnable) {
componentResized = mouseEvent -> runnable.run();
return this;
}
public ComponentListener componentMoved(final Runnable runnable) {
componentMoved = mouseEvent -> runnable.run();
return this;
}
public ComponentListener componentShown(final Runnable runnable) {
componentShown = mouseEvent -> runnable.run();
return this;
}
public ComponentListener componentHidden(final Runnable runnable) {
componentHidden = mouseEvent -> runnable.run();
return this;
}
private class MouseListenerImpl implements MouseListener {
@Override
public void mouseClicked(final MouseEvent mouseEvent) {
if (mouseClicked != null) {
mouseClicked.accept(mouseEvent);
}
}
@Override
public void mousePressed(final MouseEvent mouseEvent) {
if (mousePressed != null) {
mousePressed.accept(mouseEvent);
}
}
@Override
public void mouseReleased(final MouseEvent mouseEvent) {
if (mouseReleased != null) {
mouseReleased.accept(mouseEvent);
}
}
@Override
public void mouseEntered(final MouseEvent mouseEvent) {
if (mouseEntered != null) {
mouseEntered.accept(mouseEvent);
}
}
@Override
public void mouseExited(final MouseEvent mouseEvent) {
if (mouseExited != null) {
mouseExited.accept(mouseEvent);
}
}
}
private class MouseMotionListenerImpl implements MouseMotionListener {
@Override
public void mouseDragged(final MouseEvent mouseEvent) {
if (mouseDragged != null) {
mouseDragged.accept(mouseEvent);
}
}
@Override
public void mouseMoved(final MouseEvent mouseEvent) {
if (mouseMoved != null) {
mouseMoved.accept(mouseEvent);
}
}
}
private class ComponentListenerImpl implements java.awt.event.ComponentListener {
@Override
public void componentResized(final ComponentEvent componentEvent) {
if (componentResized != null) {
componentResized.accept(componentEvent);
}
}
@Override
public void componentMoved(final ComponentEvent componentEvent) {
if (componentMoved != null) {
componentMoved.accept(componentEvent);
}
}
@Override
public void componentShown(final ComponentEvent componentEvent) {
if (componentShown != null) {
componentShown.accept(componentEvent);
}
}
@Override
public void componentHidden(final ComponentEvent componentEvent) {
if (componentHidden != null) {
componentHidden.accept(componentEvent);
}
}
}
}

View File

@ -1,7 +1,7 @@
package de.ph87.kindermalen;
import de.ph87.kindermalen.drawing.Drawing;
import de.ph87.kindermalen.tools.Tools;
import de.ph87.kindermalen.toolbox.Toolbox;
import de.ph87.kindermalen.util.Publisher;
import lombok.Getter;
@ -10,7 +10,7 @@ public class Environment {
private final Publisher<Drawing> onDrawingChange = new Publisher<>();
private final Tools tools = new Tools();
private final Toolbox toolbox = new Toolbox();
private Drawing drawing = null;

View File

@ -5,23 +5,19 @@ import de.ph87.kindermalen.util.Subscription;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
public abstract class MyComponent extends JPanel implements ComponentListener, MouseListener, MouseMotionListener {
public abstract class MyComponent extends JPanel {
private final List<Subscription<?>> subscriptions = new ArrayList<>();
public MyComponent() {
addComponentListener(this);
addMouseListener(this);
addMouseMotionListener(this);
}
public void destruct() {
subscriptions.forEach(this::unsubscribe);
synchronized (subscriptions) {
subscriptions.forEach(Subscription::unsubscribe);
subscriptions.clear();
}
synchronized (getTreeLock()) {
for (final Component component : getComponents()) {
if (component instanceof MyComponent) {
@ -32,71 +28,20 @@ public abstract class MyComponent extends JPanel implements ComponentListener, M
}
protected <T> Subscription<T> subscribe(final Publisher<T> publisher, final Consumer<T> next) {
final Subscription<T> subscription = publisher.subscribe(next);
subscriptions.add(subscription);
return subscription;
synchronized (subscriptions) {
final Subscription<T> subscription = publisher.subscribe(next);
subscriptions.add(subscription);
return subscription;
}
}
protected void unsubscribe(final Subscription<?> subscription) {
if (!subscriptions.remove(subscription)) {
throw new RuntimeException();
synchronized (subscriptions) {
if (!subscriptions.remove(subscription)) {
throw new RuntimeException();
}
subscription.unsubscribe();
}
subscription.unsubscribe();
}
@Override
public void componentResized(final ComponentEvent e) {
// nothing
}
@Override
public void componentMoved(final ComponentEvent e) {
// nothing
}
@Override
public void componentShown(final ComponentEvent e) {
// nothing
}
@Override
public void componentHidden(final ComponentEvent e) {
// nothing
}
@Override
public void mouseClicked(final MouseEvent mouseEvent) {
// nothing
}
@Override
public void mousePressed(final MouseEvent mouseEvent) {
// nothing
}
@Override
public void mouseReleased(final MouseEvent mouseEvent) {
// nothing
}
@Override
public void mouseEntered(final MouseEvent mouseEvent) {
// nothing
}
@Override
public void mouseExited(final MouseEvent mouseEvent) {
// nothing
}
@Override
public void mouseDragged(final MouseEvent mouseEvent) {
// nothing
}
@Override
public void mouseMoved(final MouseEvent mouseEvent) {
// nothing
}
}

View File

@ -1,51 +1,45 @@
package de.ph87.kindermalen;
import de.ph87.kindermalen.tools.ToolsPanel;
import de.ph87.kindermalen.tools.tool.Tool;
import de.ph87.kindermalen.tools.tool.ToolPanel;
import de.ph87.kindermalen.tools.tool.stamp.StampPanel;
import de.ph87.kindermalen.tools.tool.stamp.StampTool;
import de.ph87.kindermalen.toolbox.ToolboxPanel;
import de.ph87.kindermalen.toolbox.tool.Tool;
import de.ph87.kindermalen.toolbox.tool.ToolPanel;
import de.ph87.kindermalen.toolbox.tool.stamp.StampPanel;
import de.ph87.kindermalen.toolbox.tool.stamp.StampTool;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseEvent;
import static de.ph87.kindermalen.CONFIG.BORDERS;
import static de.ph87.kindermalen.util.MyGridBagConstraints.C;
import static de.ph87.kindermalen.CONFIG.SIDEBAR_WIDTH;
import static de.ph87.kindermalen.util.MyGridBagConstraints.GBC;
import static java.awt.GridBagConstraints.HORIZONTAL;
import static java.awt.GridBagConstraints.NONE;
public class Sidebar extends MyComponent {
private final Environment environment;
private ToolPanel toolPanel = null;
public Sidebar(final Environment environment) {
this.environment = environment;
setPreferredSize(new Dimension(300, 0));
setPreferredSize(new Dimension(SIDEBAR_WIDTH, 0));
setLayout(new GridBagLayout());
final JButton newDrawing = new JButton("Neues Bild");
add(newDrawing, C(0, 0, 1, 0));
int row = 0;
addNewDrawingButton(environment, row++);
addToolboxPanel(environment, row++);
final ToolsPanel toolsPanel = new ToolsPanel(environment.getTools());
add(toolsPanel, C(0, 1, 1, 0.1));
subscribe(environment.getTools().getOnSelect(), this::onToolSelected);
setBackground(Color.yellow);
}
@Override
public void mousePressed(final MouseEvent mouseEvent) {
environment.newDrawing();
private void addNewDrawingButton(final Environment environment, int row) {
final JButton button = new JButton("Neues Bild");
button.setPreferredSize(new Dimension(SIDEBAR_WIDTH, 40));
add(button, GBC(0, row, 1, 0, HORIZONTAL));
ComponentListener.on(button).mouseClicked(environment::newDrawing);
}
@Override
public void paint(final Graphics g) {
super.paint(g);
if (BORDERS) {
g.setColor(Color.red);
g.drawRect(0, 0, getWidth() - 1, getHeight() - 1);
}
private void addToolboxPanel(final Environment environment, int row) {
final ToolboxPanel panel = new ToolboxPanel(environment.getToolbox());
add(panel, GBC(0, row, 1, 0, HORIZONTAL));
subscribe(environment.getToolbox().getOnSelect(), this::onToolSelected);
}
private void onToolSelected(final Tool tool) {
@ -60,11 +54,12 @@ public class Sidebar extends MyComponent {
}
if (toolPanel != null) {
add(toolPanel, C(0, 2, 1, 0.9));
add(toolPanel, GBC(0, 2, 0, 0, NONE));
toolPanel.setVisible(true);
}
revalidate();
repaint();
}
}

View File

@ -8,7 +8,8 @@ import java.awt.*;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import static de.ph87.kindermalen.util.MyGridBagConstraints.C;
import static de.ph87.kindermalen.util.MyGridBagConstraints.GBC;
import static java.awt.GridBagConstraints.BOTH;
@Slf4j
public class Window extends JFrame {
@ -17,13 +18,9 @@ public class Window extends JFrame {
public Window(final Environment environment) {
setLayout(new GridBagLayout());
final Sidebar sidebar = new Sidebar(environment);
add(sidebar, C(0, 0, 0, 1.0));
final DrawingPanel drawingPanel = new DrawingPanel(environment);
add(drawingPanel, C(1, 0, 1, 1.0));
int col = 0;
add(new Sidebar(environment), GBC(col++, 0, 0, 1, BOTH));
add(new DrawingPanel(environment), GBC(col++, 0, 1, 1, BOTH));
pack();
setExtendedState(getExtendedState() | MAXIMIZED_BOTH);

View File

@ -1,14 +1,14 @@
package de.ph87.kindermalen.drawing;
import de.ph87.kindermalen.ComponentListener;
import de.ph87.kindermalen.Environment;
import de.ph87.kindermalen.MyComponent;
import de.ph87.kindermalen.tools.tool.Tool;
import de.ph87.kindermalen.toolbox.tool.Tool;
import de.ph87.kindermalen.util.Subscription;
import de.ph87.kindermalen.util.Vector;
import lombok.extern.slf4j.Slf4j;
import java.awt.*;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
@ -36,8 +36,17 @@ public class DrawingPanel extends MyComponent {
public DrawingPanel(final Environment environment) {
this.environment = environment;
subscribe(environment.getTools().getOnSelect(), this::onToolSelect);
subscribe(environment.getToolbox().getOnSelect(), this::onToolSelect);
subscribe(environment.getOnDrawingChange(), drawing -> repaint());
ComponentListener.on(this)
.componentResized(this::calculateTransform)
.componentMoved(this::calculateTransform)
.componentShown(this::calculateTransform)
.mousePressed(this::mousePressed)
.mouseReleased(this::mouseReleased)
.mouseDragged(this::mouseDragged)
.mouseMoved(this::mouseMoved);
}
private void onToolSelect(final Tool tool) {
@ -48,33 +57,16 @@ public class DrawingPanel extends MyComponent {
toolChangeSubscription = subscribe(tool.getOnChange(), ignore -> repaint());
}
@Override
public void componentResized(final ComponentEvent e) {
calculateTransform();
}
@Override
public void componentMoved(final ComponentEvent e) {
calculateTransform();
}
@Override
public void componentShown(final ComponentEvent e) {
calculateTransform();
}
@Override
public void mousePressed(final MouseEvent mouseEvent) {
if (mouseEvent.getButton() == 3) {
environment.getDrawing().undo();
return;
}
environment.getDrawing().newRevision();
lastPoint = environment.getTools().getTool().apply(null, environment.getDrawing(), transform(mouseEvent.getPoint()));
lastPoint = environment.getToolbox().getTool().apply(null, environment.getDrawing(), transform(mouseEvent.getPoint()));
repaint();
}
@Override
public void mouseReleased(final MouseEvent mouseEvent) {
try {
environment.getDrawing().save(SAVE_DIR);
@ -83,14 +75,12 @@ public class DrawingPanel extends MyComponent {
}
}
@Override
public void mouseDragged(final MouseEvent mouseEvent) {
cursor = transform(mouseEvent.getPoint());
lastPoint = environment.getTools().getTool().apply(lastPoint, environment.getDrawing(), transform(mouseEvent.getPoint()));
lastPoint = environment.getToolbox().getTool().apply(lastPoint, environment.getDrawing(), transform(mouseEvent.getPoint()));
repaint();
}
@Override
public void mouseMoved(final MouseEvent mouseEvent) {
cursor = transform(mouseEvent.getPoint());
repaint();
@ -120,19 +110,21 @@ public class DrawingPanel extends MyComponent {
final Graphics2D g = (Graphics2D) graphics;
g.setColor(Color.gray);
g.fillRect(0, 0, getWidth(), getHeight());
g.setTransform(transform);
if (environment == null || environment.getDrawing() == null) {
if (transform == null || environment == null || environment.getDrawing() == null) {
return;
}
final Drawing drawing = environment.getDrawing();
g.setTransform(transform);
draw(g, environment.getDrawing());
}
private void draw(final Graphics2D g, final Drawing drawing) {
g.setColor(Color.white);
g.fillRect(0, 0, drawing.width, drawing.height);
g.drawImage(drawing.getImage(), 0, 0, drawing.width, drawing.height, null);
final BufferedImage preview = environment.getTools().getTool().getPreview();
final BufferedImage preview = environment.getToolbox().getTool().getPreview();
if (cursor != null && preview != null) {
g.drawImage(preview, cursor.intX() - preview.getWidth() / 2, cursor.intY() - preview.getHeight() / 2, null);
}

View File

@ -1,7 +1,7 @@
package de.ph87.kindermalen.tools;
package de.ph87.kindermalen.toolbox;
import de.ph87.kindermalen.tools.tool.Tool;
import de.ph87.kindermalen.tools.tool.stamp.StampTool;
import de.ph87.kindermalen.toolbox.tool.Tool;
import de.ph87.kindermalen.toolbox.tool.stamp.StampTool;
import de.ph87.kindermalen.util.Publisher;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
@ -10,7 +10,7 @@ import java.util.List;
@Slf4j
@Getter
public class Tools {
public class Toolbox {
private final List<Tool> tools = List.of(new StampTool());
@ -18,7 +18,7 @@ public class Tools {
private final Publisher<Tool> onSelect = new Publisher<>();
public Tools() {
public Toolbox() {
setTool(tools.get(0));
}

View File

@ -0,0 +1,33 @@
package de.ph87.kindermalen.toolbox;
import de.ph87.kindermalen.ComponentListener;
import de.ph87.kindermalen.MyComponent;
import de.ph87.kindermalen.toolbox.tool.Tool;
import java.awt.*;
import static de.ph87.kindermalen.CONFIG.TOOLBOX_BUTTON_SIZE;
public class ToolboxButton extends MyComponent {
private final Tool tool;
public ToolboxButton(final Toolbox toolbox, final Tool tool) {
this.tool = tool;
setPreferredSize(new Dimension(TOOLBOX_BUTTON_SIZE, TOOLBOX_BUTTON_SIZE));
ComponentListener.on(this).mouseReleased(() -> toolbox.setTool(tool));
}
@Override
public void paint(final Graphics g) {
g.setColor(Color.magenta);
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(Color.black);
g.setFont(g.getFont().deriveFont(12.0f));
g.drawString(tool.getName(), 0, g.getFontMetrics().getHeight());
g.drawRect(0, 0, getWidth() - 1, getHeight() - 1);
}
}

View File

@ -0,0 +1,24 @@
package de.ph87.kindermalen.toolbox;
import de.ph87.kindermalen.toolbox.tool.Tool;
import lombok.extern.slf4j.Slf4j;
import javax.swing.*;
import java.awt.*;
import static de.ph87.kindermalen.CONFIG.SIDEBAR_WIDTH;
import static de.ph87.kindermalen.CONFIG.TOOLBOX_BUTTON_SIZE;
@Slf4j
public class ToolboxPanel extends JPanel {
public ToolboxPanel(final Toolbox toolbox) {
setPreferredSize(new Dimension(SIDEBAR_WIDTH, TOOLBOX_BUTTON_SIZE));
setLayout(new FlowLayout(FlowLayout.LEADING, 0, 0));
for (final Tool tool : toolbox.getTools()) {
add(new ToolboxButton(toolbox, tool));
}
setBackground(Color.green);
}
}

View File

@ -1,4 +1,4 @@
package de.ph87.kindermalen.tools.tool;
package de.ph87.kindermalen.toolbox.tool;
import de.ph87.kindermalen.drawing.Drawing;
import de.ph87.kindermalen.util.Publisher;

View File

@ -1,4 +1,4 @@
package de.ph87.kindermalen.tools.tool;
package de.ph87.kindermalen.toolbox.tool;
import de.ph87.kindermalen.MyComponent;
import lombok.extern.slf4j.Slf4j;

View File

@ -1,4 +1,4 @@
package de.ph87.kindermalen.tools.tool.stamp;
package de.ph87.kindermalen.toolbox.tool.stamp;
import lombok.Getter;
import lombok.ToString;
@ -12,13 +12,14 @@ import java.util.HashMap;
import java.util.Map;
import static de.ph87.kindermalen.CONFIG.STAMP_BUTTON_ICON_REAL_SIZE;
import static de.ph87.kindermalen.CONFIG.STAMP_GROUP_BUTTON_ICON_REAL_SIZE;
import static de.ph87.kindermalen.util.ImageHelper.RESIZE;
@Slf4j
@Getter
@ToString
public class StampImage {
public class Stamp {
@ToString.Include
private final String name;
private final File file;
@ -27,16 +28,19 @@ public class StampImage {
private final Map<Integer, BufferedImage> sizes = new HashMap<>();
public StampImage(final File file) throws IOException {
public Stamp(final File file) throws IOException {
this.name = file.getName().substring(0, file.getName().lastIndexOf("."));
this.file = file;
this.original = ImageIO.read(file);
getSize(STAMP_BUTTON_ICON_REAL_SIZE);
getSize(STAMP_GROUP_BUTTON_ICON_REAL_SIZE);
log.debug("Stamp loaded: {}", this);
}
public BufferedImage getSize(final int size) {
return sizes.computeIfAbsent(size, s -> RESIZE(original, s, s));
synchronized (sizes) {
return sizes.computeIfAbsent(size, s -> RESIZE(original, s, s));
}
}
}

View File

@ -1,10 +1,10 @@
package de.ph87.kindermalen.tools.tool.stamp;
package de.ph87.kindermalen.toolbox.tool.stamp;
import de.ph87.kindermalen.ComponentListener;
import de.ph87.kindermalen.MyComponent;
import lombok.Getter;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import static de.ph87.kindermalen.CONFIG.*;
@ -14,7 +14,7 @@ public class StampButton extends MyComponent {
private final StampTool tool;
@Getter
private final StampImage image;
private final Stamp image;
private final BufferedImage icon;
@ -22,28 +22,29 @@ public class StampButton extends MyComponent {
private boolean hover = false;
public StampButton(final StampTool tool, final StampImage image) {
public StampButton(final StampTool tool, final Stamp image) {
this.tool = tool;
this.image = image;
setPreferredSize(new Dimension(STAMP_BUTTON_SIZE, STAMP_BUTTON_SIZE));
icon = image.getSize(STAMP_BUTTON_ICON_REAL_SIZE);
position = new Point((STAMP_BUTTON_SIZE - icon.getWidth()) / 2, (STAMP_BUTTON_SIZE - icon.getHeight()) / 2);
ComponentListener.on(this)
.mouseReleased(this::mouseReleased)
.mouseEntered(this::mouseEntered)
.mouseExited(this::mouseExited);
}
@Override
public void mouseReleased(final MouseEvent mouseEvent) {
this.tool.setImage(image);
private void mouseReleased() {
this.tool.setActiveStamp(image);
this.tool.prepare();
}
@Override
public void mouseEntered(final MouseEvent mouseEvent) {
private void mouseEntered() {
hover = true;
repaint();
}
@Override
public void mouseExited(final MouseEvent mouseEvent) {
private void mouseExited() {
hover = false;
repaint();
}
@ -58,7 +59,7 @@ public class StampButton extends MyComponent {
if (hover) {
g2.setColor(Color.yellow);
highlight = true;
} else if (tool.getImage() == image) {
} else if (tool.getActiveStamp() == image) {
g2.setColor(Color.magenta);
highlight = true;
} else {

View File

@ -0,0 +1,40 @@
package de.ph87.kindermalen.toolbox.tool.stamp;
import de.ph87.kindermalen.MyComponent;
import lombok.extern.slf4j.Slf4j;
import java.awt.*;
import java.util.Comparator;
import static de.ph87.kindermalen.CONFIG.SIDEBAR_WIDTH;
import static de.ph87.kindermalen.CONFIG.STAMP_BUTTON_SIZE;
@Slf4j
public class StampButtonList extends MyComponent {
private final StampTool stampTool;
public StampButtonList(final StampTool stampTool) {
this.stampTool = stampTool;
setLayout(new FlowLayout(FlowLayout.LEADING, 0, 0));
update();
subscribe(stampTool.getOnChange(), ignore -> update());
setBackground(Color.blue);
}
private void update() {
synchronized (getTreeLock()) {
removeAll();
if (stampTool.getActiveGroup() != null) {
stampTool.getActiveGroup().getStamps().stream()
.sorted(Comparator.comparing(Stamp::getFile))
.map(stamp -> new StampButton(stampTool, stamp))
.forEach(this::add);
setPreferredSize(new Dimension(SIDEBAR_WIDTH, getComponentCount() * STAMP_BUTTON_SIZE * STAMP_BUTTON_SIZE / SIDEBAR_WIDTH));
}
}
revalidate();
repaint();
}
}

View File

@ -0,0 +1,35 @@
package de.ph87.kindermalen.toolbox.tool.stamp;
import lombok.Getter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.List;
@Slf4j
@Getter
@ToString
public class StampGroup {
private final String name;
private final List<Stamp> stamps = new ArrayList<>();
private Stamp stamp = null;
public StampGroup(final String name, final Stamp stamp) {
this.name = name;
addStamp(stamp);
}
public void addStamp(final Stamp stamp) {
synchronized (stamps) {
stamps.add(stamp);
if (this.stamp == null || this.stamp.getName().compareTo(stamp.getName()) > 0) {
this.stamp = stamp;
}
}
}
}

View File

@ -0,0 +1,78 @@
package de.ph87.kindermalen.toolbox.tool.stamp;
import de.ph87.kindermalen.ComponentListener;
import de.ph87.kindermalen.MyComponent;
import java.awt.*;
import java.awt.image.BufferedImage;
import static de.ph87.kindermalen.CONFIG.*;
public class StampGroupButton extends MyComponent {
private final StampTool tool;
private final StampGroup group;
private final BufferedImage icon;
private final Point position;
private boolean hover = false;
public StampGroupButton(final StampTool tool, final StampGroup group) {
this.tool = tool;
this.group = group;
setPreferredSize(new Dimension(STAMP_GROUP_BUTTON_SIZE, STAMP_GROUP_BUTTON_SIZE));
icon = group.getStamp().getSize(STAMP_GROUP_BUTTON_ICON_REAL_SIZE);
position = new Point((STAMP_GROUP_BUTTON_SIZE - icon.getWidth()) / 2, (STAMP_GROUP_BUTTON_SIZE - icon.getHeight()) / 2);
ComponentListener.on(this)
.mouseReleased(this::mouseReleased)
.mouseEntered(this::mouseEntered)
.mouseExited(this::mouseExited);
}
private void mouseReleased() {
this.tool.setActiveGroup(group);
this.tool.prepare();
}
private void mouseEntered() {
hover = true;
repaint();
}
private void mouseExited() {
hover = false;
repaint();
}
@Override
public void paint(final Graphics g) {
super.paint(g);
final Graphics2D g2 = (Graphics2D) g;
boolean highlight = false;
if (hover) {
g2.setColor(Color.yellow);
highlight = true;
} else if (tool.getActiveGroup() == group) {
g2.setColor(Color.magenta);
highlight = true;
} else {
g2.setColor(Color.black);
}
if (highlight) {
g2.setStroke(STROKE_HIGHLIGHT);
g2.fillRect(0, 0, getWidth(), getHeight());
} else {
g2.setStroke(STROKE_BORDER);
}
g2.drawImage(icon, position.x, position.y, null);
g2.drawRect(0, 0, STAMP_GROUP_BUTTON_SIZE - 1, STAMP_GROUP_BUTTON_SIZE - 1);
}
}

View File

@ -0,0 +1,34 @@
package de.ph87.kindermalen.toolbox.tool.stamp;
import de.ph87.kindermalen.MyComponent;
import lombok.extern.slf4j.Slf4j;
import java.awt.*;
import java.util.Comparator;
@Slf4j
public class StampGroupButtonList extends MyComponent {
private final StampTool stampTool;
public StampGroupButtonList(final StampTool stampTool) {
this.stampTool = stampTool;
setLayout(new FlowLayout(FlowLayout.LEADING, 0, 0));
update();
subscribe(stampTool.getOnChange(), ignore -> update());
setBackground(Color.red);
}
private void update() {
synchronized (getTreeLock()) {
removeAll();
stampTool.getGroups().stream()
.sorted(Comparator.comparing(StampGroup::getName))
.map(group -> new StampGroupButton(stampTool, group))
.forEach(this::add);
}
revalidate();
repaint();
}
}

View File

@ -1,4 +1,4 @@
package de.ph87.kindermalen.tools.tool.stamp;
package de.ph87.kindermalen.toolbox.tool.stamp;
import de.ph87.kindermalen.MyComponent;
@ -6,6 +6,7 @@ import javax.swing.*;
import java.awt.*;
import static de.ph87.kindermalen.CONFIG.BORDERS;
import static de.ph87.kindermalen.CONFIG.SIDEBAR_WIDTH;
public class StampOptions extends MyComponent {
@ -16,6 +17,7 @@ public class StampOptions extends MyComponent {
private final JSlider stepSlider = new JSlider(1, 1000, 25);
public StampOptions(final StampTool tool) {
setPreferredSize(new Dimension(SIDEBAR_WIDTH, 200));
setLayout(new GridLayout(6, 1));
final JLabel sizeLabel = new JLabel("Größe:");

View File

@ -0,0 +1,23 @@
package de.ph87.kindermalen.toolbox.tool.stamp;
import de.ph87.kindermalen.toolbox.tool.ToolPanel;
import lombok.extern.slf4j.Slf4j;
import java.awt.*;
import static de.ph87.kindermalen.util.MyGridBagConstraints.GBC;
import static java.awt.GridBagConstraints.BOTH;
import static java.awt.GridBagConstraints.HORIZONTAL;
@Slf4j
public class StampPanel extends ToolPanel {
public StampPanel(final StampTool tool) {
setLayout(new GridBagLayout());
int row = 0;
add(new StampGroupButtonList(tool), GBC(0, row++, 1, 0, HORIZONTAL));
add(new StampButtonList(tool), GBC(0, row++, 1, 1, BOTH));
add(new StampOptions(tool), GBC(0, row++, 1, 0, HORIZONTAL));
}
}

View File

@ -1,9 +1,8 @@
package de.ph87.kindermalen.tools.tool.stamp;
package de.ph87.kindermalen.toolbox.tool.stamp;
import de.ph87.kindermalen.drawing.Drawing;
import de.ph87.kindermalen.tools.tool.Tool;
import de.ph87.kindermalen.toolbox.tool.Tool;
import de.ph87.kindermalen.util.Batch;
import de.ph87.kindermalen.util.Publisher;
import de.ph87.kindermalen.util.Vector;
import lombok.Getter;
import lombok.Setter;
@ -15,8 +14,6 @@ import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
import java.util.stream.Stream;
import static de.ph87.kindermalen.util.FileHelper.SCAN_FILES;
import static de.ph87.kindermalen.util.ImageHelper.ALPHA;
@ -29,12 +26,12 @@ public class StampTool extends Tool {
private static final File STAMPS_DIR = new File("./data/stamps");
private final List<StampImage> images = new ArrayList<>();
private final List<StampGroup> groups = new ArrayList<>();
private final Publisher<List<StampImage>> onListChange = new Publisher<List<StampImage>>();
private StampGroup activeGroup = null;
@Setter
private StampImage image = null;
private Stamp activeStamp = null;
@Setter
private int size = 100;
@ -47,36 +44,53 @@ public class StampTool extends Tool {
private BufferedImage prepared;
public void setActiveGroup(final StampGroup activeGroup) {
this.activeGroup = activeGroup;
onChange.publish(this);
}
public StampTool() {
super("Stempel");
final Stream<Supplier<StampImage>> runnables = SCAN_FILES(STAMPS_DIR).stream().map(stamp -> () -> load(stamp));
new Batch<>("STAMP", runnables.toList());
if (image == null && !images.isEmpty()) {
setImage(images.get(0));
new Batch<>("STAMP", SCAN_FILES(STAMPS_DIR), this::load);
}
private void load(final File file) {
try {
final Stamp stamp = new Stamp(file);
if (this.activeStamp == null) {
this.activeStamp = stamp;
}
final StampGroup group = getOrCreateGroup(file.getParentFile().getName(), stamp);
if (this.activeGroup == null) {
this.activeGroup = group;
}
onChange.publish(this);
} catch (IOException ex) {
log.error(ex.toString());
}
}
private StampImage load(final File file) {
try {
final StampImage image = new StampImage(file);
synchronized (images) {
images.add(image);
onListChange.publish(images);
}
return image;
} catch (IOException e) {
log.error(e.toString());
return null;
private StampGroup getOrCreateGroup(final String groupName, final Stamp stamp) {
synchronized (groups) {
return groups.stream()
.filter(existing -> existing.getName().equals(groupName))
.peek(existing -> existing.addStamp(stamp))
.findFirst()
.orElseGet(() -> {
final StampGroup created = new StampGroup(groupName, stamp);
groups.add(created);
return created;
});
}
}
public void prepare() {
if (this.image == null || image.getOriginal() == null) {
if (this.activeStamp == null || activeStamp.getOriginal() == null) {
return;
}
prepared = RESIZE(image.getOriginal(), size, size);
prepared = RESIZE(activeStamp.getOriginal(), size, size);
ALPHA(prepared, alpha);
log.info("Stamp prepared: {}", image.getName());
log.info("Stamp prepared: {}", activeStamp.getName());
onChange.publish(this);
}

View File

@ -1,40 +0,0 @@
package de.ph87.kindermalen.tools;
import de.ph87.kindermalen.MyComponent;
import de.ph87.kindermalen.tools.tool.Tool;
import java.awt.*;
import java.awt.event.MouseEvent;
import static de.ph87.kindermalen.CONFIG.STAMP_BUTTON_SIZE;
public class ToolButton extends MyComponent {
private final Tools tools;
private final Tool tool;
public ToolButton(final Tools tools, final Tool tool) {
this.tools = tools;
this.tool = tool;
setPreferredSize(new Dimension(STAMP_BUTTON_SIZE, STAMP_BUTTON_SIZE));
}
@Override
public void mouseReleased(final MouseEvent mouseEvent) {
tools.setTool(tool);
}
@Override
public void paint(final Graphics g) {
g.setColor(Color.magenta);
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(Color.black);
g.setFont(g.getFont().deriveFont(12.0f));
g.drawString(tool.getName(), 0, g.getFontMetrics().getHeight());
g.drawRect(0, 0, getWidth() - 1, getHeight() - 1);
}
}

View File

@ -1,30 +0,0 @@
package de.ph87.kindermalen.tools;
import de.ph87.kindermalen.tools.tool.Tool;
import lombok.extern.slf4j.Slf4j;
import javax.swing.*;
import java.awt.*;
import static de.ph87.kindermalen.CONFIG.BORDERS;
@Slf4j
public class ToolsPanel extends JPanel {
public ToolsPanel(final Tools tools) {
setLayout(new FlowLayout(FlowLayout.LEADING, 0, 0));
for (final Tool tool : tools.getTools()) {
add(new ToolButton(tools, tool));
}
}
@Override
public void paint(final Graphics g) {
super.paint(g);
if (BORDERS) {
g.setColor(Color.magenta);
g.drawRect(0, 0, getWidth() - 1, getHeight() - 1);
}
}
}

View File

@ -1,32 +0,0 @@
package de.ph87.kindermalen.tools.tool.stamp;
import de.ph87.kindermalen.MyComponent;
import lombok.extern.slf4j.Slf4j;
import java.awt.*;
import java.util.Comparator;
import java.util.List;
@Slf4j
public class StampButtonList extends MyComponent {
public StampButtonList(final StampTool stampTool) {
final FlowLayout flowLayout = new FlowLayout(FlowLayout.LEADING, 0, 0);
setLayout(flowLayout);
add(stampTool, stampTool.getImages());
subscribe(stampTool.getOnListChange(), images -> add(stampTool, images));
subscribe(stampTool.getOnChange(), ignore -> repaint());
}
private void add(final StampTool stampTool, final List<StampImage> images) {
synchronized (getTreeLock()) {
removeAll();
for (final StampImage image : images.stream().sorted(Comparator.comparing(StampImage::getFile)).toList()) {
final StampButton button = new StampButton(stampTool, image);
add(button);
}
}
revalidate();
}
}

View File

@ -1,27 +0,0 @@
package de.ph87.kindermalen.tools.tool.stamp;
import de.ph87.kindermalen.tools.tool.ToolPanel;
import lombok.extern.slf4j.Slf4j;
import java.awt.*;
import static de.ph87.kindermalen.util.MyGridBagConstraints.C;
import static java.awt.GridBagConstraints.HORIZONTAL;
@Slf4j
public class StampPanel extends ToolPanel {
private final StampButtonList list;
public StampPanel(final StampTool tool) {
setLayout(new GridBagLayout());
add(new StampOptions(tool), C(0, 0, 1, 0, HORIZONTAL));
list = new StampButtonList(tool);
add(list, C(0, 1, 1, 1));
}
public void destruct() {
list.destruct();
}
}

View File

@ -2,23 +2,25 @@ package de.ph87.kindermalen.util;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Supplier;
import java.util.function.Consumer;
@Slf4j
public class Batch<T> {
private final List<Supplier<T>> suppliers;
private final List<T> list;
private final Consumer<T> runnable;
private final Set<Thread> threads = new HashSet<>();
private boolean stop = false;
public Batch(final String name, final List<Supplier<T>> suppliers) {
this.suppliers = new ArrayList<>(suppliers);
public Batch(final String name, final List<T> list, final Consumer<T> runnable) {
this.list = list;
this.runnable = runnable;
synchronized (threads) {
for (int i = 0; i < Runtime.getRuntime().availableProcessors(); i++) {
final Thread thread = new Thread(this::threadMain, name + "-%03d".formatted(i));
@ -47,11 +49,11 @@ public class Batch<T> {
try {
log.debug("Started");
while (true) {
final Supplier<T> supplier = getNext();
if (supplier == null) {
final T item = getNext();
if (item == null) {
return;
}
final T result = supplier.get();
runnable.accept(item);
}
} finally {
synchronized (threads) {
@ -62,12 +64,12 @@ public class Batch<T> {
}
}
private Supplier<T> getNext() {
private T getNext() {
synchronized (threads) {
if (stop || suppliers.isEmpty()) {
if (stop || list.isEmpty()) {
return null;
}
return suppliers.remove(0);
return list.remove(0);
}
}

View File

@ -4,20 +4,12 @@ import java.awt.*;
public class MyGridBagConstraints extends GridBagConstraints {
public MyGridBagConstraints(final int x, final int y, final int w, final int h, final double wX, final double wY, final int anchor, final int fill, final Insets insets, final int padX, final int padY) {
private MyGridBagConstraints(final int x, final int y, final int w, final int h, final double wX, final double wY, final int anchor, final int fill, final Insets insets, final int padX, final int padY) {
super(x, y, w, h, wX, wY, anchor, fill, insets, padX, padY);
}
public static MyGridBagConstraints C(final int x, final int y, final int w, final int h, final double wX, final double wY, final int anchor, final int fill) {
return new MyGridBagConstraints(x, y, w, h, wX, wY, anchor, fill, new Insets(0, 0, 0, 0), 0, 0);
}
public static MyGridBagConstraints C(final int x, final int y, final double wX, final double wY) {
return new MyGridBagConstraints(x, y, 1, 1, wX, wY, FIRST_LINE_START, BOTH, new Insets(0, 0, 0, 0), 0, 0);
}
public static MyGridBagConstraints C(final int x, final int y, final double wX, final double wY, final int fill) {
return new MyGridBagConstraints(x, y, 1, 1, wX, wY, FIRST_LINE_START, fill, new Insets(0, 0, 0, 0), 0, 0);
public static MyGridBagConstraints GBC(final int x, final int y, final double wX, final double wY, final int fill) {
return new MyGridBagConstraints(x, y, 1, 1, wX, wY, PAGE_START, fill, new Insets(0, 0, 0, 0), 0, 0);
}
}