blob: 0a5875a17df070b59abb76026fd104045b20544f [file] [log] [blame]
// Copyright 2019 The Chromium 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:flutter/material.dart';
import 'common_widgets.dart';
import 'config_specific/ide_theme/ide_theme.dart';
const _contrastForegroundWhite = Color.fromARGB(255, 240, 240, 240);
const contrastForegroundWhite = _contrastForegroundWhite;
/// Constructs the light or dark theme for the app taking into account
/// IDE-supplied theming.
ThemeData themeFor({
@required bool isDarkTheme,
@required IdeTheme ideTheme,
}) {
// If the theme specifies a background color, use it to infer a theme.
if (isValidDarkColor(ideTheme?.backgroundColor)) {
return _darkTheme(ideTheme);
} else if (isValidLightColor(ideTheme?.backgroundColor)) {
return _lightTheme(ideTheme);
}
return isDarkTheme ? _darkTheme(ideTheme) : _lightTheme(ideTheme);
}
ThemeData _darkTheme(IdeTheme ideTheme) {
final theme = ThemeData.dark();
final background = isValidDarkColor(ideTheme?.backgroundColor)
? ideTheme?.backgroundColor
: theme.canvasColor;
return _baseTheme(
theme: theme,
ideTheme: ideTheme,
primaryColor: devtoolsGrey[900],
backgroundColor: background,
indicatorColor: devtoolsBlue[400],
selectedRowColor: devtoolsGrey[600],
);
}
ThemeData _lightTheme(IdeTheme ideTheme) {
final theme = ThemeData.light();
final background = isValidLightColor(ideTheme?.backgroundColor)
? ideTheme?.backgroundColor
: theme.canvasColor;
return _baseTheme(
theme: theme,
ideTheme: ideTheme,
primaryColor: devtoolsBlue[600],
backgroundColor: background,
indicatorColor: Colors.yellowAccent[400],
selectedRowColor: devtoolsBlue[600],
);
}
ThemeData _baseTheme({
@required ThemeData theme,
@required IdeTheme ideTheme,
@required Color primaryColor,
@required Color backgroundColor,
@required Color indicatorColor,
@required Color selectedRowColor,
}) {
return theme.copyWith(
primaryColor: primaryColor,
indicatorColor: indicatorColor,
selectedRowColor: selectedRowColor,
// Same values for both light and dark themes.
primaryColorDark: devtoolsBlue[700],
primaryColorLight: devtoolsBlue[400],
accentColor: devtoolsBlue[400],
backgroundColor: devtoolsGrey[600],
toggleableActiveColor: devtoolsBlue[400],
canvasColor: backgroundColor,
scaffoldBackgroundColor: backgroundColor,
colorScheme: theme.colorScheme.copyWith(background: backgroundColor),
outlinedButtonTheme: OutlinedButtonThemeData(
style: OutlinedButton.styleFrom(
primary: theme.colorScheme.contrastForeground,
minimumSize: const Size(buttonMinWidth, defaultButtonHeight),
fixedSize: const Size.fromHeight(defaultButtonHeight),
),
),
textButtonTheme: TextButtonThemeData(
style: TextButton.styleFrom(
primary: theme.colorScheme.contrastForeground,
minimumSize: const Size(buttonMinWidth, defaultButtonHeight),
fixedSize: const Size.fromHeight(defaultButtonHeight),
),
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
minimumSize: const Size(buttonMinWidth, defaultButtonHeight),
fixedSize: const Size.fromHeight(defaultButtonHeight),
),
),
);
}
/// Threshold used to determine whether a colour is light/dark enough for us to
/// override the default DevTools themes with.
///
/// A value of 0.5 would result in all colours being considered light/dark, and
/// a value of 0.1 allowing around only the 10% darkest/lightest colours by
/// Flutter's luminance calculation.
const _lightDarkLuminanceThreshold = 0.1;
bool isValidDarkColor(Color color) {
if (color == null) {
return false;
}
return color.computeLuminance() <= _lightDarkLuminanceThreshold;
}
bool isValidLightColor(Color color) {
if (color == null) {
return false;
}
return color.computeLuminance() >= 1 - _lightDarkLuminanceThreshold;
}
const defaultToolbarHeight = 32.0;
const defaultButtonHeight = 32.0;
const smallButtonHeight = 20.0;
const buttonMinWidth = 36.0;
const defaultIconSize = 16.0;
const actionsIconSize = 20.0;
const defaultSpacing = 16.0;
const denseSpacing = 8.0;
const denseRowSpacing = 6.0;
const defaultBorderRadius = 4.0;
const defaultElevation = 4.0;
const borderPadding = 2.0;
const densePadding = 4.0;
const smallProgressSize = 12.0;
const defaultListItemHeight = 28.0;
const defaultChartHeight = 150.0;
/// Width of all settings dialogs.
const dialogSettingsWidth = 700.0;
const defaultScrollBarOffset = 10.0;
const defaultTabBarViewPhysics = NeverScrollableScrollPhysics();
const defaultDialogWidth = 700.0;
/// Branded grey color.
///
/// Source: https://drive.google.com/open?id=1QBhMJqXyRt-CpRsHR6yw2LAfQtiNat4g
const ColorSwatch<int> devtoolsGrey = ColorSwatch<int>(0xFF202124, {
900: Color(0xFF202124),
600: Color(0xFF60646B),
100: Color(0xFFD5D7Da),
50: Color(0xFFEAEBEC), // Lerped between grey100 and white
});
/// Branded yellow color.
///
/// Source: https://drive.google.com/open?id=1QBhMJqXyRt-CpRsHR6yw2LAfQtiNat4g
const devtoolsYellow = ColorSwatch<int>(700, {
700: Color(0xFFFFC108),
});
/// Branded blue color.
///
/// Source: https://drive.google.com/open?id=1QBhMJqXyRt-CpRsHR6yw2LAfQtiNat4g
const devtoolsBlue = ColorSwatch<int>(600, {
700: Color(0xFF02569B),
600: Color(0xFF0175C2),
400: Color(0xFF13B9FD),
});
const devtoolsError = Color(0xFFAF4054);
const devtoolsWarning = Color(0xFFFDFAD5);
extension DevToolsColorScheme on ColorScheme {
bool get isLight => brightness == Brightness.light;
bool get isDark => brightness == Brightness.dark;
// Commonly used themed colors.
Color get defaultBackground => isLight ? Colors.white : Colors.black;
Color get defaultForeground =>
isLight ? Colors.black : const Color.fromARGB(255, 187, 187, 187);
/// Text color [defaultForeground] is too gray, making it hard to read the text
/// in dark theme. We should use a more white color for dark theme, but not
/// jarring white #FFFFFF.
Color get contrastForegroundWhite => _contrastForegroundWhite;
Color get contrastForeground =>
isLight ? Colors.black : _contrastForegroundWhite;
Color get grey => isLight
? const Color.fromARGB(255, 128, 128, 128)
: const Color.fromARGB(255, 128, 128, 128);
/// Background colors for charts.
Color get chartBackground => isLight ? Colors.white : const Color(0xFF2D2E31);
Color get defaultButtonIconColor =>
isLight ? const Color(0xFF24292E) : const Color(0xFF89B5F8);
Color get defaultPrimaryButtonIconColor => defaultBackground;
Color get devtoolsLink =>
isLight ? const Color(0xFF1976D2) : Colors.lightBlueAccent;
// TODO(jacobr): replace this with Theme.of(context).scaffoldBackgroundColor, but we use
// this in places where we do not have access to the context.
Color get defaultBackgroundColor =>
isLight ? Colors.grey[50] : Colors.grey[850];
Color get chartAccentColor =>
isLight ? const Color(0xFFCCCCCC) : const Color(0xFF585858);
Color get chartTextColor => isLight ? Colors.black : Colors.white;
Color get chartSubtleColor =>
isLight ? const Color(0xFF999999) : const Color(0xFF8A8A8A);
Color get toggleButtonBackgroundColor =>
isLight ? const Color(0xFFE0EEFA) : const Color(0xFF2E3C48);
// [toggleButtonForegroundColor] is the same for light and dark theme.
Color get toggleButtonForegroundColor => const Color(0xFF2196F3);
Color get functionSyntaxColor =>
isLight ? const Color(0xFF795E26) : const Color(0xFFDCDCAA);
Color get declarationsSyntaxColor =>
isLight ? const Color(0xFF267f99) : const Color(0xFF4EC9B0);
Color get modifierSyntaxColor =>
isLight ? const Color(0xFF0000FF) : const Color(0xFF569CD6);
Color get controlFlowSyntaxColor =>
isLight ? const Color(0xFFAF00DB) : const Color(0xFFC586C0);
Color get variableSyntaxColor =>
isLight ? const Color(0xFF001080) : const Color(0xFF9CDCFE);
Color get commentSyntaxColor =>
isLight ? const Color(0xFF008000) : const Color(0xFF6A9955);
Color get stringSyntaxColor =>
isLight ? const Color(0xFFB20001) : const Color(0xFFD88E73);
Color get numericConstantSyntaxColor =>
isLight ? const Color(0xFF098658) : const Color(0xFFB5CEA8);
// Bar color for current selection (hover).
Color get hoverSelectionBarColor =>
isLight ? Colors.lime[600] : Colors.yellowAccent;
// Highlight color for an selected item in the autocomplete dropdown list.
Color get autoCompleteHighlightColor =>
isLight ? Colors.grey[300] : Colors.grey[700];
// Title of the hover card.
TextStyle get hoverTitleTextStyle => TextStyle(
color: defaultForeground,
fontWeight: FontWeight.normal,
fontSize: 15,
decoration: TextDecoration.none,
);
// Items in the hover card.
TextStyle get hoverTextStyle => TextStyle(
color: defaultForeground,
fontWeight: FontWeight.normal,
fontSize: 11.5,
decoration: TextDecoration.none,
);
// Value of items in hover e.g., capacity, etc.
TextStyle get hoverValueTextStyle => TextStyle(
color: contrastForeground,
fontWeight: FontWeight.normal,
fontSize: 11.5,
decoration: TextDecoration.none,
);
// Used for custom extension event values.
TextStyle get hoverSmallValueTextStyle => TextStyle(
color: defaultForeground,
fontWeight: FontWeight.normal,
fontSize: 10,
decoration: TextDecoration.none,
);
// Last allocation timestamp displayed.
TextStyle get italicTextStyle => TextStyle(
color: defaultForeground,
fontWeight: FontWeight.normal,
fontSize: 14,
fontStyle: FontStyle.italic,
decoration: TextDecoration.none,
);
// Items in a chart's legend.
TextStyle get legendTextStyle => TextStyle(
color: defaultForeground,
fontWeight: FontWeight.normal,
fontSize: 10,
decoration: TextDecoration.none,
);
Color get expandedColor => isLight ? Colors.grey[200] : Colors.grey[800];
Color get expandedTopContentColor =>
isLight ? Colors.grey[50] : Colors.grey[850];
Color get expandedBottomContentColor =>
isLight ? Colors.grey[200] : Colors.grey[800];
Gradient get verticalGradient => LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [expandedTopContentColor, expandedBottomContentColor],
tileMode: TileMode.repeated,
);
/// TextStyle for callstack.
TextStyle get stackTraceCall => TextStyle(
color: defaultForeground,
fontWeight: FontWeight.normal,
fontSize: 12.0,
);
/// TextStyle for source file displayed in callstack.
TextStyle get stackTraceSource => TextStyle(
color: defaultForeground,
fontWeight: FontWeight.w100,
fontSize: 12,
);
}
/// Utility extension methods to the [ThemeData] class.
extension ThemeDataExtension on ThemeData {
/// Returns whether we are currently using a dark theme.
bool get isDarkTheme => brightness == Brightness.dark;
TextStyle get regularTextStyle => TextStyle(color: textTheme.bodyText2.color);
TextStyle get subtleTextStyle => TextStyle(color: unselectedWidgetColor);
TextStyle get selectedTextStyle =>
TextStyle(color: textSelectionTheme.selectionColor);
TextStyle get fixedFontStyle =>
textTheme.bodyText2.copyWith(fontFamily: 'RobotoMono');
TextStyle get subtleFixedFontStyle {
return fixedFontStyle.copyWith(color: unselectedWidgetColor);
}
}
TextStyle linkTextStyle(ColorScheme colorScheme) => TextStyle(
color: colorScheme.devtoolsLink,
decoration: TextDecoration.underline,
);
const wideSearchTextWidth = 400.0;
const defaultSearchTextWidth = 200.0;
const defaultTextFieldHeight = 32.0;
/// Default color of cursor and color used by search's TextField.
/// Guarantee that the Search TextField on all platforms renders in the same
/// color for border, label text, and cursor. Primarly, so golden screen
/// snapshots will compare successfully using the exact color.
Color searchColor = Colors.blue;
/// Default Border of search's TextField.
BorderSide searchFocusBorderColor = BorderSide(color: searchColor, width: 2);
/// A short duration to use for animations.
///
/// Use this when you want less emphasis on the animation and more on the
/// animation result, or when you have multiple animations running in sequence
/// For example, in the timeline we use this when we are zooming the flame chart
/// and scrolling to an offset immediately after.
const shortDuration = Duration(milliseconds: 50);
/// A longer duration than [shortDuration] but quicker than [defaultDuration].
///
/// Use this for thinks that would show a bit of animation, but that we want to
/// effectively seem immediate to users.
const rapidDuration = Duration(milliseconds: 100);
/// The default duration to use for animations.
const defaultDuration = Duration(milliseconds: 200);
/// A long duration to use for animations.
///
/// Use this rarely, only when you want added emphasis to an animation.
const longDuration = Duration(milliseconds: 400);
/// Builds a [defaultDuration] animation controller.
///
/// This is the standard duration to use for animations.
AnimationController defaultAnimationController(
TickerProvider vsync, {
double value,
}) {
return AnimationController(
duration: defaultDuration,
vsync: vsync,
value: value,
);
}
/// Builds a [longDuration] animation controller.
///
/// This is the standard duration to use for slow animations.
AnimationController longAnimationController(
TickerProvider vsync, {
double value,
}) {
return AnimationController(
duration: longDuration,
vsync: vsync,
value: value,
);
}
/// The default curve we use for animations.
///
/// Inspector animations benefit from a symmetric animation curve which makes
/// it easier to reverse animations.
const defaultCurve = Curves.easeInOutCubic;
/// Builds a [CurvedAnimation] with [defaultCurve].
///
/// This is the standard curve for animations in DevTools.
CurvedAnimation defaultCurvedAnimation(AnimationController parent) =>
CurvedAnimation(curve: defaultCurve, parent: parent);
Color titleSolidBackgroundColor(ThemeData theme) {
return theme.canvasColor.darken(0.2);
}
const chartFontSizeSmall = 12.0;
const lightSelection = Color(0xFFD4D7DA);