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
image

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/widget_breadcrumb
editor_module
event/events
json/json_view
metadata/annotations
metadata/metadata
metadata/properties/properties
metadata/type_registry
metadata/widget_data
metadata/widgets/button
metadata/widgets/column
metadata/widgets/container
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/button_widget
theme/widgets/column_widget
theme/widgets/container_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