Skip to content

Commit

Permalink
Import YaruColorExtension from yaru_colors.dart (ubuntu#341)
Browse files Browse the repository at this point in the history
  • Loading branch information
jpnurmi authored May 18, 2023
1 parent 552f0a4 commit efc0f16
Show file tree
Hide file tree
Showing 2 changed files with 300 additions and 0 deletions.
99 changes: 99 additions & 0 deletions lib/src/colors.dart
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,102 @@ class YaruColors {
/// Xubuntu Blue
static const Color xubuntuBlue = Color(0xFF0044AA);
}

/// Set of useful methods when working with [Color]
extension YaruColorExtension on Color {
/// Scale color attributes relatively to current ones.
/// [alpha], [hue], [saturation] and [lightness] values must be clamped between -1.0 and 1.0
Color scale({
double alpha = 0.0,
double hue = 0.0,
double saturation = 0.0,
double lightness = 0.0,
}) {
assert(alpha >= -1.0 && alpha <= 1.0);
assert(hue >= -1.0 && hue <= 1.0);
assert(saturation >= -1.0 && saturation <= 1.0);
assert(lightness >= -1.0 && lightness <= 1.0);

final hslColor = _getPatchedHslColor();

double scale(double value, double amount, [double upperLimit = 1.0]) {
var result = value;

if (amount > 0) {
result = value + (upperLimit - value) * amount;
} else if (amount < 0) {
result = value + value * amount;
}

return result.clamp(0.0, upperLimit);
}

return hslColor
.withAlpha(scale(opacity, alpha))
.withHue(scale(hslColor.hue, hue, 360.0))
.withSaturation(scale(hslColor.saturation, saturation))
.withLightness(scale(hslColor.lightness, lightness))
.toColor();
}

/// Adjust color attributes by the given values.
/// [alpha], [saturation] and [lightness] values must be clamped between -1.0 and 1.0
/// [hue] value must be clamped between -360.0 and 360.0
Color adjust({
double alpha = 0.0,
double hue = 0.0,
double saturation = 0.0,
double lightness = 0.0,
}) {
assert(alpha >= -1.0 && alpha <= 1.0);
assert(hue >= -360.0 && hue <= 360.0);
assert(saturation >= -1.0 && saturation <= 1.0);
assert(lightness >= -1.0 && lightness <= 1.0);

final hslColor = _getPatchedHslColor();

double adjust(double value, double amount, [double upperLimit = 1.0]) {
return (value + amount).clamp(0.0, upperLimit);
}

return hslColor
.withAlpha(adjust(hslColor.alpha, alpha))
.withHue(adjust(hslColor.hue, hue, 360.0))
.withSaturation(adjust(hslColor.saturation, saturation))
.withLightness(adjust(hslColor.lightness, lightness))
.toColor();
}

/// Return a copy of this color with attributes replaced by given values.
/// [alpha], [saturation] and [lightness] values must be clamped between 0.0 and 1.0
/// [hue] value must be clamped between 0.0 and 360.0
Color copyWith({
double? alpha,
double? hue,
double? saturation,
double? lightness,
}) {
assert(alpha == null || (alpha >= 0.0 && alpha <= 1.0));
assert(hue == null || (hue >= 0.0 && hue <= 360.0));
assert(saturation == null || (saturation >= 0.0 && saturation <= 1.0));
assert(lightness == null || (lightness >= 0.0 && lightness <= 1.0));

final hslColor = _getPatchedHslColor();

return hslColor
.withAlpha(alpha ?? hslColor.alpha)
.withHue(hue ?? hslColor.hue)
.withSaturation(saturation ?? hslColor.saturation)
.withLightness(lightness ?? hslColor.lightness)
.toColor();
}

HSLColor _getPatchedHslColor() {
final hslColor = HSLColor.fromColor(this);

// A pure dark color have saturation level at 1.0, which results in red when lighten it.
// We reset this value to 0.0, so the result is desaturated as expected:
return hslColor
.withSaturation(hslColor.lightness == 0.0 ? 0.0 : hslColor.saturation);
}
}
201 changes: 201 additions & 0 deletions test/yaru_color_extension_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:yaru/src/colors.dart';

final Matcher throwsAssertionError = throwsA(isA<AssertionError>());
final Color midColor = const HSLColor.fromAHSL(.5, 180, .5, .5).toColor();

void main() {
group('Color.scale() test -', () {
test('With out of range amount', () {
expect(() => midColor.scale(alpha: -1.1), throwsAssertionError);
expect(() => midColor.scale(alpha: 1.1), throwsAssertionError);
expect(() => midColor.scale(hue: -1.1), throwsAssertionError);
expect(() => midColor.scale(hue: 1.1), throwsAssertionError);
expect(() => midColor.scale(saturation: -1.1), throwsAssertionError);
expect(() => midColor.scale(saturation: 1.1), throwsAssertionError);
expect(() => midColor.scale(lightness: -1.1), throwsAssertionError);
expect(() => midColor.scale(lightness: 1.1), throwsAssertionError);
});
test('With clamped amount', () {
expect(
midColor.scale(alpha: -1.0),
const Color(0x0040bfbf),
);
expect(
midColor.scale(alpha: 1.0),
const Color(0xff40bfbf),
);
expect(
midColor.scale(hue: -1.0),
const Color(0x80bf4040),
);
expect(
midColor.scale(hue: 1.0),
const Color(0x80bf4040),
);
expect(
midColor.scale(saturation: -1.0),
const Color(0x80808080),
);
expect(
midColor.scale(saturation: 1.0),
const Color(0x8000ffff),
);
expect(
midColor.scale(lightness: -1.0),
const Color(0x80000000),
);
expect(
midColor.scale(lightness: 1.0),
const Color(0x80ffffff),
);
});
test('With medium amount', () {
expect(
midColor.scale(alpha: -0.5),
const Color(0x4040bfbf),
);
expect(
midColor.scale(alpha: 0.5),
const Color(0xc040bfbf),
);
expect(
midColor.scale(hue: -0.5),
const Color(0x8080bf40),
);
expect(
midColor.scale(hue: 0.5),
const Color(0x808040bf),
);
expect(
midColor.scale(saturation: -0.5),
const Color(0x80609f9f),
);
expect(
midColor.scale(saturation: 0.5),
const Color(0x8020dfdf),
);
expect(
midColor.scale(lightness: -0.5),
const Color(0x80206060),
);
expect(
midColor.scale(lightness: 0.5),
const Color(0x80a0dfdf),
);
});
});

group('Color.adjust() test -', () {
test('With out of range amount', () {
expect(() => midColor.adjust(alpha: -1.1), throwsAssertionError);
expect(() => midColor.adjust(alpha: 1.1), throwsAssertionError);
expect(() => midColor.adjust(hue: -360.1), throwsAssertionError);
expect(() => midColor.adjust(hue: 360.1), throwsAssertionError);
expect(() => midColor.adjust(saturation: -1.1), throwsAssertionError);
expect(() => midColor.adjust(saturation: 1.1), throwsAssertionError);
expect(() => midColor.adjust(lightness: -1.1), throwsAssertionError);
expect(() => midColor.adjust(lightness: 1.1), throwsAssertionError);
});
test('With clamped amount', () {
expect(
midColor.adjust(alpha: -1.0),
const Color(0x0040bfbf),
);
expect(
midColor.adjust(alpha: 1.0),
const Color(0xff40bfbf),
);
expect(
midColor.adjust(hue: -180),
const Color(0x80bf4040),
);
expect(
midColor.adjust(hue: 180),
const Color(0x80bf4040),
);
expect(
midColor.adjust(saturation: -1.0),
const Color(0x80808080),
);
expect(
midColor.adjust(saturation: 1.0),
const Color(0x8000ffff),
);
expect(
midColor.adjust(lightness: -1.0),
const Color(0x80000000),
);
expect(
midColor.adjust(lightness: 1.0),
const Color(0x80ffffff),
);
});
test('With medium amount', () {
expect(
midColor.adjust(alpha: -0.25),
const Color(0x4040bfbf),
);
expect(
midColor.adjust(alpha: 0.25),
const Color(0xc040bfbf),
);
expect(
midColor.adjust(hue: -90),
const Color(0x8080bf40),
);
expect(
midColor.adjust(hue: 90),
const Color(0x808040bf),
);
expect(
midColor.adjust(saturation: -0.25),
const Color(0x80609f9f),
);
expect(
midColor.adjust(saturation: 0.25),
const Color(0x8020dfdf),
);
expect(
midColor.adjust(lightness: -0.25),
const Color(0x80206060),
);
expect(
midColor.adjust(lightness: 0.25),
const Color(0x80a0dfdf),
);
});
});

group('Color.copyWith() test -', () {
test('With out of range amount', () {
expect(() => midColor.copyWith(alpha: -1.1), throwsAssertionError);
expect(() => midColor.copyWith(alpha: 1.1), throwsAssertionError);
expect(() => midColor.copyWith(hue: -360.1), throwsAssertionError);
expect(() => midColor.copyWith(hue: 360.1), throwsAssertionError);
expect(() => midColor.copyWith(saturation: -1.1), throwsAssertionError);
expect(() => midColor.copyWith(saturation: 1.1), throwsAssertionError);
expect(() => midColor.copyWith(lightness: -1.1), throwsAssertionError);
expect(() => midColor.copyWith(lightness: 1.1), throwsAssertionError);
});
test('With various amount', () {
expect(
midColor.copyWith(alpha: 0.25),
const Color(0x4040bfbf),
);
expect(
midColor.copyWith(hue: 90),
const Color(0x8080bf40),
);
expect(
midColor.copyWith(saturation: 0.75),
const Color(0x8020dfdf),
);
expect(
midColor.copyWith(lightness: 0.75),
const Color(0x80a0dfdf),
);
});
});
}

0 comments on commit efc0f16

Please sign in to comment.