Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,10 @@ three pass.
`file_system_client`, `project_repository`, `component_repository`
(components stored in `<project>/.flame_editor/components/<name>.json`) —
see "Existing modules" in [docs/architecture.md](docs/architecture.md).
- Component types: only `rectangle` (position — edited only on children —,
size, and paint with color and fill style). Components can have children
- Component types: `rectangle` (position — edited only on children —,
size, and paint with color and fill style) and `position` (a plain Flame
`PositionComponent`: position — children only — and size; draws nothing
itself, useful as a container). Components can have children
(see "Component tree" in [docs/architecture.md](docs/architecture.md)). Properties are shared between types through capability interfaces —
see "Component types & shared properties" in
[docs/architecture.md](docs/architecture.md) before adding a type or
Expand Down
13 changes: 10 additions & 3 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ component types only declare which ones they have:
| --- | --- | --- |
| Value object (Equatable, JSON, defaults) | `component_repository/lib/src/models/` | `ComponentPosition`, `ComponentSize`, `ComponentPaint` |
| Capability interface | `component_repository/lib/src/models/component_capabilities.dart` | `PositionedComponent`, `SizedComponent`, `PaintedComponent` |
| Component type | `ComponentType` enum + a subclass in `component.dart`'s library (`part` file) | `RectangleComponent implements PositionedComponent, SizedComponent, PaintedComponent` |
| Component type | `ComponentType` enum + a subclass in `component.dart`'s library (`part` file) | `RectangleComponent implements PositionedComponent, SizedComponent, PaintedComponent`, `PositionComponent implements PositionedComponent, SizedComponent` |
| Bloc event | `lib/component_editor/bloc/` | `ComponentEditorSizeChanged` (edits the selected node if it is a `SizedComponent`) |
| Editor widget (`value` + `onChanged`, bloc-agnostic) | `lib/component_editor/widgets/property_editors/` | `SizeEditor`, `PaintEditor`, built from `NumberField`, `ColorField`, `EnumField` |
| Panel wiring | `lib/component_editor/widgets/properties_panel.dart` | `if (component case SizedComponent c) SizeEditor(...)` |
Expand All @@ -166,7 +166,7 @@ reads the children for every type), and cases in
`Component.create`/`Component.fromJson`,
`buildFlameComponent`, `ComponentCodeGenerator._flameClass` and
`flameMemberNames` (the public members of its Flame class, which child
getters must not override). The panel, bloc, preview and generated code
getters must not override), and an icon in `ComponentTreePanel.iconFor`. The panel, bloc, preview and generated code
pick up its properties automatically.

**Adding a property:** add the value object and capability interface, a bloc
Expand All @@ -191,6 +191,11 @@ and are positioned relative to their parent. `ComponentPreviewGame.show`
updates the Flame components in place while the tree shape is unchanged
(`haveSameShape`) and rebuilds them otherwise.

Position components draw nothing in Flame, so the preview builds them as
`PreviewPositionComponent`, a `PositionComponent` with a faint dashed
outline, to keep them visible while unselected. The outline is preview
only: the generated code uses a plain `PositionComponent`.

The preview highlights the selected node with a magenta border while
`isSelectionHighlighted` is on (the toolbar toggle; on by default, not
persisted). Only nodes with a size (`SizedComponent`) are highlighted. The
Expand Down Expand Up @@ -242,7 +247,9 @@ member lists in `dart_names.dart` (`flameMemberNames`). It generates:
has children of its own, so the nested children get getters too
(`player.head.eye`); leaf children are plain Flame components.

