Essentially I have a TUI where I need to be able to display and edit the values of each field in the struct.
- I want to be able to create the visual layout for the fields in a loop
- I need to access the name of the fields to set the title of the visual subelements, and be able to manipulate it with commands like
to_ascii_uppercase
etc
- I need to access the value of the field in order to update the sub element valuue
- I need to be able to match on each field and get a mutable reference to each field within the match so I can update the correct field value when handling keyboard input.
I have a struct like so (in the actual project there are maybe 4-5 of these structs, that each need similar things implemented):
/// `Recipe` represents one recipe from start to finish
#[derive(Default, Debug, Clone, PartialEq)]
pub struct Recipe {
/// database ID
pub id: u64,
/// short name of recipe
pub name: String,
/// optional description
pub description: Option<String>,
/// recipe comments
pub comments: Option<String>,
/// recipe source
pub source: String,
/// recipe author
pub author: String,
/// amount made
pub amount_made: u64,
/// units for amount made.
///
/// These are not type checked at all and are treated as a base quantity internally.
/// This is just a representation of the units to display.
/// There may be a future addition that automatically calculates calories, or serving
/// sizes based on calories.
pub amount_made_units: String,
/// list of steps in recipe
pub steps: Vec<Step>,
/// list of tags on recipe
pub tags: Vec<Tag>,
//TODO: versions
/// if the recipe has unsaved changes or not
//TODO: figure out a save system
pub saved: bool,
}
and I want to do something like the following, ideally in the same order for each of the for loops. (I have omitted some of the intermediate code for brevity). Some of the fields like steps
and tags
would be skipped by a skip
attribute since those will be handled separately.
The below would ideally be generated by something that looks like generate_constraints!(struct)
, with constraints defined outside the macro.
for field in struct {
match field.name {
"comments" => constraints.push(Constraint::Min(7)).
"description" => constraints.push(Constraint::Min(7)),
_ => constraints.push(Constraint.Length(3)),
}
}
The below would be generated by something that looks like generate_blocks!(struct)
for field in struct {
let block = Block::default()
.borders(Borders::ALL)
.style(Style::default())
.title(field.name);
let paragraph = Paragraph::new(Text::styled(field.value), Style::default().fg(Color::White);
frame.render_widget(paragraph, field.value);
}
And the below would be generated somehow? Ideally, I would be able to generate the match arms with a specifiable operation rather than having to have a separate macro definition for push()
, pop()
, etc. This is in the key_handling code and would be inserted into a large nested match statement.
// for insertions
match struct.field {
"name" => struct.name.push(char);
...
last_field => struct.last_field.push(char);
}
or
// for removals
match struct.field {
"name" => _ = struct.name.pop(char);
...
last_field => _ = struct.last_field.pop(char);
}
For the constraint generation and the block generation, I could derive associated functions/methods and somehow pass in and return out what I need, but that feels a bit awkward to call a method that expands into a bunch of code. A macro call is much clearer that code is being generated.
Obviously, I could handwrite all this stuff out, but that is tedious and I would rather make the computer do it for me. Also this runs the risk of me forgetting a field or adding a new field and not implementing the rest of the functionality.
Please let me know if you need any more detail.
(complete code found here)