Skip to content

Commit 8105f7b

Browse files
SaadArdatiBirjuVachhani
authored andcommitted
Field Property Access #3
1 parent 9e9c589 commit 8105f7b

File tree

5 files changed

+295
-79
lines changed

5 files changed

+295
-79
lines changed

lib/src/api/field_access.dart

Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
import 'dart:core';
2+
3+
import '../../codelessly_api.dart';
4+
5+
/// Signature for callbacks that report that an underlying value has changed.
6+
///
7+
/// See also:
8+
///
9+
/// * [ValueSetter], for callbacks that report that a value has been set.
10+
typedef ValueChanged<T> = void Function(T value);
11+
12+
/// A utility class to access properties of a class.
13+
typedef GetterCallback<T extends Object?> = T Function();
14+
15+
/// A callback that returns the default value of a property.
16+
typedef DefaultValueCallback<T> = T? Function();
17+
18+
/// A callback that returns the options of a property.
19+
typedef FieldOptionsGetter<T> = Iterable<T> Function();
20+
21+
mixin FieldsHolder {
22+
Map<String, FieldAccess> get fields;
23+
24+
// String get dynamicKeyType;
25+
}
26+
27+
/// A class that provides extrinsic meta access to a field.
28+
// final class ExactFieldAccess<T extends Object?> extends FieldAccess<T, T> {
29+
// /// Constructs an exact field access.
30+
// const ExactFieldAccess(
31+
// super.setter,
32+
// super.getter, {
33+
// super.defaultValue,
34+
// });
35+
//
36+
// /// Constructs an exact field access with options.
37+
// const ExactFieldAccess.options(
38+
// super.setter,
39+
// super.getter, {
40+
// required super.options,
41+
// super.defaultValue,
42+
// }) : super.options();
43+
// }
44+
45+
/// A class that provides extrinsic meta access to a field.
46+
sealed class FieldAccess<GetValue extends Object?> {
47+
final GetterCallback<String> label;
48+
49+
final GetterCallback<String> description;
50+
final ValueChanged<GetValue> setter;
51+
52+
final GetterCallback<GetValue> _getter;
53+
54+
GetterCallback<GetValue> get getValue => _getter;
55+
56+
void setValue(Object? value);
57+
58+
final DefaultValueCallback<GetValue>? _defaultValue;
59+
60+
DefaultValueCallback<GetValue>? get getDefaultValue => _defaultValue;
61+
62+
dynamic get serialize;
63+
64+
String get dynamicKeyType;
65+
66+
Map<String, dynamic>? get supplementarySchema => null;
67+
68+
dynamic get schema => {
69+
'type': dynamicKeyType,
70+
'value': serialize,
71+
if (getDefaultValue?.call() case Object defaultValue)
72+
'default': defaultValue,
73+
if (supplementarySchema case Map<String, dynamic> supplementary)
74+
...supplementary
75+
};
76+
77+
/// Constructs a field access.
78+
const FieldAccess(
79+
this.label,
80+
this.description,
81+
this.setter,
82+
this._getter, {
83+
DefaultValueCallback<GetValue>? defaultValue,
84+
}) : _defaultValue = defaultValue;
85+
}
86+
87+
final class StringFieldAccess extends FieldAccess<String> {
88+
const StringFieldAccess(
89+
super.label,
90+
super.description,
91+
super.setter,
92+
super.getter, {
93+
super.defaultValue,
94+
});
95+
96+
@override
97+
String get dynamicKeyType => 'string';
98+
99+
@override
100+
dynamic get serialize => getValue();
101+
102+
@override
103+
void setValue(Object? value) => setter(value.typedValue<String>()!);
104+
}
105+
106+
final class NumFieldAccess<Number extends num> extends FieldAccess<Number> {
107+
const NumFieldAccess(
108+
super.label,
109+
super.description,
110+
super.setter,
111+
super.getter, {
112+
this.min,
113+
this.max,
114+
super.defaultValue,
115+
});
116+
117+
@override
118+
String get dynamicKeyType => 'num';
119+
120+
@override
121+
dynamic get serialize => getValue();
122+
123+
final GetterCallback<Number>? min;
124+
125+
final GetterCallback<Number>? max;
126+
127+
@override
128+
Map<String, dynamic> get supplementarySchema => {
129+
if (getValue() is double) 'fractionDigits': 2,
130+
if (min case GetterCallback<Number> min) 'min': min(),
131+
if (max case GetterCallback<Number> max) 'max': max(),
132+
};
133+
134+
@override
135+
void setValue(Object? value) => setter(value.typedValue<Number>()!);
136+
}
137+
138+
final class BoolFieldAccess extends FieldAccess<bool> {
139+
const BoolFieldAccess(
140+
super.label,
141+
super.description,
142+
super.setter,
143+
super.getter, {
144+
super.defaultValue,
145+
});
146+
147+
@override
148+
dynamic get serialize => getValue();
149+
150+
@override
151+
String get dynamicKeyType => 'bool';
152+
153+
@override
154+
void setValue(Object? value) => setter(value.typedValue<bool>()!);
155+
}
156+
157+
final class EnumFieldAccess<T extends Enum> extends FieldAccess<T> {
158+
const EnumFieldAccess(
159+
super.label,
160+
super.description,
161+
super.setter,
162+
super.getter, {
163+
required DefaultValueCallback<T>? defaultValue,
164+
required FieldOptionsGetter<T> options,
165+
}) : getOptions = options,
166+
super(defaultValue: defaultValue);
167+
168+
final FieldOptionsGetter<T> getOptions;
169+
170+
@override
171+
String get dynamicKeyType => 'options';
172+
173+
@override
174+
dynamic get serialize => getValue().name;
175+
176+
@override
177+
Map<String, dynamic> get supplementarySchema => {
178+
'options': {
179+
for (final T option in getOptions())
180+
option.name: option.name.toUpperCase(),
181+
}
182+
};
183+
184+
@override
185+
void setValue(Object? value) {
186+
final Map<String, T> allValues = getOptions().asNameMap();
187+
setter(allValues[value] ?? getDefaultValue!()!);
188+
}
189+
}
190+
191+
final class IterableFieldAccess<T extends List<Object?>>
192+
extends FieldAccess<T> {
193+
const IterableFieldAccess(
194+
super.label,
195+
super.description,
196+
super.setter,
197+
super.getter, {
198+
super.defaultValue,
199+
});
200+
201+
@override
202+
String get dynamicKeyType => 'items';
203+
204+
@override
205+
dynamic get serialize => [
206+
for (final Object? item in getValue())
207+
item is FieldAccess ? item.serialize : item,
208+
];
209+
210+
@override
211+
Map<String, dynamic> get supplementarySchema => {
212+
'items': [
213+
for (final Object? item in getValue())
214+
item is FieldAccess ? item.schema : item,
215+
],
216+
};
217+
218+
@override
219+
void setValue(Object? value) => setter(value as T);
220+
}
221+
222+
final class ColorFieldAccess extends FieldAccess<ColorRGB?> {
223+
ColorFieldAccess(
224+
super.label,
225+
super.description,
226+
super.setter,
227+
super.getter, {
228+
super.defaultValue,
229+
});
230+
231+
@override
232+
String get dynamicKeyType => 'color';
233+
234+
@override
235+
dynamic get serialize => getValue()?.toJson();
236+
237+
@override
238+
Map<String, dynamic> get supplementarySchema => {
239+
'pattern': r'^#(?:[0-9a-fA-F]{3}){1,2}$',
240+
};
241+
242+
@override
243+
void setValue(Object? value) => setter(value.typedValue<ColorRGB>());
244+
}
245+
246+
final class RadiusFieldAccess extends FieldAccess<CornerRadius> {
247+
RadiusFieldAccess(
248+
super.label,
249+
super.description,
250+
super.setter,
251+
super.getter, {
252+
super.defaultValue,
253+
});
254+
255+
@override
256+
String get dynamicKeyType => 'radius';
257+
258+
@override
259+
dynamic get serialize => getValue().toJson();
260+
261+
@override
262+
void setValue(Object? value) => setter(value.typedValue<CornerRadius>()!);
263+
}

