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 val
s, 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, ExpressionT
, 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.