Go to file
2024-04-26 14:48:30 -05:00
src/doodles lots more documentation 2024-04-26 14:48:30 -05:00
.gitignore doodles 2024-04-21 22:10:04 -05:00
poetry.lock doodles 2024-04-21 22:10:04 -05:00
pyproject.toml doodles 2024-04-21 22:10:04 -05:00
README.md api cleanup 2024-04-22 22:54:18 -05:00

Doodles

This is a library that lets you write short programs that draw images or animations.

The design goals are, in approximate order of importance:

  • Demonstrate some design patterns & concepts.
  • Provide an example of a library design.
  • Have a learning tool that's somewhat fun to play with.

Notably absent from this list: "make a useful tool" and "demonstrate the right way to do things."

This library demonstrates ways to do things, there is almost never a single correct way. Sometimes choices will be made to provide more interesting code to learn from, or a more fun API to use.

Rationales

I will document the reason that certain decisions were made, particularly when they were not my first thought.

If any decisions are unclear, I consider that a bug, please file a corresponding GitHub issue.

Mutability

Often, an API based on chaining like this would be comprised of immutable objects. Django's QuerySet mostly works in this way.

That was the original intention here as well, but once Text was added it was clear that it was going to be a lot more complex than it was worth.

Text requires an intermediate buffer to render the text to, and that buffer is then rendered to the screen. This would be incredibly expensive in a pure immutable approach, since there'd be no place to store that state.

The addition of state to the objects simplifies a lot of things, now when an object is created it can be registered with the World, if many intermediate copies were created in a chained call like Circle().pos(100, 100).color(255, 0, 0).z(10).radius(4) this approach would not be available to us.

Naming Scheme

This library makes use of @property to create getters, but uses function chaining instead of property-based setters.

This complicates things a bit, since you can't write self.x(100) and use self.x as a property.

The unorthodox decision was made to use x(100) as a setter function, and use .x_val as the property name.

This is primarily because setting properties is more common than getting properties in doodles.

Design Patterns

TODO: flesh this out with more notes

A number of design patterns are utilized in this library. I'm open to suggestions for more to add, especially where an interesting feature can demonstrate the utility.

Factory

TODO: No factory yet surprisingly, should be easy to add one to add shapes, but can consider other options.

Prototype

The Doodle class (and an override in Group) demonstrate the utility of the Prototype pattern.

Letting these objects specify how they are copied makes the desired behavior possible, the utility of which can be seen in examples/copies.py.

This would often be done via the __copy__/__deepcopy__ methods, but for simplicity as well as API compatibility this is done in .copy() here.

Singleton

The World class is treated as a Singleton and contains notes on alternate implementations.

Bridge / Strategy

TODO: the planned Renderer class will demonstrate these

Composite

The Group class (along with Doodle) forms a composite.

Command

The structure of the Doodle object is a command class. It stores the information about the action to be performed encapsulated. It's entire purpose is to provide arguments to a draw* method.

Observer

TODO: room to implement, dynamic properties?

Template Method

The update method, the draw method.

Visitor

TODO: maybe use along with group?

Flyweight Cache

Text's Font Cache is a flyweight