lib/src/api/mixins.dart

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import 'package:equatable/equatable.dart';
33
import 'package:meta/meta.dart';
44

55
import '../../codelessly_api.dart';
6-
import 'property_access.dart';
6+
import 'field_access.dart';
77

88
/// Excludes [BoxConstraintsModel] from the json if it's technically doing
99
/// nothing.
@@ -601,13 +601,17 @@ mixin GeometryMixin on BaseNode {
601601
required StrokeSide? strokeSide,
602602
}) {
603603
this.fills = fills;
604-
fields['fills'] = ExactFieldAccess<List<PaintModel>>(
604+
fields['fills'] = IterableFieldAccess<List<PaintModel>>(
605+
() => 'Fills',
606+
() => 'A list of fills applied to the node.',
605607
(value) => this.fills = value,
606608
() => this.fills,
607609
);
608610

609611
this.strokes = strokes;
610-
fields['strokes'] = ExactFieldAccess<List<PaintModel>>(
612+
fields['strokes'] = IterableFieldAccess<List<PaintModel>>(
613+
() => 'Strokes',
614+
() => 'A list of strokes applied to the node.',
611615
(value) => this.strokes = value,
612616
() => this.strokes,
613617
);
@@ -735,12 +739,16 @@ mixin CornerMixin on BaseNode {
735739
required double cornerSmoothing,
736740
}) {
737741
this.cornerRadius = cornerRadius;
738-
fields['cornerRadius'] = ExactFieldAccess<CornerRadius>(
742+
fields['cornerRadius'] = RadiusFieldAccess(
743+
() => 'Corner Radius',
744+
() => 'Radius of the corners of the node.',
739745
(value) => this.cornerRadius = value,
740746
() => this.cornerRadius,
741747
);
742748
this.cornerSmoothing = cornerSmoothing;
743-
fields['cornerSmoothing'] = ExactFieldAccess<double>(
749+
fields['cornerSmoothing'] = NumFieldAccess<double>(
750+
() => 'Corner Smoothing',
751+
() => 'Level of pixel smoothing applied to the corners.',
744752
(value) => this.cornerSmoothing = value,
745753
() => this.cornerSmoothing,
746754
);

lib/src/api/models/paint.dart

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import 'package:codelessly_json_annotation/codelessly_json_annotation.dart';
44
import 'package:equatable/equatable.dart';
55

66
import '../../../codelessly_api.dart';
7-
import '../property_access.dart';
7+
import '../field_access.dart';
88

99
part 'paint.g.dart';
1010

@@ -37,7 +37,7 @@ Object? _readId(Map json, String key) => json[key] ?? generateId();
3737
/// A solid color, gradient, or image texture that can be applied as fill or
3838
/// stroke.
3939
@JsonSerializable()
40-
class PaintModel with EquatableMixin, SerializableMixin, BagOfFieldsMixin {
40+
class PaintModel with EquatableMixin, SerializableMixin, FieldsHolder {
4141
/// identifier of this paint.
4242
@JsonKey(readValue: _readId)
4343
final String id;
@@ -174,22 +174,30 @@ class PaintModel with EquatableMixin, SerializableMixin, BagOfFieldsMixin {
174174
bool get hasImageSourceSize => sourceWidth != null && sourceHeight != null;
175175

176176
late final Map<String, FieldAccess> _fields = {
177-
'color': FieldAccess<ColorRGB?, Object?>(
178-
(value) => color = value.typedValue<ColorRGB>(),
177+
'color': ColorFieldAccess(
178+
() => 'Color',
179+
() => 'The color of the paint.',
180+
(value) => color = value,
179181
() => color,
180182
),
181-
'blendMode': ExactFieldAccess<BlendModeC>.options(
183+
'blendMode': EnumFieldAccess<BlendModeC>(
184+
() => 'Blend Mode',
185+
() => 'How this node blends with nodes behind it in the scene.',
182186
(value) => blendMode = value,
183187
() => blendMode,
184188
options: () => BlendModeC.values,
185189
defaultValue: () => BlendModeC.srcOver,
186190
),
187-
'opacity': FieldAccess<double, Object?>(
188-
(value) => opacity = value.typedValue<double>()!,
191+
'opacity': NumFieldAccess<double>(
192+
() => 'Opacity',
193+
() => 'The transparency of this layer.',
194+
(value) => opacity = value,
189195
() => opacity,
190196
),
191-
'visible': FieldAccess<bool, Object?>(
192-
(value) => visible = value.typedValue<bool>()!,
197+
'visible': BoolFieldAccess(
198+
() => 'Visible',
199+
() => 'Whether this layer is visible or not.',
200+
(value) => visible = value,
193201
() => visible,
194202
),
195203
};

lib/src/api/nodes/base_node.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ import 'package:collection/collection.dart';
77
import 'package:equatable/equatable.dart';
88

99
import '../constants.dart';
10+
import '../field_access.dart';
1011
import '../math_helper.dart';
1112
import '../mixins.dart';
1213
import '../models/models.dart';
13-
import '../property_access.dart';
1414
import '../utils.dart';
1515

1616
part '../node_processor.dart';
@@ -33,7 +33,7 @@ abstract class BaseNode
3333
EquatableMixin,
3434
VariablePropertiesMixin,
3535
ComponentMixin,
36-
BagOfFieldsMixin {
36+
FieldsHolder {
3737
/// [type] is a string representation for the type of this node. It is a
3838
/// unique key that this node class uses for static registration. It is
3939
/// usually the lowerCamelCase of the class' name. Overriding [type] is

0 commit comments

Comments
 (0)