add generated docs
414
API.md
Normal file
414
API.md
Normal file
@@ -0,0 +1,414 @@
|
||||
# API Reference
|
||||
|
||||
This document covers the public API for the Java shape editor library (`ovh.gasser.newshapes`).
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Shape (interface)](#shape-interface)
|
||||
- [AbstractShape (abstract class)](#abstractshape-abstract-class)
|
||||
- [Concrete Shapes](#concrete-shapes)
|
||||
- [SRectangle](#srectangle)
|
||||
- [SCircle](#scircle)
|
||||
- [STriangle](#striangle)
|
||||
- [SText](#stext)
|
||||
- [SCollection](#scollection)
|
||||
- [ShapeVisitor (interface)](#shapevisitor-interface)
|
||||
- [Attributes (interface)](#attributes-interface)
|
||||
- [ColorAttributes](#colorattributes)
|
||||
- [SelectionAttributes](#selectionattributes)
|
||||
- [Selection (class)](#selection-class)
|
||||
- [ResizeHandle (enum)](#resizehandle-enum)
|
||||
- [Streamable (interface)](#streamable-interface)
|
||||
|
||||
---
|
||||
|
||||
## Shape (interface)
|
||||
|
||||
**Package:** `ovh.gasser.newshapes.shapes`
|
||||
|
||||
The root interface for all drawable shapes. Every shape in the model implements this interface.
|
||||
|
||||
```java
|
||||
public interface Shape {
|
||||
void accept(ShapeVisitor visitor);
|
||||
void translate(int dx, int dy);
|
||||
void resize(ResizeHandle handle, int dx, int dy);
|
||||
Attributes getAttributes(String key);
|
||||
void addAttributes(Attributes attr);
|
||||
Rectangle getBounds();
|
||||
Shape clone();
|
||||
}
|
||||
```
|
||||
|
||||
### Method Descriptions
|
||||
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| `accept(ShapeVisitor visitor)` | Accepts a visitor, enabling rendering, export, or other operations via the Visitor pattern. |
|
||||
| `translate(int dx, int dy)` | Moves the shape by the given delta along each axis. |
|
||||
| `resize(ResizeHandle handle, int dx, int dy)` | Resizes the shape by dragging the specified handle by the given delta. |
|
||||
| `getAttributes(String key)` | Returns the `Attributes` instance stored under the given key, or `null` if absent. |
|
||||
| `addAttributes(Attributes attr)` | Adds or replaces the attribute entry keyed by `attr.getID()`. |
|
||||
| `getBounds()` | Returns a defensive copy of the shape's bounding rectangle. |
|
||||
| `clone()` | Returns a deep copy of the shape. |
|
||||
|
||||
---
|
||||
|
||||
## AbstractShape (abstract class)
|
||||
|
||||
**Package:** `ovh.gasser.newshapes.shapes`
|
||||
|
||||
Base implementation of `Shape`. Provides shared attribute storage and default implementations of `translate`, `resize`, and `getBounds`. Subclasses must implement `accept` and `clone`.
|
||||
|
||||
```java
|
||||
public abstract class AbstractShape implements Shape {
|
||||
protected final Rectangle bounds;
|
||||
// Attributes stored in a TreeMap<String, Attributes>
|
||||
|
||||
@Override
|
||||
public void translate(int dx, int dy);
|
||||
|
||||
@Override
|
||||
public void resize(ResizeHandle handle, int dx, int dy);
|
||||
|
||||
@Override
|
||||
public Rectangle getBounds();
|
||||
|
||||
@Override
|
||||
public abstract Shape clone();
|
||||
}
|
||||
```
|
||||
|
||||
### Implementation Notes
|
||||
|
||||
- **`translate()`** — Delegates directly to `bounds.translate(dx, dy)`.
|
||||
- **`resize()`** — Switches on the `ResizeHandle` enum to adjust the bounds `x`, `y`, `width`, or `height` fields accordingly. Enforces a minimum value of `1` for both width and height.
|
||||
- **`getBounds()`** — Returns `new Rectangle(this.bounds)` to prevent external mutation of internal state.
|
||||
- **`clone()`** — Abstract; each concrete subclass is responsible for providing a correct deep copy.
|
||||
|
||||
---
|
||||
|
||||
## Concrete Shapes
|
||||
|
||||
### SRectangle
|
||||
|
||||
**Package:** `ovh.gasser.newshapes.shapes`
|
||||
|
||||
Represents a rectangular shape. Extends `AbstractShape`.
|
||||
|
||||
```java
|
||||
public class SRectangle extends AbstractShape {
|
||||
|
||||
public static SRectangle create(int x, int y, int width, int height);
|
||||
|
||||
public static SRectangle create(int x, int y, int width, int height, Color color);
|
||||
}
|
||||
```
|
||||
|
||||
| Factory Method | Description |
|
||||
|----------------|-------------|
|
||||
| `create(x, y, width, height)` | Creates a rectangle with no fill color. |
|
||||
| `create(x, y, width, height, color)` | Creates a rectangle with the given fill color. |
|
||||
|
||||
---
|
||||
|
||||
### SCircle
|
||||
|
||||
**Package:** `ovh.gasser.newshapes.shapes`
|
||||
|
||||
Represents a circular shape. Extends `AbstractShape` and overrides `resize` and `getBounds` to maintain circular geometry.
|
||||
|
||||
```java
|
||||
public class SCircle extends AbstractShape {
|
||||
|
||||
public static SCircle create(int x, int y, int radius);
|
||||
|
||||
public static SCircle create(int x, int y, int radius, Color fillColor);
|
||||
|
||||
@Override
|
||||
public void resize(ResizeHandle handle, int dx, int dy);
|
||||
|
||||
@Override
|
||||
public Rectangle getBounds();
|
||||
}
|
||||
```
|
||||
|
||||
| Factory Method | Description |
|
||||
|----------------|-------------|
|
||||
| `create(x, y, radius)` | Creates a circle with no fill color. |
|
||||
| `create(x, y, radius, fillColor)` | Creates a circle with the given fill color. |
|
||||
|
||||
**Override Notes**
|
||||
|
||||
- **`resize()`** — Adjusts the radius based on the drag delta while keeping the shape circular.
|
||||
- **`getBounds()`** — Computes the bounding rectangle from the stored center point and radius rather than a raw `Rectangle`.
|
||||
|
||||
---
|
||||
|
||||
### STriangle
|
||||
|
||||
**Package:** `ovh.gasser.newshapes.shapes`
|
||||
|
||||
Represents an equilateral triangle. Extends `AbstractShape` and overrides `resize` to preserve equilateral proportions.
|
||||
|
||||
```java
|
||||
public class STriangle extends AbstractShape {
|
||||
|
||||
public static STriangle create(int x, int y, int size, Color fillColor, Color strokeColor);
|
||||
|
||||
@Override
|
||||
public void resize(ResizeHandle handle, int dx, int dy);
|
||||
}
|
||||
```
|
||||
|
||||
| Factory Method | Description |
|
||||
|----------------|-------------|
|
||||
| `create(x, y, size, fillColor, strokeColor)` | Creates an equilateral triangle with explicit fill and stroke colors. |
|
||||
|
||||
**Override Notes**
|
||||
|
||||
- **`resize()`** — Ensures that resizing keeps all sides equal, preserving the equilateral property.
|
||||
|
||||
---
|
||||
|
||||
### SText
|
||||
|
||||
**Package:** `ovh.gasser.newshapes.shapes`
|
||||
|
||||
Represents an editable text label. Extends `AbstractShape`. When the stored text is `null` or empty, the shape renders a `"Click to edit"` placeholder.
|
||||
|
||||
```java
|
||||
public class SText extends AbstractShape {
|
||||
|
||||
public static SText create(int x, int y, String text);
|
||||
|
||||
public static SText create(int x, int y, String text, String fontName, int fontStyle, int fontSize);
|
||||
}
|
||||
```
|
||||
|
||||
| Factory Method | Description |
|
||||
|----------------|-------------|
|
||||
| `create(x, y, text)` | Creates a text shape using the default font: SansSerif, PLAIN, size 16. |
|
||||
| `create(x, y, text, fontName, fontStyle, fontSize)` | Creates a text shape with a fully specified font. |
|
||||
|
||||
---
|
||||
|
||||
### SCollection
|
||||
|
||||
**Package:** `ovh.gasser.newshapes.shapes`
|
||||
|
||||
A composite shape that holds an ordered collection of child `Shape` instances. Implements both `Streamable<Shape>` and `Iterable<Shape>`. Overrides core `Shape` methods to propagate operations to all children.
|
||||
|
||||
```java
|
||||
public class SCollection extends AbstractShape
|
||||
implements Streamable<Shape>, Iterable<Shape> {
|
||||
|
||||
public static SCollection of(Shape... shapes);
|
||||
|
||||
public void add(Shape s);
|
||||
|
||||
public void remove(Shape s);
|
||||
|
||||
@Override
|
||||
public Rectangle getBounds();
|
||||
|
||||
@Override
|
||||
public Shape clone();
|
||||
|
||||
@Override
|
||||
public void translate(int dx, int dy);
|
||||
|
||||
@Override
|
||||
public void addAttributes(Attributes attr);
|
||||
}
|
||||
```
|
||||
|
||||
| Member | Description |
|
||||
|--------|-------------|
|
||||
| `of(Shape... shapes)` | Factory method. Creates a collection from the given shapes and automatically adds a `SelectionAttributes` instance. |
|
||||
| `add(Shape s)` | Adds a child shape to the collection. |
|
||||
| `remove(Shape s)` | Removes a child shape from the collection. |
|
||||
| `getBounds()` | Returns the union of all children's bounding rectangles. Returns the window size if the collection is empty. |
|
||||
| `clone()` | Deep-copies the collection by recursively cloning each child. |
|
||||
| `translate()` | Propagates the translation to every child shape. |
|
||||
| `addAttributes()` | Propagates `ColorAttributes` to all children; other attribute types are stored locally. |
|
||||
|
||||
---
|
||||
|
||||
## ShapeVisitor (interface)
|
||||
|
||||
**Package:** `ovh.gasser.newshapes`
|
||||
|
||||
Visitor interface for operations over the shape hierarchy (rendering, export, hit-testing, etc.). Each concrete shape calls the appropriate `visit` method from its `accept` implementation.
|
||||
|
||||
```java
|
||||
public interface ShapeVisitor {
|
||||
void visitRectangle(SRectangle sRectangle);
|
||||
void visitCollection(SCollection collection);
|
||||
void visitCircle(SCircle sCircle);
|
||||
void visitTriangle(STriangle sTriangle);
|
||||
void visitText(SText sText);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Attributes (interface)
|
||||
|
||||
**Package:** `ovh.gasser.newshapes.attributes`
|
||||
|
||||
Marker interface for metadata that can be attached to any `Shape`. All attribute types must provide a unique string key via `getID()`.
|
||||
|
||||
```java
|
||||
public interface Attributes {
|
||||
String getID();
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### ColorAttributes
|
||||
|
||||
**Package:** `ovh.gasser.newshapes.attributes`
|
||||
|
||||
Stores fill and stroke color information for a shape.
|
||||
|
||||
```java
|
||||
public class ColorAttributes implements Attributes {
|
||||
|
||||
public static final String ID = "COLOR_ATTRS";
|
||||
|
||||
public boolean filled;
|
||||
public boolean stroked;
|
||||
public Color filledColor;
|
||||
public Color strokedColor;
|
||||
|
||||
public ColorAttributes(boolean filled, boolean stroked,
|
||||
Color filledColor, Color strokedColor);
|
||||
|
||||
@Override
|
||||
public String getID(); // returns "COLOR_ATTRS"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Description |
|
||||
|-------|-------------|
|
||||
| `filled` | Whether the shape interior is filled. |
|
||||
| `stroked` | Whether the shape outline is drawn. |
|
||||
| `filledColor` | The fill color, used when `filled` is `true`. |
|
||||
| `strokedColor` | The stroke color, used when `stroked` is `true`. |
|
||||
|
||||
---
|
||||
|
||||
### SelectionAttributes
|
||||
|
||||
**Package:** `ovh.gasser.newshapes.attributes`
|
||||
|
||||
Tracks whether a shape is currently selected in the editor.
|
||||
|
||||
```java
|
||||
public class SelectionAttributes implements Attributes {
|
||||
|
||||
public static final String ID = "SELECTION_ATTRS";
|
||||
|
||||
public boolean selected;
|
||||
|
||||
public SelectionAttributes();
|
||||
|
||||
public SelectionAttributes(boolean selected);
|
||||
|
||||
@Override
|
||||
public String getID(); // returns "SELECTION_ATTRS"
|
||||
}
|
||||
```
|
||||
|
||||
| Constructor | Description |
|
||||
|-------------|-------------|
|
||||
| `SelectionAttributes()` | Creates an instance with `selected` defaulting to `false`. |
|
||||
| `SelectionAttributes(boolean selected)` | Creates an instance with an explicit initial selection state. |
|
||||
|
||||
---
|
||||
|
||||
## Selection (class)
|
||||
|
||||
**Package:** `ovh.gasser.newshapes`
|
||||
|
||||
Maintains the current set of selected shapes. Notifies registered `SelectionListener` instances when the selection changes. Implements `Streamable<Shape>`.
|
||||
|
||||
```java
|
||||
public class Selection implements Streamable<Shape> {
|
||||
|
||||
public void add(Shape s);
|
||||
|
||||
public void addAll(Collection<Shape> shapes);
|
||||
|
||||
public void clear();
|
||||
|
||||
public boolean isEmpty();
|
||||
|
||||
public List<Shape> getSelectedShapes();
|
||||
|
||||
public void addListener(SelectionListener listener);
|
||||
}
|
||||
```
|
||||
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| `add(Shape s)` | Adds a shape to the selection, marks it as selected via its `SelectionAttributes`, and notifies all listeners. |
|
||||
| `addAll(Collection<Shape> shapes)` | Adds multiple shapes to the selection. |
|
||||
| `clear()` | Deselects all shapes (updates their `SelectionAttributes`) and notifies all listeners. |
|
||||
| `isEmpty()` | Returns `true` if no shapes are currently selected. |
|
||||
| `getSelectedShapes()` | Returns an unmodifiable copy of the current selection list. |
|
||||
| `addListener(SelectionListener listener)` | Registers a listener to be notified on selection changes. |
|
||||
|
||||
---
|
||||
|
||||
## ResizeHandle (enum)
|
||||
|
||||
**Package:** `ovh.gasser.newshapes.shapes`
|
||||
|
||||
Identifies the eight cardinal and diagonal handles used when resizing a shape. Each constant maps to a standard AWT cursor type.
|
||||
|
||||
```java
|
||||
public enum ResizeHandle {
|
||||
NW, N, NE,
|
||||
W, E,
|
||||
SW, S, SE;
|
||||
|
||||
public int getCursorType();
|
||||
}
|
||||
```
|
||||
|
||||
| Value | Position | AWT Cursor |
|
||||
|-------|----------|------------|
|
||||
| `NW` | Top-left corner | `Cursor.NW_RESIZE_CURSOR` |
|
||||
| `N` | Top edge center | `Cursor.N_RESIZE_CURSOR` |
|
||||
| `NE` | Top-right corner | `Cursor.NE_RESIZE_CURSOR` |
|
||||
| `E` | Right edge center | `Cursor.E_RESIZE_CURSOR` |
|
||||
| `SE` | Bottom-right corner | `Cursor.SE_RESIZE_CURSOR` |
|
||||
| `S` | Bottom edge center | `Cursor.S_RESIZE_CURSOR` |
|
||||
| `SW` | Bottom-left corner | `Cursor.SW_RESIZE_CURSOR` |
|
||||
| `W` | Left edge center | `Cursor.W_RESIZE_CURSOR` |
|
||||
|
||||
**`getCursorType()`** returns the `java.awt.Cursor` constant integer appropriate for the handle's position, suitable for use with `Component.setCursor(Cursor.getPredefinedCursor(...))`.
|
||||
|
||||
---
|
||||
|
||||
## Streamable (interface)
|
||||
|
||||
**Package:** `ovh.gasser.newshapes.util`
|
||||
|
||||
A convenience interface that extends `Iterable<T>` with a default `stream()` method, allowing any collection-like type in the model to be used directly in Java stream pipelines.
|
||||
|
||||
```java
|
||||
public interface Streamable<T> extends Iterable<T> {
|
||||
|
||||
default Stream<T> stream() {
|
||||
return StreamSupport.stream(spliterator(), false);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Implemented by `SCollection` and `Selection`.
|
||||
287
ARCHITECTURE.md
Normal file
287
ARCHITECTURE.md
Normal file
@@ -0,0 +1,287 @@
|
||||
# Architecture Documentation — Java Swing Shape Editor
|
||||
|
||||
## 1. Overview
|
||||
|
||||
This application is an interactive 2D shape editor built with Java Swing, structured around the
|
||||
Model-View-Controller (MVC) architectural pattern. The model is a tree of shapes rooted at an
|
||||
`SCollection`; the view renders that tree onto a `JPanel` using a Visitor; the controller translates
|
||||
raw mouse and keyboard events into model mutations and triggers repaints.
|
||||
|
||||
The design deliberately separates concerns so that export targets (HTML, SVG) can reuse the same
|
||||
Visitor interface as the GUI renderer, and so that new shape types can be added with minimal changes
|
||||
to existing code.
|
||||
|
||||
---
|
||||
|
||||
## 2. Package Structure
|
||||
|
||||
```
|
||||
ovh.gasser.newshapes
|
||||
├── App.java — Entry point, builds JFrame, menu bar, initial model
|
||||
├── Selection.java — Manages selected shapes, notifies listeners
|
||||
├── ShapeVisitor.java — Visitor interface for shape rendering
|
||||
├── HTMLExporter.java — Exports model to HTML/CSS files
|
||||
├── SVGExporter.java — Exports model to SVG file
|
||||
├── attributes/
|
||||
│ ├── Attributes.java — Base interface (getID())
|
||||
│ ├── ColorAttributes.java — Fill/stroke colors and flags
|
||||
│ └── SelectionAttributes.java — Selection state
|
||||
├── shapes/
|
||||
│ ├── Shape.java — Core interface
|
||||
│ ├── AbstractShape.java — Base implementation (bounds, attributes map, translate, resize)
|
||||
│ ├── SRectangle.java — Rectangle shape
|
||||
│ ├── SCircle.java — Circle shape (radius-based)
|
||||
│ ├── STriangle.java — Equilateral triangle
|
||||
│ ├── SText.java — Text shape with font properties
|
||||
│ ├── SCollection.java — Composite pattern: group of shapes
|
||||
│ └── ResizeHandle.java — Enum for 8 directional resize handles
|
||||
├── ui/
|
||||
│ ├── Controller.java — Mouse/keyboard event handler (MVC controller)
|
||||
│ ├── ShapeDraftman.java — GUI rendering visitor (draws on Graphics2D)
|
||||
│ ├── ShapesView.java — JPanel view, owns Controller
|
||||
│ ├── listeners/
|
||||
│ │ ├── MenuAddListener.java — Adds shapes from menu
|
||||
│ │ ├── MenuEditListener.java — Color/border/fill changes
|
||||
│ │ └── SelectionListener.java — Interface for selection change events
|
||||
│ └── visitors/
|
||||
│ ├── HTMLDraftman.java — Generates HTML/CSS from shapes
|
||||
│ └── SVGDraftman.java — Generates SVG from shapes
|
||||
└── util/
|
||||
└── Streamable.java — Adds stream() to Iterables
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. MVC Breakdown
|
||||
|
||||
### Roles
|
||||
|
||||
| Layer | Class(es) | Responsibility |
|
||||
|---|---|---|
|
||||
| **Model** | `SCollection`, `Shape`, `AbstractShape`, subclasses | Shape tree; single source of truth for geometry and attributes |
|
||||
| **View** | `ShapesView` | `JPanel` subclass; delegates all painting to `ShapeDraftman` |
|
||||
| **Controller** | `Controller` | Receives Swing events; mutates model; triggers repaint |
|
||||
|
||||
### Data Flow
|
||||
|
||||
```
|
||||
User input (mouse / keyboard)
|
||||
|
|
||||
v
|
||||
Controller
|
||||
(MouseListener, KeyListener)
|
||||
|
|
||||
|-- hit-test shapes in model
|
||||
|-- mutate model (translate, resize, add, remove)
|
||||
|-- update Selection
|
||||
|
|
||||
v
|
||||
Model mutation complete
|
||||
|
|
||||
v
|
||||
ShapesView.repaint()
|
||||
|
|
||||
v
|
||||
ShapesView.paintComponent(Graphics g)
|
||||
|
|
||||
v
|
||||
ShapeDraftman.visit*(shape) [Visitor traversal of the shape tree]
|
||||
|
|
||||
v
|
||||
Graphics2D rendering on screen
|
||||
```
|
||||
|
||||
The view never writes to the model. The model has no reference to the view or the controller.
|
||||
Communication from model to view is limited to the repaint trigger issued by the controller after
|
||||
each mutation.
|
||||
|
||||
---
|
||||
|
||||
## 4. Design Patterns
|
||||
|
||||
### 4.1 Visitor Pattern
|
||||
|
||||
`ShapeVisitor` is the central extension point for operations over the shape tree.
|
||||
|
||||
```java
|
||||
public interface ShapeVisitor {
|
||||
void visitRectangle(SRectangle rectangle);
|
||||
void visitCircle(SCircle circle);
|
||||
void visitTriangle(STriangle triangle);
|
||||
void visitText(SText text);
|
||||
void visitCollection(SCollection collection);
|
||||
}
|
||||
```
|
||||
|
||||
Each concrete shape implements `accept(ShapeVisitor v)` by calling the appropriate method on the
|
||||
visitor:
|
||||
|
||||
```java
|
||||
// SRectangle.java
|
||||
@Override
|
||||
public void accept(ShapeVisitor v) {
|
||||
v.visitRectangle(this);
|
||||
}
|
||||
```
|
||||
|
||||
Three concrete visitors are provided:
|
||||
|
||||
| Visitor | Purpose |
|
||||
|---|---|
|
||||
| `ShapeDraftman` | Paints shapes onto a `Graphics2D` context for on-screen rendering |
|
||||
| `HTMLDraftman` | Emits HTML `<div>` elements with inline CSS for each shape |
|
||||
| `SVGDraftman` | Emits SVG primitives (`<rect>`, `<circle>`, `<polygon>`, `<text>`) |
|
||||
|
||||
Adding a new rendering target requires only a new class implementing `ShapeVisitor`; no existing
|
||||
shape or visitor code changes.
|
||||
|
||||
### 4.2 Composite Pattern
|
||||
|
||||
`SCollection` extends `AbstractShape` and holds an ordered list of `Shape` children, allowing
|
||||
shapes to be grouped and treated uniformly.
|
||||
|
||||
```
|
||||
Shape (interface)
|
||||
└── AbstractShape (abstract)
|
||||
├── SRectangle
|
||||
├── SCircle
|
||||
├── STriangle
|
||||
├── SText
|
||||
└── SCollection <-- contains List<Shape> children
|
||||
├── SRectangle
|
||||
├── SCircle
|
||||
└── SCollection <-- groups can be nested
|
||||
```
|
||||
|
||||
Key overrides in `SCollection`:
|
||||
|
||||
- `translate(dx, dy)` — propagates the translation to every child
|
||||
- `getBounds()` — returns the union of all children's bounding rectangles
|
||||
- `clone()` — performs a deep copy by cloning each child recursively
|
||||
- `accept(ShapeVisitor)` — calls `visitCollection`, which internally iterates children
|
||||
|
||||
`SCollection` also implements `Streamable<Shape>`, providing a `stream()` method that exposes the
|
||||
child list as a `Stream<Shape>` for functional-style iteration.
|
||||
|
||||
### 4.3 Observer Pattern
|
||||
|
||||
`Selection` maintains the set of currently selected shapes and a list of `SelectionListener`
|
||||
callbacks.
|
||||
|
||||
```
|
||||
Selection
|
||||
├── List<Shape> selectedShapes
|
||||
├── List<SelectionListener> listeners
|
||||
├── add(Shape) → notifyListeners()
|
||||
├── clear() → notifyListeners()
|
||||
└── notifyListeners()
|
||||
|
||||
SelectionListener (interface)
|
||||
└── onSelectionChanged(Selection selection)
|
||||
|
||||
App implements SelectionListener
|
||||
└── updates menu checkbox state (fill / border toggles)
|
||||
```
|
||||
|
||||
When the selection changes, `App` reads the `ColorAttributes` of the newly selected shapes and
|
||||
synchronises the menu checkboxes accordingly, so the UI always reflects the state of the model.
|
||||
|
||||
### 4.4 Factory Methods
|
||||
|
||||
Each concrete shape exposes a static `create()` method to centralise construction and attribute
|
||||
initialisation:
|
||||
|
||||
```java
|
||||
SRectangle.create(int x, int y, int width, int height, Color color)
|
||||
SCircle.create(int x, int y, int radius)
|
||||
STriangle.create(int x, int y, int side)
|
||||
SText.create(int x, int y, String text, Font font, Color color)
|
||||
```
|
||||
|
||||
This keeps instantiation logic off the caller and ensures every shape is created with a consistent
|
||||
default attribute set (a `ColorAttributes` and a `SelectionAttributes` are always added to the
|
||||
internal `TreeMap` during `create()`).
|
||||
|
||||
---
|
||||
|
||||
## 5. Selection and Interaction Model
|
||||
|
||||
### Mouse Interactions
|
||||
|
||||
| Gesture | Effect |
|
||||
|---|---|
|
||||
| Click on shape | Select that shape; clear previous selection |
|
||||
| Shift + click on shape | Add shape to current selection (multi-select) |
|
||||
| Click on empty space | Deselect all |
|
||||
| Click on empty space + drag | Rubber-band box selection: selects all shapes whose bounding rectangle intersects the drag rectangle |
|
||||
| Drag selected shape(s) | Translate all selected shapes by the drag delta |
|
||||
|
||||
### Keyboard Interactions
|
||||
|
||||
| Key | Effect |
|
||||
|---|---|
|
||||
| `R` | Toggle resize mode for the current selection |
|
||||
| `Delete` / `Backspace` | Remove selected shapes from the model |
|
||||
|
||||
### Resize Mode
|
||||
|
||||
When resize mode is active, the `ShapeDraftman` renders eight handles around each selected shape,
|
||||
corresponding to the eight values of the `ResizeHandle` enum:
|
||||
|
||||
```
|
||||
NW --- N --- NE
|
||||
| |
|
||||
W E
|
||||
| |
|
||||
SW --- S --- SE
|
||||
```
|
||||
|
||||
Dragging a handle resizes the shape in the direction indicated by the handle, updating the bounding
|
||||
rectangle while keeping the opposite corner anchored.
|
||||
|
||||
---
|
||||
|
||||
## 6. Attributes System
|
||||
|
||||
Shapes store their attributes in a `TreeMap<String, Attributes>` where the key is the stable ID
|
||||
string returned by `Attributes.getID()`. This map lives in `AbstractShape` and is inherited by all
|
||||
concrete shapes.
|
||||
|
||||
```
|
||||
AbstractShape
|
||||
└── TreeMap<String, Attributes> attributes
|
||||
├── "COLOR_ATTRS" → ColorAttributes
|
||||
└── "SELECTION_ATTRS" → SelectionAttributes
|
||||
```
|
||||
|
||||
### Defined Attribute Types
|
||||
|
||||
**`ColorAttributes`** (`ID = "COLOR_ATTRS"`)
|
||||
|
||||
| Field | Type | Description |
|
||||
|---|---|---|
|
||||
| `fillColor` | `Color` | Interior fill colour |
|
||||
| `strokeColor` | `Color` | Border/outline colour |
|
||||
| `filled` | `boolean` | Whether the shape is filled |
|
||||
| `stroked` | `boolean` | Whether the shape has a visible border |
|
||||
|
||||
**`SelectionAttributes`** (`ID = "SELECTION_ATTRS"`)
|
||||
|
||||
| Field | Type | Description |
|
||||
|---|---|---|
|
||||
| `selected` | `boolean` | Whether the shape is currently selected |
|
||||
|
||||
### Extensibility
|
||||
|
||||
The attribute system is open for extension. A new attribute type — for example, `OpacityAttributes`
|
||||
or `ShadowAttributes` — only needs to implement the `Attributes` interface and provide a unique ID
|
||||
string. No existing attribute class or shape class requires modification.
|
||||
|
||||
```java
|
||||
public interface Attributes {
|
||||
String getID();
|
||||
}
|
||||
```
|
||||
|
||||
Consumers retrieve attributes with a cast after a key lookup, making it straightforward to check for
|
||||
the presence of an optional attribute type without imposing it on all shapes.
|
||||
84
CONTRIBUTING.md
Normal file
84
CONTRIBUTING.md
Normal file
@@ -0,0 +1,84 @@
|
||||
# Contributing Guide
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Java 16 or later (uses switch expressions, records, etc.)
|
||||
- Maven 3.6+
|
||||
|
||||
## Building
|
||||
|
||||
```bash
|
||||
mvn compile
|
||||
```
|
||||
|
||||
## Running
|
||||
|
||||
```bash
|
||||
mvn exec:java -Dexec.mainClass=ovh.gasser.newshapes.App
|
||||
```
|
||||
|
||||
Or run `ovh.gasser.newshapes.App.main()` from your IDE.
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
mvn test
|
||||
```
|
||||
|
||||
Uses JUnit 5 (Jupiter). Tests are in `src/test/java/ovh/gasser/newshapes/`.
|
||||
|
||||
Current test coverage:
|
||||
|
||||
- Shape classes: `AbstractShapeTest`, `SRectangleTest`, `SCircleTest`, `STriangleTest`, `STextTest`
|
||||
- Exporters: `SVGExporterTest`, `HTMLExporterTest`
|
||||
- See `TESTING_HANDOFF.md` at the project root for detailed coverage analysis and recommendations.
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
src/
|
||||
├── main/java/ovh/gasser/newshapes/ — Application source
|
||||
│ ├── shapes/ — Shape hierarchy
|
||||
│ ├── attributes/ — Attribute types
|
||||
│ ├── ui/ — Swing UI (view, controller, listeners)
|
||||
│ │ ├── listeners/ — Menu and selection listeners
|
||||
│ │ └── visitors/ — HTML/SVG visitor implementations
|
||||
│ └── util/ — Utility interfaces
|
||||
└── test/java/ovh/gasser/newshapes/ — JUnit 5 tests
|
||||
├── shapes/ — Shape unit tests
|
||||
└── exporters/ — Exporter integration tests
|
||||
```
|
||||
|
||||
## Adding a New Shape
|
||||
|
||||
1. Create a class extending `AbstractShape` in `ovh.gasser.newshapes.shapes`.
|
||||
2. Implement `accept(ShapeVisitor visitor)` — call `visitor.visitYourShape(this)`.
|
||||
3. Implement `clone()` — return a deep copy.
|
||||
4. Provide a static `create()` factory method.
|
||||
5. Optionally override `resize()` if the shape needs custom resize behavior (e.g., `SCircle` maintains radius).
|
||||
6. Add a `visitYourShape()` method to the `ShapeVisitor` interface.
|
||||
7. Implement the new visit method in all three visitors: `ShapeDraftman`, `HTMLDraftman`, `SVGDraftman`.
|
||||
8. Add a menu item in `App.buildFileMenu()` with a `MenuAddListener`.
|
||||
9. Write tests.
|
||||
|
||||
## Adding a New Attribute
|
||||
|
||||
1. Create a class implementing `Attributes` in `ovh.gasser.newshapes.attributes`.
|
||||
2. Define a `public static final String ID` constant.
|
||||
3. Implement `getID()` returning the ID.
|
||||
4. Use `shape.addAttributes(new YourAttribute(...))` to attach, and `shape.getAttributes(YourAttribute.ID)` to retrieve.
|
||||
|
||||
## Adding a New Exporter
|
||||
|
||||
1. Create a visitor class implementing `ShapeVisitor` in `ovh.gasser.newshapes.ui.visitors`.
|
||||
2. Implement all visit methods to generate the target format.
|
||||
3. Create an exporter class (like `HTMLExporter` / `SVGExporter`) that uses the visitor and writes to a file.
|
||||
4. Wire it up in `App.buildFileMenu()`.
|
||||
|
||||
## Conventions
|
||||
|
||||
- Static factory methods (`create()`) over public constructors.
|
||||
- `getBounds()` always returns a defensive copy.
|
||||
- Shapes are cloneable via the `clone()` method.
|
||||
- Attributes are keyed by string IDs and stored in a `TreeMap`.
|
||||
- Logging via SLF4J (`LoggerFactory.getLogger(YourClass.class)`).
|
||||
100
README.md
Normal file
100
README.md
Normal file
@@ -0,0 +1,100 @@
|
||||
# Reactive Shapes
|
||||
|
||||
An interactive Java Swing application for creating, editing, and exporting 2D shapes.
|
||||
|
||||
**Authors**: Alexandre Colicchio and Thibaud Gasser
|
||||
**Artifact**: `ovh.gasser:shapes:1.0-SNAPSHOT`
|
||||
**Package**: `ovh.gasser.newshapes`
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
- **Shape creation** — rectangles, circles, triangles, and text labels
|
||||
- **Selection** — click to select, shift-click for multi-select, drag to draw a selection box
|
||||
- **Move** — drag selected shapes to reposition them on the canvas
|
||||
- **Resize** — toggle resize mode with `R` to reveal 8 directional handles per shape
|
||||
- **Color editing** — independently set fill and stroke colors on any selection
|
||||
- **Export** — render the canvas to HTML/CSS or SVG
|
||||
- **Composite shapes** — group shapes into named collections and manipulate them as a unit
|
||||
- **Clone** — duplicate the current selection with `C`
|
||||
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Requirements
|
||||
|
||||
- Java 16 or later
|
||||
- Maven 3.6 or later
|
||||
|
||||
### Build
|
||||
|
||||
```bash
|
||||
mvn compile
|
||||
```
|
||||
|
||||
### Run
|
||||
|
||||
```bash
|
||||
mvn exec:java -Dexec.mainClass=ovh.gasser.newshapes.App
|
||||
```
|
||||
|
||||
### Test
|
||||
|
||||
```bash
|
||||
mvn test
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Keyboard Shortcuts
|
||||
|
||||
| Key | Action |
|
||||
|----------|---------------------------------|
|
||||
| `R` | Toggle resize mode |
|
||||
| `Delete` | Delete selected shapes |
|
||||
| `C` | Clone the current selection |
|
||||
| `A` | Randomize colors of selection |
|
||||
| `H` | Export canvas to HTML |
|
||||
|
||||
---
|
||||
|
||||
## Documentation Index
|
||||
|
||||
| Document | Description |
|
||||
|---|---|
|
||||
| [Architecture](ARCHITECTURE.md) | Design patterns, MVC structure, class relationships |
|
||||
| [Shape System](SHAPES.md) | Shape hierarchy, attributes, and composite pattern |
|
||||
| [Visitor & Exporters](VISITORS.md) | Visitor pattern, rendering, HTML/SVG export |
|
||||
| [API Reference](API.md) | Public interfaces and key class reference |
|
||||
| [Contributing](CONTRIBUTING.md) | Development setup, build, testing, conventions |
|
||||
|
||||
---
|
||||
|
||||
## Project Status
|
||||
|
||||
See the [feature roadmap](../TODO.md) at the repository root for a full list of open and planned work.
|
||||
|
||||
**Completed**
|
||||
|
||||
- Text shapes
|
||||
- Resize mode with 8-handle control points
|
||||
|
||||
**Planned**
|
||||
|
||||
- Undo/redo support
|
||||
- Save and load canvas state
|
||||
- Freeform polygon shapes
|
||||
|
||||
---
|
||||
|
||||
## Tech Stack
|
||||
|
||||
| Component | Technology |
|
||||
|-----------|------------|
|
||||
| Language | Java 16 |
|
||||
| Build | Maven |
|
||||
| UI | Swing |
|
||||
| Logging | SLF4J + Logback |
|
||||
| Testing | JUnit 5 |
|
||||
219
SHAPES.md
Normal file
219
SHAPES.md
Normal file
@@ -0,0 +1,219 @@
|
||||
# Shape System
|
||||
|
||||
## Shape Hierarchy
|
||||
|
||||
```
|
||||
Shape (interface)
|
||||
└── AbstractShape (abstract class)
|
||||
├── SRectangle
|
||||
├── SCircle
|
||||
├── STriangle
|
||||
├── SText
|
||||
└── SCollection (Composite)
|
||||
```
|
||||
|
||||
## The Shape Interface
|
||||
|
||||
Core contract defined in `ovh.gasser.newshapes.shapes.Shape`:
|
||||
|
||||
```java
|
||||
public interface Shape {
|
||||
void accept(ShapeVisitor visitor);
|
||||
void translate(int dx, int dy);
|
||||
void resize(ResizeHandle handle, int dx, int dy);
|
||||
Attributes getAttributes(String key);
|
||||
void addAttributes(Attributes attr);
|
||||
Rectangle getBounds();
|
||||
Shape clone();
|
||||
}
|
||||
```
|
||||
|
||||
Every shape must support visitor dispatch, spatial transformation, attribute storage, bounds computation, and deep copying.
|
||||
|
||||
---
|
||||
|
||||
## AbstractShape -- Base Implementation
|
||||
|
||||
`ovh.gasser.newshapes.shapes.AbstractShape` provides common behavior for all concrete shapes.
|
||||
|
||||
### Bounds
|
||||
|
||||
A `protected final Rectangle bounds` field stores the shape's bounding rectangle. `getBounds()` always returns a **defensive copy** (`new Rectangle(this.bounds)`) to prevent external mutation.
|
||||
|
||||
### Attributes
|
||||
|
||||
Stored in a `TreeMap<String, Attributes>` keyed by `Attributes.getID()`. Lookup is O(log n). Attributes are added or replaced via `addAttributes()` and retrieved via `getAttributes(key)`.
|
||||
|
||||
### Translate
|
||||
|
||||
Delegates directly to `bounds.translate(dx, dy)`.
|
||||
|
||||
### Resize
|
||||
|
||||
Uses a switch expression over the `ResizeHandle` enum to determine which edges to adjust:
|
||||
|
||||
| Handle | X Adjustment | Y Adjustment | Width Adjustment | Height Adjustment |
|
||||
|--------|-------------|-------------|-----------------|-------------------|
|
||||
| N | -- | `+dy` | -- | `-dy` |
|
||||
| S | -- | -- | -- | `+dy` |
|
||||
| E | -- | -- | `+dx` | -- |
|
||||
| W | `+dx` | -- | `-dx` | -- |
|
||||
| NE | -- | `+dy` | `+dx` | `-dy` |
|
||||
| NW | `+dx` | `+dy` | `-dx` | `-dy` |
|
||||
| SE | -- | -- | `+dx` | `+dy` |
|
||||
| SW | `+dx` | -- | `-dx` | `+dy` |
|
||||
|
||||
After resizing, width and height are clamped to a minimum of 1 pixel.
|
||||
|
||||
---
|
||||
|
||||
## Concrete Shapes
|
||||
|
||||
### SRectangle
|
||||
|
||||
The simplest shape. Uses `AbstractShape`'s bounds directly with no overrides for resize or bounds computation.
|
||||
|
||||
**Factory methods:**
|
||||
|
||||
```java
|
||||
SRectangle.create(int x, int y, int width, int height)
|
||||
SRectangle.create(int x, int y, int width, int height, Color color)
|
||||
```
|
||||
|
||||
### SCircle
|
||||
|
||||
Maintains a `radius` field alongside the inherited bounds.
|
||||
|
||||
- **`resize()`**: Overridden to adjust radius while keeping circular proportions.
|
||||
- **`getBounds()`**: Computed from center and radius: `new Rectangle(x - radius, y - radius, 2 * radius, 2 * radius)`.
|
||||
|
||||
**Factory methods:**
|
||||
|
||||
```java
|
||||
SCircle.create(int x, int y, int radius)
|
||||
SCircle.create(int x, int y, int radius, Color fillColor)
|
||||
```
|
||||
|
||||
### STriangle
|
||||
|
||||
Equilateral triangle defined by position and size.
|
||||
|
||||
- **`resize()`**: Overridden to maintain equilateral proportions based on a single size parameter.
|
||||
|
||||
**Factory method:**
|
||||
|
||||
```java
|
||||
STriangle.create(int x, int y, int size, Color fillColor, Color strokeColor)
|
||||
```
|
||||
|
||||
### SText
|
||||
|
||||
Text shape with font properties (`fontName`, `fontStyle`, `fontSize`). Displays a `"Click to edit"` placeholder when text is null or empty.
|
||||
|
||||
**Factory methods:**
|
||||
|
||||
```java
|
||||
SText.create(int x, int y, String text) // Default: SansSerif, PLAIN, 16
|
||||
SText.create(int x, int y, String text, String fontName, int fontStyle, int fontSize) // Custom font
|
||||
```
|
||||
|
||||
### SCollection -- Composite Pattern
|
||||
|
||||
Groups shapes into a tree structure. Extends `AbstractShape` and implements `Streamable<Shape>` (which extends `Iterable<Shape>`).
|
||||
|
||||
**Key behaviors:**
|
||||
|
||||
| Method | Behavior |
|
||||
|-------------------|-----------------------------------------------------------------------------------------------|
|
||||
| `getBounds()` | Union of all children's bounds. Returns `App.WIN_SIZE` if the collection is empty. |
|
||||
| `translate(dx,dy)`| Propagates to all children (does **not** use `AbstractShape`'s bounds). |
|
||||
| `clone()` | Deep copies all children into a new collection. |
|
||||
| `addAttributes()` | Propagates `ColorAttributes` to all children. Stores other attribute types locally via super. |
|
||||
| `getAttributes()` | For `ColorAttributes`, returns the first child's attributes. Other types use super. |
|
||||
| `add(Shape)` | Appends a child shape. |
|
||||
| `remove(Shape)` | Removes a child shape; logs an error if the shape is not found. |
|
||||
|
||||
**Factory method:**
|
||||
|
||||
```java
|
||||
SCollection.of(Shape... shapes) // Auto-adds SelectionAttributes
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Attributes System
|
||||
|
||||
### Design
|
||||
|
||||
Attributes are stored as a map in `AbstractShape`:
|
||||
|
||||
```java
|
||||
private final Map<String, Attributes> attributes = new TreeMap<>();
|
||||
```
|
||||
|
||||
Each attribute type defines a unique string ID. This provides an extensible, type-safe way to attach arbitrary metadata to shapes without modifying the shape classes.
|
||||
|
||||
### Built-in Attributes
|
||||
|
||||
#### ColorAttributes
|
||||
|
||||
ID: `"COLOR_ATTRS"`
|
||||
|
||||
Controls shape rendering appearance:
|
||||
|
||||
| Field | Type | Description |
|
||||
|----------------|-----------|--------------------------------------|
|
||||
| `filled` | `boolean` | Whether the shape interior is filled |
|
||||
| `stroked` | `boolean` | Whether the shape outline is drawn |
|
||||
| `filledColor` | `Color` | Interior fill color |
|
||||
| `strokedColor` | `Color` | Stroke/border color |
|
||||
|
||||
```java
|
||||
new ColorAttributes(boolean filled, boolean stroked, Color filledColor, Color strokedColor)
|
||||
```
|
||||
|
||||
#### SelectionAttributes
|
||||
|
||||
ID: `"SELECTION_ATTRS"`
|
||||
|
||||
Tracks whether a shape is currently selected:
|
||||
|
||||
| Field | Type | Description |
|
||||
|------------|-----------|---------------------------|
|
||||
| `selected` | `boolean` | Current selection state |
|
||||
|
||||
```java
|
||||
new SelectionAttributes() // Default: false
|
||||
new SelectionAttributes(true) // Explicitly selected
|
||||
```
|
||||
|
||||
Added automatically by shape factory methods and by `Selection.add()`.
|
||||
|
||||
### Extending the System
|
||||
|
||||
To add a new attribute type:
|
||||
|
||||
1. Implement the `Attributes` interface.
|
||||
2. Define a `public static final String ID` constant.
|
||||
3. Implement `getID()` returning the ID.
|
||||
4. Attach with `shape.addAttributes(new MyAttribute(...))`.
|
||||
5. Retrieve with `(MyAttribute) shape.getAttributes(MyAttribute.ID)`.
|
||||
|
||||
---
|
||||
|
||||
## ResizeHandle Enum
|
||||
|
||||
Eight directional handles for interactive resizing:
|
||||
|
||||
| Value | Cursor Type |
|
||||
|-------|-------------------------|
|
||||
| `NW` | `Cursor.NW_RESIZE_CURSOR` |
|
||||
| `N` | `Cursor.N_RESIZE_CURSOR` |
|
||||
| `NE` | `Cursor.NE_RESIZE_CURSOR` |
|
||||
| `E` | `Cursor.E_RESIZE_CURSOR` |
|
||||
| `SE` | `Cursor.SE_RESIZE_CURSOR` |
|
||||
| `S` | `Cursor.S_RESIZE_CURSOR` |
|
||||
| `SW` | `Cursor.SW_RESIZE_CURSOR` |
|
||||
| `W` | `Cursor.W_RESIZE_CURSOR` |
|
||||
|
||||
Used by `AbstractShape.resize()` to determine which bounds edges to adjust, and by `Controller` to set the mouse cursor during resize interactions.
|
||||
188
VISITORS.md
Normal file
188
VISITORS.md
Normal file
@@ -0,0 +1,188 @@
|
||||
# Visitor Pattern and Exporters
|
||||
|
||||
## Overview
|
||||
|
||||
The project uses the **Visitor pattern** to separate shape data from rendering and export logic. This allows adding new output formats without modifying shape classes.
|
||||
|
||||
---
|
||||
|
||||
## ShapeVisitor Interface
|
||||
|
||||
```java
|
||||
public interface ShapeVisitor {
|
||||
void visitRectangle(SRectangle sRectangle);
|
||||
void visitCollection(SCollection collection);
|
||||
void visitCircle(SCircle sCircle);
|
||||
void visitTriangle(STriangle sTriangle);
|
||||
void visitText(SText sText);
|
||||
}
|
||||
```
|
||||
|
||||
### How It Works
|
||||
|
||||
Each `Shape` implements `accept(ShapeVisitor visitor)` by calling the appropriate `visit*` method on the visitor, passing itself:
|
||||
|
||||
```java
|
||||
// In SRectangle:
|
||||
@Override
|
||||
public void accept(ShapeVisitor visitor) {
|
||||
visitor.visitRectangle(this);
|
||||
}
|
||||
```
|
||||
|
||||
For `SCollection`, the visitor iterates over children to traverse the tree:
|
||||
|
||||
```java
|
||||
// Inside a visitor's visitCollection():
|
||||
for (Shape child : collection) {
|
||||
child.accept(this);
|
||||
}
|
||||
```
|
||||
|
||||
This double-dispatch mechanism lets each visitor decide how to handle each shape type without the shapes knowing anything about rendering.
|
||||
|
||||
---
|
||||
|
||||
## Visitor Implementations
|
||||
|
||||
### 1. ShapeDraftman -- GUI Rendering
|
||||
|
||||
**Location:** `ovh.gasser.newshapes.ui.ShapeDraftman`
|
||||
|
||||
Renders shapes onto a `Graphics2D` context for the Swing view (`ShapesView`).
|
||||
|
||||
**Responsibilities:**
|
||||
|
||||
- Draws rectangles (`fillRect`/`drawRect`), circles (`fillOval`/`drawOval`), triangles (`fillPolygon`/`drawPolygon`), and text (`drawString`) using `Graphics2D`.
|
||||
- Reads `ColorAttributes` to determine fill/stroke colors and whether to fill or stroke.
|
||||
- Draws **selection handles** (small squares) when a shape is selected.
|
||||
- In **resize mode**, renders all 8 directional handles (NW, N, NE, E, SE, S, SW, W) on the shape bounds.
|
||||
- In **normal mode**, shows only the SE corner handle for selected shapes.
|
||||
|
||||
### 2. HTMLDraftman -- HTML/CSS Export
|
||||
|
||||
**Location:** `ovh.gasser.newshapes.ui.visitors.HTMLDraftman`
|
||||
|
||||
Generates HTML `<div>` elements with CSS for absolute positioning.
|
||||
|
||||
**Output format:**
|
||||
|
||||
| Shape | HTML Representation |
|
||||
|-------------|--------------------------------------------------------------------------|
|
||||
| Rectangle | `<div>` with `position: absolute`, `left`, `top`, `width`, `height` |
|
||||
| Circle | `<div>` with `border-radius: 50%` |
|
||||
| Triangle | `<div>` using CSS border tricks |
|
||||
| Text | Styled `<div>` with font properties |
|
||||
| Collection | Recursively visits children |
|
||||
|
||||
Colors are mapped to CSS `background-color` and `border` properties.
|
||||
|
||||
**Used by:** `HTMLExporter`, which writes `out.html` and `style.css`.
|
||||
|
||||
### 3. SVGDraftman -- SVG Export
|
||||
|
||||
**Location:** `ovh.gasser.newshapes.ui.visitors.SVGDraftman`
|
||||
|
||||
Generates SVG XML elements.
|
||||
|
||||
**Output format:**
|
||||
|
||||
| Shape | SVG Element |
|
||||
|-------------|---------------------------------------------------------------------------|
|
||||
| Rectangle | `<rect x="..." y="..." width="..." height="..." fill="..." stroke="...">` |
|
||||
| Circle | `<circle cx="..." cy="..." r="..." fill="..." stroke="...">` |
|
||||
| Triangle | `<polygon points="...">` with computed vertex coordinates |
|
||||
| Text | `<text>` with font attributes |
|
||||
| Collection | `<g>` group containing child elements |
|
||||
|
||||
Color attributes are converted to SVG `fill` and `stroke` attributes using `rgb(r,g,b)` format.
|
||||
|
||||
**Used by:** `SVGExporter`, which writes `out.svg`.
|
||||
|
||||
---
|
||||
|
||||
## Exporters
|
||||
|
||||
### HTMLExporter
|
||||
|
||||
```java
|
||||
new HTMLExporter(model).export(); // Writes out.html + style.css
|
||||
```
|
||||
|
||||
Creates an `HTMLDraftman`, visits the entire model tree, then writes the accumulated HTML structure and CSS styles to separate files.
|
||||
|
||||
### SVGExporter
|
||||
|
||||
```java
|
||||
new SVGExporter(model).export(); // Writes out.svg
|
||||
```
|
||||
|
||||
Creates an `SVGDraftman`, visits the entire model tree, wraps the output in SVG boilerplate (`<svg>` root element with namespace), and writes to a single file.
|
||||
|
||||
---
|
||||
|
||||
## Adding a New Visitor / Exporter
|
||||
|
||||
### Step 1: Create the Visitor
|
||||
|
||||
Create a class implementing `ShapeVisitor` (typically in `ovh.gasser.newshapes.ui.visitors`):
|
||||
|
||||
```java
|
||||
public class MyFormatDraftman implements ShapeVisitor {
|
||||
@Override
|
||||
public void visitRectangle(SRectangle rect) {
|
||||
Rectangle bounds = rect.getBounds();
|
||||
ColorAttributes colors = (ColorAttributes) rect.getAttributes(ColorAttributes.ID);
|
||||
// Generate your format output...
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitCollection(SCollection collection) {
|
||||
for (Shape child : collection) {
|
||||
child.accept(this); // Recursive traversal
|
||||
}
|
||||
}
|
||||
|
||||
// ... implement visitCircle, visitTriangle, visitText
|
||||
}
|
||||
```
|
||||
|
||||
### Step 2: Create the Exporter
|
||||
|
||||
```java
|
||||
public class MyFormatExporter {
|
||||
private final SCollection model;
|
||||
|
||||
public MyFormatExporter(SCollection model) {
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
public void export() throws FileNotFoundException {
|
||||
MyFormatDraftman draftman = new MyFormatDraftman();
|
||||
model.accept(draftman);
|
||||
// Write draftman output to file(s)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Step 3: Wire It Up
|
||||
|
||||
Add a menu item in `App.buildFileMenu()`:
|
||||
|
||||
```java
|
||||
JMenuItem myExportItem = new JMenuItem("Export to MyFormat");
|
||||
myExportItem.addActionListener(evt -> {
|
||||
try {
|
||||
new MyFormatExporter(model).export();
|
||||
} catch (FileNotFoundException e) {
|
||||
logger.error("Could not export: {}", e.getMessage());
|
||||
}
|
||||
});
|
||||
menuFile.add(myExportItem);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Known Issues
|
||||
|
||||
- **HTMLDraftman.visitTriangle()** uses `this.hashCode()` instead of `sTriangle.hashCode()` for CSS class naming. This is a bug that could cause CSS class collisions when multiple triangles are exported.
|
||||
Reference in New Issue
Block a user