diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d3a6d06..d09c4145 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ ## [0.0.3] +* Implemented `Checkbox` * Implemented `ProgressCircle` and `ProgressBar` * Implemented the `Switch` widget diff --git a/README.md b/README.md index e3ae3278..23237015 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,12 @@ dragging left or right. See the documentation for all customization options. # Buttons +## Checkbox + +| off | on | mixed | +| --- | -- | ----- | +| ![](https://developer.apple.com/design/human-interface-guidelines/macos/images/CheckBoxes_Deselected.svg) | ![](https://developer.apple.com/design/human-interface-guidelines/macos/images/CheckBoxes_Selected.svg) | ![](https://developer.apple.com/design/human-interface-guidelines/macos/images/CheckBoxes_Mixed.svg) | + ## PushButton diff --git a/example/.gitignore b/example/.gitignore index 0fa6b675..527ef53e 100644 --- a/example/.gitignore +++ b/example/.gitignore @@ -44,3 +44,5 @@ app.*.map.json /android/app/debug /android/app/profile /android/app/release + +/windows/ \ No newline at end of file diff --git a/example/lib/main.dart b/example/lib/main.dart index 6bd962f6..e0f30067 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -18,7 +18,7 @@ class MyApp extends StatelessWidget { title: 'macos_ui example', theme: MacosThemeData.light(), darkTheme: MacosThemeData.dark(), - themeMode: ThemeMode.dark, + // themeMode: ThemeMode.dark, debugShowCheckedModeBanner: false, home: Demo(), ); @@ -42,13 +42,17 @@ class _DemoState extends State { child: Text('Sidebar'), ), body: Column( - mainAxisAlignment: MainAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ PushButton( buttonSize: ButtonSize.small, child: Text('Button'), onPressed: () {}, ), + Checkbox( + value: value, + onChanged: (v) => setState(() => value = v), + ), ], ), ); diff --git a/example/pubspec.lock b/example/pubspec.lock index ace9095e..077fec03 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -127,7 +127,7 @@ packages: name: split_view url: "https://pub.dartlang.org" source: hosted - version: "2.0.1+1" + version: "2.0.1+2" stack_trace: dependency: transitive description: diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 5c3c4074..2f6b6b17 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -18,5 +18,3 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - -flutter: diff --git a/lib/macos_ui.dart b/lib/macos_ui.dart index 32d9a821..aaf9bf8e 100644 --- a/lib/macos_ui.dart +++ b/lib/macos_ui.dart @@ -1,8 +1,9 @@ library macos_ui; /// todo: package-level docs +export 'package:flutter/widgets.dart'; export 'package:flutter/cupertino.dart' - show CupertinoColors, CupertinoDynamicColor; + show CupertinoColors, CupertinoDynamicColor, CupertinoIcons; export 'package:flutter/material.dart' show Brightness, @@ -30,6 +31,7 @@ export 'src/macos_app.dart'; export 'src/styles/macos_theme.dart'; export 'src/styles/macos_theme_data.dart'; export 'src/styles/typography.dart'; +export 'src/buttons/checkbox.dart'; export 'src/styles/typography.dart'; export 'src/util.dart'; export 'src/util.dart'; diff --git a/lib/src/buttons/checkbox.dart b/lib/src/buttons/checkbox.dart new file mode 100644 index 00000000..661ba54f --- /dev/null +++ b/lib/src/buttons/checkbox.dart @@ -0,0 +1,106 @@ +import 'package:macos_ui/macos_ui.dart'; + +/// A checkbox is a type of button that lets the user choose between +/// two opposite states, actions, or values. A selected checkbox is +/// considered on when it contains a checkmark and off when it's empty. +/// A checkbox is almost always followed by a title unless it appears in +/// a checklist. +class Checkbox extends StatelessWidget { + /// Creates a checkbox. + /// + /// [size] must be non-negative + const Checkbox({ + Key? key, + required this.value, + required this.onChanged, + this.size = 16.0, + this.activeColor, + this.disabledColor = CupertinoColors.quaternaryLabel, + this.offBorderColor = CupertinoColors.tertiaryLabel, + }) : assert(size >= 0), + super(key: key); + + /// Whether the checkbox is checked or not. If null, it'll be considered + /// mixed. + final bool? value; + + /// Called whenever the state of the checkbox changes. If null, the checkbox + /// will be considered disabled + final ValueChanged? onChanged; + + /// The size of the checkbox. It must be non-negative. + final double size; + + /// The background color when the checkbox is on or mixed. If null, + /// [Style.activeColor] is used + final Color? activeColor; + + /// The background color when the checkbox is disabled. [CupertinoColors.quaternaryLabel] + /// is used by default + final Color disabledColor; + + /// The color of the border when the checkbox is off. [CupertinoColors.tertiaryLabel] + /// is used by default + final Color offBorderColor; + + /// Whether the checkbox is mixed or not. + bool get isMixed => value == null; + + /// Whether the checkbox is disabled or not. + bool get isDisabled => onChanged == null; + + @override + Widget build(BuildContext context) { + assert(debugCheckHasMacosTheme(context)); + bool isLight = context.macosTheme.brightness != Brightness.dark; + return GestureDetector( + onTap: () { + if (value == null || value == false) { + onChanged?.call(true); + } else { + onChanged?.call(false); + } + }, + child: Container( + height: size, + width: size, + alignment: Alignment.center, + decoration: isDisabled || value == null || value == true + ? BoxDecoration( + color: CupertinoDynamicColor.resolve( + isDisabled + ? disabledColor + : activeColor ?? + context.macosTheme.primaryColor ?? + CupertinoColors.activeBlue, + context, + ), + borderRadius: BorderRadius.circular(4.0), + ) + : BoxDecoration( + color: isLight ? null : CupertinoColors.tertiaryLabel, + border: Border.all( + style: isLight ? BorderStyle.solid : BorderStyle.none, + width: 0.5, + color: CupertinoDynamicColor.resolve( + offBorderColor, + context, + ), + ), + borderRadius: BorderRadius.circular(4.0), + ), + child: Icon( + isDisabled + ? null + : isMixed + ? CupertinoIcons.minus + : value == false + ? null + : CupertinoIcons.check_mark, + color: CupertinoColors.white, + size: (size - 3).clamp(0, size), + ), + ), + ); + } +}