napa_widgets
Inspectable and serializable wrappers around standard Flutter widgets. Build widget trees at runtime, serialize them to JSON, deserialize them back, and inspect or modify properties on the fly — with optional Lua scripting for custom painting and gesture logic.
Features
- Runtime inspection — every widget exposes its properties through a unified
Inspectableinterface, making it easy to build property editors and live preview tools - JSON serialization — full round-trip
toJson()/decode()for the entire widget tree, including nested children and complex types - Widget registration — a global registry allows custom widgets to plug into the same decode pipeline
- Lua scripting —
NapaCustomPaint,NapaFlow, andNapaGestureDetectoraccept inline Lua scripts for runtime-defined drawing and interaction logic - 70+ widgets covered — layout, scrolling, animation, semantics, text, interaction, and more
Usage
Composing a widget tree
Build a tree of NapaWidgets and call toWidget() to get the standard Flutter widget:
import 'package:napa_widgets/napa_widgets.dart';
final napaTree = NapaPadding(
padding: const EdgeInsets.all(16),
child: NapaColumn(
children: [
NapaText('Hello, world!'),
NapaSizedBox(height: 8),
NapaContainer(
color: const Color(0xFF2196F3),
width: 120,
height: 40,
),
],
),
);
// Convert to a real Flutter widget
Widget flutterWidget = napaTree.toWidget();
Serialization
Serialize a widget tree to JSON and restore it:
// Serialize
Map<String, dynamic> json = napaTree.toJson();
// Deserialize
NapaWidget? restored = NapaWidget.decode(json);
A serialized NapaText looks like:
{
"_name": "Text",
"data": "Hello, world!",
"style": {
"fontSize": 16.0,
"color": { "r": 0.0, "g": 0.0, "b": 0.0, "a": 1.0, "colorSpace": "sRGB" }
}
}
Runtime inspection
Every widget exposes its properties via the Inspectable mixin:
final widget = NapaContainer(color: Colors.red, width: 100, height: 80);
for (final prop in widget.properties) {
print('${prop.name}: ${prop.getValue(widget)}');
}
// Modify a property at runtime
final colorProp = widget.properties.firstWhere((p) => p.name == 'color');
colorProp.setValue(widget, Colors.blue, null);
Registering custom widgets
Custom NapaWidget subclasses can be registered once at app startup:
NapaWidget.registerNapaWidget('MyWidget', MyNapaWidget.decode);
After registration, NapaWidget.decode(json) will dispatch to your class whenever _name == 'MyWidget'.
Lua scripting
NapaCustomPaint accepts an inline Lua script that implements a paint(canvas, size) function. The ui.* namespace exposes ~80 drawing primitives:
final painter = NapaCustomPaint(
painter: NapaCustomPainter(
type: NapaCustomPainterType.lua,
script: '''
function paint(canvas, size)
local paint = ui.newPaint()
ui.paintSetColor(paint, ui.newColor(0.2, 0.6, 1.0, 1.0))
ui.canvasDrawRect(canvas,
ui.newRect(0, 0, size.width, size.height), paint)
end
''',
),
);
Implicit animations
All Flutter implicit animation widgets are supported. Changing a property and rebuilding produces a smooth transition:
NapaAnimatedContainer(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
width: isExpanded ? 200 : 100,
color: isExpanded ? Colors.blue : Colors.grey,
child: NapaText('Tap me'),
)
Rich text
NapaRichText uses the NapaTextSpan data class for fully serializable styled text trees:
NapaRichText(
text: NapaTextSpan(
children: [
NapaTextSpan(text: 'Hello ', style: NapaTextStyle(fontSize: 16)),
NapaTextSpan(
text: 'world',
style: NapaTextStyle(fontSize: 16, color: const Color(0xFF2196F3)),
),
],
),
)
Widget coverage
| Widget | Status | Lua scripting | Notes |
|---|---|---|---|
| AbsorbPointer | ✅ | ||
| Align | ✅ | ||
| AnimatedAlign | ✅ | ||
| AnimatedContainer | ✅ | ||
| AnimatedCrossFade | ✅ | ||
| AnimatedOpacity | ✅ | ||
| AnimatedPadding | ✅ | ||
| AnimatedPositioned | ✅ | Must be child of Stack | |
| AnimatedRotation | ✅ | ||
| AnimatedScale | ✅ | ||
| AnimatedSize | ✅ | ||
| AnimatedSlide | ✅ | ||
| AnimatedSwitcher | ✅ | ||
| AspectRatio | ✅ | ||
| BackdropFilter | ✅ | ||
| Baseline | ✅ | ||
| Center | ✅ | ||
| ClipOval | ✅ | No CustomClipper support | |
| ClipPath | ⏳ | No CustomClipper support | |
| ClipRect | ✅ | No CustomClipper support | |
| ClipRRect | ✅ | No CustomClipper support | |
| ClipRSuperellipse | ✅ | No CustomClipper support | |
| ColoredBox | ✅ | ||
| Column | ✅ | ||
| ConstrainedBox | ✅ | ||
| Container | ✅ | ||
| CustomPaint | ✅ | ✅ | |
| DecoratedBox | ✅ | ||
| DefaultTextStyle | ✅ | ||
| Directionality | ✅ | ||
| ExcludeSemantics | ✅ | ||
| Expanded | ✅ | ||
| FittedBox | ✅ | ||
| Flex | ✅ | ||
| Flexible | ✅ | ||
| Flow | ⏳ | ✅ | |
| Focus | ✅ | No event callbacks | |
| FocusScope | ✅ | No event callbacks | |
| FractionallySizedBox | ✅ | ||
| GestureDetector | ✅ | ✅ | |
| GridView | ✅ | GridView.count variant | |
| Icon | ⏳ | ||
| IgnorePointer | ✅ | ||
| Image | ✅ | ||
| ImageFiltered | ✅ | ||
| IndexedStack | ✅ | ||
| InteractiveViewer | ✅ | ||
| IntrinsicHeight | ✅ | ||
| IntrinsicWidth | ✅ | ||
| LimitedBox | ✅ | ||
| Listener | ✅ | ||
| ListView | ✅ | ||
| MergeSemantics | ✅ | ||
| MouseRegion | ✅ | ||
| Offstage | ✅ | ||
| Opacity | ✅ | ||
| OverflowBox | ✅ | ||
| Padding | ✅ | ||
| PageView | ✅ | ||
| PhysicalModel | ✅ | ||
| Positioned | ✅ | ||
| RepaintBoundary | ✅ | ||
| RichText | ✅ | ||
| RotatedBox | ✅ | ||
| Row | ✅ | ||
| SafeArea | ✅ | ||
| Semantics | ✅ | ||
| SingleChildScrollView | ✅ | ||
| SizedBox | ✅ | ||
| SizedOverflowBox | ✅ | ||
| Stack | ✅ | ||
| Text | ✅ | ||
| Transform | ✅ | ||
| UnconstrainedBox | ✅ | ||
| Visibility | ✅ | ||
| Wrap | ✅ |
✅ Implemented ⏳ Pending
Architecture
All widgets extend NapaWidget, which mixes in Inspectable from the inspectable_property package:
NapaWidget (abstract, Inspectable mixin)
├── NapaStatelessWidget
├── NapaStatefulWidget
├── NapaRenderObjectWidget
│ ├── NapaSingleChildRenderObjectWidget
│ └── NapaMultiChildRenderObjectWidget
└── NapaProxyWidget
└── NapaParentDataWidget
Each widget implements three core capabilities:
toWidget()— renders to a real FlutterWidgettoJson()— serializes toMap<String, dynamic>(includes_namefield)static decode(data)— deserializes from JSON
Libraries
- napa_widgets
- scripting/lua/lua_ui_lib
- scripting/napa_script_type
- widgets/data/image_wrapper
- widgets/data/napa_color_filter
- widgets/data/napa_image_filter
- widgets/data/napa_mask_filter
- widgets/data/napa_matrix4
- widgets/data/napa_paint
- widgets/data/napa_shader
- widgets/data/napa_text_decoration
- widgets/data/napa_text_scaler
- widgets/data/napa_text_span
- widgets/data/napa_text_style
- widgets/napa_absorb_pointer
- widgets/napa_align
- widgets/napa_animated_align
- widgets/napa_animated_container
- widgets/napa_animated_cross_fade
- widgets/napa_animated_opacity
- widgets/napa_animated_padding
- widgets/napa_animated_positioned
- widgets/napa_animated_rotation
- widgets/napa_animated_scale
- widgets/napa_animated_size
- widgets/napa_animated_slide
- widgets/napa_animated_switcher
- widgets/napa_aspect_ratio
- widgets/napa_backdrop_filter
- widgets/napa_baseline
- widgets/napa_box_scroll_view
- widgets/napa_center
- widgets/napa_clip_oval
- widgets/napa_clip_path
- widgets/napa_clip_rect
- widgets/napa_clip_rrect
- widgets/napa_clip_rsuperellipse
- widgets/napa_colored_box
- widgets/napa_column
- widgets/napa_constrained_box
- widgets/napa_container
- widgets/napa_custom_paint
- widgets/napa_decorated_box
- widgets/napa_default_text_style
- widgets/napa_directionality
- widgets/napa_exclude_semantics
- widgets/napa_expanded
- widgets/napa_fitted_box
- widgets/napa_flex
- widgets/napa_flexible
- widgets/napa_flow
- widgets/napa_focus
- widgets/napa_focus_scope
- widgets/napa_fractionally_sized_box
- widgets/napa_gesture_detector
- widgets/napa_grid_view
- widgets/napa_icon
- widgets/napa_ignore_pointer
- widgets/napa_image
- widgets/napa_image_filtered
- widgets/napa_indexed_stack
- widgets/napa_interactive_viewer
- widgets/napa_intrinsic_height
- widgets/napa_intrinsic_width
- widgets/napa_limited_box
- widgets/napa_list_view
- widgets/napa_listener
- widgets/napa_merge_semantics
- widgets/napa_mouse_region
- widgets/napa_multi_child_render_object_widget
- widgets/napa_offstage
- widgets/napa_opacity
- widgets/napa_overflow_box
- widgets/napa_padding
- widgets/napa_page_view
- widgets/napa_parent_data_widget
- widgets/napa_physical_model
- widgets/napa_positioned
- widgets/napa_proxy_widget
- widgets/napa_render_object_widget
- widgets/napa_repaint_boundary
- widgets/napa_rich_text
- widgets/napa_rotated_box
- widgets/napa_row
- widgets/napa_safe_area
- widgets/napa_scroll_view
- widgets/napa_semantics
- widgets/napa_single_child_render_object_widget
- widgets/napa_single_child_scroll_view
- widgets/napa_sized_box
- widgets/napa_sized_overflow_box
- widgets/napa_stack
- widgets/napa_stateful_widget
- widgets/napa_stateless_widget
- widgets/napa_text
- widgets/napa_transform
- widgets/napa_unconstrained_box
- widgets/napa_visibility
- widgets/napa_widget
- widgets/napa_wrap
- widgets/tojson_extensions