Exit Full View

Vectorial / DesignChoices.md

Design Choices

The data structure is made up of large objects, such as Diagram, Shape etc.

These large objects have attributes, such as points, colors etc. Each attribute is NOT a simple data types, such as Color or Vector2. Instead, they can be Expressions, which are calculated dynamically.

For example, if we have a circle and a line from the center to somewhere on the circumference, then the line's start and end points are NOT simple Vector2, but instead are DependantVector2, which can be evaluated to a Vector2.

Core API

For maximum flexibility, and ease of testing, I've split the application into subprojects.

vectorial-core has no GUI, and no dependency on OpenGL nor JavaFX. It is essentially the vectorial model.

vectorial-graphics has various JavaFX controls, which can render a vectorial diagram, but has no GUI (i.e. no buttons, toolbars etc.). This allows other applications to embed vectorial diagrams. vectorial-graphics depends on vectorial-core

Finally, we have the vectorial application itself (which is the top-level project). This depends on vectorial-graphics and vectorial-core.

Notable Classes

For a broad overview of Vectorial's data structure, look at Document, Diagram and Shape. Then look at Shape's subclass : GeometryShape, as well as Geometry and ContiguousGeometry (which has subclasses : Line and Bezier).

To understand how Vectorial works at a lower level, look at Attributes, Attribute and Expression.

If you wish to programmatically modify a Document/Diagram, then you will need to understand History, Batch and Change.

To understand the GUI : MainWindow and DiagramView. Note that the GUI make heavy use of two external libraries : fxessentials and harbourfx. Both of these are also written by me (NickTheCoder).

Finally, to understand how diagrams are rendered (using OpenGL), look at ImageTransfer, Renderer and OpenGL. You could also look at the subclasses of ShaderProgram if you want to play with the OpenGL Shaders!

Conventions

Objects which have Expressions should declare them as vals, and also have a convenience method of the same name which evaluates the expression.

e.g. Line.start is a Vector2Expression and Line.start() returns a Vector2.

Undo/Redo

Years of experience has shown me that adding undo/redo as an afterthought is not a good idea! The core API give a read-only view of the model, with extra Change objects. To modify the model, we create a Change object, and add it to the History. The Change will be applied, and the listeners (such as the views) are notified.

For example, to move a Shape, we create a MoveShape (a subclass of Change), and add it to History.

This gives us strong separation of the API from the implementation. None of the mutable fields of the model are visible from the API.

Expression Language

This section is subject to change!

StringExpression is a subclass of Expression, which is defined using a String. StringExpression.parse() converts the String into an Expression.

I haven't started work on the expression language. It will use ANTLR to parse it.

Each Shape will have an immutable ID (int) as well as a mutable name (String). Both are unique to a Diagram. When we renamed a Shape, all expressions must be checked, and names updated accordingly. I may implement this as a separate ANTLR parser???

Expressions don't have to be used. When editing shapes via the GUI, we can use Dependant<T> subclasses directly. e.g. A line's start can be fixed to the center of a circle using an Expression :

shape[MyShape].center
(Note the syntax will likely change, as I haven't thought it through yet)

or directly using the appropriate DependantVector2 subclass which references the circle's center.

Ideally, we should be able to flip between both. i.e. Expression.parse() returns the DependantVector2 subclass. Also, Dependant.toExpression() returns a String suitable for Expression.

class Expression

This section is subject to change!

T is the type variable, such as Vector2, Color, Float etc.

To make the code look cleaner, Expression will never be used directly. Instead, for each type T, we have an additional interface, which removes the type variable. e.g. :

interface FloatExpression : Expression<Float>

Partial Free Expressions

This section is subject to change!

Suppose we have line from the center of a circle, to somewhere on its circumference. The start is fixed, but the end can be anywhere on the circumference. i.e. It still has 1 degree of freedom.

In this case the expression will need to expose this degree of freedom (and the GUI will allow it to be changed via a DragHandle).

Example subclasses of Freedom : None, FreeVector2, DistanceFromLineStart, RatioAlongLine, AroundEllipse ...

Type Coercion

This section is subject to change!

Number types are automatically (and silently) coerced. i.e. floats, double and integers can be used within Expressions interchangeably without errors.

Note, Angles will have their own type (maybe?), and will NOT be automatically coerced to number types.