Skip to content

Commit 0f49f09

Browse files
committed
Add support for flutter shaders
1 parent 66a64b2 commit 0f49f09

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+2492
-28
lines changed

packages/command/bin/flutter_gen_command.dart

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import 'package:flutter_gen_core/flutter_generator.dart';
55
import 'package:flutter_gen_core/utils/cast.dart';
66
import 'package:flutter_gen_core/version.gen.dart';
77

8-
98
void main(List<String> args) {
109
final parser = ArgParser();
1110
parser.addOption(

packages/command/test/flutter_gen_command_test.dart

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import 'package:flutter_gen_core/version.gen.dart';
44
import 'package:test/test.dart';
55
import 'package:test_process/test_process.dart';
66

7-
87
final separator = Platform.pathSeparator;
98

109
void main() {

packages/core/lib/flutter_generator.dart

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import 'package:dart_style/dart_style.dart';
44
import 'package:flutter_gen_core/generators/assets_generator.dart';
55
import 'package:flutter_gen_core/generators/colors_generator.dart';
66
import 'package:flutter_gen_core/generators/fonts_generator.dart';
7+
import 'package:flutter_gen_core/generators/shaders_generator.dart';
78
import 'package:flutter_gen_core/settings/config.dart';
89
import 'package:flutter_gen_core/utils/file.dart';
910
import 'package:path/path.dart';
@@ -13,13 +14,15 @@ class FlutterGenerator {
1314
this.pubspecFile, {
1415
this.buildFile,
1516
this.assetsName = 'assets.gen.dart',
17+
this.shadersName = 'shaders.gen.dart',
1618
this.colorsName = 'colors.gen.dart',
1719
this.fontsName = 'fonts.gen.dart',
1820
});
1921

2022
final File pubspecFile;
2123
final File? buildFile;
2224
final String assetsName;
25+
final String shadersName;
2326
final String colorsName;
2427
final String fontsName;
2528

@@ -69,6 +72,17 @@ class FlutterGenerator {
6972
stdout.writeln('[FlutterGen] Generated: $assetsPath');
7073
}
7174

75+
if (flutterGen.shaders.enabled && flutter.shaders.isNotEmpty) {
76+
final generated = await generateShaders(
77+
ShadersGenConfig.fromConfig(pubspecFile, config),
78+
formatter,
79+
);
80+
final shadersPath =
81+
normalize(join(pubspecFile.parent.path, output, shadersName));
82+
writer(generated, shadersPath);
83+
stdout.writeln('[FlutterGen] Generated: $shadersPath');
84+
}
85+
7286
if (flutterGen.fonts.enabled && flutter.fonts.isNotEmpty) {
7387
final generated = generateFonts(
7488
FontsGenConfig.fromConfig(config),

packages/core/lib/generators/shaders_generator.dart

Lines changed: 602 additions & 0 deletions
Large diffs are not rendered by default.

packages/core/lib/settings/config_default.dart

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,24 @@ flutter_gen:
3434
class_name: Assets
3535
exclude: []
3636
37+
shaders:
38+
# Optional
39+
enabled: true
40+
# Optional
41+
outputs:
42+
# Optional
43+
# Set to true if you want this package to be a package dependency
44+
# See: https://flutter.dev/docs/development/ui/assets-and-images#from-packages
45+
package_parameter_enabled: false
46+
# Optional
47+
# Available values:
48+
# - camel-case
49+
# - snake-case
50+
# - dot-delimiter
51+
style: dot-delimiter
52+
class_name: Shaders
53+
exclude: []
54+
3755
fonts:
3856
# Optional
3957
enabled: true
@@ -55,4 +73,6 @@ flutter:
5573
assets: []
5674
# See: https://flutter.dev/docs/cookbook/design/fonts#2-declare-the-font-in-the-pubspec
5775
fonts: []
76+
# See: https://docs.flutter.dev/ui/design/graphics/fragment-shaders
77+
shaders: []
5878
''';
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
class FlavoredShader {
2+
const FlavoredShader({
3+
required this.path,
4+
this.flavors = const {},
5+
});
6+
7+
final String path;
8+
final Set<String> flavors;
9+
10+
FlavoredShader copyWith({String? path, Set<String>? flavors}) {
11+
return FlavoredShader(
12+
path: path ?? this.path,
13+
flavors: flavors ?? this.flavors,
14+
);
15+
}
16+
17+
@override
18+
String toString() => 'FlavoredShader(path: $path, flavors: $flavors)';
19+
}

packages/core/lib/settings/pubspec.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,15 @@ class Flutter {
2929
Flutter({
3030
required this.assets,
3131
required this.fonts,
32+
required this.shaders,
3233
});
3334

3435
@JsonKey(name: 'assets', required: true)
3536
final List<Object> assets;
3637

38+
@JsonKey(name: 'shaders', required: true)
39+
final List<Object> shaders;
40+
3741
@JsonKey(name: 'fonts', required: true)
3842
final List<FlutterFonts> fonts;
3943

@@ -57,6 +61,7 @@ class FlutterGen {
5761
required this.lineLength,
5862
required this.parseMetadata,
5963
required this.assets,
64+
required this.shaders,
6065
required this.fonts,
6166
required this.integrations,
6267
required this.colors,
@@ -74,6 +79,9 @@ class FlutterGen {
7479
@JsonKey(name: 'assets', required: true)
7580
final FlutterGenAssets assets;
7681

82+
@JsonKey(name: 'shaders', required: true)
83+
final FlutterGenAssets shaders;
84+
7785
@JsonKey(name: 'fonts', required: true)
7886
final FlutterGenFonts fonts;
7987

packages/core/lib/settings/pubspec.g.dart

Lines changed: 6 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
import 'package:dartx/dartx.dart';
2+
import 'package:flutter_gen_core/utils/identifer.dart';
3+
import 'package:flutter_gen_core/utils/string.dart';
4+
import 'package:mime/mime.dart' show lookupMimeType;
5+
import 'package:path/path.dart' as p;
6+
7+
/// https://github.com/dart-lang/mime/blob/master/lib/src/default_extension_map.dart
8+
class ShaderType {
9+
ShaderType({
10+
required this.rootPath,
11+
required this.path,
12+
required this.flavors,
13+
});
14+
15+
final String rootPath;
16+
final String path;
17+
final Set<String> flavors;
18+
19+
final List<ShaderType> _children = List.empty(growable: true);
20+
21+
bool get isDefaultShadersDirectory => path == 'shaders' || path == 'shader';
22+
23+
String? get mime => lookupMimeType(path);
24+
25+
bool get isIgnoreFile {
26+
switch (baseName) {
27+
case '.DS_Store':
28+
return true;
29+
}
30+
31+
switch (extension) {
32+
case '.DS_Store':
33+
case '.swp':
34+
return true;
35+
}
36+
37+
return false;
38+
}
39+
40+
bool get isUnKnownMime => mime == null;
41+
42+
/// Returns a name for this shader.
43+
String get name => p.withoutExtension(path);
44+
45+
String get baseName => p.basenameWithoutExtension(path);
46+
47+
String get extension => p.extension(path);
48+
49+
/// Returns the full absolute path for reading the shader file.
50+
String get fullPath => p.join(rootPath, path);
51+
52+
// Replace to Posix style for Windows separator.
53+
String get posixStylePath => path.replaceAll(r'\', r'/');
54+
55+
List<ShaderType> get children => _children.sortedBy((e) => e.path);
56+
57+
void addChild(ShaderType type) {
58+
_children.add(type);
59+
}
60+
61+
@override
62+
String toString() => 'ShaderType('
63+
'rootPath: $rootPath, '
64+
'path: $path, '
65+
'flavors: $flavors'
66+
')';
67+
}
68+
69+
/// Represents a ShaderType with modifiers on it to mutate the [name] to ensure
70+
/// it is unique in a larger list and a valid dart identifer.
71+
///
72+
/// See [ShaderTypeIterable.mapToUniqueShaderType] for the algorithm.
73+
class UniqueShaderType extends ShaderType {
74+
UniqueShaderType({
75+
required ShaderType shaderType,
76+
this.style = camelCase,
77+
this.basenameOnly = false,
78+
this.needExtension = false,
79+
this.suffix = '',
80+
}) : super(
81+
rootPath: shaderType.rootPath,
82+
path: shaderType.path,
83+
flavors: shaderType.flavors,
84+
);
85+
86+
/// Convert the shader name to a correctly styled name, e.g camelCase or
87+
/// snakeCase.
88+
final String Function(String) style;
89+
90+
/// Include just the basename of the shader in the [name],
91+
/// e.g. 'images/image.png' -> 'image'.
92+
final bool basenameOnly;
93+
94+
/// Include the extension in the [name], e.g. 'image.png' -> 'imagePng'.
95+
bool needExtension;
96+
97+
/// Optional suffix to append to the [name] to make it unique. Typically just
98+
/// one or more '_' characters.
99+
String suffix;
100+
101+
/// Returns a identifier name, which is ideally unique and valid.
102+
@override
103+
String get name {
104+
// Omit root directory from the name if it is either shader or shaders.
105+
// TODO(bramp): Maybe move this into the _flatStyleDefinition
106+
String result = path.replaceFirst(RegExp(r'^shader(s)?[/\\]'), '');
107+
if (basenameOnly) {
108+
result = p.basename(result);
109+
}
110+
if (!needExtension) {
111+
result = p.withoutExtension(result);
112+
}
113+
return style(convertToIdentifier(result)) + suffix;
114+
}
115+
116+
@override
117+
String toString() {
118+
return 'UniqueShaderType{'
119+
'rootPath: $rootPath, '
120+
'path: $path, '
121+
'style: $style, '
122+
'needExtension: $needExtension, '
123+
'suffix: $suffix}';
124+
}
125+
}
126+
127+
extension ShaderTypeIterable on Iterable<ShaderType> {
128+
/// Takes a Iterable<ShaderType> and mutates the ShaderType's to ensure each
129+
/// ShaderType has a unique name.
130+
///
131+
/// The strategy is as follows:
132+
///
133+
/// 1) Convert the shader file name, to a valid dart identifier, that is
134+
/// - Replace non ASCII chars with ASCII.
135+
/// - Ensure the name starts with a letter (not number or _).
136+
/// - Style the name per the camelCase or snakeCase rules.
137+
/// 2) Use the shader name without extension. If unique and not a dart reserved
138+
/// word use that.
139+
/// 3) Use the shader name with extension. If unique and not a dart reserved
140+
/// word use that.
141+
/// 4) If there are any collisions, append a underscore suffix to each item
142+
/// until they are unique.
143+
///
144+
/// Because the name change can cause it to clash with an existing name, the
145+
/// code is run iteratively until no collision are found. This can be a little
146+
/// more expensive, but it simplier.
147+
///
148+
Iterable<UniqueShaderType> mapToUniqueShaderType(
149+
String Function(String) style, {
150+
bool justBasename = false,
151+
}) {
152+
List<UniqueShaderType> shaders = map((e) => UniqueShaderType(
153+
shaderType: e,
154+
style: style,
155+
needExtension: false,
156+
suffix: '',
157+
basenameOnly: justBasename,
158+
)).toList();
159+
160+
while (true) {
161+
// Check if we have any name collisions.
162+
final dups = shaders.groupBy((e) => e.name).values;
163+
164+
// No more duplicates, so we can bail.
165+
if (dups.every((list) => list.length == 1)) break;
166+
167+
// Otherwise start to process the list and mutate the shaders as needed.
168+
shaders = dups
169+
.map((list) {
170+
assert(list.isNotEmpty,
171+
'The groupBy list of shaders should not be empty.');
172+
173+
// Check the first element in the list. Since we grouped by each
174+
// list element should have the same name.
175+
final name = list[0].name;
176+
final isValidIdentifer = isValidVariableIdentifier(name);
177+
178+
// TODO(bramp): In future we should also check this name doesn't collide
179+
// with the integration's class name (e.g ShaderGenImage).
180+
181+
// No colissions for this name, carry on.
182+
if (list.length == 1 && isValidIdentifer) {
183+
return list;
184+
}
185+
186+
// We haven't added filename extensions yet, let's try that first
187+
if (!list.every((e) => e.needExtension)) {
188+
for (final e in list) {
189+
e.needExtension = true;
190+
}
191+
192+
return list;
193+
}
194+
195+
// Ok, we must resolve the conflicts by adding suffixes.
196+
String suffix = '';
197+
list.forEachIndexed((shader, index) {
198+
// Shouldn't need to mutate the first item (unless it's an invalid
199+
// identifer).
200+
if (index == 0 && isValidIdentifer) return;
201+
202+
// Append a extra suffixes to each item so they hopefully become unique
203+
suffix = '${suffix}_';
204+
shader.suffix += suffix;
205+
});
206+
207+
return list;
208+
})
209+
.flatten()
210+
.toList();
211+
}
212+
213+
assert(shaders.map((e) => e.name).distinct().length == shaders.length,
214+
'There are duplicate names in the shader list.');
215+
216+
return shaders;
217+
}
218+
}

0 commit comments

Comments
 (0)