Commit 3fbfe732 authored by Adam Barth's avatar Adam Barth

Properly paint disabled switches

Also, refactor more common code into RenderToggleable and handle dark themes
more correctly.

Fixes #601
parent d449eec6
......@@ -38,6 +38,7 @@ class _SelectionControlsDemoState extends State<SelectionControlsDemo> {
new Row(<Widget>[
new Checkbox(value: _checkboxValue, onChanged: _setCheckboxValue),
new Checkbox(value: false), // Disabled
new Checkbox(value: true), // Disabled
], justifyContent: FlexJustifyContent.spaceAround),
new Row(<int>[0, 1, 2].map((int i) {
return new Radio<int>(
......@@ -52,6 +53,7 @@ class _SelectionControlsDemoState extends State<SelectionControlsDemo> {
new Row(<Widget>[
new Switch(value: _switchValue, onChanged: _setSwitchValue),
new Switch(value: false), // Disabled
new Switch(value: true), // Disabled
], justifyContent: FlexJustifyContent.spaceAround),
], justifyContent: FlexJustifyContent.spaceAround);
}
......
......@@ -35,28 +35,13 @@ class Checkbox extends StatelessComponent {
final bool value;
final ValueChanged<bool> onChanged;
bool get _enabled => onChanged != null;
Widget build(BuildContext context) {
ThemeData themeData = Theme.of(context);
if (_enabled) {
Color uncheckedColor = themeData.brightness == ThemeBrightness.light
? Colors.black54
: Colors.white70;
return new _CheckboxRenderObjectWidget(
value: value,
onChanged: onChanged,
uncheckedColor: uncheckedColor,
accentColor: themeData.accentColor
);
}
Color disabledColor = themeData.brightness == ThemeBrightness.light
? Colors.black26
: Colors.white30;
return new _CheckboxRenderObjectWidget(
value: value,
uncheckedColor: disabledColor,
accentColor: disabledColor
activeColor: themeData.accentColor,
inactiveColor: onChanged != null ? themeData.unselectedColor : themeData.disabledColor,
onChanged: onChanged
);
}
}
......@@ -65,30 +50,31 @@ class _CheckboxRenderObjectWidget extends LeafRenderObjectWidget {
_CheckboxRenderObjectWidget({
Key key,
this.value,
this.uncheckedColor,
this.accentColor,
this.activeColor,
this.inactiveColor,
this.onChanged
}) : super(key: key) {
assert(uncheckedColor != null);
assert(accentColor != null);
assert(value != null);
assert(activeColor != null);
assert(inactiveColor != null);
}
final bool value;
final Color uncheckedColor;
final Color accentColor;
final Color activeColor;
final Color inactiveColor;
final ValueChanged<bool> onChanged;
_RenderCheckbox createRenderObject() => new _RenderCheckbox(
value: value,
accentColor: accentColor,
uncheckedColor: uncheckedColor,
activeColor: activeColor,
inactiveColor: inactiveColor,
onChanged: onChanged
);
void updateRenderObject(_RenderCheckbox renderObject, _CheckboxRenderObjectWidget oldWidget) {
renderObject.value = value;
renderObject.uncheckedColor = uncheckedColor;
renderObject.accentColor = accentColor;
renderObject.activeColor = activeColor;
renderObject.inactiveColor = inactiveColor;
renderObject.onChanged = onChanged;
}
}
......@@ -102,29 +88,16 @@ const double _kOffset = kRadialReactionRadius - _kEdgeSize / 2.0;
class _RenderCheckbox extends RenderToggleable {
_RenderCheckbox({
bool value,
Color uncheckedColor,
Color accentColor,
Color activeColor,
Color inactiveColor,
ValueChanged<bool> onChanged
}): _uncheckedColor = uncheckedColor,
super(
}): super(
value: value,
accentColor: accentColor,
activeColor: activeColor,
inactiveColor: inactiveColor,
onChanged: onChanged,
size: const Size(2 * kRadialReactionRadius, 2 * kRadialReactionRadius)
) {
assert(uncheckedColor != null);
assert(accentColor != null);
}
Color get uncheckedColor => _uncheckedColor;
Color _uncheckedColor;
void set uncheckedColor(Color value) {
assert(value != null);
if (value == _uncheckedColor)
return;
_uncheckedColor = value;
markNeedsPaint();
}
);
void paint(PaintingContext context, Offset offset) {
final Canvas canvas = context.canvas;
......@@ -136,7 +109,7 @@ class _RenderCheckbox extends RenderToggleable {
// Choose a color between grey and the theme color
Paint paint = new Paint()
..strokeWidth = _kStrokeWidth
..color = uncheckedColor;
..color = inactiveColor;
// The rrect contracts slightly during the transition animation from checked states.
// Because we have a stroke size of 2, we should have a minimum 1.0 inset.
......@@ -161,7 +134,7 @@ class _RenderCheckbox extends RenderToggleable {
new Point(_kEdgeSize / 2.0, _kEdgeSize / 2.0),
_kEdgeSize * (_kMidpoint - position.value) * 8.0, <Color>[
const Color(0x00000000),
uncheckedColor
inactiveColor
]);
canvas.drawRect(innerRect, paint);
}
......@@ -169,13 +142,15 @@ class _RenderCheckbox extends RenderToggleable {
if (position.value > _kMidpoint) {
double t = (position.value - _kMidpoint) / (1.0 - _kMidpoint);
// First draw a rounded rect outline then fill inner rectangle with accent color.
if (onChanged != null) {
// First draw a rounded rect outline then fill inner rectangle with the active color
paint
..color = accentColor.withAlpha((t * 255).floor())
..color = activeColor.withAlpha((t * 255).floor())
..style = ui.PaintingStyle.stroke;
canvas.drawRRect(rrect, paint);
paint.style = ui.PaintingStyle.fill;
canvas.drawRect(innerRect, paint);
}
// White inner check
paint
......
......@@ -20,6 +20,7 @@ class Colors {
static const white70 = const Color(0xB3FFFFFF);
static const white30 = const Color(0x4DFFFFFF); // disabled radio buttons and text of disabled flat buttons in dark theme (30% white)
static const white12 = const Color(0x1FFFFFFF); // background of disabled raised buttons in dark theme
static const white10 = const Color(0x1AFFFFFF);
static const Map<int, Color> red = const <int, Color>{
50: const Color(0xFFFFEBEE),
......
......@@ -7,7 +7,6 @@ import 'dart:ui' as ui;
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'colors.dart';
import 'constants.dart';
import 'theme.dart';
import 'toggleable.dart';
......@@ -31,9 +30,7 @@ class Radio<T> extends StatelessComponent {
bool get _enabled => onChanged != null;
Color _getInactiveColor(ThemeData themeData) {
if (!_enabled)
return themeData.brightness == ThemeBrightness.light ? Colors.black26 : Colors.white30;
return themeData.brightness == ThemeBrightness.light ? Colors.black54 : Colors.white70;
return _enabled ? themeData.unselectedColor : themeData.disabledColor;
}
void _handleChanged(bool selected) {
......@@ -45,8 +42,8 @@ class Radio<T> extends StatelessComponent {
ThemeData themeData = Theme.of(context);
return new _RadioRenderObjectWidget(
selected: value == groupValue,
activeColor: themeData.accentColor,
inactiveColor: _getInactiveColor(themeData),
accentColor: themeData.accentColor,
onChanged: _enabled ? _handleChanged : null
);
}
......@@ -56,30 +53,31 @@ class _RadioRenderObjectWidget extends LeafRenderObjectWidget {
_RadioRenderObjectWidget({
Key key,
this.selected,
this.activeColor,
this.inactiveColor,
this.accentColor,
this.onChanged
}) : super(key: key) {
assert(selected != null);
assert(activeColor != null);
assert(inactiveColor != null);
assert(accentColor != null);
}
final bool selected;
final Color inactiveColor;
final Color accentColor;
final Color activeColor;
final ValueChanged<bool> onChanged;
_RenderRadio createRenderObject() => new _RenderRadio(
value: selected,
accentColor: accentColor,
activeColor: activeColor,
inactiveColor: inactiveColor,
onChanged: onChanged
);
void updateRenderObject(_RenderRadio renderObject, _RadioRenderObjectWidget oldWidget) {
renderObject.value = selected;
renderObject.activeColor = activeColor;
renderObject.inactiveColor = inactiveColor;
renderObject.accentColor = accentColor;
renderObject.onChanged = onChanged;
}
}
......@@ -87,29 +85,16 @@ class _RadioRenderObjectWidget extends LeafRenderObjectWidget {
class _RenderRadio extends RenderToggleable {
_RenderRadio({
bool value,
Color activeColor,
Color inactiveColor,
Color accentColor,
ValueChanged<bool> onChanged
}): _inactiveColor = inactiveColor,
super(
}): super(
value: value,
accentColor: accentColor,
activeColor: activeColor,
inactiveColor: inactiveColor,
onChanged: onChanged,
size: const Size(2 * kRadialReactionRadius, 2 * kRadialReactionRadius)
) {
assert(inactiveColor != null);
assert(accentColor != null);
}
Color get inactiveColor => _inactiveColor;
Color _inactiveColor;
void set inactiveColor(Color value) {
assert(value != null);
if (value == _inactiveColor)
return;
_inactiveColor = value;
markNeedsPaint();
}
);
bool get isInteractive => super.isInteractive && !value;
......@@ -119,11 +104,11 @@ class _RenderRadio extends RenderToggleable {
paintRadialReaction(canvas, offset + const Offset(kRadialReactionRadius, kRadialReactionRadius));
Point center = (offset & size).center;
Color activeColor = onChanged != null ? accentColor : inactiveColor;
Color radioColor = onChanged != null ? activeColor : inactiveColor;
// Outer circle
Paint paint = new Paint()
..color = Color.lerp(inactiveColor, activeColor, position.value)
..color = Color.lerp(inactiveColor, radioColor, position.value)
..style = ui.PaintingStyle.stroke
..strokeWidth = 2.0;
canvas.drawCircle(center, _kOuterRadius, paint);
......
......@@ -10,6 +10,7 @@ import 'package:flutter/painting.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'colors.dart';
import 'constants.dart';
import 'shadows.dart';
import 'theme.dart';
......@@ -23,9 +24,28 @@ class Switch extends StatelessComponent {
final ValueChanged<bool> onChanged;
Widget build(BuildContext context) {
ThemeData themeData = Theme.of(context);
final isDark = themeData.brightness == ThemeBrightness.dark;
Color activeThumbColor = themeData.accentColor;
Color activeTrackColor = activeThumbColor.withAlpha(0x80);
Color inactiveThumbColor;
Color inactiveTrackColor;
if (onChanged != null) {
inactiveThumbColor = isDark ? Colors.grey[400] : Colors.grey[50];
inactiveTrackColor = isDark ? Colors.white30 : Colors.black26;
} else {
inactiveThumbColor = isDark ? Colors.grey[800] : Colors.grey[400];
inactiveTrackColor = isDark ? Colors.white10 : Colors.black12;
}
return new _SwitchRenderObjectWidget(
value: value,
accentColor: Theme.of(context).accentColor,
activeColor: activeThumbColor,
inactiveColor: inactiveThumbColor,
activeTrackColor: activeTrackColor,
inactiveTrackColor: inactiveTrackColor,
onChanged: onChanged
);
}
......@@ -35,45 +55,58 @@ class _SwitchRenderObjectWidget extends LeafRenderObjectWidget {
_SwitchRenderObjectWidget({
Key key,
this.value,
this.accentColor,
this.activeColor,
this.inactiveColor,
this.activeTrackColor,
this.inactiveTrackColor,
this.onChanged
}) : super(key: key);
final bool value;
final Color accentColor;
final Color activeColor;
final Color inactiveColor;
final Color activeTrackColor;
final Color inactiveTrackColor;
final ValueChanged<bool> onChanged;
_RenderSwitch createRenderObject() => new _RenderSwitch(
value: value,
accentColor: accentColor,
activeColor: activeColor,
inactiveColor: inactiveColor,
activeTrackColor: activeTrackColor,
inactiveTrackColor: inactiveTrackColor,
onChanged: onChanged
);
void updateRenderObject(_RenderSwitch renderObject, _SwitchRenderObjectWidget oldWidget) {
renderObject.value = value;
renderObject.accentColor = accentColor;
renderObject.activeColor = activeColor;
renderObject.inactiveColor = inactiveColor;
renderObject.activeTrackColor = activeTrackColor;
renderObject.inactiveTrackColor = inactiveTrackColor;
renderObject.onChanged = onChanged;
}
}
const Color _kThumbOffColor = const Color(0xFFFAFAFA);
const Color _kTrackOffColor = const Color(0x42000000);
const double _kTrackHeight = 14.0;
const double _kTrackWidth = 29.0;
const double _kTrackRadius = _kTrackHeight / 2.0;
const double _kThumbRadius = 10.0;
const double _kSwitchWidth = _kTrackWidth - 2 * _kTrackRadius + 2 * kRadialReactionRadius;
const double _kSwitchHeight = 2 * kRadialReactionRadius;
const int _kTrackAlpha = 0x80;
class _RenderSwitch extends RenderToggleable {
_RenderSwitch({
bool value,
Color accentColor,
Color activeColor,
Color inactiveColor,
Color activeTrackColor,
Color inactiveTrackColor,
ValueChanged<bool> onChanged
}) : super(
value: value,
accentColor: accentColor,
activeColor: activeColor,
inactiveColor: inactiveColor,
onChanged: onChanged,
minRadialReactionRadius: _kThumbRadius,
size: const Size(_kSwitchWidth, _kSwitchHeight)
......@@ -84,6 +117,26 @@ class _RenderSwitch extends RenderToggleable {
..onEnd = _handleDragEnd;
}
Color get activeTrackColor => _activeTrackColor;
Color _activeTrackColor;
void set activeTrackColor(Color value) {
assert(value != null);
if (value == _activeTrackColor)
return;
_activeTrackColor = value;
markNeedsPaint();
}
Color get inactiveTrackColor => _inactiveTrackColor;
Color _inactiveTrackColor;
void set inactiveTrackColor(Color value) {
assert(value != null);
if (value == _inactiveTrackColor)
return;
_inactiveTrackColor = value;
markNeedsPaint();
}
double get _trackInnerLength => size.width - 2.0 * kRadialReactionRadius;
HorizontalDragGestureRecognizer _drag;
......@@ -121,13 +174,10 @@ class _RenderSwitch extends RenderToggleable {
void paint(PaintingContext context, Offset offset) {
final PaintingCanvas canvas = context.canvas;
Color thumbColor = _kThumbOffColor;
Color trackColor = _kTrackOffColor;
if (position.status == PerformanceStatus.forward
|| position.status == PerformanceStatus.completed) {
thumbColor = accentColor;
trackColor = accentColor.withAlpha(_kTrackAlpha);
}
final bool isActive = onChanged != null;
Color thumbColor = isActive ? Color.lerp(inactiveColor, activeColor, position.progress) : inactiveColor;
Color trackColor = isActive ? Color.lerp(inactiveTrackColor, activeTrackColor, position.progress) : inactiveTrackColor;
// Paint the track
Paint paint = new Paint()
......
......@@ -96,6 +96,18 @@ class ThemeData {
return const IconThemeData(color: IconThemeColor.black);
}
Color get unselectedColor {
if (brightness == ThemeBrightness.dark)
return Colors.white70;
return Colors.black54;
}
Color get disabledColor {
if (brightness == ThemeBrightness.dark)
return Colors.white30;
return Colors.black26;
}
/// The foreground color for widgets (knobs, text, etc)
Color get accentColor => _accentColor;
Color _accentColor;
......
......@@ -18,12 +18,17 @@ abstract class RenderToggleable extends RenderConstrainedBox {
RenderToggleable({
bool value,
Size size,
Color accentColor,
Color activeColor,
Color inactiveColor,
this.onChanged,
double minRadialReactionRadius: 0.0
}) : _value = value,
_accentColor = accentColor,
_activeColor = activeColor,
_inactiveColor = inactiveColor,
super(additionalConstraints: new BoxConstraints.tight(size)) {
assert(value != null);
assert(activeColor != null);
assert(inactiveColor != null);
_tap = new TapGestureRecognizer(router: FlutterBinding.instance.pointerRouter)
..onTapDown = _handleTapDown
..onTap = _handleTap
......@@ -44,6 +49,7 @@ abstract class RenderToggleable extends RenderConstrainedBox {
bool get value => _value;
bool _value;
void set value(bool value) {
assert(value != null);
if (value == _value)
return;
_value = value;
......@@ -53,12 +59,23 @@ abstract class RenderToggleable extends RenderConstrainedBox {
_position.play(value ? AnimationDirection.forward : AnimationDirection.reverse);
}
Color get accentColor => _accentColor;
Color _accentColor;
void set accentColor(Color value) {
if (value == _accentColor)
Color get activeColor => _activeColor;
Color _activeColor;
void set activeColor(Color value) {
assert(value != null);
if (value == _activeColor)
return;
_accentColor = value;
_activeColor = value;
markNeedsPaint();
}
Color get inactiveColor => _inactiveColor;
Color _inactiveColor;
void set inactiveColor(Color value) {
assert(value != null);
if (value == _inactiveColor)
return;
_inactiveColor = value;
markNeedsPaint();
}
......@@ -112,7 +129,8 @@ abstract class RenderToggleable extends RenderConstrainedBox {
void paintRadialReaction(Canvas canvas, Offset offset) {
if (!reaction.isDismissed) {
Paint reactionPaint = new Paint()..color = accentColor.withAlpha(kRadialReactionAlpha);
// TODO(abarth): We should have a different reaction color when position is zero.
Paint reactionPaint = new Paint()..color = activeColor.withAlpha(kRadialReactionAlpha);
canvas.drawCircle(offset.toPoint(), reaction.value, reactionPaint);
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment