introduce system color palette (#163335)
Introduce system colors: * Add new `SystemColor` class. * Add `PlatformDispatcher.systemColors` getter. The web engine part for https://github.com/flutter/flutter/issues/118853
This commit is contained in:
parent
7819cee8d6
commit
9b356c17c3
@ -42633,6 +42633,7 @@ ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/font_fallbacks.dart + ../../.
|
||||
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/fonts.dart + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/frame_service.dart + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/frame_timing_recorder.dart + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/high_contrast.dart + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/html/backdrop_filter.dart + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/html/bitmap_canvas.dart + ../../../flutter/LICENSE
|
||||
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/html/canvas.dart + ../../../flutter/LICENSE
|
||||
@ -45600,6 +45601,7 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/font_fallbacks.dart
|
||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/fonts.dart
|
||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/frame_service.dart
|
||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/frame_timing_recorder.dart
|
||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/high_contrast.dart
|
||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/backdrop_filter.dart
|
||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/bitmap_canvas.dart
|
||||
FILE: ../../../flutter/lib/web_ui/lib/src/engine/html/canvas.dart
|
||||
|
@ -1508,6 +1508,216 @@ class PlatformDispatcher {
|
||||
external static double _getScaledFontSize(double unscaledFontSize, int configurationId);
|
||||
}
|
||||
|
||||
/// A color specified in the operating system UI color palette.
|
||||
///
|
||||
/// The static getters in this class, such as [accentColor] and [buttonText],
|
||||
/// provide standard system colors defined by the
|
||||
/// [W3C CSS specification](https://drafts.csswg.org/css-color/#css-system-colors).
|
||||
///
|
||||
/// As of the current release, system colors are supported on web only. To check
|
||||
/// if the current platform supports system colors, use the static
|
||||
/// [platformProvidesSystemColors] field. If the field is `false`, other
|
||||
/// functions in this class will throw [UnsupportedError].
|
||||
///
|
||||
/// This class is typically used in conjunction with
|
||||
/// [AccessibilityFeatures.highContrast]. In particular, on Windows, when a user
|
||||
/// enables high-contrast mode, they may also pick specific colors that should
|
||||
/// be used by application user interfaces. While it is common for applications
|
||||
/// to use custom color themes and design languages, in high-contrast mode it is
|
||||
/// recommended that widgets use system-specified colors to make content more
|
||||
/// legible for users.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * https://drafts.csswg.org/css-color/#css-system-colors
|
||||
/// * https://developer.mozilla.org/en-US/docs/Web/CSS/system-color
|
||||
/// * https://developer.mozilla.org/en-US/docs/Web/CSS/@media/forced-colors
|
||||
final class SystemColor {
|
||||
/// Creates an instance of a system color.
|
||||
///
|
||||
/// [name] is the name of the color. System colors provided by static getters
|
||||
/// in this class, such as [accentColor] and [buttonText], use standard names
|
||||
/// defined by the [W3C CSS specification](https://drafts.csswg.org/css-color/#css-system-colors).
|
||||
///
|
||||
/// [value] is the color value, if this color name is supported, and null if
|
||||
/// it's unsupported.
|
||||
const SystemColor({required this.name, this.value});
|
||||
|
||||
/// Standard system color name, as defined by W3C CSS specification.
|
||||
///
|
||||
/// System color names in Flutter are case-sensitive. This is so that color
|
||||
/// names can be easily used as [Map] keys. This is in contrast to CSS, where
|
||||
/// system color names are not case-sensitive. That is, specifying
|
||||
/// `background-color: aCcEnTcOlOr` is equivalent to specifying
|
||||
/// `background-color: AccentColor`.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * https://drafts.csswg.org/css-color/#css-system-colors
|
||||
final String name;
|
||||
|
||||
/// The color value used for the color named [name], if supported.
|
||||
///
|
||||
/// If [isSupported] is false, the [value] is null. If [isSupported] is true,
|
||||
/// the [value] is not null.
|
||||
final Color? value;
|
||||
|
||||
/// Returns true if the current platform provides the system color with the
|
||||
/// given [name].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [platformProvidesSystemColors], which returns whether the current
|
||||
/// platform provides system colors.
|
||||
bool get isSupported => value != null;
|
||||
|
||||
/// Returns true if the current platform provides system colors.
|
||||
///
|
||||
/// As of the current release, system colors are supported on web only.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [isSupported], which returns whether a specific color is supported.
|
||||
static bool get platformProvidesSystemColors => false;
|
||||
|
||||
static UnsupportedError _systemColorUnsupportedError() {
|
||||
return UnsupportedError('SystemColor not supported on the current platform.');
|
||||
}
|
||||
|
||||
/// Returns system color named "AccentColor".
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * https://drafts.csswg.org/css-color/#css-system-colors
|
||||
static SystemColor get accentColor => throw _systemColorUnsupportedError();
|
||||
|
||||
/// Returns system color named "AccentColorText".
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * https://drafts.csswg.org/css-color/#css-system-colors
|
||||
static SystemColor get accentColorText => throw _systemColorUnsupportedError();
|
||||
|
||||
/// Returns system color named "ActiveText".
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * https://drafts.csswg.org/css-color/#css-system-colors
|
||||
static SystemColor get activeText => throw _systemColorUnsupportedError();
|
||||
|
||||
/// Returns system color named "ButtonBorder".
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * https://drafts.csswg.org/css-color/#css-system-colors
|
||||
static SystemColor get buttonBorder => throw _systemColorUnsupportedError();
|
||||
|
||||
/// Returns system color named "ButtonFace".
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * https://drafts.csswg.org/css-color/#css-system-colors
|
||||
static SystemColor get buttonFace => throw _systemColorUnsupportedError();
|
||||
|
||||
/// Returns system color named "ButtonText".
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * https://drafts.csswg.org/css-color/#css-system-colors
|
||||
static SystemColor get buttonText => throw _systemColorUnsupportedError();
|
||||
|
||||
/// Returns system color named "Canvas".
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * https://drafts.csswg.org/css-color/#css-system-colors
|
||||
static SystemColor get canvas => throw _systemColorUnsupportedError();
|
||||
|
||||
/// Returns system color named "CanvasText".
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * https://drafts.csswg.org/css-color/#css-system-colors
|
||||
static SystemColor get canvasText => throw _systemColorUnsupportedError();
|
||||
|
||||
/// Returns system color named "Field".
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * https://drafts.csswg.org/css-color/#css-system-colors
|
||||
static SystemColor get field => throw _systemColorUnsupportedError();
|
||||
|
||||
/// Returns system color named "FieldText".
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * https://drafts.csswg.org/css-color/#css-system-colors
|
||||
static SystemColor get fieldText => throw _systemColorUnsupportedError();
|
||||
|
||||
/// Returns system color named "GrayText".
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * https://drafts.csswg.org/css-color/#css-system-colors
|
||||
static SystemColor get grayText => throw _systemColorUnsupportedError();
|
||||
|
||||
/// Returns system color named "Highlight".
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * https://drafts.csswg.org/css-color/#css-system-colors
|
||||
static SystemColor get highlight => throw _systemColorUnsupportedError();
|
||||
|
||||
/// Returns system color named "HighlightText".
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * https://drafts.csswg.org/css-color/#css-system-colors
|
||||
static SystemColor get highlightText => throw _systemColorUnsupportedError();
|
||||
|
||||
/// Returns system color named "LinkText".
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * https://drafts.csswg.org/css-color/#css-system-colors
|
||||
static SystemColor get linkText => throw _systemColorUnsupportedError();
|
||||
|
||||
/// Returns system color named "Mark".
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * https://drafts.csswg.org/css-color/#css-system-colors
|
||||
static SystemColor get mark => throw _systemColorUnsupportedError();
|
||||
|
||||
/// Returns system color named "MarkText".
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * https://drafts.csswg.org/css-color/#css-system-colors
|
||||
static SystemColor get markText => throw _systemColorUnsupportedError();
|
||||
|
||||
/// Returns system color named "SelectedItem".
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * https://drafts.csswg.org/css-color/#css-system-colors
|
||||
static SystemColor get selectedItem => throw _systemColorUnsupportedError();
|
||||
|
||||
/// Returns system color named "SelectedItemText".
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * https://drafts.csswg.org/css-color/#css-system-colors
|
||||
static SystemColor get selectedItemText => throw _systemColorUnsupportedError();
|
||||
|
||||
/// Returns system color named "VisitedText".
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * https://drafts.csswg.org/css-color/#css-system-colors
|
||||
static SystemColor get visitedText => throw _systemColorUnsupportedError();
|
||||
}
|
||||
|
||||
/// Configuration of the platform.
|
||||
///
|
||||
/// Immutable class (but can't use @immutable in dart:ui)
|
||||
|
@ -153,6 +153,75 @@ abstract class PlatformDispatcher {
|
||||
double scaleFontSize(double unscaledFontSize);
|
||||
}
|
||||
|
||||
final class SystemColor {
|
||||
const SystemColor({required this.name, this.value});
|
||||
final String name;
|
||||
final Color? value;
|
||||
bool get isSupported => value != null;
|
||||
static bool get platformProvidesSystemColors => true;
|
||||
|
||||
static SystemColor _lookUp(String name) {
|
||||
return engine.SystemColorPaletteDetector.instance.systemColors[name]!;
|
||||
}
|
||||
|
||||
static SystemColor get accentColor => _accentColor;
|
||||
static final SystemColor _accentColor = _lookUp('AccentColor');
|
||||
|
||||
static SystemColor get accentColorText => _accentColorText;
|
||||
static final SystemColor _accentColorText = _lookUp('AccentColorText');
|
||||
|
||||
static SystemColor get activeText => _activeText;
|
||||
static final SystemColor _activeText = _lookUp('ActiveText');
|
||||
|
||||
static SystemColor get buttonBorder => _buttonBorder;
|
||||
static final SystemColor _buttonBorder = _lookUp('ButtonBorder');
|
||||
|
||||
static SystemColor get buttonFace => _buttonFace;
|
||||
static final SystemColor _buttonFace = _lookUp('ButtonFace');
|
||||
|
||||
static SystemColor get buttonText => _buttonText;
|
||||
static final SystemColor _buttonText = _lookUp('ButtonText');
|
||||
|
||||
static SystemColor get canvas => _canvas;
|
||||
static final SystemColor _canvas = _lookUp('Canvas');
|
||||
|
||||
static SystemColor get canvasText => _canvasText;
|
||||
static final SystemColor _canvasText = _lookUp('CanvasText');
|
||||
|
||||
static SystemColor get field => _field;
|
||||
static final SystemColor _field = _lookUp('Field');
|
||||
|
||||
static SystemColor get fieldText => _fieldText;
|
||||
static final SystemColor _fieldText = _lookUp('FieldText');
|
||||
|
||||
static SystemColor get grayText => _grayText;
|
||||
static final SystemColor _grayText = _lookUp('GrayText');
|
||||
|
||||
static SystemColor get highlight => _highlight;
|
||||
static final SystemColor _highlight = _lookUp('Highlight');
|
||||
|
||||
static SystemColor get highlightText => _highlightText;
|
||||
static final SystemColor _highlightText = _lookUp('HighlightText');
|
||||
|
||||
static SystemColor get linkText => _linkText;
|
||||
static final SystemColor _linkText = _lookUp('LinkText');
|
||||
|
||||
static SystemColor get mark => _mark;
|
||||
static final SystemColor _mark = _lookUp('Mark');
|
||||
|
||||
static SystemColor get markText => _markText;
|
||||
static final SystemColor _markText = _lookUp('MarkText');
|
||||
|
||||
static SystemColor get selectedItem => _selectedItem;
|
||||
static final SystemColor _selectedItem = _lookUp('SelectedItem');
|
||||
|
||||
static SystemColor get selectedItemText => _selectedItemText;
|
||||
static final SystemColor _selectedItemText = _lookUp('SelectedItemText');
|
||||
|
||||
static SystemColor get visitedText => _visitedText;
|
||||
static final SystemColor _visitedText = _lookUp('VisitedText');
|
||||
}
|
||||
|
||||
enum FramePhase {
|
||||
vsyncStart,
|
||||
buildStart,
|
||||
|
@ -67,6 +67,7 @@ export 'engine/font_fallbacks.dart';
|
||||
export 'engine/fonts.dart';
|
||||
export 'engine/frame_service.dart';
|
||||
export 'engine/frame_timing_recorder.dart';
|
||||
export 'engine/high_contrast.dart';
|
||||
export 'engine/html/backdrop_filter.dart';
|
||||
export 'engine/html/bitmap_canvas.dart';
|
||||
export 'engine/html/canvas.dart';
|
||||
|
@ -735,6 +735,13 @@ extension DomElementExtension on DomElement {
|
||||
external void setPointerCapture(num? pointerId);
|
||||
}
|
||||
|
||||
extension type DomCSS(JSObject _) implements JSObject {
|
||||
external bool supports(String proeprty, String value);
|
||||
}
|
||||
|
||||
@JS('CSS')
|
||||
external DomCSS get domCSS;
|
||||
|
||||
@JS()
|
||||
@staticInterop
|
||||
class DomCSSStyleDeclaration {}
|
||||
|
192
engine/src/flutter/lib/web_ui/lib/src/engine/high_contrast.dart
Normal file
192
engine/src/flutter/lib/web_ui/lib/src/engine/high_contrast.dart
Normal file
@ -0,0 +1,192 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
import 'dart:js_interop';
|
||||
|
||||
import 'package:ui/ui.dart' as ui;
|
||||
|
||||
import 'dom.dart';
|
||||
|
||||
/// Signature of functions added as a listener to high contrast changes
|
||||
typedef HighContrastListener = void Function(bool enabled);
|
||||
|
||||
/// Determines if high contrast is enabled using media query 'forced-colors: active' for Windows
|
||||
class HighContrastSupport {
|
||||
static HighContrastSupport instance = HighContrastSupport();
|
||||
static const String _highContrastMediaQueryString = '(forced-colors: active)';
|
||||
|
||||
final List<HighContrastListener> _listeners = <HighContrastListener>[];
|
||||
|
||||
/// Reference to css media query that indicates whether high contrast is on.
|
||||
final DomMediaQueryList _highContrastMediaQuery = domWindow.matchMedia(
|
||||
_highContrastMediaQueryString,
|
||||
);
|
||||
late final DomEventListener _onHighContrastChangeListener = createDomEventListener(
|
||||
_onHighContrastChange,
|
||||
);
|
||||
|
||||
bool get isHighContrastEnabled => _highContrastMediaQuery.matches;
|
||||
|
||||
/// Adds function to the list of listeners on high contrast changes
|
||||
void addListener(HighContrastListener listener) {
|
||||
if (_listeners.isEmpty) {
|
||||
_highContrastMediaQuery.addListener(_onHighContrastChangeListener);
|
||||
}
|
||||
_listeners.add(listener);
|
||||
}
|
||||
|
||||
/// Removes function from the list of listeners on high contrast changes
|
||||
void removeListener(HighContrastListener listener) {
|
||||
_listeners.remove(listener);
|
||||
if (_listeners.isEmpty) {
|
||||
_highContrastMediaQuery.removeListener(_onHighContrastChangeListener);
|
||||
}
|
||||
}
|
||||
|
||||
JSVoid _onHighContrastChange(DomEvent event) {
|
||||
final DomMediaQueryListEvent mqEvent = event as DomMediaQueryListEvent;
|
||||
final bool isHighContrastEnabled = mqEvent.matches!;
|
||||
for (final HighContrastListener listener in _listeners) {
|
||||
listener(isHighContrastEnabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const List<String> systemColorNames = <String>[
|
||||
'AccentColor',
|
||||
'AccentColorText',
|
||||
'ActiveText',
|
||||
'ButtonBorder',
|
||||
'ButtonFace',
|
||||
'ButtonText',
|
||||
'Canvas',
|
||||
'CanvasText',
|
||||
'Field',
|
||||
'FieldText',
|
||||
'GrayText',
|
||||
'Highlight',
|
||||
'HighlightText',
|
||||
'LinkText',
|
||||
'Mark',
|
||||
'MarkText',
|
||||
'SelectedItem',
|
||||
'SelectedItemText',
|
||||
'VisitedText',
|
||||
];
|
||||
|
||||
class SystemColorPaletteDetector {
|
||||
SystemColorPaletteDetector() {
|
||||
final hostDetector = createDomHTMLDivElement();
|
||||
hostDetector.style
|
||||
..position = 'absolute'
|
||||
..transform = 'translate(-10000, -10000)';
|
||||
domDocument.body!.appendChild(hostDetector);
|
||||
|
||||
final colorDetectors = <String, DomHTMLElement>{};
|
||||
|
||||
for (final systemColorName in systemColorNames) {
|
||||
final detector = createDomHTMLDivElement();
|
||||
detector.style.backgroundColor = systemColorName;
|
||||
detector.innerText = '$systemColorName detector';
|
||||
hostDetector.appendChild(detector);
|
||||
colorDetectors[systemColorName] = detector;
|
||||
}
|
||||
|
||||
final results = <String, ui.SystemColor>{};
|
||||
|
||||
colorDetectors.forEach((systemColorName, detector) {
|
||||
final computedDetector = domWindow.getComputedStyle(detector);
|
||||
final computedColor = computedDetector.backgroundColor;
|
||||
|
||||
final isSupported = domCSS.supports('color', systemColorName);
|
||||
ui.Color? value;
|
||||
if (isSupported) {
|
||||
value = parseCssRgb(computedColor);
|
||||
}
|
||||
|
||||
results[systemColorName] = ui.SystemColor(name: systemColorName, value: value);
|
||||
});
|
||||
systemColors = results;
|
||||
|
||||
// Once colors have been detected, this element is no longer needed.
|
||||
hostDetector.remove();
|
||||
}
|
||||
|
||||
static SystemColorPaletteDetector instance = SystemColorPaletteDetector();
|
||||
|
||||
late final Map<String, ui.SystemColor> systemColors;
|
||||
}
|
||||
|
||||
/// Parses CSS RGB color written as `rgb(r, g, b)` or `rgba(r, g, b, a)`.
|
||||
ui.Color? parseCssRgb(String rgbString) {
|
||||
// Remove leading and trailing whitespace.
|
||||
rgbString = rgbString.trim();
|
||||
|
||||
final isRgb = rgbString.startsWith('rgb(');
|
||||
final isRgba = rgbString.startsWith('rgba(');
|
||||
|
||||
if ((!isRgb && !isRgba) || !rgbString.endsWith(')')) {
|
||||
assert(() {
|
||||
print('Bad CSS color "$rgbString": not an rgb or rgba color.');
|
||||
return true;
|
||||
}());
|
||||
return null;
|
||||
}
|
||||
|
||||
assert(isRgb || isRgba);
|
||||
|
||||
// Extract the comma-separated values.
|
||||
final valuesString = rgbString.substring(isRgb ? 4 : 5, rgbString.length - 1);
|
||||
final values = valuesString.split(',');
|
||||
|
||||
// Check if there are exactly three values for RGB, and four values for RGBA.
|
||||
if ((isRgb && values.length != 3) || (isRgba && values.length != 4)) {
|
||||
assert(() {
|
||||
print(
|
||||
'Bad CSS color "$rgbString": wrong number of color componets. For ${isRgb ? 'rgb' : 'rgba'} color, expected ${isRgb ? 3 : 4} components, but found ${values.length}.',
|
||||
);
|
||||
return true;
|
||||
}());
|
||||
return null;
|
||||
}
|
||||
|
||||
// Parse the values as integers.
|
||||
final r = int.tryParse(values[0].trim());
|
||||
final g = int.tryParse(values[1].trim());
|
||||
final b = int.tryParse(values[2].trim());
|
||||
|
||||
// Check if the values are valid integers between 0 and 255.
|
||||
if (r == null ||
|
||||
g == null ||
|
||||
b == null ||
|
||||
r < 0 ||
|
||||
r > 255 ||
|
||||
g < 0 ||
|
||||
g > 255 ||
|
||||
b < 0 ||
|
||||
b > 255) {
|
||||
assert(() {
|
||||
print(
|
||||
'Bad CSS color "$rgbString": one of RGB components failed to parse or outside the 0-255 range: r = $r, g = $g, b = $b.',
|
||||
);
|
||||
return true;
|
||||
}());
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isRgb) {
|
||||
return ui.Color.fromRGBO(r, g, b, 1.0);
|
||||
} else {
|
||||
assert(isRgba);
|
||||
final a = double.tryParse(values[3].trim());
|
||||
if (a == null || a < 0.0 || a > 1.0) {
|
||||
assert(() {
|
||||
print('Bad CSS color "$rgbString": alpha component outside the 0.0-1.0 range: $a');
|
||||
return true;
|
||||
}());
|
||||
return null;
|
||||
} else {
|
||||
return ui.Color.fromRGBO(r, g, b, a);
|
||||
}
|
||||
}
|
||||
}
|
@ -13,55 +13,11 @@ import 'package:ui/ui_web/src/ui_web.dart' as ui_web;
|
||||
|
||||
import '../engine.dart';
|
||||
|
||||
/// Signature of functions added as a listener to high contrast changes
|
||||
typedef HighContrastListener = void Function(bool enabled);
|
||||
typedef _KeyDataResponseCallback = void Function(bool handled);
|
||||
|
||||
const StandardMethodCodec standardCodec = StandardMethodCodec();
|
||||
const JSONMethodCodec jsonCodec = JSONMethodCodec();
|
||||
|
||||
/// Determines if high contrast is enabled using media query 'forced-colors: active' for Windows
|
||||
class HighContrastSupport {
|
||||
static HighContrastSupport instance = HighContrastSupport();
|
||||
static const String _highContrastMediaQueryString = '(forced-colors: active)';
|
||||
|
||||
final List<HighContrastListener> _listeners = <HighContrastListener>[];
|
||||
|
||||
/// Reference to css media query that indicates whether high contrast is on.
|
||||
final DomMediaQueryList _highContrastMediaQuery = domWindow.matchMedia(
|
||||
_highContrastMediaQueryString,
|
||||
);
|
||||
late final DomEventListener _onHighContrastChangeListener = createDomEventListener(
|
||||
_onHighContrastChange,
|
||||
);
|
||||
|
||||
bool get isHighContrastEnabled => _highContrastMediaQuery.matches;
|
||||
|
||||
/// Adds function to the list of listeners on high contrast changes
|
||||
void addListener(HighContrastListener listener) {
|
||||
if (_listeners.isEmpty) {
|
||||
_highContrastMediaQuery.addListener(_onHighContrastChangeListener);
|
||||
}
|
||||
_listeners.add(listener);
|
||||
}
|
||||
|
||||
/// Removes function from the list of listeners on high contrast changes
|
||||
void removeListener(HighContrastListener listener) {
|
||||
_listeners.remove(listener);
|
||||
if (_listeners.isEmpty) {
|
||||
_highContrastMediaQuery.removeListener(_onHighContrastChangeListener);
|
||||
}
|
||||
}
|
||||
|
||||
JSVoid _onHighContrastChange(DomEvent event) {
|
||||
final DomMediaQueryListEvent mqEvent = event as DomMediaQueryListEvent;
|
||||
final bool isHighContrastEnabled = mqEvent.matches!;
|
||||
for (final HighContrastListener listener in _listeners) {
|
||||
listener(isHighContrastEnabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Platform event dispatcher.
|
||||
///
|
||||
/// This is the central entry point for platform messages and configuration
|
||||
|
@ -0,0 +1,171 @@
|
||||
// Copyright 2013 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:test/bootstrap/browser.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:ui/src/engine.dart';
|
||||
import 'package:ui/ui.dart' as ui;
|
||||
|
||||
void main() {
|
||||
internalBootstrapBrowserTest(() => testMain);
|
||||
}
|
||||
|
||||
void testMain() {
|
||||
test('parseCssColor(rgb)', () {
|
||||
final color1 = parseCssRgb('rgb(12, 34, 56)');
|
||||
expect(color1, const ui.Color(0xff0c2238));
|
||||
|
||||
final color2 = parseCssRgb('rgb(255, 0, 0)');
|
||||
expect(color2, const ui.Color(0xffff0000));
|
||||
|
||||
final color3 = parseCssRgb('rgb(0, 255, 0)');
|
||||
expect(color3, const ui.Color(0xff00ff00));
|
||||
|
||||
final color4 = parseCssRgb('rgb(0, 0, 255)');
|
||||
expect(color4, const ui.Color(0xff0000ff));
|
||||
|
||||
final color5 = parseCssRgb('rgb(255,255,255)');
|
||||
expect(color5, const ui.Color(0xffffffff));
|
||||
|
||||
final color6 = parseCssRgb('rgb(0,0,0)');
|
||||
expect(color6, const ui.Color(0xff000000));
|
||||
|
||||
final color7 = parseCssRgb(' rgb( 10, 20 ,30 ) ');
|
||||
expect(color7, const ui.Color(0xff0a141e));
|
||||
|
||||
// Invalid input:
|
||||
expect(parseCssRgb('rgb(256, 0, 0)'), isNull);
|
||||
expect(parseCssRgb('rgb(255, 0)'), isNull);
|
||||
expect(parseCssRgb('rgb255,0,0'), isNull);
|
||||
});
|
||||
|
||||
test('parseCssColor(rgba)', () {
|
||||
final color1 = parseCssRgb('rgba(12, 34, 56, 0.5)');
|
||||
expect(color1?.toCssString(), const ui.Color.fromRGBO(12, 34, 56, 0.5).toCssString());
|
||||
|
||||
final color2 = parseCssRgb('rgba(255, 0, 0, 0.0)');
|
||||
expect(color2, const ui.Color.fromRGBO(255, 0, 0, 0.0));
|
||||
|
||||
final color3 = parseCssRgb('rgba(0, 255, 0, 1.0)');
|
||||
expect(color3, const ui.Color.fromRGBO(0, 255, 0, 1.0));
|
||||
|
||||
final color4 = parseCssRgb('rgba(0, 0, 255, 0.7)');
|
||||
expect(color4, const ui.Color.fromRGBO(0, 0, 255, 0.7));
|
||||
|
||||
final color5 = parseCssRgb('rgba(255,255,255,0.2)');
|
||||
expect(color5, const ui.Color.fromRGBO(255, 255, 255, 0.2));
|
||||
|
||||
final color6 = parseCssRgb('rgba(0,0,0,1.0)');
|
||||
expect(color6, const ui.Color.fromRGBO(0, 0, 0, 1.0));
|
||||
|
||||
final color7 = parseCssRgb(' rgba( 10, 20 ,30, 0.8 ) ');
|
||||
expect(color7, const ui.Color.fromRGBO(10, 20, 30, 0.8));
|
||||
|
||||
// Invalid input:
|
||||
expect(parseCssRgb('rgba(256, 0, 0, 0.1)'), isNull);
|
||||
expect(parseCssRgb('rgba(255, 0, 0.1)'), isNull);
|
||||
expect(parseCssRgb('rgb255,0,0,0.1'), isNull);
|
||||
expect(parseCssRgb('rgba(12, 34, 56, -0.1)'), isNull);
|
||||
expect(parseCssRgb('rgba(12, 34, 56, 1.1)'), isNull);
|
||||
});
|
||||
|
||||
test('ForcedColorPaletteDetector', () {
|
||||
const systemColorNames = <String>[
|
||||
'AccentColor',
|
||||
'AccentColorText',
|
||||
'ActiveText',
|
||||
'ButtonBorder',
|
||||
'ButtonFace',
|
||||
'ButtonText',
|
||||
'Canvas',
|
||||
'CanvasText',
|
||||
'Field',
|
||||
'FieldText',
|
||||
'GrayText',
|
||||
'Highlight',
|
||||
'HighlightText',
|
||||
'LinkText',
|
||||
'Mark',
|
||||
'MarkText',
|
||||
'SelectedItem',
|
||||
'SelectedItemText',
|
||||
'VisitedText',
|
||||
];
|
||||
|
||||
final detector = SystemColorPaletteDetector();
|
||||
expect(detector.systemColors.keys, containsAll(systemColorNames));
|
||||
|
||||
expect(
|
||||
detector.systemColors.values.where((color) => color.isSupported),
|
||||
// Different browser/OS combinations support different colors. It's
|
||||
// impractical to encode the precise number for each combo. Instead, this
|
||||
// test only makes sure that at least some "reasonable" number of colors
|
||||
// were detected successfully. If the number is too low, it's a red flag.
|
||||
// Perhaps the parsing logic is flawed, or the logic that enumerates the
|
||||
// colors.
|
||||
hasLength(greaterThan(15)),
|
||||
);
|
||||
});
|
||||
|
||||
test('SystemColor', () {
|
||||
const supportedColor = ui.SystemColor(
|
||||
name: 'SupportedColor',
|
||||
value: ui.Color.fromRGBO(1, 2, 3, 0.5),
|
||||
);
|
||||
expect(supportedColor.name, 'SupportedColor');
|
||||
expect(supportedColor.value, isNotNull);
|
||||
expect(supportedColor.isSupported, isTrue);
|
||||
|
||||
const unsupportedColor = ui.SystemColor(name: 'UnsupportedColor');
|
||||
expect(unsupportedColor.name, 'UnsupportedColor');
|
||||
expect(unsupportedColor.value, isNull);
|
||||
expect(unsupportedColor.isSupported, isFalse);
|
||||
|
||||
expect(ui.SystemColor.accentColor.name, 'AccentColor');
|
||||
expect(ui.SystemColor.accentColorText.name, 'AccentColorText');
|
||||
expect(ui.SystemColor.activeText.name, 'ActiveText');
|
||||
expect(ui.SystemColor.buttonBorder.name, 'ButtonBorder');
|
||||
expect(ui.SystemColor.buttonFace.name, 'ButtonFace');
|
||||
expect(ui.SystemColor.buttonText.name, 'ButtonText');
|
||||
expect(ui.SystemColor.canvas.name, 'Canvas');
|
||||
expect(ui.SystemColor.canvasText.name, 'CanvasText');
|
||||
expect(ui.SystemColor.field.name, 'Field');
|
||||
expect(ui.SystemColor.fieldText.name, 'FieldText');
|
||||
expect(ui.SystemColor.grayText.name, 'GrayText');
|
||||
expect(ui.SystemColor.highlight.name, 'Highlight');
|
||||
expect(ui.SystemColor.highlightText.name, 'HighlightText');
|
||||
expect(ui.SystemColor.linkText.name, 'LinkText');
|
||||
expect(ui.SystemColor.mark.name, 'Mark');
|
||||
expect(ui.SystemColor.markText.name, 'MarkText');
|
||||
expect(ui.SystemColor.selectedItem.name, 'SelectedItem');
|
||||
expect(ui.SystemColor.selectedItemText.name, 'SelectedItemText');
|
||||
expect(ui.SystemColor.visitedText.name, 'VisitedText');
|
||||
|
||||
final allColors = <ui.SystemColor>[
|
||||
ui.SystemColor.accentColor,
|
||||
ui.SystemColor.accentColorText,
|
||||
ui.SystemColor.activeText,
|
||||
ui.SystemColor.buttonBorder,
|
||||
ui.SystemColor.buttonFace,
|
||||
ui.SystemColor.buttonText,
|
||||
ui.SystemColor.canvas,
|
||||
ui.SystemColor.canvasText,
|
||||
ui.SystemColor.field,
|
||||
ui.SystemColor.fieldText,
|
||||
ui.SystemColor.grayText,
|
||||
ui.SystemColor.highlight,
|
||||
ui.SystemColor.highlightText,
|
||||
ui.SystemColor.linkText,
|
||||
ui.SystemColor.mark,
|
||||
ui.SystemColor.markText,
|
||||
ui.SystemColor.selectedItem,
|
||||
ui.SystemColor.selectedItemText,
|
||||
ui.SystemColor.visitedText,
|
||||
];
|
||||
|
||||
for (final color in allColors) {
|
||||
expect(color.value != null, color.isSupported);
|
||||
}
|
||||
});
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user