Velix UI Editor
This package implements a wysiwyg UI editor and runtime engine with JSON as the persistence format.
Goals and Design Principles
Developer tool
The editor and the engine are ment to simplify the development process but not replace it. So, it's not one of those no-code tools ( e.g. FlutterFlow ) but more a developer tool.
On the other hand it still offers all of the typical features of a wysiwyg editor:
- drag & drop
- widget tree
- dynamic palette
- property editors
- undo
- live preview
- i18n
- shortcuts
Currently the approach is to be able to design individual pages only.
Model Based
Every aspect is model based and pluggable avoiding any hardcoded logic. This relates to different aspects.
- the set of widget types
- the configuration data of every widget
- the component that is responsible to render a widget
- the property editors that are rendered.
Let's look at the different aspects:
Widget Data Definition
A widget is defined by a set of configuration properties declared as a class.
Example
@Dataclass()
@DeclareWidget(name: "button", i18n: "editor:widgets.button.title", group: "widgets", icon: Icons.text_fields)
@JsonSerializable(discriminator: "button")
class ButtonWidgetData extends WidgetData {
// instance data
@DeclareProperty(groupI18N: "editor:groups.general", i18n: "editor:widgets.button.label")
String label;
@DeclareProperty(group: "Font Properties")
Font? font;
@DeclareProperty(group: "Layout Properties")
Padding? padding;
// constructor
ButtonWidgetData({super.type = "button", super.children = const [], required this.label, this.font, this.padding});
}
Different annotations are used to register the widget type and its meta-data on startup.
This process relies on the basic velix code generator triggered by the @Dataclass annotation.
Widget Renderer
Every widget type - in our case a button type - requires a renderer, that will consume the configuration properties.
Example
@Injectable()
class ButtonWidgetBuilder extends WidgetBuilder<ButtonWidgetData> {
// constructor
ButtonWidgetBuilder() : super(name: "button");
// internal
// override
@override
Widget create(ButtonWidgetData data, Environment environment) {
return ElevatedButton(
...
);
}
}
The @Injectable annotation is used by the velix dependency injection framework and will make sure, that a singleton instance is
created on startup of the corresponding container, which will in turn register the builder in a widget registry.
Property Editor
The same logic is applied for property editors, which are used in a generic property panel.
Example
Injectable()
class StringEditorBuilder extends PropertyEditorBuilder<String> {
// override
@override
Widget buildEditor({
required MessageBus messageBus,
required CommandStack commandStack,
required FieldDescriptor property,
required String label,
required dynamic object,
required dynamic value,
required ValueChanged<dynamic> onChanged,
}) {
return _StringEditorStateful(
label: label,
value: value ?? "",
onChanged: onChanged,
);
}
}
This class will register an editor for properties of type String.
JSON data format
Widgets are stored as a tree-structure in JSON that is a 1:1 mapping of the properties and defined converters ( e.g. font weight ).
Example
{
"type": "container",
"children": [
{
"type": "text",
"children": [],
"label": "Hello World"
},
{
"type": "button",
"children": [],
"label": "PRESS",
"font": {
"size": 12,
"weight": 100,
"style": "normal"
},
"padding": {
"left": 1,
"top": 1,
"right": 1,
"bottom": 1
}
},
{
"type": "text",
"children": [],
"label": "Second Text"
}
]
}
Generic approach avoiding code generation
The framework tries to avoid any additional - on top of the meta-data - code generators, as they introduce additional build steps and dependencies which can be avoided.
The only problematic part are actions, that need to access the surrounding infrastructure ( e.g. a page method ) including passing parameters - literal or variable references - or simple parameter expressions.
The current approach is to utilize generated meta-data ( e.g. class structures ) in order to allow for smart input code editors, and to evaluate the resulting expressions based on the concrete runtime data using the expression library.
Work in progress :-).
Libraries
- actions/action_evaluator
- actions/action_parser
- actions/autocomplete
- actions/eval
- actions/expressions
- actions/infer_types
- actions/parser
- actions/types
- actions/visitor
- commands/command
- commands/command_stack
- commands/property_changed_command
- commands/reparent_command
- components/class_picker
- components/color_picker
- components/file_path_selector
- components/focusable_region
- components/font_picker
- components/locale_switcher
- components/panel_header
- components/svg_icon
- components/toast
- dynamic_widget
- edit_widget
- editor
- editor.types.g
- editor/canvas
- editor/docking_container
- editor/editor
- editor/error_messages
- editor/layout_canvas
- editor/panel_switcher
- editor/settings
- editor/settings_panel
- editor/test_model
- editor_module
- event/events
- json/json_view
- metadata/annotations
- metadata/metadata
- metadata/properties/properties
- metadata/type_registry
- metadata/widget_data
- metadata/widgets/column
- metadata/widgets/container
- metadata/widgets/dropdown
- metadata/widgets/for
- metadata/widgets/grid
- metadata/widgets/label
- metadata/widgets/list
- metadata/widgets/row
- metadata/widgets/sheet
- metadata/widgets/stack
- metadata/widgets/switch
- metadata/widgets/text
- palette/palette_view
- persistence/persistence
- property_panel/compound_property_editor
- property_panel/editor/alignment_editor
- property_panel/editor/bool_editor
- property_panel/editor/code_editor
- property_panel/editor/color_editor
- property_panel/editor/font_editor
- property_panel/editor/font_style_editor
- property_panel/editor/font_weight_editor
- property_panel/editor/grid_editor
- property_panel/editor/int_editor
- property_panel/editor/paddding_editor
- property_panel/editor/string_editor
- property_panel/editor/template_editor
- property_panel/editor/value_editor
- property_panel/editor_builder
- property_panel/editor_registry
- property_panel/enum_editor
- property_panel/list_property_editor
- property_panel/property_panel
- svg_icons
- theme/abstract_widget
- theme/theme
- theme/widget_builder
- theme/widgets/column_widget
- theme/widgets/container_widget
- theme/widgets/dropdown_widget
- theme/widgets/for_widget
- theme/widgets/grid_widget
- theme/widgets/label_widget
- theme/widgets/list_widget
- theme/widgets/row_widget
- theme/widgets/sheet_widget
- theme/widgets/stack_widget
- theme/widgets/switch_widget
- theme/widgets/text_widget
- tree/tree_view
- util/assets
- util/message_bus
- util/ordered_async_value_notifier
- validate/validate
- widget_container