Files
Reboot-Launcher/dependencies/fluent_ui-3.12.0/lib/src/controls/inputs/toggle_switch.dart
Alessandro Autiero 55467152c9 Made build portable
2022-10-07 17:23:30 +02:00

427 lines
14 KiB
Dart

import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
/// The toggle switch represents a physical switch that allows users to
/// turn things on or off, like a light switch. Use toggle switch controls
/// to present users with two mutually exclusive options (such as on/off),
/// where choosing an option provides immediate results.
///
/// Use a toggle switch for binary operations that take effect right after
/// the user flips the toggle switch
///
/// ![ToggleSwitch Preview](https://docs.microsoft.com/en-us/windows/uwp/design/controls-and-patterns/images/toggleswitches01.png)
///
/// Think of the toggle switch as a physical power switch for a device: you
/// flip it on or off when you want to enable or disable the action performed
/// by the device.
///
/// See also:
/// - [Checkbox]
/// - [RadioButton]
/// - [ToggleButton]
/// - [RadioButton]
class ToggleSwitch extends StatefulWidget {
/// Creates a toggle switch.
const ToggleSwitch({
Key? key,
required this.checked,
required this.onChanged,
this.style,
this.content,
this.semanticLabel,
this.thumb,
this.focusNode,
this.autofocus = false,
this.onDisabledPress,
this.enabled = true
}) : super(key: key);
/// Whether the [ToggleSwitch] is checked
final bool checked;
/// Called when the value of the [ToggleSwitch] should change.
///
/// This callback passes a new value, but doesn't update its state
/// internally.
///
/// If this callback is null, the ToggleSwitch is disabled.
final ValueChanged<bool>? onChanged;
/// The thumb of this [ToggleSwitch]. If this is null, defaults to [DefaultToggleSwitchThumb]
final Widget? thumb;
/// The style of this [ToggleSwitch].
///
/// This style is mescled with [ThemeData.toggleSwitchThemeData]
final ToggleSwitchThemeData? style;
/// The content of the radio button.
///
/// This, if non-null, is displayed at the right of the switcher,
/// and is affected by user touch.
///
/// Usually a [Text] or [Icon] widget
final Widget? content;
/// The `semanticLabel` of this [ToggleSwitch]
final String? semanticLabel;
/// {@macro flutter.widgets.Focus.focusNode}
final FocusNode? focusNode;
/// {@macro flutter.widgets.Focus.autofocus}
final bool autofocus;
final Function()? onDisabledPress;
final bool enabled;
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
..add(FlagProperty('checked', value: checked, ifFalse: 'unchecked'))
..add(ObjectFlagProperty('onChanged', onChanged, ifNull: 'disabled'))
..add(
FlagProperty('autofocus', value: autofocus, ifFalse: 'manual focus'))
..add(DiagnosticsProperty<ToggleSwitchThemeData>('style', style))
..add(StringProperty('semanticLabel', semanticLabel))
..add(ObjectFlagProperty<FocusNode>.has('focusNode', focusNode));
}
@override
_ToggleSwitchState createState() => _ToggleSwitchState();
}
class _ToggleSwitchState extends State<ToggleSwitch> {
bool _dragging = false;
Alignment? _alignment;
void _handleAlignmentChanged(
Offset localPosition, double sliderGestureWidth) {
setState(() {
_alignment = Alignment(
(localPosition.dx / sliderGestureWidth).clamp(-1, 1),
0,
);
});
}
@override
Widget build(BuildContext context) {
assert(debugCheckHasFluentTheme(context));
final ToggleSwitchThemeData style =
ToggleSwitchTheme.of(context).merge(widget.style);
final sliderGestureWidth = 45.0 + (style.padding?.horizontal ?? 0.0);
return HoverButton(
autofocus: widget.autofocus,
semanticLabel: widget.semanticLabel,
margin: style.margin,
focusNode: widget.focusNode,
onPressed: !widget.enabled ? () => widget.onDisabledPress?.call() : () => widget.onChanged!(!widget.checked),
onHorizontalDragStart: (e) {
if (!widget.enabled) {
widget.onDisabledPress?.call();
return;
}
_handleAlignmentChanged(e.localPosition, sliderGestureWidth);
setState(() => _dragging = true);
},
onHorizontalDragUpdate: (e) {
if (!widget.enabled) {
widget.onDisabledPress?.call();
return;
}
_handleAlignmentChanged(e.localPosition, sliderGestureWidth);
if (!_dragging) setState(() => _dragging = true);
},
onHorizontalDragEnd: (e) {
if (!widget.enabled) {
widget.onDisabledPress?.call();
return;
}
if (_alignment != null) {
if (_alignment!.x >= 0.5) {
widget.onChanged!(true);
} else {
widget.onChanged!(false);
}
setState(() {
_alignment = null;
_dragging = false;
});
}
},
builder: (context, states) {
Widget child = AnimatedContainer(
alignment: _alignment ??
(widget.checked ? Alignment.centerRight : Alignment.centerLeft),
height: 20,
width: 40,
duration: style.animationDuration ?? Duration.zero,
curve: style.animationCurve ?? Curves.linear,
padding: style.padding,
decoration: widget.checked
? style.checkedDecoration?.resolve(states)
: style.uncheckedDecoration?.resolve(states),
child: widget.thumb ??
DefaultToggleSwitchThumb(
checked: widget.checked,
style: style,
states: _dragging ? {ButtonStates.pressing} : states,
),
);
if (widget.content != null) {
child = Row(mainAxisSize: MainAxisSize.min, children: [
child,
const SizedBox(width: 10.0),
widget.content!,
]);
}
return Semantics(
checked: widget.checked,
child: FocusBorder(
focused: states.isFocused,
child: child,
),
);
},
);
}
}
class DefaultToggleSwitchThumb extends StatelessWidget {
const DefaultToggleSwitchThumb({
Key? key,
required this.checked,
required this.style,
required this.states,
}) : super(key: key);
final bool checked;
final ToggleSwitchThemeData? style;
final Set<ButtonStates> states;
@override
Widget build(BuildContext context) {
const checkedFactor = 1;
return AnimatedContainer(
duration: style?.animationDuration ?? Duration.zero,
curve: style?.animationCurve ?? Curves.linear,
margin: states.isHovering
? const EdgeInsets.all(1.0 + checkedFactor)
: const EdgeInsets.symmetric(
horizontal: 2.0 + checkedFactor,
vertical: 2.0 + checkedFactor,
),
height: 18,
width: 12 + (states.isHovering ? 2 : 0) + (states.isPressing ? 5 : 0),
decoration: checked
? style?.checkedThumbDecoration?.resolve(states)
: style?.uncheckedThumbDecoration?.resolve(states),
);
}
}
class ToggleSwitchTheme extends InheritedTheme {
/// Creates a button theme that controls how descendant [ToggleSwitch]es should
/// look like.
const ToggleSwitchTheme({
Key? key,
required Widget child,
required this.data,
}) : super(key: key, child: child);
final ToggleSwitchThemeData data;
/// Creates a button theme that controls how descendant [ToggleSwitch]es should
/// look like, and merges in the current button theme, if any.
static Widget merge({
Key? key,
required ToggleSwitchThemeData data,
required Widget child,
}) {
return Builder(builder: (BuildContext context) {
return ToggleSwitchTheme(
key: key,
data: _getInheritedToggleSwitchThemeData(context).merge(data),
child: child,
);
});
}
/// The data from the closest instance of this class that encloses the given
/// context.
///
/// Defaults to [ThemeData.toggleSwitchTheme]
///
/// Typical usage is as follows:
///
/// ```dart
/// ToggleSwitchThemeData theme = ToggleSwitchTheme.of(context);
/// ```
static ToggleSwitchThemeData of(BuildContext context) {
assert(debugCheckHasFluentTheme(context));
return ToggleSwitchThemeData.standard(FluentTheme.of(context)).merge(
_getInheritedToggleSwitchThemeData(context),
);
}
static ToggleSwitchThemeData _getInheritedToggleSwitchThemeData(
BuildContext context) {
final ToggleSwitchTheme? checkboxTheme =
context.dependOnInheritedWidgetOfExactType<ToggleSwitchTheme>();
return checkboxTheme?.data ?? FluentTheme.of(context).toggleSwitchTheme;
}
@override
Widget wrap(BuildContext context, Widget child) {
return ToggleSwitchTheme(data: data, child: child);
}
@override
bool updateShouldNotify(ToggleSwitchTheme oldWidget) {
return oldWidget.data != data;
}
}
@immutable
class ToggleSwitchThemeData with Diagnosticable {
final ButtonState<Decoration?>? checkedThumbDecoration;
final ButtonState<Decoration?>? uncheckedThumbDecoration;
final ButtonState<Decoration?>? checkedDecoration;
final ButtonState<Decoration?>? uncheckedDecoration;
final EdgeInsetsGeometry? padding;
final EdgeInsetsGeometry? margin;
final Duration? animationDuration;
final Curve? animationCurve;
const ToggleSwitchThemeData({
this.padding,
this.margin,
this.animationDuration,
this.animationCurve,
this.checkedThumbDecoration,
this.uncheckedThumbDecoration,
this.checkedDecoration,
this.uncheckedDecoration,
});
factory ToggleSwitchThemeData.standard(ThemeData style) {
final defaultThumbDecoration = BoxDecoration(
borderRadius: BorderRadius.circular(100),
);
final defaultDecoration = BoxDecoration(
borderRadius: BorderRadius.circular(100),
);
return ToggleSwitchThemeData(
checkedDecoration: ButtonState.resolveWith((states) {
return defaultDecoration.copyWith(
color: ButtonThemeData.checkedInputColor(style, states),
border: Border.all(
width: 1,
color: Colors.transparent,
),
);
}),
uncheckedDecoration: ButtonState.resolveWith((states) {
return defaultDecoration.copyWith(
color: ButtonThemeData.uncheckedInputColor(style, states),
border: Border.all(
width: 1,
color: style.borderInputColor
),
);
}),
margin: const EdgeInsets.all(4),
animationDuration: style.fastAnimationDuration,
animationCurve: style.animationCurve,
checkedThumbDecoration: ButtonState.resolveWith((states) {
return defaultThumbDecoration.copyWith(
color: style.checkedColor
);
}),
uncheckedThumbDecoration: ButtonState.resolveWith((states) {
return defaultThumbDecoration.copyWith(
color: style.uncheckedColor
);
}),
);
}
static ToggleSwitchThemeData lerp(
ToggleSwitchThemeData? a,
ToggleSwitchThemeData? b,
double t,
) {
return ToggleSwitchThemeData(
margin: EdgeInsetsGeometry.lerp(a?.margin, b?.margin, t),
padding: EdgeInsetsGeometry.lerp(a?.padding, b?.padding, t),
animationCurve: t < 0.5 ? a?.animationCurve : b?.animationCurve,
animationDuration: lerpDuration(a?.animationDuration ?? Duration.zero,
b?.animationDuration ?? Duration.zero, t),
checkedThumbDecoration: ButtonState.lerp(a?.checkedThumbDecoration,
b?.checkedThumbDecoration, t, Decoration.lerp),
uncheckedThumbDecoration: ButtonState.lerp(a?.uncheckedThumbDecoration,
b?.uncheckedThumbDecoration, t, Decoration.lerp),
checkedDecoration: ButtonState.lerp(
a?.checkedDecoration, b?.checkedDecoration, t, Decoration.lerp),
uncheckedDecoration: ButtonState.lerp(
a?.uncheckedDecoration, b?.uncheckedDecoration, t, Decoration.lerp),
);
}
ToggleSwitchThemeData merge(ToggleSwitchThemeData? style) {
return ToggleSwitchThemeData(
margin: style?.margin ?? margin,
padding: style?.padding ?? padding,
animationCurve: style?.animationCurve ?? animationCurve,
animationDuration: style?.animationDuration ?? animationDuration,
checkedThumbDecoration:
style?.checkedThumbDecoration ?? checkedThumbDecoration,
uncheckedThumbDecoration:
style?.uncheckedThumbDecoration ?? uncheckedThumbDecoration,
checkedDecoration: style?.checkedDecoration ?? checkedDecoration,
uncheckedDecoration: style?.uncheckedDecoration ?? uncheckedDecoration,
);
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<EdgeInsetsGeometry?>('margin', margin));
properties
.add(DiagnosticsProperty<EdgeInsetsGeometry?>('padding', padding));
properties
.add(DiagnosticsProperty<Curve?>('animationCurve', animationCurve));
properties.add(
DiagnosticsProperty<Duration?>('animationDuration', animationDuration));
properties.add(ObjectFlagProperty<ButtonState<Decoration?>?>.has(
'checkedDecoration',
checkedDecoration,
));
properties.add(ObjectFlagProperty<ButtonState<Decoration?>?>.has(
'uncheckedDecoration',
uncheckedDecoration,
));
properties.add(ObjectFlagProperty<ButtonState<Decoration?>?>.has(
'checkedThumbDecoration',
checkedThumbDecoration,
));
properties.add(ObjectFlagProperty<ButtonState<Decoration?>?>.has(
'uncheckedThumbDecoration',
uncheckedThumbDecoration,
));
}
}