I am working on a vector drawing application (in java) and I am struggling with the the separation between my model classes and the view/controller classes.
Some background:
You can draw different shapes:
rectangles, lines and pie segments
There are 4 tools to manipulate the shapes on the canvas:
scale-tool, move-tool, rotate-tool and morph-tool
For this question the morph tool is the most interesting one: It lets you change a shape by dragging one of it's points and adjusting the other properties as shown in this graphic:
These Transformation rules are different for each shape and I think they are part of the model's business logic but in a way they need to be exposed to the view/controller (the tool classes) so they can apply the correct one.
Additionally the shapes are internally represented via different values: - The rectangle is stored as center, width, height, rotation - The line is stored as start and end point - The pie segment is stored as center, radius, angle1, angle2
I plan to add more shapes in the future, like stars, speech bubbles or arrows, each with their own control points.
I also plan to add more tools in the future, like rotating or scaling groups of shapes.
The control points for each tool are different. Eg when using the scale tool, you can not grab the center point, but each scaling control points needs to be associated with one pivot point (or multiple to let the user choose from).
For the simple shapes like rectangle, line and pie the control points are the same for each instance of the class but futures shapes like a bezier path or a star (with configurable spike count) would have a different amount of control points per instance.
So the question is whats a good way to model and implement these control points?
As they are slightly different for each tool and carry some tool/controller specific data they belong to the tool/controller in some way. But as they are also specific for each type of shape and carry very important domain logic they also belong to the model.
I would like to avoid the combinatoric explosion of adding a special type of control point for each combination of tool/shape whenever one tool or shape is added.
Update: To give another example: In the future it may occur that I have a idea for a new shape I want to support: the arc. It is similar to the pie segment but looks a bit different and behaves completely different when dragging the control points.
To implement this I would like to be able to just create a ArcShape class implementing my Shape interface and be done.
Basic Considerations
First of all let us make some definitions for simplicity.
Entity
is a Domain Model object, which defines all the structure and behaviour, i.e. logic.
EntityUI
is the graphical control that represents the Entity
in the UI.
So basically, for Shape
classes I think ShapeUI
must be pretty much aware of the structure of the Shape
. The structure is mainly composed of the control points I guess. In other words, having all the information about the control points (maybe vectors in future), the ShapeUI
will be able to draw itself on the UI.
Initial Suggestions
What I would suggest for the Shape
classes, is that the Shape
class defines all the behaviour. The ShapeUI
class will be aware of Shape
class and keep a reference to one it is representing, by which it will have access to the control points, as well as be able to manipulate them, e.g. set their locations. The Observer
pattern simply asks to be used in this context. Particularly, the Shape
class may implement the Observable
and the ShapeUI
will implement Observer
and subscribe to the corresponding Shape
object.
So basically what will happen in this case, the ShapeUI
object will handle all UI operations, and will be responsible for updating the Shape
parameters, e.g. control point locations. Afterwards, as soon as a location update occurs, the Shape
object executes its logic upon the state change and then blindly (without being aware of ShapeUI
) notifies the ShapeUI
about the updated state. So correspondingly the ShapeUI
will draw the new state. Here you will gain low-coupled model and view.
As for the Tools
, my own opinion is that each Tool
must know how to manipulate each type of Shape
, i.e. the per shape manipulation logic must be implemented inside the Tool
class. For decoupling the view and the model, it is pretty much the same as for the Shape
. The ToolUI
class handles where the cursor is clicked, what ShapeUI
was it clicked on, what control point was it clicked on, etc. By obtaining this information, ToolUI
will pass it to the appropriate Tool
object, which will then apply the logic based on the received parameters.
Handling Different Shape Types
Now when it comes to the Tool
treating different Shape
s in their own ways, I think the Abstract Factory
pattern steps in. Each tool will implement an Abstract Factory
where we will provide manipulation implementations for each type of Shape
.
Summary
Based on what I suggested, here is the draft Domain Model:
To get the whole idea out of my suggestions, I am also posting the Sequence Diagram for a specific Use Case:
Using ToolUI
the user clicks on ShapeUI
's ControlPointUI
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With