## Introduction `constructivism` is a Rust sample-library designed to simplify the construction of structured data by defining and manipulating sequences of Constructs. This README provides an overview of how to use `constructivism` and how it can be inlined into you project using `constructivist` library. ## Installation To use Constructivism in your Rust project, add it as a dependency in your `Cargo.toml` file: ```toml [dependencies] constructivism = "0.0.2" ``` Or let the cargo do the stuff: ```bash cargo add constructivism ``` Constructivism can be inlined into you library as for example `your_library_constructivism` within `constructivist` crate. See [instructions](./crates/constructivist). ## Guide See also [examples/tutorial.rs](examples/tutorial.rs) ### Getting Started Usually you start with ```rust use constructivism::*; ``` ### Constructs and Sequences 1.1. **Constructs**: Constructivism revolves around the concept of Constructs. You can derive construct like this: ```rust #[derive(Construct)] pub struct Node { hidden: bool, position: (f32, f32), } ``` 1.2 **`construct!`**: You can use the `construct!` macro to create instances of Constructs. Please ***note*** the dots at the beginning of the each param, they are required and you will find this syntax quite useful. ```rust fn create_node() { let node = construct!(Node { .position: (10., 10.), .hidden: true }); assert_eq!(node.position.0, 10.); assert_eq!(node.hidden, true); } ``` 1.3 **Sequences**: A Construct can be declared only in front of another Construct. `constructivism` comes with only Nothing, () construct. The `Self -> Base` relation called Sequence in `constructivism`. You can omit the Sequence declaration, `Self -> Nothing` used in this case. If you want to derive Construct on the top of another meaningful Construct, you have to specify Sequence directly with `#[construct(/* Sequence */)]` attribute. ```rust #[derive(Construct)] #[construct(Rect -> Node)] pub struct Rect { size: (f32, f32), } ``` 1.4 **Constructing Sequences**: The Sequence for the Rect in example above becomes `Rect -> Node -> Nothing`. You can `construct!` the entire sequence within a single call: ```rust fn create_sequence() { let (rect, node /* nothing */) = construct!(Rect { .hidden, // You can write just `.hidden` instead of `.hidden: true` .position: (10., 10.), .size: (10., 10.), }); assert_eq!(rect.size.0, 10.); assert_eq!(node.position.1, 10.); assert_eq!(node.hidden, false); } ``` 1.5 **Params**: There are different kind of Params (the things you passing to `construct!(..)`): - Common: use `Default::default()` if not passed to `construct!(..)` - Default: use provided value if not passed to `construct!(..)` - Required: must be passed to `construct!(..)` - Skip: can't be passed to `construct!(..)`, use Default::default() or provided value You configure behavior using `#[param]` attribute when deriving: ```rust #[derive(Construct)] #[construct(Follow -> Node)] pub struct Follow { offset: (f32, f32), // Common, no #[param] #[param(required)] // Required target: Entity, #[param(default = Anchor::Center)] // Default anchor: Anchor, #[param(skip)] // Skip with Default::default() last_computed_distance: f32, #[param(skip = FollowState::None)] // Skip with provided value state: FollowState, } #[derive(PartialEq, Debug, Copy, Clone)] pub struct Entity; pub enum Anchor { Left, Center, Right, } pub enum FollowState { None, Initialized(f32) } ``` 1.6 **Passing params**: When passing params to `construct!(..)` you have to pass all required for Sequence params, or you will get the compilation error. You can omit non-required params. ```rust fn create_elements() { // omit everything, default param values will be used let (rect, node, /* nothing */) = construct!(Rect); assert_eq!(node.hidden, false); assert_eq!(rect.size.0, 0.); // you have to pass target to Follow, the rest can be omitted.. let (follow, node) = construct!(Follow { .target: Entity }); assert_eq!(follow.offset.0, 0.); assert_eq!(node.hidden, false); // ..or specified: let (follow, node) = construct!(Follow { .hidden, .target: Entity, .offset: (10., 10.), // last_computed_distance param is skipped, uncommenting // the next line will result in compilation error // error: no field `last_computed_distance` on type `&follow_construct::Params` // .last_computed_distance: 10. }); assert_eq!(follow.offset.0, 10.); assert_eq!(node.hidden, true); } ``` ### Design and Methods 2.1 **Designs and Methods**: Every Construct has its own Design. You can implement methods for a Construct's design: ```rust impl NodeDesign { pub fn move_to(&self, entity: Entity, position: (f32, f32)) { } } impl RectDesign { pub fn expand_to(&self, entity: Entity, size: (f32, f32)) { } } ``` 2.2 **Calling Methods**: You can call methods on a Construct's design. Method resolution follows the sequence order: ```rust fn use_design() { let rect_entity = Entity; design!(Rect).expand_to(rect_entity, (10., 10.)); design!(Rect).move_to(rect_entity, (10., 10.)); // move_to implemented for NodeDesign } ``` ### Segments 3.1 **Segments**: Segments allow you to define and insert segments into a Construct's sequence: ```rust #[derive(Segment)] pub struct Input { disabled: bool, } #[derive(Construct)] #[construct(Button -> Input -> Rect)] pub struct Button { pressed: bool } ``` 3.2 **Sequence with Segments**: The Sequence for Button becomes `Button -> Input -> Rect -> Node -> Nothing`. You can instance the entire sequence of a Construct containing segments within a single `construct!` call: ```rust fn create_button() { let (button, input, rect, node) = construct!(Button { .disabled: true }); assert_eq!(button.pressed, false); assert_eq!(input.disabled, true); assert_eq!(rect.size.0, 100.); assert_eq!(node.position.0, 0.); } ``` 3.3 **Segment Design**: Segment has its own Design as well. And the method call resolves within the Sequence order as well. Segment's designs has one generic parameter - the next segment/construct, so you have to respect it when implement Segment's Design: ```rust impl InputDesign { fn focus(&self, entity: Entity) { /* do the focus stuff */ } } fn focus_button() { let btn = Entity; design!(Button).focus(btn); } ``` ### Props 4.1 **Props**: By deriving Constructs or Segments you also get the ability to set and get properties on items with respect of Sequence: ```rust fn button_props() { let (mut button, mut input, mut rect, mut node) = construct!(Button); // You can access to props knowing only the top-level Construct let pos /* Prop */ = prop!(Button.position); let size /* Prop */ = prop!(Button.size); let disabled /* Prop */ = prop!(Button.disabled); let pressed /* Prop4.2 **Expand props**: If you have field with Construct type, you can access this fields props as well: ```rust #[derive(Construct, Default)] #[construct(Vec2 -> Nothing)] pub struct Vec2 { x: f32, y: f32, } #[derive(Construct)] #[construct(Node2d -> Nothing)] pub struct Node2d { #[prop(construct)] // You have to mark expandable props with #[prop(construct)] position: Vec2, } fn modify_position_x() { let mut node = construct!(Node2d); assert_eq!(node.position.x, 0.); assert_eq!(node.position.y, 0.); let x = prop!(Node2d.position.x); x.set(&mut node, 100.); assert_eq!(node.position.x, 100.); assert_eq!(node.position.y, 0.); } ``` ### Custom Constructors 5.1 **Custom Constructors**: Sometimes you may want to implement Construct for a foreign type or provide a custom constructor. You can use `derive_construct!` for this purpose: ```rust pub struct ProgressBar { min: f32, val: f32, max: f32, } impl ProgressBar { pub fn min(&self) -> f32 { self.min } pub fn set_min(&mut self, min: f32) { self.min = min; if self.max < min { self.max = min; } if self.val < min { self.val = min; } } pub fn max(&self) -> f32 { self.max } pub fn set_max(&mut self, max: f32) { self.max = max; if self.min > max { self.min = max; } if self.val > max { self.val = max; } } pub fn val(&self) -> f32 { self.val } pub fn set_val(&mut self, val: f32) { self.val = val.max(self.min).min(self.max) } } derive_construct! { // Sequence seq => ProgressBar -> Rect; // Constructor, all params with default values construct => (min: f32 = 0., max: f32 = 1., val: f32 = 0.) -> { if max < min { max = min; } val = val.min(max).max(min); Self { min, val, max } }; // Props using getters and setters props => { min: f32 = [min, set_min]; max: f32 = [max, set_max]; val: f32 = [val, set_val]; }; } ``` 5.2 **Using Custom Constructors**: The provided constructor will be called when creating instances: ```rust fn create_progress_bar() { let (pb, _, _) = construct!(ProgressBar { .val: 100. }); assert_eq!(pb.min, 0.); assert_eq!(pb.max, 1.); assert_eq!(pb.val, 1.); } ``` 5.3 **Custom Construct Props**: In the example above `derive_construct!` declares props using getters and setters. This setters and getters are called when you use `Prop::get` and `Prop::set` ```rust fn modify_progress_bar() { let (mut pb, _, _) = construct!(ProgressBar {}); let min = prop!(ProgressBar.min); let val = prop!(ProgressBar.val); let max = prop!(ProgressBar.max); assert_eq!(pb.val, 0.); val.set(&mut pb, 2.); assert_eq!(pb.val, 1.0); //because default for max = 1.0 min.set(&mut pb, 5.); max.set(&mut pb, 10.); assert_eq!(pb.min, 5.); assert_eq!(pb.val, 5.); assert_eq!(pb.max, 10.); } ``` 5.4 **Deriving Segments**: You can derive Segments in a similar way: ```rust pub struct Range { min: f32, max: f32, val: f32, } derive_segment! { // use `seg` to provide type you want to derive Segment seg => Range; construct => (min: f32 = 0., max: f32 = 1., val: f32 = 0.) -> { if max < min { max = min; } val = val.min(max).max(min); Self { min, val, max } }; // Props using fields directly props => { min: f32 = value; max: f32 = value; val: f32 = value; }; } #[derive(Construct)] #[construct(Slider -> Range -> Rect)] pub struct Slider; fn create_slider() { let (slider, range, _, _) = construct!(Slider { .val: 10. }); assert_eq!(range.min, 0.0); assert_eq!(range.max, 1.0); assert_eq!(range.val, 1.0); } ``` ## Limitations - only public structs (or enums with `constructable!`) - no generics supported yet (looks very possible) - limited number of params for the whole inheritance tree (default version compiles with 16, tested with 64) - only static structs/enums (no lifetimes) ## Cost I didn't perform any stress-tests. It should run pretty fast: there is no heap allocations, only some deref calls per `construct!` per defined prop per depth level. Cold compilation time grows with number of params limit (1.5 mins for 64), but the size of the binary doesn't changes. ## Roadmap - [ ] add `#![forbid(missing_docs)]` to the root of each crate - [ ] docstring bypassing - [ ] generics - [ ] union params, so you can pas only one param from group. For example, Range could have `min`, `max`, `abs` and `rel` constructor params, and you can't pass `abs` and `rel` both. - [ ] nested construct inference (looks like possible): ```rust #[derive(Construct, Default)] pub struct Vec2 { x: f32, y: f32 } #[derive(Construct)] pub struct Div { position: Vec2, size: Vec2, } fn step_inference() { let div = construct!(Div { position: {{ x: 23., y: 20. }}, size: {{ x: 23., y: 20. }} }) } ``` ## Contributing I welcome contributions to Constructivism! If you'd like to contribute or have any questions, please feel free to open an issue or submit a pull request. ## License The `constructivism` is dual-licensed under either: - MIT License ([LICENSE-MIT](LICENSE-MIT) or [http://opensource.org/licenses/MIT](http://opensource.org/licenses/MIT)) - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)) This means you can select the license you prefer! This dual-licensing approach is the de-facto standard in the Rust ecosystem and there are [very good reasons](https://github.com/bevyengine/bevy/issues/2373) to include both.