Compare commits

...

10 Commits

36 changed files with 1010 additions and 584 deletions

13
pom.xml
View File

@ -9,21 +9,22 @@
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<maven.compiler.release>21</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.4.5</version>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>2.0.9</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
<version>1.18.34</version>
</dependency>
<dependency>
<groupId>com.mortennobel</groupId>

View File

@ -12,6 +12,16 @@ 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 = 35;
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,19 +1,16 @@
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 de.ph87.kindermalen.util.Subscription;
import lombok.Getter;
import java.util.function.Consumer;
@Getter
public class Environment {
private final Publisher<Drawing> onNewDrawing = new Publisher<>();
private final Publisher<Drawing> onDrawingChange = new Publisher<>();
private final Tools tools = new Tools();
private final Toolbox toolbox = new Toolbox();
private Drawing drawing = null;
@ -22,12 +19,8 @@ public class Environment {
}
public void newDrawing() {
drawing = new Drawing(1920, 1080);
onNewDrawing.publish(drawing);
}
public Subscription<Drawing> onNewDrawing(final Consumer<Drawing> next) {
return onNewDrawing.subscribe(next);
drawing = new Drawing(1920, 1080, onDrawingChange::publish);
onDrawingChange.publish(drawing);
}
}

View File

@ -0,0 +1,14 @@
package de.ph87.kindermalen;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class Main {
public static void main(String[] args) {
final Environment environment = new Environment();
final Window window = new Window(environment);
window.setVisible(true);
}
}

View File

@ -0,0 +1,47 @@
package de.ph87.kindermalen;
import de.ph87.kindermalen.util.Publisher;
import de.ph87.kindermalen.util.Subscription;
import javax.swing.*;
import java.awt.*;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
public abstract class MyComponent extends JPanel {
private final List<Subscription<?>> subscriptions = new ArrayList<>();
public void destruct() {
synchronized (subscriptions) {
subscriptions.forEach(Subscription::unsubscribe);
subscriptions.clear();
}
synchronized (getTreeLock()) {
for (final Component component : getComponents()) {
if (component instanceof MyComponent) {
((MyComponent) component).destruct();
}
}
}
}
protected <T> Subscription<T> subscribe(final Publisher<T> publisher, final Consumer<T> next) {
synchronized (subscriptions) {
final Subscription<T> subscription = publisher.subscribe(next);
subscriptions.add(subscription);
return subscription;
}
}
protected void unsubscribe(final Subscription<?> subscription) {
synchronized (subscriptions) {
if (!subscriptions.remove(subscription)) {
throw new RuntimeException();
}
subscription.unsubscribe();
}
}
}

View File

@ -1,65 +1,64 @@
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.util.MouseListener;
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 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 JPanel {
public class Sidebar extends MyComponent {
private ToolPanel toolPanel = null;
private StampPanel stamp;
public Sidebar(final Environment environment) {
setPreferredSize(new Dimension(300, 0));
setPreferredSize(new Dimension(SIDEBAR_WIDTH, 0));
setLayout(new GridBagLayout());
final JButton newDrawing = new JButton("Neues Bild");
MouseListener.onPress(newDrawing, e -> environment.newDrawing());
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));
environment.getTools().onToolSelected(this::onToolSelected);
setBackground(Color.yellow);
}
@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 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);
}
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) {
if (toolPanel != null) {
toolPanel.setVisible(false);
toolPanel.destruct();
remove(toolPanel);
toolPanel = null;
}
if (tool instanceof StampTool) {
if (stamp == null) {
stamp = new StampPanel((StampTool) tool);
add(stamp, C(0, 2, 1, 0.9));
}
toolPanel = stamp;
toolPanel = new StampPanel((StampTool) tool);
}
if (toolPanel != null) {
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);
@ -64,10 +61,4 @@ public class Window extends JFrame {
setLocation(bounds.x, bounds.y);
}
public static void main(String[] args) {
final Environment environment = new Environment();
final Window window = new Window(environment);
window.setVisible(true);
}
}

View File

