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:
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:
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:
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:
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:
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:
SCollection.of(Shape... shapes) // Auto-adds SelectionAttributes
Attributes System
Design
Attributes are stored as a map in AbstractShape:
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 |
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 |
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:
- Implement the
Attributesinterface. - Define a
public static final String IDconstant. - Implement
getID()returning the ID. - Attach with
shape.addAttributes(new MyAttribute(...)). - 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.