diff --git a/dev/benchmarks/complex_layout/windows/flutter/generated_plugins.cmake b/dev/benchmarks/complex_layout/windows/flutter/generated_plugins.cmake new file mode 100644 index 000000000000..b93c4c30c167 --- /dev/null +++ b/dev/benchmarks/complex_layout/windows/flutter/generated_plugins.cmake @@ -0,0 +1,23 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/examples/flutter_view/windows/flutter/generated_plugins.cmake b/examples/flutter_view/windows/flutter/generated_plugins.cmake new file mode 100644 index 000000000000..b93c4c30c167 --- /dev/null +++ b/examples/flutter_view/windows/flutter/generated_plugins.cmake @@ -0,0 +1,23 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/examples/platform_view/windows/flutter/generated_plugins.cmake b/examples/platform_view/windows/flutter/generated_plugins.cmake new file mode 100644 index 000000000000..b93c4c30c167 --- /dev/null +++ b/examples/platform_view/windows/flutter/generated_plugins.cmake @@ -0,0 +1,23 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/packages/flutter/lib/src/cupertino/switch.dart b/packages/flutter/lib/src/cupertino/switch.dart index 26d86ce8b842..095a3285aba7 100644 --- a/packages/flutter/lib/src/cupertino/switch.dart +++ b/packages/flutter/lib/src/cupertino/switch.dart @@ -74,6 +74,7 @@ class CupertinoSwitch extends StatefulWidget { this.trackColor, this.thumbColor, this.applyTheme, + this.focusColor, this.dragStartBehavior = DragStartBehavior.start, }) : assert(value != null), assert(dragStartBehavior != null); @@ -125,6 +126,11 @@ class CupertinoSwitch extends StatefulWidget { /// Defaults to [CupertinoColors.white] when null. final Color? thumbColor; + /// The color to use for the focus highlight for keyboard interactions. + /// + /// Defaults to a a slightly transparent [activeColor]. + final Color? focusColor; + /// {@template flutter.cupertino.CupertinoSwitch.applyTheme} /// Whether to apply the ambient [CupertinoThemeData]. /// @@ -178,8 +184,14 @@ class _CupertinoSwitchState extends State with TickerProviderSt late AnimationController _reactionController; late Animation _reaction; + late bool isFocused; + bool get isInteractive => widget.onChanged != null; + late final Map> _actionMap = >{ + ActivateIntent: CallbackAction(onInvoke: _handleTap), + }; + // A non-null boolean value that changes to true at the end of a drag if the // switch must be animated to the position indicated by the widget's value. bool needsPositionAnimation = false; @@ -188,6 +200,8 @@ class _CupertinoSwitchState extends State with TickerProviderSt void initState() { super.initState(); + isFocused = false; + _tap = TapGestureRecognizer() ..onTapDown = _handleTapDown ..onTapUp = _handleTapUp @@ -253,7 +267,7 @@ class _CupertinoSwitchState extends State with TickerProviderSt _reactionController.forward(); } - void _handleTap() { + void _handleTap([Intent? _]) { if (isInteractive) { widget.onChanged!(!widget.value); _emitVibration(); @@ -322,9 +336,19 @@ class _CupertinoSwitchState extends State with TickerProviderSt } } + void _onShowFocusHighlight(bool showHighlight) { + setState(() { isFocused = showHighlight; }); + } + @override Widget build(BuildContext context) { final CupertinoThemeData theme = CupertinoTheme.of(context); + final Color activeColor = CupertinoDynamicColor.resolve( + widget.activeColor + ?? ((widget.applyTheme ?? theme.applyThemeToAll) ? theme.primaryColor : null) + ?? CupertinoColors.systemGreen, + context, + ); if (needsPositionAnimation) { _resumePositionAnimation(); } @@ -332,19 +356,29 @@ class _CupertinoSwitchState extends State with TickerProviderSt cursor: isInteractive && kIsWeb ? SystemMouseCursors.click : MouseCursor.defer, child: Opacity( opacity: widget.onChanged == null ? _kCupertinoSwitchDisabledOpacity : 1.0, - child: _CupertinoSwitchRenderObjectWidget( - value: widget.value, - activeColor: CupertinoDynamicColor.resolve( - widget.activeColor - ?? ((widget.applyTheme ?? theme.applyThemeToAll) ? theme.primaryColor : null) - ?? CupertinoColors.systemGreen, - context, + child: FocusableActionDetector( + onShowFocusHighlight: _onShowFocusHighlight, + actions: _actionMap, + enabled: isInteractive, + child: _CupertinoSwitchRenderObjectWidget( + value: widget.value, + activeColor: activeColor, + trackColor: CupertinoDynamicColor.resolve(widget.trackColor ?? CupertinoColors.secondarySystemFill, context), + thumbColor: CupertinoDynamicColor.resolve(widget.thumbColor ?? CupertinoColors.white, context), + // Opacity, lightness, and saturation values were aproximated with + // color pickers on the switches in the macOS settings. + focusColor: CupertinoDynamicColor.resolve( + widget.focusColor ?? + HSLColor + .fromColor(activeColor.withOpacity(0.80)) + .withLightness(0.69).withSaturation(0.835) + .toColor(), + context), + onChanged: widget.onChanged, + textDirection: Directionality.of(context), + isFocused: isFocused, + state: this, ), - trackColor: CupertinoDynamicColor.resolve(widget.trackColor ?? CupertinoColors.secondarySystemFill, context), - thumbColor: CupertinoDynamicColor.resolve(widget.thumbColor ?? CupertinoColors.white, context), - onChanged: widget.onChanged, - textDirection: Directionality.of(context), - state: this, ), ), ); @@ -367,8 +401,10 @@ class _CupertinoSwitchRenderObjectWidget extends LeafRenderObjectWidget { required this.activeColor, required this.trackColor, required this.thumbColor, + required this.focusColor, required this.onChanged, required this.textDirection, + required this.isFocused, required this.state, }); @@ -376,9 +412,11 @@ class _CupertinoSwitchRenderObjectWidget extends LeafRenderObjectWidget { final Color activeColor; final Color trackColor; final Color thumbColor; + final Color focusColor; final ValueChanged? onChanged; final _CupertinoSwitchState state; final TextDirection textDirection; + final bool isFocused; @override _RenderCupertinoSwitch createRenderObject(BuildContext context) { @@ -387,8 +425,10 @@ class _CupertinoSwitchRenderObjectWidget extends LeafRenderObjectWidget { activeColor: activeColor, trackColor: trackColor, thumbColor: thumbColor, + focusColor: focusColor, onChanged: onChanged, textDirection: textDirection, + isFocused: isFocused, state: state, ); } @@ -401,8 +441,10 @@ class _CupertinoSwitchRenderObjectWidget extends LeafRenderObjectWidget { ..activeColor = activeColor ..trackColor = trackColor ..thumbColor = thumbColor + ..focusColor = focusColor ..onChanged = onChanged - ..textDirection = textDirection; + ..textDirection = textDirection + ..isFocused = isFocused; } } @@ -426,8 +468,10 @@ class _RenderCupertinoSwitch extends RenderConstrainedBox { required Color activeColor, required Color trackColor, required Color thumbColor, + required Color focusColor, ValueChanged? onChanged, required TextDirection textDirection, + required bool isFocused, required _CupertinoSwitchState state, }) : assert(value != null), assert(activeColor != null), @@ -435,9 +479,11 @@ class _RenderCupertinoSwitch extends RenderConstrainedBox { _value = value, _activeColor = activeColor, _trackColor = trackColor, + _focusColor = focusColor, _thumbPainter = CupertinoThumbPainter.switchThumb(color: thumbColor), _onChanged = onChanged, _textDirection = textDirection, + _isFocused = isFocused, _state = state, super(additionalConstraints: const BoxConstraints.tightFor(width: _kSwitchWidth, height: _kSwitchHeight)) { state.position.addListener(markNeedsPaint); @@ -490,6 +536,17 @@ class _RenderCupertinoSwitch extends RenderConstrainedBox { markNeedsPaint(); } + Color get focusColor => _focusColor; + Color _focusColor; + set focusColor(Color value) { + assert(value != null); + if (value == _focusColor) { + return; + } + _focusColor = value; + markNeedsPaint(); + } + ValueChanged? get onChanged => _onChanged; ValueChanged? _onChanged; set onChanged(ValueChanged? value) { @@ -515,6 +572,17 @@ class _RenderCupertinoSwitch extends RenderConstrainedBox { markNeedsPaint(); } + bool get isFocused => _isFocused; + bool _isFocused; + set isFocused(bool value) { + assert(value != null); + if(value == _isFocused) { + return; + } + _isFocused = value; + markNeedsPaint(); + } + bool get isInteractive => onChanged != null; @override @@ -570,6 +638,18 @@ class _RenderCupertinoSwitch extends RenderConstrainedBox { final RRect trackRRect = RRect.fromRectAndRadius(trackRect, const Radius.circular(_kTrackRadius)); canvas.drawRRect(trackRRect, paint); + if(_isFocused) { + // Paints a border around the switch in the focus color. + final RRect borderTrackRRect = trackRRect.inflate(1.75); + + final Paint borderPaint = Paint() + ..color = focusColor + ..style = PaintingStyle.stroke + ..strokeWidth = 3.5; + + canvas.drawRRect(borderTrackRRect, borderPaint); + } + final double currentThumbExtension = CupertinoThumbPainter.extension * currentReactionValue; final double thumbLeft = lerpDouble( trackRect.left + _kTrackInnerStart - CupertinoThumbPainter.radius, diff --git a/packages/flutter/test/cupertino/switch_test.dart b/packages/flutter/test/cupertino/switch_test.dart index 2644de993268..c32dc69c9952 100644 --- a/packages/flutter/test/cupertino/switch_test.dart +++ b/packages/flutter/test/cupertino/switch_test.dart @@ -48,6 +48,39 @@ void main() { expect(value, isTrue); }); + testWidgets('CupertinoSwitch can be toggled by keyboard shortcuts', (WidgetTester tester) async { + bool value = true; + Widget buildApp({bool enabled = true}) { + return CupertinoApp( + home: CupertinoPageScaffold( + child: Center( + child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) { + return CupertinoSwitch( + value: value, + onChanged: enabled ? (bool newValue) { + setState(() { + value = newValue; + }); + } : null, + ); + }), + ), + ), + ); + } + await tester.pumpWidget(buildApp()); + await tester.pumpAndSettle(); + expect(value, isTrue); + await tester.sendKeyEvent(LogicalKeyboardKey.tab); + await tester.pumpAndSettle(); + await tester.sendKeyEvent(LogicalKeyboardKey.space); + await tester.pumpAndSettle(); + expect(value, isFalse); + await tester.sendKeyEvent(LogicalKeyboardKey.space); + await tester.pumpAndSettle(); + expect(value, isTrue); + }); + testWidgets('Switch emits light haptic vibration on tap', (WidgetTester tester) async { final Key switchKey = UniqueKey(); bool value = false;