-
Notifications
You must be signed in to change notification settings - Fork 0
FormBuilder.md
FormBuilder is Macro Studio's schema-driven dialog system. Commands describe the inputs they need as Python dictionaries, and FormBuilder turns those descriptions into consistent, theme-aware eDock forms.
This keeps command implementations focused on automation logic instead of repeated dialog code.
When a user adds or edits a command with fields, Macro Studio follows this path:
Command.fields
↓
FormBuilder.get_data(...)
↓
The field registry creates widgets
↓
The user edits and validates values
↓
A values dictionary is returned to the command
A command with no fields is added immediately. A command with fields opens a FormBuilder dialog first. The same builder is also used by project and variable dialogs.
A schema contains a title, optional submit button text, and a list of fields:
schema = {
"title": "Type a greeting",
"submit_text": "Add",
"fields": [
{
"name": "text",
"title": "Message",
"value_type": "string",
"default_value": "Hello from Macro Studio",
"required": True,
},
],
}Common field properties:
| Property | Purpose |
|---|---|
name |
Key used in the returned values dictionary |
title |
Label shown beside the widget |
value_type |
Widget and value format to create |
default_value |
Initial value |
required |
Whether the user must provide a value |
place_holder |
Hint shown when a text field is empty |
options |
Choices for choice, radio, and checkbox fields |
visible_if |
Show the field only when one rule matches |
visible_if_all |
Show the field only when every rule matches |
visible_if_any |
Show the field when at least one rule matches |
enabled_if |
Keep the field visible but disable it unless the rule matches |
hidden |
Remove the field from the form |
os |
Restrict the field to an operating system |
Unknown or omitted value_type values fall back to a string field.
Use FormBuilder.get_data when a command or dialog needs user input:
from ...ui.forms.form_builder import FormBuilder
data = FormBuilder.get_data(
schema={
"title": "Create a note",
"submit_text": "Create",
"fields": [
{
"name": "title",
"title": "Title",
"value_type": "string",
"required": True,
},
{
"name": "body",
"title": "Body",
"value_type": "textarea",
"place_holder": "Write your note...",
},
],
},
values={"title": "", "body": ""},
parent=window,
)
if data is None:
return # The user pressed Cancel.
title = data["title"]
body = data["body"]get_data returns a dictionary after Save/Add, and None when the dialog is cancelled. Values are normalized by the field handler, so integers, booleans, ranges, colors, paths, and structured fields arrive in their expected form.
The default registry includes:
string, text, textarea, code, python_code, integer, int, float, double, min_max, range, boolean, bool, choice, radio_group, checkbox_group, color, mouse_position, file, folder, captured_image, screen_region, audio, variable, comment, macro_group, status, result, and message.
Aliases such as text_area, multiline, directory, and position are supported for readability in command schemas.
A common pattern is to show a position editor only when the user chooses positional clicking:
fields = [
{
"name": "click_mode",
"title": "Click Mode",
"value_type": "choice",
"options": ["current", "position"],
"default_value": "current",
},
{
"name": "position",
"title": "Position",
"value_type": "mouse_position",
"default_value": {"x": 0, "y": 0},
"visible_if": {
"field": "click_mode",
"operator": "==",
"value": "position",
},
},
]When click_mode is current, the position field is hidden. When it changes to position, FormBuilder reevaluates the rule immediately and resizes the dialog.
For multiple dependencies, use visible_if_all or visible_if_any:
{
"name": "x_variable",
"title": "X Variable",
"value_type": "variable",
"visible_if_all": [
{"field": "click_mode", "operator": "==", "value": "position"},
{"field": "position_type", "operator": "==", "value": "variable"},
],
}Supported comparison operators include ==, !=, in, not in, empty, not empty, is true, and is false.
{
"name": "format",
"title": "Output format",
"value_type": "choice",
"options": ["plain_text", "markdown", "json"],
"default_value": "plain_text",
"required": True,
}Required fields are checked on submit. Empty visible fields are highlighted and the dialog stays open until they are fixed.
For comments and jump targets, FormBuilder can validate that a selected comment exists:
{
"name": "target",
"title": "Jump to comment",
"value_type": "comment",
"required": True,
}Macro Studio supplies the current runtime comments and variables to the form, so these fields can offer live project choices.
When Macro Studio opens a command form, it passes project context:
data = FormBuilder.get_data(
schema={"title": "Command", "fields": command.fields},
values=command.default_values(),
parent=window,
runtime_variables=window.collect_defined_variables(),
runtime_comments=controller.collect_defined_comments(),
)This enables:
-
value_type: "variable"fields to list existing variables - adding a new variable from a variable selector
-
value_type: "comment"fields to list comment markers -
value_type: "macro_group"fields to list project macro groups
A field can derive a preview from the current form values with compute_value:
{
"name": "preview",
"title": "Preview",
"value_type": "status",
"required": False,
"compute_value": lambda values: {
"value": f"{values.get('prefix', '')}{values.get('name', '')}",
"status": "info",
},
}Computed fields refresh whenever another field changes. status, result, and message fields are informational and are not required by default.
For a project-specific widget, implement a field handler following the existing handlers in ui/forms/fields/, then register it:
from ...ui.forms.form_builder import FormBuilder
FormBuilder.register_field("my_type", MyFieldHandler(context))The handler is responsible for creating the widget, reading its value, writing a value, and connecting its change signal. Prefer adding a reusable handler when the field is useful to more than one command.
- Keep schemas declarative and let
execute()consume the returned values. - Give every field a stable
name; it becomes part of saved macro data. - Use
default_valueso a new command is usable immediately. - Use conditional visibility to avoid irrelevant options.
- Mark informational fields as
required: False. - Use
variable,comment, andmacro_grouptypes instead of duplicating project-selection widgets. - Keep callbacks side-effect-free; use them to calculate display state or update form context.