Every generated file starts with `ComponentCodeGenerator.header`. The
Every generated file starts with `ComponentCodeGenerator.header`.
`dart:ui` is only imported when a node of the tree has a paint (an unused
import is a warning that `ignore_for_file: type=lint` doesn't hide). The
repository never overwrites or deletes a file without it
(`GeneratedFileConflictException`), so hand-written code in the output
folder is safe.
Expand Down
1 change: 1 addition & 0 deletions lib/component_editor/widgets/component_tree_panel.dart
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ class ComponentTreePanel extends StatelessWidget {
static IconData iconFor(ComponentType type) {
return switch (type) {
ComponentType.rectangle => Icons.crop_square,
ComponentType.position => Icons.crop_free,
};
}

Expand Down
3 changes: 3 additions & 0 deletions lib/component_editor/widgets/preview/flame_mapping.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'package:component_repository/component_repository.dart';
import 'package:flame/components.dart' as flame;
import 'package:flame_component_editor/component_editor/widgets/preview/preview_position_component.dart';
import 'package:flutter/painting.dart';

/// Builds the Flame component that previews the root [component], with its
Expand All @@ -24,6 +25,8 @@ flame.PositionComponent _placeRoot(flame.PositionComponent root) {
flame.PositionComponent _build(Component component) {
final target = switch (component) {
RectangleComponent() => flame.RectangleComponent(),
// Outlined, since a plain PositionComponent would be invisible.
PositionComponent() => PreviewPositionComponent(),
};
applyProperties(target, component);
// Children added before mounting are stored right away, in order.
Expand Down
1 change: 1 addition & 0 deletions lib/component_editor/widgets/preview/preview.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export 'component_preview_game.dart';
export 'flame_mapping.dart';
export 'preview_position_component.dart';
export 'selection_highlight.dart';
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import 'dart:math' as math;

import 'package:flame/components.dart' as flame;
import 'package:flutter/painting.dart';

/// {@template preview_position_component}
/// The Flame component that previews a position component.
///
/// A `PositionComponent` draws nothing itself, so the preview outlines it
/// with a faint dashed line, letting users see it while it isn't selected.
/// The outline only exists in the preview: the generated code uses a plain
/// `PositionComponent`.
/// {@endtemplate}
class PreviewPositionComponent extends flame.PositionComponent {
/// {@macro preview_position_component}
new();

/// The color of the outline.
static const color = Color(0x66FFFFFF);

/// The length of each dash, and of the gap after it.
static const dashLength = 4.0;

static final _paint = Paint()
..color = color
..style = PaintingStyle.stroke
..strokeWidth = 1;

@override
void render(Canvas canvas) {
final rect = size.toRect();
final corners = [
rect.topLeft,
rect.topRight,
rect.bottomRight,
rect.bottomLeft,
];
for (var i = 0; i < corners.length; i++) {
_drawDashedLine(canvas, corners[i], corners[(i + 1) % corners.length]);
}
}

static void _drawDashedLine(Canvas canvas, Offset from, Offset to) {
final length = (to - from).distance;
if (length == 0) return;
final direction = (to - from) / length;
for (var start = 0.0; start < length; start += dashLength * 2) {
final end = math.min(start + dashLength, length);
canvas.drawLine(from + direction * start, from + direction * end, _paint);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,14 @@ class ComponentCodeGenerator {
..writeln()
..writeln('// coverage:ignore-file')
..writeln('// ignore_for_file: type=lint')
..writeln()
..writeln("import 'dart:ui';")
..writeln()
..writeln();
// `dart:ui` is only needed for paints; an unused import is a warning.
if (component.nodes.any((node) => node is PaintedComponent)) {
buffer
..writeln("import 'dart:ui';")
..writeln();
}
buffer
..writeln("import 'package:flame/components.dart';")
..writeln();

Expand Down Expand Up @@ -122,6 +127,7 @@ class ComponentCodeGenerator {
static String _flameClass(Component component) {
return switch (component) {
RectangleComponent() => 'RectangleComponent',
PositionComponent() => 'PositionComponent',
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ bool isDartKeyword(String name) => _keywords.contains(name);
Set<String> flameMemberNames(ComponentType type) {
return switch (type) {
ComponentType.rectangle => _rectangleComponentMembers,
ComponentType.position => _positionComponentMembers,
};
}

Expand All @@ -78,9 +79,9 @@ const _keywords = {
'sync', 'typedef', 'when', 'yield',
};

/// Public members of Flame's `RectangleComponent` (Flame 2.0.0-dev.1),
/// Public members of Flame's `PositionComponent` (Flame 2.0.0-dev.1),
/// including the inherited ones.
const _rectangleComponentMembers = {
const _positionComponentMembers = {
'absoluteAngle',
'absoluteAngleWithoutReflection',
'absoluteCenter',
Expand Down Expand Up @@ -113,7 +114,6 @@ const _rectangleComponentMembers = {
'debugPaint',
'debugTextPaint',
'decorator',
'deletePaint',
'descendants',
'distance',
'findGame',
Expand All @@ -125,19 +125,11 @@ const _rectangleComponentMembers = {
'flipHorizontallyAroundCenter',
'flipVertically',
'flipVerticallyAroundCenter',
'getAlpha',
'getEdge',
'getOpacity',
'getPaint',
'getVertex',
'globalVertices',
'handleHotReload',
'handleResize',
'hasChildren',
'hasPaintLayers',
'hashCode',
'height',
'hue',
'isFlippedHorizontally',
'isFlippedVertically',
'isLoaded',
Expand All @@ -146,43 +138,30 @@ const _rectangleComponentMembers = {
'isMounting',
'isRemoved',
'isRemoving',
'isSolid',
'key',
'lastChild',
'loadSettled',
'loaded',
'localToParent',
'lookAt',
'makeOpaque',
'makeTransparent',
'manuallyPositioned',
'mounted',
'nativeAngle',
'noSuchMethod',
'onChanged',
'onChildrenChanged',
'onGameResize',
'onHotReload',
'onLoad',
'onMount',
'onParentResize',
'onRemove',
'opacity',
'opacityProviderOf',
'opacityProviderOfList',
'paint',
'paintLayers',
'paintLayersInternal',
'parent',
'parentToLocal',
'position',
'positionOf',
'positionOfAnchor',
'possibleIntersectionVertices',
'priority',
'propagateToChildren',
'rebalanceChildren',
'refreshVertices',
'remove',
'removeAll',
'removeFromParent',
Expand All @@ -192,23 +171,16 @@ const _rectangleComponentMembers = {
'renderChild',
'renderContext',
'renderDebugMode',
'renderShape',
'renderTree',
'renderTreeWithoutDecorator',
'runtimeType',
'scale',
'scaledSize',
'setAlpha',
'setByRect',
'setColor',
'setLoaded',
'setMounted',
'setOpacity',
'setPaint',
'setRemoved',
'shrinkToBounds',
'size',
'tint',
'toAbsoluteRect',
'toLocal',
'toRect',
Expand All @@ -218,8 +190,44 @@ const _rectangleComponentMembers = {
'transformMatrix',
'update',
'updateTree',
'vertices',
'width',
'x',
'y',
};

/// Public members of Flame's `RectangleComponent` (Flame 2.0.0-dev.1),
/// including the inherited ones: the `PositionComponent` ones, plus those
/// of `HasPaint`, `ShapeComponent` and `PolygonComponent`.
const Set<String> _rectangleComponentMembers = {
..._positionComponentMembers,
'deletePaint',
'getAlpha',
'getEdge',
'getOpacity',
'getPaint',
'getVertex',
'globalVertices',
'hasPaintLayers',
'hue',
'isSolid',
'makeOpaque',
'makeTransparent',
'manuallyPositioned',
'onChanged',
'opacity',
'opacityProviderOf',
'opacityProviderOfList',
'paint',
'paintLayers',
'paintLayersInternal',
'possibleIntersectionVertices',
'refreshVertices',
'renderShape',
'setAlpha',
'setColor',
'setOpacity',
'setPaint',
'shrinkToBounds',
'tint',
'vertices',
};
3 changes: 3 additions & 0 deletions packages/component_repository/lib/src/models/component.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:component_repository/component_repository.dart';
import 'package:equatable/equatable.dart';

part 'position_component.dart';
part 'rectangle_component.dart';

/// {@template component}
Expand All @@ -21,6 +22,7 @@ sealed class Component extends Equatable {
factory create(String name, ComponentType type) {
return switch (type) {
ComponentType.rectangle => RectangleComponent(name: name),
ComponentType.position => PositionComponent(name: name),
};
}

Expand All @@ -33,6 +35,7 @@ sealed class Component extends Equatable {
final type = ComponentType.fromKey(json['type']) ?? ComponentType.rectangle;
final component = switch (type) {
ComponentType.rectangle => RectangleComponent._fromJson(json),
ComponentType.position => PositionComponent._fromJson(json),
};
return component.copyWithChildren(_childrenFromJson(json['children']));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@
/// Each value maps to a Flame component class.
enum ComponentType {
/// A rectangle, backed by Flame's `RectangleComponent`.
rectangle(key: 'rectangle', label: 'Rectangle');
rectangle(key: 'rectangle', label: 'Rectangle'),

/// A plain component that draws nothing itself, backed by Flame's
/// `PositionComponent`.
position(key: 'position', label: 'Position');

new({required this.key, required this.label});

Expand Down
Loading
Loading