Skip to content

Commit 1edd861

Browse files
authored
Change hover color calculation (#652)
* Redo hover color * Update golden tests * Fix PR issues * Update windows-latest goldens * Remove failing test * Remove selected --------- Co-authored-by: Pante <[email protected]>
1 parent c2b5353 commit 1edd861

Some content is hidden

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

51 files changed

+71
-91
lines changed

.github/workflows/md_lint.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,5 @@ jobs:
2525
- uses: lycheeverse/lychee-action@v2
2626
with:
2727
# GitHub seems to have tightened their secondary rate limits causing previously working checks to fail.
28-
args: --base . --no-progress './**/*.md' --exclude '(github)\.com'
28+
args: --base-url . --no-progress './**/*.md' --exclude '(github)\.com'
2929
token: ${{ steps.generate-token.outputs.token }}

forui/CHANGELOG.md

Lines changed: 7 additions & 0 deletions

forui/example/lib/main.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ class _ApplicationState extends State<Application> with SingleTickerProviderStat
3939
locale: const Locale('en', 'US'),
4040
localizationsDelegates: FLocalizations.localizationsDelegates,
4141
supportedLocales: FLocalizations.supportedLocales,
42+
// theme: FThemes.zinc.light.toApproximateMaterialTheme(),
4243
builder: (context, child) => FTheme(data: FThemes.zinc.light, child: child!),
4344
home: Builder(
4445
builder: (context) {

forui/example/lib/sandbox.dart

Lines changed: 4 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -34,36 +34,10 @@ class _SandboxState extends State<Sandbox> with SingleTickerProviderStateMixin {
3434
return Column(
3535
spacing: 5,
3636
children: [
37-
FAutocomplete.builder(
38-
hint: 'Type to search fruits',
39-
filter: (query) async {
40-
await Future.delayed(const Duration(seconds: 1));
41-
return query.isEmpty
42-
? fruits
43-
: fruits.where((fruit) => fruit.toLowerCase().startsWith(query.toLowerCase()));
44-
},
45-
contentBuilder: (context, query, suggestions) => [
46-
for (final suggestion in suggestions) FAutocompleteItem(value: suggestion),
47-
],
48-
),
49-
FMultiSelect<String>.rich(
50-
format: Text.new,
51-
children: const [
52-
FSelectItem(title: Text('Apple'), value: 'Apple'),
53-
FSelectItem(title: Text('Banana'), value: 'Banana'),
54-
FSelectItem(title: Text('Cherry'), value: 'Cherry'),
55-
FSelectItem(title: Text('Date'), value: 'Date'),
56-
],
57-
),
58-
FSelect<String>.rich(
59-
format: (s) => s,
60-
children: const [
61-
// FSelectItem(title: Text('Apple'), value: 'Apple'),
62-
// FSelectItem(title: Text('Banana'), value: 'Banana'),
63-
// FSelectItem(title: Text('Cherry'), value: 'Cherry'),
64-
// FSelectItem(title: Text('Date'), value: 'Date'),
65-
],
66-
),
37+
FButton(style: FButtonStyle.primary(), onPress: () {}, child: Text('Primary')),
38+
FButton(style: FButtonStyle.secondary(), onPress: () {}, child: Text('Secondary')),
39+
FButton(style: FButtonStyle.ghost(), onPress: () {}, child: Text('Ghost')),
40+
FButton(style: FButtonStyle.destructive(), onPress: () {}, child: Text('Destructive')),
6741
],
6842
);
6943
}

forui/lib/src/theme/colors.dart

Lines changed: 52 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
import 'dart:ui';
2+
13
import 'package:flutter/foundation.dart';
4+
import 'package:flutter/material.dart';
25
import 'package:flutter/services.dart';
3-
import 'package:flutter/widgets.dart';
46

57
import 'package:meta/meta.dart';
68

@@ -21,8 +23,11 @@ import 'package:forui/forui.dart';
2123
/// Each color group includes a `-Foreground` suffixed color, i.e. [primaryForeground], used to color text and other
2224
/// visual elements on top of their respective background colors.
2325
///
24-
/// Hovered and disabled colors are derived by adjusting the opacity. To derive these colors, use the [hover] and
25-
/// [disable] methods. The opacity can be adjusted with [enabledHoveredOpacity] and [disabledOpacity].
26+
/// Hovered colors are derived by adjusting the lightness of the original color. To derive these colors, use the [hover]
27+
/// method. The lightness can be adjusted with [hoverLighten].
28+
///
29+
/// Disabled colors are derived by adjusting the opacity. To derive these colors, use the [disable] method. The opacity
30+
/// can be adjusted with [disabledOpacity].
2631
///
2732
/// See [FThemes] for predefined themes and color schemes.
2833
final class FColors with Diagnosticable {
@@ -104,11 +109,21 @@ final class FColors with Diagnosticable {
104109
/// The border color.
105110
final Color border;
106111

107-
/// The opacity of the foreground color when a widget is hovered and enabled. Defaults to 0.9.
112+
/// The percentage to lighten dark colors by. A higher value will result in a more pronounced lightening effect.
113+
///
114+
/// Defaults to 0.075.
108115
///
109116
/// ## Contract
110-
/// Throws [AssertionError] if the value is less than 0 or greater than 1.
111-
final double enabledHoveredOpacity;
117+
/// `0.0 <= hoverLighten <= 1.0`
118+
final double hoverLighten;
119+
120+
/// The percentage to darken light colors by. A higher value will result in a more pronounced darkening effect.
121+
///
122+
/// Defaults to 0.05.
123+
///
124+
/// ## Contract
125+
/// `0.0 <= hoverDarken <= 1.0`
126+
final double hoverDarken;
112127

113128
/// The opacity of the foreground color when a widget is disabled. Defaults to 0.5.
114129
///
@@ -137,19 +152,31 @@ final class FColors with Diagnosticable {
137152
required this.error,
138153
required this.errorForeground,
139154
required this.border,
140-
this.enabledHoveredOpacity = 0.9,
155+
this.hoverLighten = 0.075,
156+
this.hoverDarken = 0.05,
141157
this.disabledOpacity = 0.5,
142-
}) : assert(
143-
0 <= enabledHoveredOpacity && enabledHoveredOpacity <= 1,
144-
'The enabledHoveredOpacity must be between 0 and 1.',
145-
),
158+
}) : assert(0.0 <= hoverLighten && hoverLighten <= 1.0, 'The hoverLighten must be between 0 and 1.'),
159+
assert(0.0 <= hoverDarken && hoverDarken <= 1.0, 'The hoverDarken must be between 0 and 1.'),
146160
assert(0 <= disabledOpacity && disabledOpacity <= 1, 'The disabledOpacity must be between 0 and 1.');
147161

148-
/// Returns a hovered color for the [foreground] on the [background].
162+
/// Generates a hovered variant of the given [color] by darkening light colors and lighting dark colors based on their
163+
/// HSL lightness.
149164
///
150-
/// [FColors.background] is used if [background] is not given.
151-
Color hover(Color foreground, [Color? background]) =>
152-
Color.alphaBlend(foreground.withValues(alpha: enabledHoveredOpacity), background ?? this.background);
165+
/// Colors at the extremes (very light or very dark) will be adjusted more aggressively than colors in the middle.
166+
///
167+
/// The lightening and darkening are controlled by [hoverLighten] and [hoverDarken].
168+
Color hover(Color color) {
169+
final hsl = HSLColor.fromColor(color);
170+
final l = hsl.lightness;
171+
172+
// More aggressive color change when close to extremes & less when in the middle.
173+
final (space, factor, sign) = l > 0.5 ? (1.0 - l, hoverDarken, -1) : (l, hoverLighten, 1);
174+
final aggressiveness = 1 + ((0.5 - space) / 0.5);
175+
final adjustment = factor * aggressiveness * sign;
176+
final lightness = clampDouble(l + adjustment, 0, 1);
177+
178+
return hsl.withLightness(lightness).toColor();
179+
}
153180

154181
/// Returns a disabled color for the [foreground] on the [background].
155182
///
@@ -189,7 +216,8 @@ final class FColors with Diagnosticable {
189216
Color? error,
190217
Color? errorForeground,
191218
Color? border,
192-
double? enabledHoveredOpacity,
219+
double? hoverLighten,
220+
double? hoverDarken,
193221
double? disabledOpacity,
194222
}) => FColors(
195223
brightness: brightness ?? this.brightness,
@@ -208,7 +236,8 @@ final class FColors with Diagnosticable {
208236
error: error ?? this.error,
209237
errorForeground: errorForeground ?? this.errorForeground,
210238
border: border ?? this.border,
211-
enabledHoveredOpacity: enabledHoveredOpacity ?? this.enabledHoveredOpacity,
239+
hoverLighten: hoverLighten ?? this.hoverLighten,
240+
hoverDarken: hoverDarken ?? this.hoverDarken,
212241
disabledOpacity: disabledOpacity ?? this.disabledOpacity,
213242
);
214243

@@ -232,7 +261,8 @@ final class FColors with Diagnosticable {
232261
..add(ColorProperty('error', error))
233262
..add(ColorProperty('errorForeground', errorForeground))
234263
..add(ColorProperty('border', border))
235-
..add(PercentProperty('enabledHoveredOpacity', enabledHoveredOpacity))
264+
..add(PercentProperty('hoverLighten', hoverLighten))
265+
..add(PercentProperty('hoverDarken', hoverDarken))
236266
..add(PercentProperty('disabledOpacity', disabledOpacity));
237267
}
238268

@@ -256,7 +286,8 @@ final class FColors with Diagnosticable {
256286
error == other.error &&
257287
errorForeground == other.errorForeground &&
258288
border == other.border &&
259-
enabledHoveredOpacity == other.enabledHoveredOpacity &&
289+
hoverLighten == other.hoverLighten &&
290+
hoverDarken == other.hoverDarken &&
260291
disabledOpacity == other.disabledOpacity;
261292

262293
@override
@@ -277,6 +308,7 @@ final class FColors with Diagnosticable {
277308
error.hashCode ^
278309
errorForeground.hashCode ^
279310
border.hashCode ^
280-
enabledHoveredOpacity.hashCode ^
311+
hoverLighten.hashCode ^
312+
hoverDarken.hashCode ^
281313
disabledOpacity.hashCode;
282314
}
-23 Bytes
-23 Bytes
4 Bytes
4 Bytes
179 Bytes

0 commit comments

Comments
 (0)