@ -1,6 +1,7 @@
package de.ph87.kindermalen.drawing;
import lombok.Getter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import javax.imageio.ImageIO;
@ -10,50 +11,47 @@ import java.io.File;
import java.io.IOException;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.function.Consumer;
@Slf4j
@ToString
public class Drawing {
@Getter
private final int width;
public final int width;
public final int height;
private final Consumer<Drawing> onChange;
public final ZonedDateTime created = ZonedDateTime.now();
@Getter
private final int height;
@ToString.Exclude
private final Layer layer;
private final Layer current;
private final ZonedDateTime created = ZonedDateTime.now();
public Drawing(final int width, final int height) {
public Drawing(final int width, final int height, final Consumer<Drawing> onChange) {
this.width = width;
this.height = height;
current = new Layer(width, height);
this.onChange = onChange;
layer = new Layer(width, height, l -> onChange.accept(this));
log.info("New Drawing: {}", this);
}
public Graphics2D getGraphics() {
return (Graphics2D) current.getCurrent().getGraphics();
}
public void publish() {
current.publish();
}
public void undo() {
current.undo();
layer.undo();
}
public void redo() {
current.redo();
layer.redo();
}
public void newRevision() {
current.newRevision();
layer.newRevision();
}
public void save(final File dir) throws IOException {
final int revision = current.getRevision();
final BufferedImage image = current.getCurrent();
final int revision = layer.getRevision();
final BufferedImage image = layer.getBuffer();
final File subdir = new File(dir, created.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
final File file = new File(subdir, "%05d".formatted(revision) + ".png");
if (subdir.mkdirs()) {
@ -69,7 +67,7 @@ public class Drawing {
}
public Image getImage() {
return current.getCurrent();
return layer.getBuffer();
}
}

View File

@ -1,16 +1,14 @@
package de.ph87.kindermalen.drawing;
import de.ph87.kindermalen.ComponentListener;
import de.ph87.kindermalen.Environment;
import de.ph87.kindermalen.tools.tool.Tool;
import de.ph87.kindermalen.util.MouseListener;
import de.ph87.kindermalen.MyComponent;
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 javax.swing.*;
import java.awt.*;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
@ -20,13 +18,13 @@ import java.io.File;
import java.io.IOException;
@Slf4j
public class DrawingPanel extends JPanel {
public class DrawingPanel extends MyComponent {
private static final File SAVE_DIR = new File("./data/images");
private final Environment environment;
private Subscription<Tool> toolSubscription = null;
private Subscription<Tool> toolChangeSubscription = null;
private Vector lastPoint = null;
@ -38,44 +36,60 @@ public class DrawingPanel extends JPanel {
public DrawingPanel(final Environment environment) {
this.environment = environment;
MouseListener.onPress(this, this::onPress);
MouseListener.onRelease(this, this::onRelease);
MouseListener.onMove(this, this::onMove);
MouseListener.onDrag(this, this::onDrag);
environment.getTools().onToolSelected(tool -> {
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) {
repaint();
if (toolSubscription != null) {
toolSubscription.unsubscribe();
if (toolChangeSubscription != null) {
unsubscribe(toolChangeSubscription);
}
toolSubscription = tool.onChange(ignore -> repaint());
});
environment.onNewDrawing(drawing -> repaint());
addComponentListener(new ComponentListener() {
@Override
public void componentResized(final ComponentEvent e) {
calculateTransform();
toolChangeSubscription = subscribe(tool.getOnChange(), ignore -> repaint());
}
@Override
public void componentMoved(final ComponentEvent e) {
calculateTransform();
public void mousePressed(final MouseEvent mouseEvent) {
if (mouseEvent.getButton() == 3) {
environment.getDrawing().undo();
return;
}
environment.getDrawing().newRevision();
lastPoint = environment.getToolbox().getTool().apply(null, environment.getDrawing(), transform(mouseEvent.getPoint()));
repaint();
}
@Override
public void componentShown(final ComponentEvent e) {
public void mouseReleased(final MouseEvent mouseEvent) {
try {
environment.getDrawing().save(SAVE_DIR);
} catch (IOException ex) {
log.error(ex.toString());
}
}
@Override
public void componentHidden(final ComponentEvent e) {
public void mouseDragged(final MouseEvent mouseEvent) {
cursor = transform(mouseEvent.getPoint());
lastPoint = environment.getToolbox().getTool().apply(lastPoint, environment.getDrawing(), transform(mouseEvent.getPoint()));
repaint();
}
});
public void mouseMoved(final MouseEvent mouseEvent) {
cursor = transform(mouseEvent.getPoint());
repaint();
}
private void calculateTransform() {
final Image image = environment.getDrawing().getImage();
final Rectangle box = fitInside(image.getWidth(null), image.getHeight(null), getWidth(), getHeight(), false);
final double scale = (double) box.width / image.getWidth(null);
final Drawing drawing = environment.getDrawing();
final Rectangle box = fitInside(drawing.width, drawing.height, getWidth(), getHeight(), false);
final double scale = (double) box.width / drawing.width;
transform = new AffineTransform();
transform.translate(box.x, box.y);
transform.scale(scale, scale);
@ -84,28 +98,6 @@ public class DrawingPanel extends JPanel {
} catch (NoninvertibleTransformException ex) {
log.error(ex.toString());
}
System.out.printf("%.0f%%\n", 100.0 * scale);
}
private void onMove(final MouseEvent mouseEvent) {
cursor = transform(mouseEvent.getPoint());
repaint();
}
private void onPress(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()));
repaint();
}
private void onDrag(final MouseEvent mouseEvent) {
cursor = transform(mouseEvent.getPoint());
lastPoint = environment.getTools().getTool().apply(lastPoint, environment.getDrawing(), transform(mouseEvent.getPoint()));
repaint();
}
private Vector transform(final Point point) {
@ -113,32 +105,26 @@ public class DrawingPanel extends JPanel {
return new Vector(transformed);
}
private void onRelease(final MouseEvent mouseEvent) {
try {
environment.getDrawing().save(SAVE_DIR);
} catch (IOException ex) {
log.error(ex.toString());
}
}
@Override
public void paint(final Graphics graphics) {
final Graphics2D g = (Graphics2D) graphics;
g.setColor(Color.gray);
g.fillRect(0, 0, getWidth(), getHeight());
g.setTransform(transform);
if (environment == null) {
if (transform == null || environment == null || environment.getDrawing() == null) {
return;
}
final Image image = environment.getDrawing().getImage();
g.setTransform(transform);
draw(g, environment.getDrawing());
}
private void draw(final Graphics2D g, final Drawing drawing) {
g.setColor(Color.white);
g.fillRect(0, 0, image.getWidth(null), image.getHeight(null));
g.drawImage(image, 0, 0, image.getWidth(null), image.getHeight(null), null);
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,10 +1,9 @@
package de.ph87.kindermalen.drawing;
import de.ph87.kindermalen.util.Publisher;
import de.ph87.kindermalen.util.Subscription;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
@ -19,24 +18,25 @@ public class Layer {
private final int height;
@Getter
private BufferedImage current;
private final Consumer<Layer> onChange;
private List<BufferedImage> history = new ArrayList<>();
@Getter
private BufferedImage buffer;
@Getter
private int index = 0;
@Getter
private int revision = 0;
private final Publisher<BufferedImage> onChange = new Publisher<>();
public Layer(final int width, final int height) {
public Layer(final int width, final int height, final Consumer<Layer> onChange) {
this.width = width;
this.height = height;
this.current = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
this.history.add(current);
this.onChange = onChange;
this.buffer = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
this.history.add(buffer);
}
public void newRevision() {
@ -46,19 +46,19 @@ public class Layer {
history = history.subList(0, keep);
log.info("{} Revisions deleted", old - keep);
}
current = COPY(current);
buffer = COPY(buffer);
index = history.size();
revision++;
history.add(current);
log.info("Revision {} created", index);
history.add(buffer);
log.debug("Revision {} created", index);
}
public void undo() {
if (index > 0) {
index--;
current = history.get(index);
buffer = history.get(index);
log.info("UNDO: Revision {} loaded", index);
publish();
onChange.accept(this);
} else {
log.warn("No UNDO steps left.");
}
@ -67,9 +67,9 @@ public class Layer {
public void redo() {
if (canRedo()) {
index++;
current = history.get(index);
buffer = history.get(index);
log.info("REDO: Revision {} loaded", index);
publish();
onChange.accept(this);
} else {
log.warn("No REDO steps left.");
}
@ -79,12 +79,10 @@ public class Layer {
return index < history.size() - 1;
}
public void publish() {
onChange.publish(current);
}
public Subscription<BufferedImage> onChange(final Consumer<BufferedImage> next) {
return onChange.subscribe(next);
public void draw(final BufferedImage image, final int x, final int y) {
final Graphics2D g = (Graphics2D) buffer.getGraphics();
g.drawImage(image, x, y, null);
onChange.accept(this);
}
}

View File

@ -0,0 +1,31 @@
package de.ph87.kindermalen.toolbox;
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;
import java.util.List;
@Slf4j
@Getter
public class Toolbox {
private final List<Tool> tools = List.of(new StampTool());
private Tool tool;
private final Publisher<Tool> onSelect = new Publisher<>();
public Toolbox() {
setTool(tools.get(0));
}
public void setTool(final Tool tool) {
this.tool = tool;
log.info("Tool selected: {}", tool.getName());
onSelect.publish(tool);
}
}

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,19 +1,17 @@
package de.ph87.kindermalen.tools.tool;
package de.ph87.kindermalen.toolbox.tool;
import de.ph87.kindermalen.drawing.Drawing;
import de.ph87.kindermalen.util.Publisher;
import de.ph87.kindermalen.util.Subscription;
import de.ph87.kindermalen.util.Vector;
import lombok.Getter;
import java.awt.image.BufferedImage;
import java.util.function.Consumer;
@Getter
public abstract class Tool {
protected final Publisher<Tool> onChange = new Publisher<>();
@Getter
private final String name;
protected Tool(final String name) {
@ -24,8 +22,4 @@ public abstract class Tool {
public abstract BufferedImage getPreview();
public Subscription<Tool> onChange(final Consumer<Tool> next) {
return onChange.subscribe(next);
}
}

View File

@ -0,0 +1,9 @@
package de.ph87.kindermalen.toolbox.tool;
import de.ph87.kindermalen.MyComponent;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public abstract class ToolPanel extends MyComponent {
}

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;
@ -11,13 +11,15 @@ import java.io.IOException;
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 Stamp {
@ToString.Include
private final String name;
private final File file;
@ -30,11 +32,15 @@ public class Stamp {
this.name = file.getName().substring(0, file.getName().lastIndexOf("."));
this.file = file;
this.original = ImageIO.read(file);
log.info("Stamp loaded: {}", this);
getSize(STAMP_BUTTON_ICON_REAL_SIZE);
getSize(STAMP_GROUP_BUTTON_ICON_REAL_SIZE);
log.debug("Stamp loaded: {}", this);
}
public BufferedImage getSize(final int size) {
synchronized (sizes) {
return sizes.computeIfAbsent(size, s -> RESIZE(original, s, s));
}
}
}

View File

@ -1,21 +1,20 @@
package de.ph87.kindermalen.tools.tool.stamp;
package de.ph87.kindermalen.toolbox.tool.stamp;
import de.ph87.kindermalen.util.MouseListener;
import de.ph87.kindermalen.ComponentListener;
import de.ph87.kindermalen.MyComponent;
import lombok.Getter;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import static de.ph87.kindermalen.CONFIG.*;
public class StampButton extends JPanel {
public class StampButton extends MyComponent {
private final StampTool tool;
@Getter
private final Stamp stamp;
private final Stamp image;
private final BufferedImage icon;
@ -23,28 +22,29 @@ public class StampButton extends JPanel {
private boolean hover = false;
public StampButton(final StampTool tool, final Stamp stamp) {
public StampButton(final StampTool tool, final Stamp image) {
this.tool = tool;
this.stamp = stamp;
this.image = image;
setPreferredSize(new Dimension(STAMP_BUTTON_SIZE, STAMP_BUTTON_SIZE));
MouseListener.onRelease(this, this::onRelease);
MouseListener.onEnter(this, this::onEnter);
MouseListener.onExit(this, this::onExit);
icon = stamp.getSize(STAMP_BUTTON_SIZE - STROKE_HIGHLIGHT_WIDTH * 2);
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);
}
private void onRelease(final MouseEvent mouseEvent) {
this.tool.setStamp(stamp);
private void mouseReleased() {
this.tool.setActiveStamp(image);
this.tool.prepare();
}
private void onEnter(final MouseEvent mouseEvent) {
private void mouseEntered() {
hover = true;
repaint();
}
private void onExit(final MouseEvent mouseEvent) {
private void mouseExited() {
hover = false;
repaint();
}
@ -59,7 +59,7 @@ public class StampButton extends JPanel {
if (hover) {
g2.setColor(Color.yellow);
highlight = true;
} else if (tool.getStamp() == stamp) {
} else if (tool.getActiveStamp() == image) {
g2.setColor(Color.magenta);
highlight = true;
} else {

View File

@ -0,0 +1,39 @@
package de.ph87.kindermalen.toolbox.tool.stamp;
import de.ph87.kindermalen.*;
import lombok.extern.slf4j.*;
import java.awt.*;
import java.util.*;
import static de.ph87.kindermalen.CONFIG.*;
@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, ((int) Math.ceil((double) getComponentCount() * STAMP_BUTTON_SIZE / SIDEBAR_WIDTH) * STAMP_BUTTON_SIZE)));
}
}
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,11 +1,14 @@
package de.ph87.kindermalen.tools.tool.stamp;
package de.ph87.kindermalen.toolbox.tool.stamp;
import de.ph87.kindermalen.MyComponent;
import javax.swing.*;
import java.awt.*;
import static de.ph87.kindermalen.CONFIG.BORDERS;
import static de.ph87.kindermalen.CONFIG.SIDEBAR_WIDTH;
public class StampOptionsPanel extends JPanel {
public class StampOptions extends MyComponent {
private final JSlider sizeSlider = new JSlider(10, 1000, 100);
@ -13,7 +16,8 @@ public class StampOptionsPanel extends JPanel {
private final JSlider stepSlider = new JSlider(1, 1000, 25);
public StampOptionsPanel(final StampTool tool) {
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

@ -0,0 +1,123 @@
package de.ph87.kindermalen.toolbox.tool.stamp;
import de.ph87.kindermalen.drawing.Drawing;
import de.ph87.kindermalen.toolbox.tool.Tool;
import de.ph87.kindermalen.util.Batch;
import de.ph87.kindermalen.util.Vector;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import static de.ph87.kindermalen.util.FileHelper.SCAN_FILES;
import static de.ph87.kindermalen.util.ImageHelper.ALPHA;
import static de.ph87.kindermalen.util.ImageHelper.RESIZE;
@Slf4j
@Getter
@ToString
public class StampTool extends Tool {
private static final File STAMPS_DIR = new File("./data/stamps");
private final List<StampGroup> groups = new ArrayList<>();
private StampGroup activeGroup = null;
@Setter
private Stamp activeStamp = null;
@Setter
private int size = 100;
@Setter
private double alpha = 0.75;
@Setter
private int step = 25;
private BufferedImage prepared;
public void setActiveGroup(final StampGroup activeGroup) {
this.activeGroup = activeGroup;
onChange.publish(this);
}
public StampTool() {
super("Stempel");
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 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.activeStamp == null || activeStamp.getOriginal() == null) {
return;
}
prepared = RESIZE(activeStamp.getOriginal(), size, size);
ALPHA(prepared, alpha);
log.info("Stamp prepared: {}", activeStamp.getName());
onChange.publish(this);
}
@Override
public Vector apply(Vector last, final Drawing drawing, final Vector current) {
if (last == null) {
apply(drawing, current);
return current;
}
final Vector vector = new Vector(last, current);
for (int rest = (int) Math.floor(vector.length); rest >= step; rest -= step) {
final Vector point = last.plus(vector.withLength(step));
apply(drawing, point);
last = point;
}
return last;
}
private void apply(final Drawing drawing, final Vector point) {
final int x = point.intX() - prepared.getWidth() / 2;
final int y = point.intY() - prepared.getHeight() / 2;
drawing.getLayer().draw(prepared, x, y);
}
@Override
public BufferedImage getPreview() {
return prepared;
}
}

View File

@ -1,32 +0,0 @@
package de.ph87.kindermalen.tools;
import de.ph87.kindermalen.tools.tool.Tool;
import de.ph87.kindermalen.util.MouseListener;
import java.awt.*;
import static de.ph87.kindermalen.CONFIG.STAMP_BUTTON_SIZE;
public class ToolButton extends Component {
private final Tool tool;
public ToolButton(final Tools tools, final Tool tool) {
this.tool = tool;
setPreferredSize(new Dimension(STAMP_BUTTON_SIZE, STAMP_BUTTON_SIZE));
MouseListener.onRelease(this, e -> 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,38 +0,0 @@
package de.ph87.kindermalen.tools;
import de.ph87.kindermalen.tools.tool.Tool;
import de.ph87.kindermalen.tools.tool.stamp.StampTool;
import de.ph87.kindermalen.util.Publisher;
import de.ph87.kindermalen.util.Subscription;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
import java.util.function.Consumer;
@Slf4j
public class Tools {
@Getter
private final List<Tool> tools = List.of(new StampTool());
@Getter
private Tool tool;
private final Publisher<Tool> onToolSelected = new Publisher<>();
public Tools() {
setTool(tools.get(0));
}
public void setTool(final Tool tool) {
this.tool = tool;
log.info("Tool selected: {}", tool.getName());
onToolSelected.publish(tool);
}
public Subscription<Tool> onToolSelected(final Consumer<Tool> next) {
return onToolSelected.subscribe(next);
}
}

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,10 +0,0 @@
package de.ph87.kindermalen.tools.tool;
import lombok.extern.slf4j.Slf4j;
import javax.swing.*;
@Slf4j
public abstract class ToolPanel extends JPanel {
}

View File

@ -1,23 +0,0 @@
package de.ph87.kindermalen.tools.tool.stamp;
import lombok.extern.slf4j.Slf4j;
import javax.swing.*;
import java.awt.*;
import static de.ph87.kindermalen.CONFIG.BORDERS;
@Slf4j
public class StampListPanel extends JPanel {
public StampListPanel(final StampTool tool) {
setLayout(new FlowLayout(FlowLayout.LEADING, 0, 0));
tool.getStamps().forEach(stamp -> add(new StampButton(tool, stamp)));
tool.onChange(ignore -> repaint());
repaint();
if (BORDERS) {
setBackground(Color.green.brighter().brighter());
}
}
}

View File

@ -1,20 +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 {
public StampPanel(final StampTool tool) {
setLayout(new GridBagLayout());
add(new StampOptionsPanel(tool), C(0, 0, 1, 0, HORIZONTAL));
add(new StampListPanel(tool), C(0, 1, 1, 1));
}
}

View File

@ -1,96 +0,0 @@
package de.ph87.kindermalen.tools.tool.stamp;
import de.ph87.kindermalen.drawing.Drawing;
import de.ph87.kindermalen.tools.tool.Tool;
import de.ph87.kindermalen.util.Vector;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Objects;
import static de.ph87.kindermalen.util.FileHelper.SCAN_FILES;
import static de.ph87.kindermalen.util.ImageHelper.ALPHA;
import static de.ph87.kindermalen.util.ImageHelper.RESIZE;
@Slf4j
@Getter
@ToString
public class StampTool extends Tool {
private static final File STAMPS_DIR = new File("./data/stamps");
private final List<Stamp> stamps;
@Setter
private Stamp stamp = null;
@Setter
private int size = 100;
@Setter
private double alpha = 0.75;
@Setter
private int step = 25;
private BufferedImage prepared;
public StampTool() {
super("Stempel");
stamps = SCAN_FILES(STAMPS_DIR).stream().map(this::load).filter(Objects::nonNull).toList();
if (stamp == null && !stamps.isEmpty()) {
setStamp(stamps.get(0));
}
}
private Stamp load(final File file) {
try {
return new Stamp(file);
} catch (IOException e) {
log.error(e.toString());
return null;
}
}
public void prepare() {
if (this.stamp == null || stamp.getOriginal() == null) {
return;
}
prepared = RESIZE(stamp.getOriginal(), size, size);
ALPHA(prepared, alpha);
log.info("Stamp prepared: {}", stamp.getName());
onChange.publish(this);
}
@Override
public Vector apply(Vector last, final Drawing drawing, final Vector current) {
if (last == null) {
apply(drawing, current);
return current;
}
final Vector vector = new Vector(last, current);
for (int rest = (int) Math.floor(vector.length); rest >= step; rest -= step) {
final Vector point = last.plus(vector.withLength(step));
apply(drawing, point);
last = point;
}
return last;
}
private void apply(final Drawing drawing, final Vector point) {
drawing.getGraphics().drawImage(prepared, point.intX() - prepared.getWidth() / 2, point.intY() - prepared.getHeight() / 2, null);
drawing.publish();
}
@Override
public BufferedImage getPreview() {
return prepared;
}
}

View File

@ -0,0 +1,76 @@
package de.ph87.kindermalen.util;
import lombok.extern.slf4j.Slf4j;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
@Slf4j
public class Batch<T> {
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<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));
threads.add(thread);
thread.start();
}
}
}
public void stop() {
synchronized (threads) {
stop = true;
threads.notifyAll();
}
}
public void join() throws InterruptedException {
synchronized (threads) {
while (!threads.isEmpty()) {
threads.wait();
}
}
}
private void threadMain() {
try {
log.debug("Started");
while (true) {
final T item = getNext();
if (item == null) {
return;
}
runnable.accept(item);
}
} finally {
synchronized (threads) {
threads.remove(Thread.currentThread());
threads.notifyAll();
log.debug("Terminated");
}
}
}
private T getNext() {
synchronized (threads) {
if (stop || list.isEmpty()) {
return null;
}
return list.remove(0);
}
}
}

View File

@ -1,109 +0,0 @@
package de.ph87.kindermalen.util;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.util.function.Consumer;
public class MouseListener {
public static void onPress(final Component component, final Consumer<MouseEvent> onPress) {
component.addMouseListener(new MouseListenerImpl() {
@Override
public void mousePressed(final MouseEvent mouseEvent) {
onPress.accept(mouseEvent);
}
});
}
public static void onRelease(final Component component, final Consumer<MouseEvent> release) {
component.addMouseListener(new MouseListenerImpl() {
@Override
public void mouseReleased(final MouseEvent mouseEvent) {
if (release != null) {
release.accept(mouseEvent);
}
}
});
}
public static void onMove(final Component component, final Consumer<MouseEvent> onMove) {
component.addMouseMotionListener(new MouseMotionListenerImpl() {
@Override
public void mouseMoved(final MouseEvent mouseEvent) {
onMove.accept(mouseEvent);
}
});
}
public static void onDrag(final Component component, final Consumer<MouseEvent> onDrag) {
component.addMouseMotionListener(new MouseMotionListenerImpl() {
@Override
public void mouseDragged(final MouseEvent mouseEvent) {
onDrag.accept(mouseEvent);
}
});
}
public static void onEnter(final Component component, final Consumer<MouseEvent> onEnter) {
component.addMouseListener(new MouseListenerImpl() {
@Override
public void mouseEntered(final MouseEvent mouseEvent) {
onEnter.accept(mouseEvent);
}
});
}
public static void onExit(final Component component, final Consumer<MouseEvent> onExit) {
component.addMouseListener(new MouseListenerImpl() {
@Override
public void mouseExited(final MouseEvent mouseEvent) {
onExit.accept(mouseEvent);
}
});
}
private static class MouseListenerImpl implements java.awt.event.MouseListener {
@Override
public void mouseClicked(final MouseEvent mouseEvent) {
}
@Override
public void mousePressed(final MouseEvent mouseEvent) {
}
@Override
public void mouseReleased(final MouseEvent mouseEvent) {
}
@Override
public void mouseEntered(final MouseEvent mouseEvent) {
}
@Override
public void mouseExited(final MouseEvent mouseEvent) {
}
}
private static class MouseMotionListenerImpl implements MouseMotionListener {
@Override
public void mouseDragged(final MouseEvent mouseEvent) {
}
@Override
public void mouseMoved(final MouseEvent mouseEvent) {
}
}
}

View File

@ -4,19 +4,11 @@ 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) {
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, FIRST_LINE_START, fill, new Insets(0, 0, 0, 0), 0, 0);
}

View File

@ -37,7 +37,9 @@ public class Publisher<T> {
private void unsubscribe(final Subscription<T> subscription) {
synchronized (subscriptions) {
subscriptions.remove(subscription);
if (!subscriptions.remove(subscription)) {
throw new RuntimeException();
}
}
}