Skip to content

Commit 61989c4

Browse files
authored
Support MaterialAccentColor (#40)
* Use color library * Buckner logic accent swatch * Accent color generator * Update README * Refactor
1 parent 8b4f879 commit 61989c4

File tree

10 files changed

+167
-63
lines changed

10 files changed

+167
-63
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,3 +110,5 @@ node_modules/
110110
coverage/
111111
test/.test_coverage.dart
112112

113+
## Test resources
114+
test_resources/lib

README.md

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -499,12 +499,19 @@ flutter_gen:
499499
- assets/color/colors2.xml
500500
```
501501

502-
[FlutterGen] generates [MaterialColor](https://api.flutter.dev/flutter/material/MaterialColor-class.html) class
503-
if the element has the attribute `type="material"`, otherwise a normal [Color](https://api.flutter.dev/flutter/material/Colors-class.html) class will be generated.
502+
[FlutterGen] can generate a [Color](https://api.flutter.dev/flutter/material/Colors-class.html) class based on the `name` attribute and the color hex value.
503+
If the element has the attribute `type`, then a specially color will be generated.
504+
505+
Currently supported special color types:
506+
- [MaterialColor](https://api.flutter.dev/flutter/material/MaterialColor-class.html)
507+
- [MaterialAccentColor](https://api.flutter.dev/flutter/material/MaterialAccentColor-class.html)
508+
509+
> Noticed that there is no official material color generation algorithm. The implementation is based on the [mcg](https://github.com/mbitson/mcg) project.
504510

505511
```xml
506512
<color name="milk_tea">#F5CB84</color>
507513
<color name="cinnamon" type="material">#955E1C</color>
514+
<color name="yellow_ocher" type="material material-accent">#DF9527</color>
508515
```
509516

510517
These configurations will generate **`colors.gen.dart`** under the **`lib/gen/`** directory by default.
@@ -572,6 +579,15 @@ class ColorName {
572579
900: Color(0xFFCA670E),
573580
},
574581
);
582+
static const MaterialAccentColor yellowOcherAccent = MaterialAccentColor(
583+
0xFFFFBCA3,
584+
<int, Color>{
585+
100: Color(0xFFFFE8E0),
586+
200: Color(0xFFFFBCA3),
587+
400: Color(0xFFFFA989),
588+
700: Color(0xFFFF9E7A),
589+
},
590+
);
575591
}
576592
577593
```
@@ -593,6 +609,10 @@ If you wish to contribute a change to any of the existing plugins in this repo,
593609
please review our [contribution guide](https://github.com/FlutterGen/flutter_gen/blob/master/CONTRIBUTING.md)
594610
and open a [pull request](https://github.com/FlutterGen/flutter_gen/pulls).
595611

612+
## Credits
613+
614+
The material color generation implementation is based on [mcg](https://github.com/mbitson/mcg) and [TinyColor](https://github.com/bgrins/TinyColor).
615+
596616
### Milestone
597617

598618
- [ ] Documentation (English proofreading)

example/assets/color/colors.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,5 @@
55
<color name="gray_70">#EEEEEE</color>
66
<color name="gray_410">#979797</color>
77
<color name="crimson_red" type="material">#CF2A2A</color>
8-
<color name="yellow_ocher" type="material">#DF9527</color>
8+
<color name="yellow_ocher" type="material material-accent">#DF9527</color>
99
</resources>

example/lib/gen/colors.gen.dart

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,13 @@ class ColorName {
4747
900: Color(0xFFCA670E),
4848
},
4949
);
50+
static const MaterialAccentColor yellowOcherAccent = MaterialAccentColor(
51+
0xFFFFBCA3,
52+
<int, Color>{
53+
100: Color(0xFFFFE8E0),
54+
200: Color(0xFFFFBCA3),
55+
400: Color(0xFFFFA989),
56+
700: Color(0xFFFF9E7A),
57+
},
58+
);
5059
}

lib/src/generators/colors_generator.dart

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -48,46 +48,67 @@ String generateColors(
4848
.distinctBy((color) => color.name)
4949
.sortedBy((color) => color.name)
5050
.map(_colorStatement)
51-
.forEach(buffer.writeln);
51+
.forEach(buffer.write);
5252

5353
buffer.writeln('}');
5454
return formatter.format(buffer.toString());
5555
}
5656

5757
String _colorStatement(_Color color) {
58-
final hex = colorFromHex(color.hex);
59-
if (color.type == 'material') {
60-
final swatch = swatchFromPrimaryHex(hex);
61-
return '''
58+
final buffer = StringBuffer();
59+
if (color.isMaterial) {
60+
final swatch = swatchFromPrimaryHex(color.hex);
61+
final statement = '''
6262
static const MaterialColor ${color.name.camelCase()} = MaterialColor(
6363
${swatch[500]},
6464
<int, Color>{
6565
${swatch.entries.map((e) => '${e.key}: Color(${e.value}),').join('\n')}
6666
},
6767
);''';
68-
} else if (color.type == null) {
69-
return ' static const Color ${color.name.camelCase()} = Color($hex);';
68+
buffer.writeln(statement);
69+
}
70+
if (color.isMaterialAccent) {
71+
final accentSwatch = accentSwatchFromPrimaryHex(color.hex);
72+
final statement = '''
73+
static const MaterialAccentColor ${color.name.camelCase()}Accent = MaterialAccentColor(
74+
${accentSwatch[200]},
75+
<int, Color>{
76+
${accentSwatch.entries.map((e) => '${e.key}: Color(${e.value}),').join('\n')}
77+
},
78+
);''';
79+
buffer.writeln(statement);
7080
}
71-
throw 'Not supported color type ${color.type}.';
81+
if (color.isNormal) {
82+
final statement =
83+
'''static const Color ${color.name.camelCase()} = Color(${colorFromHex(color.hex)});''';
84+
buffer.writeln(statement);
85+
}
86+
return buffer.toString();
7287
}
7388

7489
class _Color {
7590
const _Color(
7691
this.name,
77-
this.type,
7892
this.hex,
93+
this._types,
7994
);
8095

8196
_Color.fromXmlElement(XmlElement element)
8297
: this(
8398
element.getAttribute('name'),
84-
element.getAttribute('type'),
8599
element.text,
100+
element.getAttribute('type')?.split(' ') ?? List.empty(),
86101
);
87102

88103
final String name;
89104

90105
final String hex;
91106

92-
final String type;
107+
final List<String> _types;
108+
109+
bool get isNormal => _types.isEmpty;
110+
111+
bool get isMaterial => _types.contains('material');
112+
113+
bool get isMaterialAccent => _types.contains('material-accent');
93114
}

lib/src/utils/color.dart

Lines changed: 83 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import 'package:color/color.dart';
2+
13
String colorFromHex(String hexColor) {
24
hexColor = hexColor.toUpperCase().replaceFirst('#', '');
35
if (hexColor.length == 6) {
@@ -7,68 +9,101 @@ String colorFromHex(String hexColor) {
79
}
810

911
/// [Material Design Color Generator](https://github.com/mbitson/mcg)
10-
/// Constantin logic: https://github.com/mbitson/mcg/blob/858cffea0d79ac143d590d110fbe20a1ea54d59d/scripts/controllers/ColorGeneratorCtrl.js#L238
12+
/// Constantin/Buckner logic: https://github.com/mbitson/mcg/blob/858cffea0d79ac143d590d110fbe20a1ea54d59d/scripts/controllers/ColorGeneratorCtrl.js#L238
1113
Map<int, String> swatchFromPrimaryHex(String primaryHex) {
12-
final primary = _Rgb.fromHex(int.parse(primaryHex));
13-
const baseLight = _Rgb.fromHex(0xffffff);
14+
final primary = Color.hex(primaryHex);
15+
final baseLight = Color.hex("ffffff");
1416
final baseDark = primary * primary;
1517
return {
16-
50: _Rgb.mix(baseLight, primary, 12).toHexString(),
17-
100: _Rgb.mix(baseLight, primary, 30).toHexString(),
18-
200: _Rgb.mix(baseLight, primary, 50).toHexString(),
19-
300: _Rgb.mix(baseLight, primary, 70).toHexString(),
20-
400: _Rgb.mix(baseLight, primary, 85).toHexString(),
21-
500: _Rgb.mix(baseLight, primary, 100).toHexString(),
22-
600: _Rgb.mix(baseDark, primary, 87).toHexString(),
23-
700: _Rgb.mix(baseDark, primary, 70).toHexString(),
24-
800: _Rgb.mix(baseDark, primary, 54).toHexString(),
25-
900: _Rgb.mix(baseDark, primary, 25).toHexString(),
18+
50: _mix(baseLight, primary, 12).toHexString(),
19+
100: _mix(baseLight, primary, 30).toHexString(),
20+
200: _mix(baseLight, primary, 50).toHexString(),
21+
300: _mix(baseLight, primary, 70).toHexString(),
22+
400: _mix(baseLight, primary, 85).toHexString(),
23+
500: _mix(baseLight, primary, 100).toHexString(),
24+
600: _mix(baseDark, primary, 87).toHexString(),
25+
700: _mix(baseDark, primary, 70).toHexString(),
26+
800: _mix(baseDark, primary, 54).toHexString(),
27+
900: _mix(baseDark, primary, 25).toHexString(),
2628
};
2729
}
2830

29-
class _Rgb {
30-
const _Rgb(int r, int g, int b)
31-
: value = ((0xff << 24) |
32-
((r & 0xff) << 16) |
33-
((g & 0xff) << 8) |
34-
((b & 0xff) << 0)) &
35-
0xFFFFFFFF;
36-
37-
const _Rgb.fromHex(int value) : value = (value | 0xFF000000) & 0xFFFFFFFF;
38-
39-
final int value;
40-
41-
int get r => (0x00ff0000 & value) >> 16;
42-
43-
int get g => (0x0000ff00 & value) >> 8;
31+
/// Buckner logic: https://github.com/mbitson/mcg/blob/858cffea0d79ac143d590d110fbe20a1ea54d59d/scripts/controllers/ColorGeneratorCtrl.js#L275
32+
Map<int, String> accentSwatchFromPrimaryHex(String primaryHex) {
33+
final primary = Color.hex(primaryHex);
34+
final baseDark = primary * primary;
35+
final baseTriad = primary.tetrad();
36+
return {
37+
100:
38+
_mix(baseDark, baseTriad[3], 15).saturate(80).lighten(48).toHexString(),
39+
200:
40+
_mix(baseDark, baseTriad[3], 15).saturate(80).lighten(36).toHexString(),
41+
400: _mix(baseDark, baseTriad[3], 15)
42+
.saturate(100)
43+
.lighten(31)
44+
.toHexString(),
45+
700: _mix(baseDark, baseTriad[3], 15)
46+
.saturate(100)
47+
.lighten(28)
48+
.toHexString(),
49+
};
50+
}
4451

45-
int get b => (0x000000ff & value) >> 0;
52+
extension _ColorExt on Color {
53+
String toHexString() {
54+
return '0xFF${toHexColor().toString().toUpperCase()}';
55+
}
4656

4757
// https://github.com/mbitson/mcg/blob/858cffea0d79ac143d590d110fbe20a1ea54d59d/scripts/controllers/ColorGeneratorCtrl.js#L221
48-
_Rgb operator *(_Rgb other) {
49-
return _Rgb(
50-
(r * other.r / 255).floor(),
51-
(g * other.g / 255).floor(),
52-
(b * other.b / 255).floor(),
58+
Color operator *(Color other) {
59+
return Color.rgb(
60+
(toRgbColor().r * other.toRgbColor().r / 255).floor(),
61+
(toRgbColor().g * other.toRgbColor().g / 255).floor(),
62+
(toRgbColor().b * other.toRgbColor().b / 255).floor(),
5363
);
5464
}
5565

56-
String toHexString() {
57-
return '0x${value.toRadixString(16).padLeft(8, '0').toUpperCase()}';
66+
// https://github.com/bgrins/TinyColor/blob/ab58ca0a3738dc06b7e64c749cebfd5d6fb5044c/tinycolor.js#L647
67+
List<Color> tetrad() {
68+
final hsl = toHslColor();
69+
return [
70+
this,
71+
Color.hsl((hsl.h + 90) % 360, hsl.s, hsl.l),
72+
Color.hsl((hsl.h + 180) % 360, hsl.s, hsl.l),
73+
Color.hsl((hsl.h + 270) % 360, hsl.s, hsl.l),
74+
];
5875
}
5976

60-
// https://github.com/bgrins/TinyColor/blob/96592a5cacdbf4d4d16cd7d39d4d6dd28da9bd5f/tinycolor.js#L701
61-
static _Rgb mix(
62-
_Rgb color1,
63-
_Rgb color2,
64-
int amount,
65-
) {
77+
// https://github.com/bgrins/TinyColor/blob/ab58ca0a3738dc06b7e64c749cebfd5d6fb5044c/tinycolor.js#L580
78+
Color saturate(int amount) {
6679
assert(amount >= 0 && amount <= 100);
67-
final p = amount / 100;
68-
return _Rgb(
69-
((color2.r - color1.r) * p + color1.r).round(),
70-
((color2.g - color1.g) * p + color1.g).round(),
71-
((color2.b - color1.b) * p + color1.b).round(),
72-
);
80+
final hsl = toHslColor();
81+
final s = (hsl.s + amount).clamp(0, 100);
82+
return Color.hsl(hsl.h, s, hsl.l);
7383
}
84+
85+
// https://github.com/bgrins/TinyColor/blob/ab58ca0a3738dc06b7e64c749cebfd5d6fb5044c/tinycolor.js#L592
86+
Color lighten(int amount) {
87+
assert(amount >= 0 && amount <= 100);
88+
final hsl = toHslColor();
89+
final l = (hsl.l + amount).clamp(0, 100);
90+
return Color.hsl(hsl.h, hsl.s, l);
91+
}
92+
}
93+
94+
// https://github.com/bgrins/TinyColor/blob/96592a5cacdbf4d4d16cd7d39d4d6dd28da9bd5f/tinycolor.js#L701
95+
Color _mix(
96+
Color color1,
97+
Color color2,
98+
int amount,
99+
) {
100+
assert(amount >= 0 && amount <= 100);
101+
final p = amount / 100;
102+
final _color1 = color1.toRgbColor();
103+
final _color2 = color2.toRgbColor();
104+
return Color.rgb(
105+
((_color2.r - _color1.r) * p + _color1.r).round(),
106+
((_color2.g - _color1.g) * p + _color1.g).round(),
107+
((_color2.b - _color1.b) * p + _color1.b).round(),
108+
);
74109
}

pubspec.lock

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,13 @@ packages:
9999
url: "https://pub.dartlang.org"
100100
source: hosted
101101
version: "1.14.13"
102+
color:
103+
dependency: "direct main"
104+
description:
105+
name: color
106+
url: "https://pub.dartlang.org"
107+
source: hosted
108+
version: "2.1.1"
102109
convert:
103110
dependency: transitive
104111
description:

pubspec.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ dependencies:
2424
xml: ^4.2.0
2525
dart_style: ^1.3.6
2626
dartx: ^0.5.0
27+
color: ^2.1.1
2728

2829
dev_dependencies:
2930
test:

test_resources/actual_data/colors.gen.dart

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,13 @@ class ColorName {
4747
900: Color(0xFFCA670E),
4848
},
4949
);
50+
static const MaterialAccentColor yellowOcherAccent = MaterialAccentColor(
51+
0xFFFFBCA3,
52+
<int, Color>{
53+
100: Color(0xFFFFE8E0),
54+
200: Color(0xFFFFBCA3),
55+
400: Color(0xFFFFA989),
56+
700: Color(0xFFFF9E7A),
57+
},
58+
);
5059
}

test_resources/assets/color/colors.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,5 @@
55
<color name="gray_70">#EEEEEE</color>
66
<color name="gray_410">#979797</color>
77
<color name="crimson_red" type="material">#CF2A2A</color>
8-
<color name="yellow_ocher" type="material">#DF9527</color>
8+
<color name="yellow_ocher" type="material material-accent">#DF9527</color>
99
</resources>

0 commit comments

Comments
 (0)